"use client"; import React, { useEffect, useState, useCallback, useMemo } 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"; import { Bookmark, BookmarkCheck } from "lucide-react"; import { useModal } from "@/context/ModalContext"; import Modal from "@/components/ExamModal"; import { Question } from "@/types/exam"; import QuestionItem from "@/components/QuestionItem"; // 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 }) => { // const [bookmark, setBookmark] = useState(false); // return ( //
//

// {question.id}. {question.question} //

//
//
// //
//
// {Object.entries(question.options).map(([key, value]) => ( // // ))} //
//
// ); // } // ); // QuestionItem.displayName = "QuestionItem"; export default function ExamPage() { // All hooks at the top - no conditional calls const router = useRouter(); const { id } = useParams(); const time = useSearchParams().get("time"); const { isOpen, close } = useModal(); const { setInitialTime, stopTimer } = useTimer(); const { currentAttempt, setAnswer, getAnswer, submitExam: submitExamContext, setApiResponse, isExamStarted, isExamCompleted, isHydrated, isInitialized, currentExam, } = useExam(); // State management const [questions, setQuestions] = useState(null); const [loading, setLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [submissionLoading, setSubmissionLoading] = useState(false); const [componentState, setComponentState] = useState< "loading" | "redirecting" | "ready" >("loading"); // Combined initialization effect useEffect(() => { let mounted = true; const initializeComponent = async () => { // Wait for hydration and initialization if (!isHydrated || !isInitialized || isSubmitting) { return; } // Check exam state and handle redirects if (!isExamStarted()) { if (mounted) { setComponentState("redirecting"); setTimeout(() => { if (mounted) router.push("/unit"); }, 100); } return; } if (isExamCompleted()) { if (mounted) { setComponentState("redirecting"); setTimeout(() => { if (mounted) router.push("/exam/results"); }, 100); } return; } // Component is ready to render if (mounted) { setComponentState("ready"); } }; initializeComponent(); return () => { mounted = false; }; }, [ isHydrated, isInitialized, isExamStarted, isExamCompleted, isSubmitting, router, ]); // Fetch questions effect useEffect(() => { if (componentState !== "ready") return; 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, componentState]); 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 = useCallback(() => { if (window.confirm("Are you sure you want to quit the exam?")) { stopTimer(); router.push("/unit"); } }, [stopTimer, router]); 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); }; }, [showExitDialog]); const answeredSet = useMemo(() => { if (!currentAttempt) return new Set(); return new Set(currentAttempt.answers.map((a) => String(a.questionId))); }, [currentAttempt]); // Show loading/redirecting state if (componentState === "loading" || componentState === "redirecting") { const loadingText = componentState === "redirecting" ? "Redirecting..." : "Loading..."; return (

{loadingText}

); } // Show submission loading if (submissionLoading) { return (

Submitting...

); } // Render the main exam interface return (
{currentAttempt ? (

Questions: {currentExam?.questions.length}

Answers:{" "} {currentAttempt?.answers.length}

Skipped:{" "} {currentExam?.questions.length - currentAttempt?.answers.length}

{currentExam?.questions.map((q, idx) => { const answered = answeredSet.has(String(q.id)); return (
{idx + 1}
); })}
) : (

No attempt data.

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