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