"use client"; import { useEffect, useState, useTransition } from "react"; import Link from "next/link"; import { useParams } from "next/navigation"; import ProgressBar from "@/components/ProgressBar"; import { getPracticeBySlug, mockPracticeModules } from "@/lib/data/mockPractice"; import { getPracticeAttempts, submitPracticeAttempt } from "@/app/(protected)/practice/[slug]/actions"; type AttemptRecord = { id: string; scorePercent: number; correctCount: number; totalQuestions: number; completedAt: Date; }; export default function PracticeExercisePage() { const params = useParams<{ slug: string }>(); const practiceModule = getPracticeBySlug(params.slug); const [started, setStarted] = useState(false); const [index, setIndex] = useState(0); const [score, setScore] = useState(0); const [finished, setFinished] = useState(false); const [selected, setSelected] = useState(null); const [selectedAnswers, setSelectedAnswers] = useState([]); const [attempts, setAttempts] = useState([]); const [isSaving, startTransition] = useTransition(); const loadAttempts = () => { if (!practiceModule) return; startTransition(async () => { const result = await getPracticeAttempts(practiceModule.slug); setAttempts(result as AttemptRecord[]); }); }; useEffect(() => { loadAttempts(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [practiceModule?.slug]); if (!practiceModule) { return (

Practice module not found

The requested practice slug does not exist in mock data.

Back to practice
); } if (!practiceModule.isInteractive || !practiceModule.questions?.length) { return (

{practiceModule.title}

This practice module is scaffolded and will be enabled in a later iteration.

); } const total = practiceModule.questions.length; const current = practiceModule.questions[index]; const progress = Math.round((index / total) * 100); const pick = (choiceIndex: number) => { if (selected !== null) return; setSelected(choiceIndex); if (choiceIndex === current.answerIndex) { setScore((value) => value + 1); } }; const next = () => { if (selected === null) return; setSelectedAnswers((prev) => { const nextAnswers = [...prev]; nextAnswers[index] = selected; return nextAnswers; }); if (index + 1 >= total) { const finalAnswers = [...selectedAnswers]; finalAnswers[index] = selected; startTransition(async () => { await submitPracticeAttempt({ slug: practiceModule.slug, selectedAnswers: finalAnswers, }); loadAttempts(); }); setFinished(true); return; } setIndex((value) => value + 1); setSelected(null); }; const restart = () => { setStarted(true); setIndex(0); setScore(0); setSelected(null); setSelectedAnswers([]); setFinished(false); }; const start = () => { setStarted(true); setFinished(false); setIndex(0); setScore(0); setSelected(null); setSelectedAnswers([]); }; if (finished) { return (

Practice Results

Exercise complete

Final score: {score}/{total}

Back to modules

Attempt History

{attempts.length === 0 ? (

No attempts recorded yet.

) : (
    {attempts.map((attempt) => (
  • Score {attempt.correctCount}/{attempt.totalQuestions} ({attempt.scorePercent}%) on{" "} {new Date(attempt.completedAt).toLocaleString()}
  • ))}
)}
); } if (!started) { return (

Practice Session

{practiceModule.title}

{practiceModule.description}

Questions

{total}

Estimated time

{Math.max(3, total * 2)} min

Difficulty

{practiceModule.difficulty ?? "Intermediate"}

Back to modules

Attempt History

{attempts.length === 0 ? (

No attempts recorded yet for this module.

) : (
    {attempts.map((attempt) => (
  • Score {attempt.correctCount}/{attempt.totalQuestions} ({attempt.scorePercent}%) on{" "} {new Date(attempt.completedAt).toLocaleString()}
  • ))}
)}
); } return (

{practiceModule.title} | Question {index + 1} / {total}

Score: {score}/{total}

{mockPracticeModules.map((module, moduleIndex) => { const isActive = module.slug === practiceModule.slug; return (
{moduleIndex === 0 ? "A/" : moduleIndex === 1 ? "[]" : "O"}

{module.title}

{module.description}

); })}

Attempt History

{attempts.length === 0 ? (

No attempts recorded yet.

) : (
    {attempts.map((attempt) => (
  • Score {attempt.correctCount}/{attempt.totalQuestions} ({attempt.scorePercent}%) on{" "} {new Date(attempt.completedAt).toLocaleString()}
  • ))}
)}

Prompt

{current.prompt.replace("Spanish term: ", "")}

{current.choices.map((choice, choiceIndex) => { const isPicked = selected === choiceIndex; const isCorrect = choiceIndex === current.answerIndex; const stateClass = selected === null ? "border-slate-300 hover:bg-slate-50" : isCorrect ? "border-[#2f9d73] bg-[#edf9f4]" : isPicked ? "border-[#bf3c5f] bg-[#fff1f4]" : "border-slate-300"; return ( ); })}
); }