"use client"; import { useEffect, useState, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Progress } from "@/components/ui/progress"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Label } from "@/components/ui/label"; interface QuizOption { value: string; label: string; } interface QuizQuestion { id: string; type: string; paragraphText: string; question: string; options: QuizOption[]; correctAnswer: string; explanation: string; } interface AnswerFeedback { correct: boolean; correctAnswer: string; explanation: string; score?: number; passed?: boolean; completed: boolean; } interface AnswerRecord { questionId: string; correct: boolean; selectedAnswer: string; correctAnswer: string; explanation: string; } type QuizPhase = "loading" | "ready" | "active" | "feedback" | "results"; const TYPE_LABELS: Record = { "person-vs-function": "Person vs. Function", "materiality-disclaimer": "Materiality Disclaimer", "specificity": "Specificity Level", "spac-exception": "SPAC Exception", }; export default function QuizPage() { const router = useRouter(); const [phase, setPhase] = useState("loading"); const [questions, setQuestions] = useState([]); const [quizSessionId, setQuizSessionId] = useState(""); const [currentIndex, setCurrentIndex] = useState(0); const [selectedAnswer, setSelectedAnswer] = useState(""); const [feedback, setFeedback] = useState(null); const [answers, setAnswers] = useState([]); const [finalScore, setFinalScore] = useState(0); const [finalPassed, setFinalPassed] = useState(false); const [submitting, setSubmitting] = useState(false); useEffect(() => { async function checkStatus() { const res = await fetch("/api/quiz"); if (!res.ok) { router.push("/login"); return; } const data = await res.json(); if (data.hasPassedQuiz) { router.push("/label"); return; } setPhase("ready"); } checkStatus(); }, [router]); const startQuiz = useCallback(async () => { const res = await fetch("/api/quiz", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "start" }), }); if (!res.ok) return; const data = await res.json(); setQuizSessionId(data.quizSessionId); setQuestions(data.questions); setCurrentIndex(0); setSelectedAnswer(""); setFeedback(null); setAnswers([]); setPhase("active"); }, []); const submitAnswer = useCallback(async () => { if (!selectedAnswer || submitting) return; setSubmitting(true); const question = questions[currentIndex]; const res = await fetch("/api/quiz", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "answer", quizSessionId, questionId: question.id, answer: selectedAnswer, }), }); if (!res.ok) { setSubmitting(false); return; } const data: AnswerFeedback = await res.json(); setFeedback(data); setAnswers((prev) => [ ...prev, { questionId: question.id, correct: data.correct, selectedAnswer, correctAnswer: data.correctAnswer, explanation: data.explanation, }, ]); if (data.completed) { setFinalScore(data.score!); setFinalPassed(data.passed!); } setPhase("feedback"); setSubmitting(false); }, [selectedAnswer, submitting, questions, currentIndex, quizSessionId]); const nextQuestion = useCallback(() => { if (feedback?.completed) { setPhase("results"); return; } setCurrentIndex((prev) => prev + 1); setSelectedAnswer(""); setFeedback(null); setPhase("active"); }, [feedback]); if (phase === "loading") { return (

Checking quiz status...

); } if (phase === "ready") { return (
Labeling Quiz Before you begin labeling, you must pass a short quiz testing your knowledge of the labeling codebook. You need at least 7 out of 8 correct answers to pass.

The quiz covers four question types:

  • Person vs. Function {" "} — Distinguishing management role descriptions from risk management process descriptions
  • Materiality Disclaimers {" "} — Identifying materiality assessments vs. cross-references
  • QV Fact Counting {" "} — Determining specificity levels based on verifiable facts
  • SPAC Exceptions {" "} — Recognizing shell company / SPAC disclosures
); } if (phase === "active" || phase === "feedback") { const question = questions[currentIndex]; const progress = ((currentIndex + (phase === "feedback" ? 1 : 0)) / questions.length) * 100; return (
Question {currentIndex + 1} of {questions.length} {TYPE_LABELS[question.type] ?? question.type}

{question.paragraphText}

{question.question}

{question.options.map((option) => { let optionClass = ""; if (phase === "feedback" && feedback) { if (option.value === feedback.correctAnswer) { optionClass = "border-green-500 bg-green-50 dark:bg-green-950/30"; } else if ( option.value === selectedAnswer && !feedback.correct ) { optionClass = "border-red-500 bg-red-50 dark:bg-red-950/30"; } } return (
); })}
{phase === "feedback" && feedback && ( {feedback.correct ? "Correct!" : "Incorrect"} {feedback.explanation} )}
{phase === "active" && ( )} {phase === "feedback" && ( )}
); } if (phase === "results") { const incorrectAnswers = answers.filter((a) => !a.correct); return (
Quiz Results You scored{" "} {finalScore}/{questions.length} {" "} —{" "} {finalPassed ? "Passed!" : "Failed"} {finalPassed ? ( Great work! You demonstrated strong knowledge of the labeling codebook. You may now proceed to labeling. ) : ( <> Not quite there You need at least 7 out of 8 correct to pass. Review the explanations below and try again. {incorrectAnswers.length > 0 && (

Questions you got wrong:

{incorrectAnswers.map((a) => { const question = questions.find( (q) => q.id === a.questionId, ); if (!question) return null; return (
{TYPE_LABELS[question.type] ?? question.type}

{question.paragraphText.slice(0, 150)} {question.paragraphText.length > 150 ? "..." : ""}

Your answer: {a.selectedAnswer} {" "} —{" "} Correct: {a.correctAnswer}

{a.explanation}

); })}
)} )}
{finalPassed ? ( ) : ( )}
); } return null; }