From 6531e3318dfb8fc44855bd3620b46800e53d875d Mon Sep 17 00:00:00 2001 From: Reviflow Bot Date: Thu, 5 Feb 2026 22:00:52 +0100 Subject: [PATCH] feat(quiz): implement two-step answer validation logic --- frontend/src/features/quiz/QuestionCard.tsx | 106 ++++++++++++-------- frontend/src/features/quiz/QuizPage.tsx | 34 +++++-- 2 files changed, 93 insertions(+), 47 deletions(-) diff --git a/frontend/src/features/quiz/QuestionCard.tsx b/frontend/src/features/quiz/QuestionCard.tsx index 117079f..3f7213e 100644 --- a/frontend/src/features/quiz/QuestionCard.tsx +++ b/frontend/src/features/quiz/QuestionCard.tsx @@ -10,6 +10,7 @@ interface QuestionCardProps { currentStep: number; totalSteps: number; explanation?: string; + isValidated: boolean; // New prop } export function QuestionCard({ @@ -20,24 +21,22 @@ export function QuestionCard({ onSelectOption, currentStep, totalSteps, - explanation + explanation, + isValidated // Destructure new prop }: QuestionCardProps) { const [animation, setAnimation] = useState<'pop' | 'shake' | null>(null); useEffect(() => { - if (selectedOption !== null) { + if (isValidated && selectedOption !== null) { if (selectedOption === correctAnswer) { setAnimation('pop'); } else { setAnimation('shake'); } - // Removed auto-hide timer for animation to keep state clear, - // but we might want to keep the effect. - // The user wants "time to read", so we shouldn't auto-advance too fast in parent. const timer = setTimeout(() => setAnimation(null), 500); return () => clearTimeout(timer); } - }, [selectedOption, correctAnswer]); + }, [isValidated, selectedOption, correctAnswer]); // Trigger on validation const isCorrect = selectedOption === correctAnswer; @@ -60,42 +59,69 @@ export function QuestionCard({

{question}

- {options.map((option, index) => ( - - ))} + {options.map((option, index) => { + // Logic for visual states + let borderClass = 'border-slate-100 dark:border-slate-700'; + let bgClass = 'hover:bg-indigo-50/30 dark:hover:bg-indigo-900/20'; + let textClass = ''; + let opacityClass = ''; + + if (selectedOption === index) { + if (!isValidated) { + // Selected but not validated: Blue/Neutral + borderClass = 'border-indigo-500 dark:border-indigo-400'; + bgClass = 'bg-indigo-50 dark:bg-indigo-900/20'; + textClass = 'text-indigo-700 dark:text-indigo-300'; + } else { + // Validated: Green or Red + if (index === correctAnswer) { + borderClass = 'border-green-500'; + bgClass = 'bg-green-50 dark:bg-green-900/20'; + textClass = 'text-green-700 dark:text-green-400'; + } else { + borderClass = 'border-orange-400'; + bgClass = 'bg-orange-50 dark:bg-orange-900/20'; + textClass = 'text-orange-700 dark:text-orange-400'; + } + } + } else if (isValidated && index === correctAnswer) { + // Show correct answer if user got it wrong + borderClass = 'border-green-500'; + bgClass = 'bg-green-50/50 dark:bg-green-900/10'; + textClass = 'text-green-700 dark:text-green-400'; + } else if (isValidated) { + // Fade out other options when validated + opacityClass = 'opacity-50'; + } + + return ( + + ); + })}
- {selectedOption !== null && ( + {isValidated && selectedOption !== null && (
diff --git a/frontend/src/features/quiz/QuizPage.tsx b/frontend/src/features/quiz/QuizPage.tsx index b3d9b2d..e11c586 100644 --- a/frontend/src/features/quiz/QuizPage.tsx +++ b/frontend/src/features/quiz/QuizPage.tsx @@ -37,6 +37,7 @@ export function QuizPage() { const [quizData, setQuizData] = useState(null); const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [selectedOption, setSelectedOption] = useState(null); + const [isValidated, setIsValidated] = useState(false); // New state for validation const [score, setScore] = useState(0); const [answers, setAnswers] = useState([]); // Track details const [isFinished, setIsFinished] = useState(false); @@ -129,19 +130,26 @@ export function QuizPage() { if (currentQuestionIndex < (quizData?.questions.length || 0) - 1) { setCurrentQuestionIndex(prev => prev + 1); setSelectedOption(null); + setIsValidated(false); // Reset validation for next question } else { setIsFinished(true); } }; + // Change: Selected option just updates state, doesn't lock yet const handleOptionSelect = (index: number) => { - if (selectedOption !== null) return; // Prevent changing answer + if (isValidated) return; // Prevent changing answer if already validated setSelectedOption(index); + }; - if (!quizData) return; + // New: Validate the answer + const handleValidate = () => { + if (selectedOption === null || isValidated || !quizData) return; + + setIsValidated(true); const currentQ = quizData.questions[currentQuestionIndex]; - const isCorrect = index === currentQ.correct_answer; + const isCorrect = selectedOption === currentQ.correct_answer; if (isCorrect) { setScore(prev => prev + 1); @@ -150,7 +158,7 @@ export function QuizPage() { // Record answer details setAnswers(prev => [...prev, { question: currentQ.question, - user_answer: currentQ.options[index], + user_answer: currentQ.options[selectedOption], correct_answer: currentQ.options[currentQ.correct_answer], is_correct: isCorrect, original_content: null @@ -311,13 +319,25 @@ export function QuizPage() { currentStep={currentQuestionIndex + 1} totalSteps={quizData.questions.length} explanation={quizData.questions[currentQuestionIndex].explanation} + isValidated={isValidated} // Pass validation state /> - {/* NEXT BUTTON */} - {selectedOption !== null && ( + {/* VALIDATE BUTTON */} + {selectedOption !== null && !isValidated && ( + + )} + + {/* NEXT BUTTON (Only show after validation) */} + {isValidated && (