Files
examjam-frontend/app/exam/[id]/page.tsx

110 lines
3.5 KiB
TypeScript

"use client";
import React, { useEffect, useCallback, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Header from "@/components/Header";
import QuestionItem from "@/components/QuestionItem";
import BackgroundWrapper from "@/components/BackgroundWrapper";
import { useExamStore } from "@/stores/examStore";
import { useTimerStore } from "@/stores/timerStore";
export default function ExamPage() {
const router = useRouter();
const searchParams = useSearchParams();
const test_id = searchParams.get("test_id") || "";
const type = searchParams.get("type") || "";
const { test, answers, startExam, setAnswer, submitExam, cancelExam } =
useExamStore();
const { resetTimer, stopTimer } = useTimerStore();
const [isSubmitting, setIsSubmitting] = useState(false);
// Start exam + timer automatically
useEffect(() => {
if (type && test_id) {
startExam(type, test_id).then((fetchedTest) => {
if (fetchedTest?.metadata.time_limit_minutes) {
resetTimer(fetchedTest.metadata.time_limit_minutes * 60, () => {
// Timer ended → auto-submit exam
stopTimer();
submitExam(type);
router.push(`/exam/results`);
});
}
});
}
}, [type, test_id, startExam, resetTimer, submitExam, router]);
const showExitDialog = useCallback(() => {
if (window.confirm("Are you sure you want to quit the exam?")) {
stopTimer();
cancelExam();
router.push(`/categories/${type}s`);
}
}, [stopTimer, cancelExam, router, type]);
if (!test) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="flex flex-col items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-900 mb-4"></div>
<p className="text-lg font-medium text-gray-900">Loading exam...</p>
</div>
</div>
);
}
const handleSubmitExam = async (type: string) => {
try {
stopTimer();
setIsSubmitting(true);
await submitExam(type);
router.push(`/exam/results`);
} finally {
setIsSubmitting(false);
}
};
return (
<div className="min-h-screen bg-gray-50">
{/* Header with live timer */}
<Header />
{/* Questions */}
<BackgroundWrapper>
<div className="container mx-auto px-6 py-8 mb-20">
{test.questions.map((q, idx) => (
<div id={`question-${idx}`} key={q.question_id}>
<QuestionItem
question={q}
index={idx}
selectedAnswer={answers[idx]}
onSelect={(answer) => setAnswer(idx, answer)}
/>
</div>
))}
{/* Bottom submit bar */}
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 flex">
<button
onClick={() => handleSubmitExam(type)}
disabled={isSubmitting}
className="flex-1 bg-blue-900 text-white p-6 font-bold text-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-blue-800 transition-colors flex justify-center items-center gap-2"
>
{isSubmitting ? (
<>
<span className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></span>
Submitting...
</>
) : (
"Submit"
)}
</button>
</div>
</div>
</BackgroundWrapper>
</div>
);
}