"use client"; import React, { useEffect, useState, useCallback } from "react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useTimer } from "@/context/TimerContext"; import { useExam } from "@/context/ExamContext"; import { API_URL, getToken } from "@/lib/auth"; import Header from "@/components/Header"; // Types interface Question { id: number; question: string; options: Record; } interface QuestionItemProps { question: Question; selectedAnswer?: string; handleSelect: (questionId: number, option: string) => void; } const QuestionItem = React.memo( ({ question, selectedAnswer, handleSelect }) => (

{question.id}. {question.question}

{Object.entries(question.options).map(([key, value]) => ( ))}
) ); QuestionItem.displayName = "QuestionItem"; export default function ExamPage() { const router = useRouter(); const { id } = useParams(); const time = useSearchParams().get("time"); const { setInitialTime, stopTimer } = useTimer(); const { currentAttempt, setAnswer, getAnswer, submitExam: submitExamContext, setApiResponse, isExamStarted, isExamCompleted, isHydrated, isInitialized, currentExam, } = useExam(); const [questions, setQuestions] = useState(null); const [loading, setLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [submissionLoading, setSubmissionLoading] = useState(false); useEffect(() => { console.log( "hydrated:", isHydrated, "initialized:", isInitialized, "exam:", currentExam ); }, [isHydrated, isInitialized, currentExam]); // Initial checks useEffect(() => { if (!isHydrated || !isInitialized || isSubmitting) return; if (!isExamStarted()) return router.push("/unit"); if (isExamCompleted()) return router.push("/exam/results"); }, [ isHydrated, isInitialized, isExamStarted, isExamCompleted, isSubmitting, router, ]); // Fetch questions useEffect(() => { const fetchQuestions = async () => { try { const response = await fetch(`${API_URL}/mock/${id}`); const data = await response.json(); setQuestions(data.questions); } catch (error) { console.error("Error fetching questions:", error); } finally { setLoading(false); } }; fetchQuestions(); if (time) setInitialTime(Number(time)); }, [id, time, setInitialTime]); const handleSelect = useCallback( (questionId: number, option: string) => { setAnswer(questionId.toString(), option); }, [setAnswer] ); const handleSubmit = async () => { if (!currentAttempt) return console.error("No exam attempt found"); stopTimer(); setSubmissionLoading(true); setIsSubmitting(true); const answersForAPI = currentAttempt.answers.reduce( (acc, { questionId, answer }) => { acc[+questionId] = answer; return acc; }, {} as Record ); try { const response = await fetch(`${API_URL}/submit`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${await getToken()}`, }, body: JSON.stringify({ mock_id: id, data: answersForAPI }), }); if (!response.ok) throw new Error((await response.json()).message || "Submission failed"); const responseData = await response.json(); submitExamContext(); setApiResponse(responseData); router.push("/exam/results"); } catch (error) { console.error("Error submitting answers:", error); setIsSubmitting(false); } finally { setSubmissionLoading(false); } }; const showExitDialog = () => { if (window.confirm("Are you sure you want to quit the exam?")) { stopTimer(); router.push("/unit"); } }; useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { e.preventDefault(); e.returnValue = ""; }; const handlePopState = (e: PopStateEvent) => { e.preventDefault(); showExitDialog(); }; window.addEventListener("beforeunload", handleBeforeUnload); window.addEventListener("popstate", handlePopState); return () => { window.removeEventListener("beforeunload", handleBeforeUnload); window.removeEventListener("popstate", handlePopState); }; }, []); if (submissionLoading) { return (

Submitting...

); } return (
{loading ? (
) : (
{questions?.map((q) => ( ))}
)}
); }