Display "active clue"
This commit is contained in:
parent
48210b9efd
commit
87e85ed52f
@ -4,7 +4,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite --host",
|
"start": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"pretty": "prettier -w src",
|
"pretty": "prettier -w src",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { debounce } from "lodash";
|
|
||||||
|
|
||||||
|
import { debounce } from "./utils";
|
||||||
import { socket } from "./socket";
|
import { socket } from "./socket";
|
||||||
import { useAppSelector } from "./hooks";
|
import { useAppSelector } from "./hooks";
|
||||||
import { selectCanBuzz, selectSignature } from "./store/contestantSlice";
|
import { selectCanBuzz, selectSignature } from "./store/contestantSlice";
|
||||||
@ -16,15 +16,11 @@ const Contestant = () => {
|
|||||||
socket.emit("contestant-join", { room, signature });
|
socket.emit("contestant-join", { room, signature });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleBuzz = debounce(
|
const handleBuzz = debounce(() => {
|
||||||
() => {
|
if (canBuzz) {
|
||||||
if (canBuzz) {
|
socket.emit("buzz");
|
||||||
socket.emit("buzz");
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
1000,
|
|
||||||
{ leading: true, trailing: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -3,13 +3,15 @@ import { useParams } from "react-router-dom";
|
|||||||
|
|
||||||
import { socket } from "./socket";
|
import { socket } from "./socket";
|
||||||
import { useAppSelector } from "./hooks";
|
import { useAppSelector } from "./hooks";
|
||||||
import { selectCategories } from "./store/cluesSlice";
|
import { selectCategories, selectActiveClue } from "./store/cluesSlice";
|
||||||
import Pregame from "./host/Pregame";
|
import Pregame from "./host/Pregame";
|
||||||
|
import ActiveClue from "./host/ActiveClue";
|
||||||
import CluesDisplay from "./host/CluesDisplay";
|
import CluesDisplay from "./host/CluesDisplay";
|
||||||
|
|
||||||
const Host = () => {
|
const Host = () => {
|
||||||
const { room } = useParams();
|
const { room } = useParams();
|
||||||
|
|
||||||
|
const activeClue = useAppSelector(selectActiveClue);
|
||||||
const categories = useAppSelector(selectCategories);
|
const categories = useAppSelector(selectCategories);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -18,6 +20,8 @@ const Host = () => {
|
|||||||
|
|
||||||
if (categories.length < 6) {
|
if (categories.length < 6) {
|
||||||
return <Pregame />;
|
return <Pregame />;
|
||||||
|
} else if (activeClue) {
|
||||||
|
return <ActiveClue activeClue={activeClue} />;
|
||||||
} else {
|
} else {
|
||||||
return <CluesDisplay />;
|
return <CluesDisplay />;
|
||||||
}
|
}
|
||||||
|
27
src/host/ActiveClue.tsx
Normal file
27
src/host/ActiveClue.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Container, Stack, Button } from "react-bootstrap";
|
||||||
|
|
||||||
|
import { useAppDispatch } from "../hooks";
|
||||||
|
import { setActiveClue } from "../store/cluesSlice";
|
||||||
|
import type { Clue } from "../store/cluesSlice";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
activeClue: Clue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO implement timer
|
||||||
|
|
||||||
|
const ActiveClue = ({ activeClue }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<p>{activeClue.question}</p>
|
||||||
|
<Stack gap="3" className="text-center">
|
||||||
|
<Button onClick={() => dispatch(setActiveClue(null))}>
|
||||||
|
Start Timer
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActiveClue;
|
@ -3,9 +3,16 @@ import { Container, Button, Stack } from "react-bootstrap";
|
|||||||
|
|
||||||
import { useAppSelector } from "../hooks";
|
import { useAppSelector } from "../hooks";
|
||||||
import { selectCategories } from "../store/cluesSlice";
|
import { selectCategories } from "../store/cluesSlice";
|
||||||
|
import { socket } from "../socket";
|
||||||
|
import { debounce } from "../utils";
|
||||||
|
|
||||||
const CluesDisplay = () => {
|
const CluesDisplay = () => {
|
||||||
const categories = useAppSelector(selectCategories);
|
const categories = useAppSelector(selectCategories);
|
||||||
|
|
||||||
|
const activateClue = debounce((name: string, value: number) => {
|
||||||
|
socket.emit("activate-clue", { name, value });
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Stack gap={3} className="text-center">
|
<Stack gap={3} className="text-center">
|
||||||
@ -13,7 +20,9 @@ const CluesDisplay = () => {
|
|||||||
<Fragment key={name}>
|
<Fragment key={name}>
|
||||||
<h2>{name}</h2>
|
<h2>{name}</h2>
|
||||||
{[200, 400, 600, 800, 1000].map((value) => (
|
{[200, 400, 600, 800, 1000].map((value) => (
|
||||||
<Button key={value}>{value}</Button>
|
<Button key={value} onClick={() => activateClue(name, value)}>
|
||||||
|
{value}
|
||||||
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
@ -2,7 +2,8 @@ import { io } from "socket.io-client";
|
|||||||
import store from "./store";
|
import store from "./store";
|
||||||
import { setRoomCode } from "./store/socketSlice";
|
import { setRoomCode } from "./store/socketSlice";
|
||||||
import { addContestant } from "./store/displaySlice";
|
import { addContestant } from "./store/displaySlice";
|
||||||
import { setCategories } from "./store/cluesSlice";
|
import { setCategories, setActiveClue } from "./store/cluesSlice";
|
||||||
|
import type { Clue } from "./store/cluesSlice";
|
||||||
|
|
||||||
export const socket = io(`${window.location.hostname}:5000`);
|
export const socket = io(`${window.location.hostname}:5000`);
|
||||||
|
|
||||||
@ -25,4 +26,8 @@ export const setup = () => {
|
|||||||
socket.on("categories", (data: Record<string, Clue>) => {
|
socket.on("categories", (data: Record<string, Clue>) => {
|
||||||
store.dispatch(setCategories(data));
|
store.dispatch(setCategories(data));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("active-clue", (data: Clue) => {
|
||||||
|
store.dispatch(setActiveClue(data));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,32 +1,48 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import type { RootState } from "./";
|
import type { RootState } from "./";
|
||||||
|
|
||||||
|
interface CluesState {
|
||||||
|
activeClue: Clue | null;
|
||||||
|
categories: Category[];
|
||||||
|
}
|
||||||
|
|
||||||
interface Category {
|
interface Category {
|
||||||
name: string;
|
name: string;
|
||||||
clues: Clue[];
|
clues: Clue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Clue {
|
export interface Clue {
|
||||||
value: number;
|
value: number;
|
||||||
question: string;
|
question: string;
|
||||||
answer: string;
|
answer: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: Category[] = [];
|
const initialState: CluesState = {
|
||||||
|
activeClue: null,
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
|
||||||
export const cluesSlice = createSlice({
|
export const cluesSlice = createSlice({
|
||||||
name: "clues",
|
name: "clues",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setCategories: (state, action: PayloadAction<Record<string, Clue[]>>) =>
|
setCategories: (
|
||||||
Object.entries(action.payload).map(([name, clues]) => ({
|
state,
|
||||||
|
{ payload }: PayloadAction<Record<string, Clue[]>>
|
||||||
|
) => {
|
||||||
|
state.categories = Object.entries(payload).map(([name, clues]) => ({
|
||||||
name,
|
name,
|
||||||
clues,
|
clues,
|
||||||
})),
|
}));
|
||||||
|
},
|
||||||
|
setActiveClue: (state, { payload }: PayloadAction<Clue | null>) => {
|
||||||
|
state.activeClue = payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setCategories } = cluesSlice.actions;
|
export const { setCategories, setActiveClue } = cluesSlice.actions;
|
||||||
export const selectCategories = (state: RootState) => state.clues;
|
export const selectCategories = (state: RootState) => state.clues.categories;
|
||||||
|
export const selectActiveClue = (state: RootState) => state.clues.activeClue;
|
||||||
|
|
||||||
export default cluesSlice.reducer;
|
export default cluesSlice.reducer;
|
||||||
|
4
src/utils.ts
Normal file
4
src/utils.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { debounce as lodashDebounce } from "lodash";
|
||||||
|
|
||||||
|
export const debounce = (fn: (...args: unknown[]) => unknown) =>
|
||||||
|
lodashDebounce(fn, 1000, { leading: true, trailing: false });
|
@ -1,11 +1,19 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react';
|
import react from "@vitejs/plugin-react";
|
||||||
import eslint from 'vite-plugin-eslint';
|
import eslint from "vite-plugin-eslint";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
{...react(), ...eslint(), apply: 'build'},
|
{ ...react(), ...eslint(), apply: "build" },
|
||||||
{...react(), ...eslint({ failOnWarning: false, failOnError: false }), apply: 'serve', enforce: 'post'},
|
{
|
||||||
|
...react(),
|
||||||
|
...eslint({ failOnWarning: false, failOnError: true }),
|
||||||
|
apply: "serve",
|
||||||
|
enforce: "post",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user