diff --git a/apps/client/src/components/Toast.jsx b/apps/client/src/components/Toast.jsx new file mode 100644 index 0000000..46e62c5 --- /dev/null +++ b/apps/client/src/components/Toast.jsx @@ -0,0 +1,12 @@ +import { LuCheck } from 'react-icons/lu'; + +export default function Toast({ message }) { + return ( +
+ + + + {message} +
+ ); +} diff --git a/apps/client/src/hooks/useSession.js b/apps/client/src/hooks/useSession.js index a3b97db..37e4a08 100644 --- a/apps/client/src/hooks/useSession.js +++ b/apps/client/src/hooks/useSession.js @@ -4,10 +4,11 @@ import socket from '../socket'; /** * Custom hook — manages socket subscriptions for a session room. * - * Both Host and Participant need the same three socket events: - * session_joined → exposes the question object - * vote_update → keeps results live - * session_closed → calls onClose() to navigate home + * Both Host and Participant need the same socket events: + * session_joined → exposes the question object + * vote_update → keeps results live + * session_closed → calls onClose() to navigate home + * question_changed → updates question, resets results, calls onQuestionChanged() * * Extracting this into a hook means neither page owns the subscription * logic: they just call useSession() and receive { question, results }. @@ -21,9 +22,10 @@ import socket from '../socket'; * Inline arrows would create new references each render, making * socket.off() a no-op and stacking duplicate handlers over time. */ -export function useSession(code, onClose) { +export function useSession(code, onClose, onQuestionChanged) { const [question, setQuestion] = useState(null); const [results, setResults] = useState([]); + const [hasMore, setHasMore] = useState(true); useEffect(() => { function onSessionJoined({ question: q }) { @@ -35,18 +37,26 @@ export function useSession(code, onClose) { function onSessionClosed() { onClose(); } + function onQuestionChangedHandler({ question: q, hasMore: more }) { + setQuestion(q); + setResults([]); + setHasMore(more); + onQuestionChanged?.(); + } socket.on('session_joined', onSessionJoined); socket.on('vote_update', onVoteUpdate); socket.on('session_closed', onSessionClosed); + socket.on('question_changed', onQuestionChangedHandler); socket.emit('join_session', code); return () => { socket.off('session_joined', onSessionJoined); socket.off('vote_update', onVoteUpdate); socket.off('session_closed', onSessionClosed); + socket.off('question_changed', onQuestionChangedHandler); }; - }, [code, onClose]); + }, [code, onClose, onQuestionChanged]); - return { question, results }; + return { question, results, hasMore }; } diff --git a/apps/client/src/pages/Host.jsx b/apps/client/src/pages/Host.jsx index 97bf734..6162534 100644 --- a/apps/client/src/pages/Host.jsx +++ b/apps/client/src/pages/Host.jsx @@ -1,15 +1,16 @@ import { useState } from 'react'; -import { LuPower, LuShare2 } from 'react-icons/lu'; +import { LuPower, LuShare2, LuSkipForward } from 'react-icons/lu'; import Layout from '../components/Layout'; import ConfirmModal from '../components/ConfirmModal'; import ResultBar from '../components/ResultBar'; import { useSession } from '../hooks/useSession'; import { BAR_COLORS, calcPct } from '../utils'; +import socket from '../socket'; const SERVER_URL = import.meta.env.VITE_SERVER_URL ?? 'http://localhost:3001'; export default function Host({ code, onClose }) { - const { question, results } = useSession(code, onClose); + const { question, results, hasMore } = useSession(code, onClose); const [showConfirm, setShowConfirm] = useState(false); async function handleClose() { @@ -65,6 +66,14 @@ export default function Host({ code, onClose }) {
+ +
+ +
+
How it works
+

- {question.text} -

+ Select your answer and submit. Results appear live once + you've voted — and you can change your mind until the + host closes the question. +

+ + ) : ( + /* ── Waiting phase ── */ +
+
+
+
+
-
- {question.answers.map((answer) => (
setSelectedId(answer.id)} + style={{ + fontSize: '15px', + fontWeight: 500, + color: 'var(--rd-text)', + marginBottom: '4px', + }} > -
-
-
- {answer.text} + Vote submitted +
+
+ Waiting for others…
- ))} -
- -
+ {selectedAnswer && ( +
+
+ + {selectedAnswer.text} + +
+ )} -
-
How it works
-

- Select your answer and submit. Results appear live once - you've voted — and you can change your mind until the host - closes the question. -

-
-
- ) : ( - /* ── Waiting phase ── */ -
-
-
-
-