diff --git a/frontend/src/features/jobs/PostJobPage.jsx b/frontend/src/features/jobs/PostJobPage.jsx index 9acfd9f..c337eec 100644 --- a/frontend/src/features/jobs/PostJobPage.jsx +++ b/frontend/src/features/jobs/PostJobPage.jsx @@ -26,6 +26,9 @@ export default function PostJobPage({ onUnauthorized, onCancel, onViewJobs }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [expired, setExpired] = useState(() => new Set()); + // Index of the question currently on screen. Only this one is mounted, so only + // its countdown runs; advancing remounts the next card with a fresh clock. + const [currentIndex, setCurrentIndex] = useState(0); // Live countdown per question, kept in a ref so ticking never re-renders the page. const timeLeft = useRef({}); @@ -35,11 +38,31 @@ export default function PostJobPage({ onUnauthorized, onCancel, onViewJobs }) { const sent = useRef({ answers: false, followup: false }); const limit = quiz?.time_limit_seconds ?? 75; + const questions = quiz?.questions ?? []; + const currentQuestion = questions[currentIndex] ?? null; + const isLastQuestion = currentIndex >= questions.length - 1; function markExpired(id) { setExpired((prev) => (prev.has(id) ? prev : new Set(prev).add(id))); } + // Moving on forfeits whatever time is left on the current question; the clock is + // per question, so there is no going back to spend it later. + function advance() { + if (isLastQuestion) { + handleSubmit(); + } else { + setCurrentIndex(currentIndex + 1); + } + } + + // Expiry locks the answer as-is and moves straight on, so the poster never sits + // on a dead card. Running out on the last question commits the round. + function handleExpire(id) { + markExpired(id); + advance(); + } + function reset() { setQuiz(null); setAnswers({}); @@ -48,6 +71,7 @@ export default function PostJobPage({ onUnauthorized, onCancel, onViewJobs }) { setResult(null); setError(""); setExpired(new Set()); + setCurrentIndex(0); timeLeft.current = {}; inputSignal.current = {}; sent.current = { answers: false, followup: false }; @@ -122,13 +146,6 @@ export default function PostJobPage({ onUnauthorized, onCancel, onViewJobs }) { } } - // When every question's clock has run out, submit whatever is there. - useEffect(() => { - if (quiz && !followup && !result && expired.size >= quiz.questions.length) { - handleSubmit(); - } - }, [expired, quiz, followup, result]); - const followupExpired = followup && expired.has(followup.followup.id); useEffect(() => { if (followupExpired && !result) handleFollowUp(); @@ -180,18 +197,20 @@ export default function PostJobPage({ onUnauthorized, onCancel, onViewJobs }) {
- {quiz.questions.map((q) => ( + {currentQuestion && ( (timeLeft.current[id] = s)} - onExpire={markExpired} + onExpire={handleExpire} onInputSignal={(id, sig) => (inputSignal.current[id] = sig)} onAnswerChange={(id, val) => setAnswers((prev) => ({ ...prev, [id]: val }))} /> - ))} + )}
@@ -200,8 +219,12 @@ export default function PostJobPage({ onUnauthorized, onCancel, onViewJobs }) { Cancel )} -
diff --git a/frontend/src/features/quiz/QuizPage.jsx b/frontend/src/features/quiz/QuizPage.jsx index dae3a92..95a7ee3 100644 --- a/frontend/src/features/quiz/QuizPage.jsx +++ b/frontend/src/features/quiz/QuizPage.jsx @@ -27,6 +27,9 @@ export default function QuizPage({ const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [expired, setExpired] = useState(() => new Set()); + // Index of the question currently on screen. Only this one is mounted, so only + // its countdown runs; advancing remounts the next card with a fresh clock. + const [currentIndex, setCurrentIndex] = useState(0); // Live countdown per question, kept in a ref so ticking never re-renders the page. const timeLeft = useRef({}); @@ -36,11 +39,31 @@ export default function QuizPage({ const sent = useRef({ answers: false, followup: false }); const limit = quiz?.time_limit_seconds ?? 75; + const questions = quiz?.questions ?? []; + const currentQuestion = questions[currentIndex] ?? null; + const isLastQuestion = currentIndex >= questions.length - 1; function markExpired(id) { setExpired((prev) => (prev.has(id) ? prev : new Set(prev).add(id))); } + // Moving on forfeits whatever time is left on the current question; the clock is + // per question, so there is no going back to spend it later. + function advance() { + if (isLastQuestion) { + handleSubmit(); + } else { + setCurrentIndex(currentIndex + 1); + } + } + + // Expiry locks the answer as-is and moves straight on, so the candidate never + // sits on a dead card. Running out on the last question commits the round. + function handleExpire(id) { + markExpired(id); + advance(); + } + function handleReset() { setQuiz(null); setAnswers({}); @@ -49,6 +72,7 @@ export default function QuizPage({ setResult(null); setError(""); setExpired(new Set()); + setCurrentIndex(0); timeLeft.current = {}; inputSignal.current = {}; sent.current = { answers: false, followup: false }; @@ -63,6 +87,7 @@ export default function QuizPage({ setFollowup(null); setFollowupAnswer(""); setExpired(new Set()); + setCurrentIndex(0); timeLeft.current = {}; inputSignal.current = {}; sent.current = { answers: false, followup: false }; @@ -94,6 +119,7 @@ export default function QuizPage({ setFollowup(null); setFollowupAnswer(""); setExpired(new Set()); + setCurrentIndex(0); timeLeft.current = {}; inputSignal.current = {}; sent.current = { answers: false, followup: false }; @@ -155,12 +181,6 @@ export default function QuizPage({ } } - useEffect(() => { - if (quiz && !followup && !result && expired.size >= quiz.questions.length) { - handleSubmit(); - } - }, [expired, quiz, followup, result]); - const followupExpired = followup && expired.has(followup.followup.id); useEffect(() => { if (followupExpired && !result) handleFollowUp(); @@ -270,28 +290,32 @@ export default function QuizPage({ - {quiz.questions.map((q, idx) => ( + {currentQuestion && ( (timeLeft.current[id] = s)} - onExpire={markExpired} + onExpire={handleExpire} onInputSignal={(id, sig) => (inputSignal.current[id] = sig)} onAnswerChange={(id, val) => setAnswers((prev) => ({ ...prev, [id]: val }))} /> - ))} + )}