Display "active clue"
This commit is contained in:
		@@ -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",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user