fix(nav): fix exam flow navigation

chore(zustand): refactor auth code for zustand store
This commit is contained in:
shafin-r
2025-09-09 20:45:30 +06:00
parent c3ead879ad
commit 108d34988d
11 changed files with 172 additions and 126 deletions

View File

@ -10,7 +10,6 @@ import { useTimerStore } from "@/stores/timerStore";
export default function ExamPage() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const test_id = searchParams.get("test_id") || "";
const type = searchParams.get("type") || "";
@ -23,55 +22,46 @@ export default function ExamPage() {
// Start exam + timer automatically
useEffect(() => {
if (type && test_id) {
startExam(type, test_id).then((fetchedTest) => {
if (fetchedTest?.metadata.time_limit_minutes) {
setStatus("in-progress"); // ✅ make sure exam status is set here
resetTimer(fetchedTest.metadata.time_limit_minutes * 60, () => {
// Timer ended → auto-submit
setStatus("finished");
stopTimer();
submitExam(type);
router.replace(`/exam/results`);
});
}
});
}
if (!type || !test_id) return;
const initExam = async () => {
const fetchedTest = await startExam(type, test_id);
if (!fetchedTest) return;
setStatus("in-progress");
const timeLimit = fetchedTest.metadata.time_limit_minutes;
if (timeLimit) {
resetTimer(timeLimit * 60, async () => {
// Auto-submit when timer ends
stopTimer();
setStatus("finished");
await submitExam(type);
router.replace("/exam/results");
});
}
};
initExam();
}, [
type,
test_id,
startExam,
resetTimer,
stopTimer,
submitExam,
router,
setStatus,
stopTimer,
]);
// useEffect(() => {
// const handlePopState = (event: PopStateEvent) => {
// if (status === "in-progress") {
// const confirmExit = window.confirm(
// "Are you sure you want to quit the exam?"
// );
// if (confirmExit) {
// setStatus("finished");
// stopTimer();
// cancelExam();
// router.replace(`/categories/${type}s`);
// } else {
// // User canceled → push them back to current page
// router.replace(pathname, { scroll: false });
// }
// } else {
// router.replace(`/categories/${type}s`);
// }
// };
// window.addEventListener("popstate", handlePopState);
// return () => window.removeEventListener("popstate", handlePopState);
// }, [status, router, pathname, type, setStatus, stopTimer, cancelExam]);
if (isSubmitting) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<p className="text-lg font-medium text-gray-900">Submitting exam...</p>
</div>
);
}
if (!test) {
return (
@ -85,12 +75,18 @@ export default function ExamPage() {
}
const handleSubmitExam = async (type: string) => {
setIsSubmitting(true);
stopTimer();
try {
setStatus("finished"); // ✅ mark exam finished
stopTimer();
setIsSubmitting(true);
await submitExam(type);
router.replace(`/exam/results`); // ✅ replace to prevent back nav
const result = await submitExam(type); // throws if fails
if (!result) throw new Error("Submission failed");
router.replace("/exam/results"); // navigate
} catch (err) {
console.error("Submit exam failed:", err);
alert("Failed to submit exam. Please try again.");
} finally {
setIsSubmitting(false);
}
@ -137,6 +133,3 @@ export default function ExamPage() {
</div>
);
}
function cancelExam() {
throw new Error("Function not implemented.");
}

View File

@ -13,7 +13,6 @@ import {
import DestructibleAlert from "@/components/DestructibleAlert";
import BackgroundWrapper from "@/components/BackgroundWrapper";
import { API_URL, getToken } from "@/lib/auth";
import { Test } from "@/types/exam";
import { Metadata } from "@/types/exam";
import { useExamStore } from "@/stores/examStore";
@ -21,15 +20,9 @@ function PretestPageContent() {
const router = useRouter();
const searchParams = useSearchParams();
const [examData, setExamData] = useState<Test>();
// Get params from URL search params
const id = searchParams.get("test_id") || "";
const typeParam = searchParams.get("type");
const type =
typeParam === "mock" || typeParam === "subject" || typeParam === "topic"
? typeParam
: null;
const type = searchParams.get("type");
const [metadata, setMetadata] = useState<Metadata | null>(null);
const [loading, setLoading] = useState(true);
@ -56,10 +49,8 @@ function PretestPageContent() {
const data = await questionResponse.json();
const fetchedMetadata: Metadata = data.metadata;
const fetchedQuestions: Test = data.questions;
setMetadata(fetchedMetadata);
setExamData(fetchedQuestions);
} catch (error) {
console.error(error);
setError(error instanceof Error ? error.message : "An error occurred");
@ -122,7 +113,7 @@ function PretestPageContent() {
}
function handleStartExam() {
if (!examData) return;
if (!metadata) return;
setStatus("in-progress");
router.push(
@ -135,7 +126,7 @@ function PretestPageContent() {
<div className="flex-1 overflow-y-auto mb-20">
{metadata ? (
<div className="mx-10 mt-10 gap-6 pb-6 space-y-6">
<button onClick={() => router.back()}>
<button onClick={() => router.replace(`/categories/${type}s`)}>
<ArrowLeft size={30} color="black" />
</button>

View File

@ -11,7 +11,6 @@ import { getResultViews } from "@/lib/gallery-views";
export default function ResultsPage() {
const router = useRouter();
const { result, clearResult, setStatus, status } = useExamStore();
useEffect(() => {
const handlePopState = () => {
if (status !== "finished") {
@ -34,9 +33,8 @@ export default function ResultsPage() {
}
const handleBackToHome = () => {
setStatus("not-started"); // ✅ reset exam flow
clearResult(); // ✅ clear stored results
router.replace("/categories"); // ✅ prevent re-entry
clearResult();
router.replace("/categories");
};
const views = getResultViews(result);