"use client"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import React, { useEffect, useState, useCallback, useReducer } from "react"; import { useTimer } from "@/context/TimerContext"; import { API_URL, getToken } from "@/lib/auth"; // Types interface Question { id: number; question: string; options: Record; } interface QuestionItemProps { question: Question; selectedAnswer: string | undefined; handleSelect: (questionId: number, option: string) => void; } interface AnswerState { [questionId: number]: string; } interface AnswerAction { type: "SELECT_ANSWER"; questionId: number; option: string; } // Components const QuestionItem = React.memo( ({ question, selectedAnswer, handleSelect }) => (

{question.id}. {question.question}

{Object.entries(question.options).map(([key, value]) => ( ))}
) ); QuestionItem.displayName = "QuestionItem"; const reducer = (state: AnswerState, action: AnswerAction): AnswerState => { switch (action.type) { case "SELECT_ANSWER": return { ...state, [action.questionId]: action.option }; default: return state; } }; export default function ExamPage() { const router = useRouter(); const params = useParams(); const searchParams = useSearchParams(); const id = params.id as string; const time = searchParams.get("time"); const { setInitialTime, stopTimer } = useTimer(); const [questions, setQuestions] = useState(null); const [answers, dispatch] = useReducer(reducer, {}); const [loading, setLoading] = useState(true); const [submissionLoading, setSubmissionLoading] = useState(false); const fetchQuestions = async () => { try { const response = await fetch(`${API_URL}/mock/${id}`, { method: "GET", }); const data = await response.json(); setQuestions(data.questions); } catch (error) { console.error("Error fetching questions:", error); } finally { setLoading(false); } }; useEffect(() => { fetchQuestions(); if (time) { setInitialTime(Number(time)); } }, [id, time, setInitialTime]); const handleSelect = useCallback((questionId: number, option: string) => { dispatch({ type: "SELECT_ANSWER", questionId, option }); }, []); const handleSubmit = async () => { stopTimer(); setSubmissionLoading(true); const payload = { mock_id: id, data: answers, }; try { const response = await fetch(`${API_URL}/submit`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${await getToken()}`, }, body: JSON.stringify(payload), }); if (!response.ok) { const errorData = await response.json(); console.error( "Submission failed:", errorData.message || "Unknown error" ); return; } const responseData = await response.json(); router.push( `/exam/results?id=${id}&answers=${encodeURIComponent( JSON.stringify(responseData) )}` ); } catch (error) { console.error("Error submitting answers:", error); } finally { setSubmissionLoading(false); } }; const showExitDialog = () => { if (window.confirm("Are you sure you want to quit the exam?")) { stopTimer(); router.push("/unit"); } }; // Handle browser back button useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { e.preventDefault(); e.returnValue = ""; return ""; }; 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((question) => ( ))}
)}
); }