From d42a42a8d18881441307ccdecdb0adaf82a255e2 Mon Sep 17 00:00:00 2001 From: shafin-r Date: Mon, 7 Jul 2025 21:01:11 +0600 Subject: [PATCH] fix(exam): fix pretest screen start exam button --- app/(tabs)/paper/page.tsx | 3 +- app/exam/[id]/page.tsx | 147 +++++++++++++++----------------------- app/exam/pretest/page.tsx | 35 ++++++--- app/exam/results/page.tsx | 46 +++++++----- app/layout.tsx | 2 +- app/page.tsx | 2 +- components/Header.tsx | 3 + context/ExamContext.tsx | 10 ++- public/next.svg | 1 - types/exam.d.ts | 1 + 10 files changed, 122 insertions(+), 128 deletions(-) delete mode 100644 public/next.svg diff --git a/app/(tabs)/paper/page.tsx b/app/(tabs)/paper/page.tsx index bbc1322..c2c7214 100644 --- a/app/(tabs)/paper/page.tsx +++ b/app/(tabs)/paper/page.tsx @@ -32,7 +32,6 @@ export default function PaperScreen() { method: "GET", }); const fetchedQuestionData: Mock[] = await questionResponse.json(); - console.log(fetchedQuestionData[0]?.id); setQuestions(fetchedQuestionData); } catch (error) { setErrorMsg(error instanceof Error ? error.message : "An error occurred"); @@ -93,6 +92,8 @@ export default function PaperScreen() { displayTabTitle={null} displayUser={false} displaySubject={name} + image={undefined} + examDuration={undefined} />
diff --git a/app/exam/[id]/page.tsx b/app/exam/[id]/page.tsx index 38102ac..9a41f9d 100644 --- a/app/exam/[id]/page.tsx +++ b/app/exam/[id]/page.tsx @@ -1,7 +1,7 @@ "use client"; -import { useParams, useRouter, useSearchParams } from "next/navigation"; 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"; @@ -16,11 +16,10 @@ interface Question { interface QuestionItemProps { question: Question; - selectedAnswer: string | undefined; + selectedAnswer?: string; handleSelect: (questionId: number, option: string) => void; } -// Components const QuestionItem = React.memo( ({ question, selectedAnswer, handleSelect }) => (
@@ -55,16 +54,10 @@ QuestionItem.displayName = "QuestionItem"; export default function ExamPage() { const router = useRouter(); - const params = useParams(); - const searchParams = useSearchParams(); - const [isSubmitting, setIsSubmitting] = useState(false); - - const id = params.id as string; - const time = searchParams.get("time"); + const { id } = useParams(); + const time = useSearchParams().get("time"); const { setInitialTime, stopTimer } = useTimer(); - - // Use exam context instead of local state const { currentAttempt, setAnswer, @@ -75,85 +68,77 @@ export default function ExamPage() { 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); - // Check if exam is properly started useEffect(() => { - if (!isHydrated) return; - if (!isInitialized) return; - if (isSubmitting) return; // Don't redirect while submitting + console.log( + "hydrated:", + isHydrated, + "initialized:", + isInitialized, + "exam:", + currentExam + ); + }, [isHydrated, isInitialized, currentExam]); - if (!isExamStarted()) { - router.push("/unit"); - return; - } - - if (isExamCompleted()) { - router.push("/exam/results"); - return; - } + // 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, - router, - isInitialized, isSubmitting, + router, ]); - 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); - } - }; - + // 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)); - } + if (time) setInitialTime(Number(time)); }, [id, time, setInitialTime]); const handleSelect = useCallback( (questionId: number, option: string) => { - // Store answer in context instead of local reducer setAnswer(questionId.toString(), option); }, [setAnswer] ); const handleSubmit = async () => { - if (!currentAttempt) { - console.error("No exam attempt found"); - return; - } + if (!currentAttempt) return console.error("No exam attempt found"); stopTimer(); setSubmissionLoading(true); - setIsSubmitting(true); // Add this line + setIsSubmitting(true); - // Convert context answers to the format your API expects - const answersForAPI = currentAttempt.answers.reduce((acc, answer) => { - acc[parseInt(answer.questionId)] = answer.answer; - return acc; - }, {} as Record); - - const payload = { - mock_id: id, - data: answersForAPI, - }; + const answersForAPI = currentAttempt.answers.reduce( + (acc, { questionId, answer }) => { + acc[+questionId] = answer; + return acc; + }, + {} as Record + ); try { const response = await fetch(`${API_URL}/submit`, { @@ -162,33 +147,19 @@ export default function ExamPage() { "Content-Type": "application/json", Authorization: `Bearer ${await getToken()}`, }, - body: JSON.stringify(payload), + body: JSON.stringify({ mock_id: id, data: answersForAPI }), }); - if (!response.ok) { - const errorData = await response.json(); - console.error( - "Submission failed:", - errorData.message || "Unknown error" - ); - setIsSubmitting(false); // Reset on error - return; - } + if (!response.ok) + throw new Error((await response.json()).message || "Submission failed"); const responseData = await response.json(); - - // Submit exam in context (this will store the completed attempt) - const completedAttempt = submitExamContext(); - - // Store API response in context for results page + submitExamContext(); setApiResponse(responseData); - - // Navigate to results without URL parameters router.push("/exam/results"); - console.log("I'm here"); } catch (error) { console.error("Error submitting answers:", error); - setIsSubmitting(false); // Reset on error + setIsSubmitting(false); } finally { setSubmissionLoading(false); } @@ -201,12 +172,10 @@ export default function ExamPage() { } }; - // Handle browser back button useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { e.preventDefault(); e.returnValue = ""; - return ""; }; const handlePopState = (e: PopStateEvent) => { @@ -225,12 +194,10 @@ export default function ExamPage() { if (submissionLoading) { return ( -
-
-
-
-

Submitting...

-
+
+
+
+

Submitting...

); @@ -252,11 +219,11 @@ export default function ExamPage() {
) : (
- {questions?.map((question) => ( + {questions?.map((q) => ( ))} diff --git a/app/exam/pretest/page.tsx b/app/exam/pretest/page.tsx index ad73818..d9c1290 100644 --- a/app/exam/pretest/page.tsx +++ b/app/exam/pretest/page.tsx @@ -7,6 +7,7 @@ import DestructibleAlert from "@/components/DestructibleAlert"; import BackgroundWrapper from "@/components/BackgroundWrapper"; import { API_URL } from "@/lib/auth"; import { useExam } from "@/context/ExamContext"; +import { Exam } from "@/types/exam"; interface Metadata { metadata: { @@ -20,7 +21,7 @@ interface Metadata { export default function PretestPage() { const router = useRouter(); const searchParams = useSearchParams(); - const [examData, setExamData] = useState(); + const [examData, setExamData] = useState(); const { startExam, setCurrentExam } = useExam(); // Get params from URL search params @@ -41,14 +42,13 @@ export default function PretestPage() { const questionResponse = await fetch(`${API_URL}/mock/${id}`, { method: "GET", }); - const data = await questionResponse.json(); - console.log(data); - if (!questionResponse.ok) { throw new Error("Failed to fetch questions"); } - setExamData(data); + const data = await questionResponse.json(); const fetchedMetadata: Metadata = data; + + setExamData(data); setMetadata(fetchedMetadata); } catch (error) { console.error(error); @@ -79,6 +79,25 @@ export default function PretestPage() { ); } + const { isHydrated, isInitialized, currentExam } = useExam(); + + useEffect(() => { + console.log( + "hydrated:", + isHydrated, + "initialized:", + isInitialized, + "exam:", + currentExam + ); + }, [isHydrated, isInitialized, currentExam]); + + function handleStartExam() { + console.log(id); + setCurrentExam(examData); + startExam(); + router.push(`/exam/${id}?time=${metadata?.metadata.duration}`); + } return ( @@ -176,11 +195,7 @@ export default function PretestPage() {
+
+
+

Loading...

+
); @@ -85,8 +81,12 @@ export default function ResultsPage() { const apiResponse = getApiResponse(); const handleBackToHome = () => { - router.push("/unit"); clearExam(); + + // Give time for state to fully reset before pushing new route + setTimeout(() => { + router.push("/unit"); + }, 400); // 50–100ms is usually enough }; const timeTaken = @@ -100,18 +100,24 @@ export default function ResultsPage() { return (
-
+ +

Keep up the good work!

{/* Score Display */} -
-
-
- {examResults.score}% +
+
+
Accuracy:
+
+ {((examResults.score / examResults.totalQuestions) * 100).toFixed( + 1 + )} + %
-
Final Score
@@ -122,7 +128,11 @@ export default function ResultsPage() {
{apiResponse.questions?.map((question) => ( - + ))}
diff --git a/app/layout.tsx b/app/layout.tsx index aada37a..bc4ac14 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -15,7 +15,7 @@ const montserrat = Montserrat({ export const metadata: Metadata = { title: "ExamJam", - description: "Your exam preparation platform", + description: "The best place to prepare for your exams!", }; export default function RootLayout({ diff --git a/app/page.tsx b/app/page.tsx index fd7f826..85b4521 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -58,7 +58,7 @@ export default function Home() { {/* Action Buttons */}