feat(error): add error handling on test screen

This commit is contained in:
shafin-r
2026-02-07 20:21:47 +06:00
parent c9db96f97f
commit 8cfcb11f0a
4 changed files with 52 additions and 48 deletions

View File

@ -18,9 +18,12 @@ import {
} from "../../components/ui/card"; } from "../../components/ui/card";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useExamConfigStore } from "../../stores/useExamConfigStore";
export const Practice = () => { export const Practice = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const userXp = useExamConfigStore.getState().userXp;
return ( return (
<main className="h-fit max-w-7xl mx-auto px-8 sm:px-6 lg:px-8 py-8 space-y-4"> <main className="h-fit max-w-7xl mx-auto px-8 sm:px-6 lg:px-8 py-8 space-y-4">
<header className="flex justify-between items-center"> <header className="flex justify-between items-center">
@ -29,7 +32,7 @@ export const Practice = () => {
</div> </div>
<div className="bg-purple-100 rounded-full w-fit py-2 px-4 flex items-center gap-2"> <div className="bg-purple-100 rounded-full w-fit py-2 px-4 flex items-center gap-2">
<div className="h-2 w-2 bg-linear-to-br from-purple-400 to-purple-500 rounded-full"></div> <div className="h-2 w-2 bg-linear-to-br from-purple-400 to-purple-500 rounded-full"></div>
<span className="font-satoshi-bold text-md">0</span> <span className="font-satoshi-bold text-md">{userXp}</span>
</div> </div>
</header> </header>
<section> <section>

View File

@ -37,6 +37,7 @@ import type { Leaderboard } from "../../types/leaderboard";
import { api } from "../../utils/api"; import { api } from "../../utils/api";
import { Card, CardContent } from "../../components/ui/card"; import { Card, CardContent } from "../../components/ui/card";
import { LeaderboardRowSkeleton } from "../../components/LeaderboardSkeleton"; import { LeaderboardRowSkeleton } from "../../components/LeaderboardSkeleton";
import { useExamConfigStore } from "../../stores/useExamConfigStore";
export const Rewards = () => { export const Rewards = () => {
const user = useAuthStore((state) => state.user); const user = useAuthStore((state) => state.user);
@ -45,6 +46,8 @@ export const Rewards = () => {
const [leaderboard, setLeaderboard] = useState<Leaderboard>(); const [leaderboard, setLeaderboard] = useState<Leaderboard>();
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const { setUserXp } = useExamConfigStore();
useEffect(() => { useEffect(() => {
const fetchLeaderboard = async () => { const fetchLeaderboard = async () => {
if (!user) return; if (!user) return;
@ -64,6 +67,7 @@ export const Rewards = () => {
const response = await api.fetchLeaderboard(token); const response = await api.fetchLeaderboard(token);
setLeaderboard(response); setLeaderboard(response);
setUserXp(response.user_rank.total_xp);
setLoading(false); setLoading(false);
} catch (error) { } catch (error) {
setLoading(false); setLoading(false);

View File

@ -6,7 +6,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "../../../components/ui/card"; } from "../../../components/ui/card";
import { Check, Loader2 } from "lucide-react"; import { Check, Loader2, Unplug } from "lucide-react";
import { api } from "../../../utils/api"; import { api } from "../../../utils/api";
import { useAuthStore } from "../../../stores/authStore"; import { useAuthStore } from "../../../stores/authStore";
@ -46,6 +46,7 @@ export const Test = () => {
const [eliminated, setEliminated] = useState<Record<string, Set<string>>>({}); const [eliminated, setEliminated] = useState<Record<string, Set<string>>>({});
const [showExitDialog, setShowExitDialog] = useState(false); const [showExitDialog, setShowExitDialog] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (blocker.state === "blocked") { if (blocker.state === "blocked") {
@ -94,6 +95,7 @@ export const Test = () => {
// ✅ NOW start module phase // ✅ NOW start module phase
useSatExam.getState().startExam(); useSatExam.getState().startExam();
} catch (error) { } catch (error) {
setError(`Failed to start exam session: ${error}`);
console.error("Failed to start exam session:", error); console.error("Failed to start exam session:", error);
} }
}; };
@ -308,6 +310,36 @@ export const Test = () => {
switch (phase) { switch (phase) {
case "IDLE": case "IDLE":
if (error) {
return (
<main className="min-h-screen px-8 py-8 w-full space-y-6 flex flex-col justify-center">
<Card className="p-4 bg-red-100 ring-1 ring-red-500 w-full">
<CardContent className="flex items-center justify-between w-full gap-4">
<Unplug size={80} color="red" />
<span className="font-satoshi-bold text-red-500">{error}</span>
</CardContent>
</Card>
<div className="flex justify-between gap-4">
<button
onClick={() => {
useExamConfigStore.getState().clearPayload();
navigate(`/student/home`);
}}
className="font-satoshi rounded-3xl text-lg w-full py-4 bg-linear-to-br from-slate-500 to-slate-600 text-white active:bg-linear-to-br active:from-slate-600 active:to-slate-700 "
>
Go Back
</button>
<button
onClick={() => startExam()}
className="font-satoshi rounded-3xl text-lg w-full py-4 bg-linear-to-br from-purple-500 to-purple-600 text-white active:bg-linear-to-br active:from-purple-600 active:to-purple-700 "
>
Retry
</button>
</div>
</main>
);
}
return ( return (
<main className="min-h-screen px-8 py-8 w-full space-y-6 flex flex-col items-center justify-center"> <main className="min-h-screen px-8 py-8 w-full space-y-6 flex flex-col items-center justify-center">
<Card className=""> <Card className="">
@ -315,46 +347,6 @@ export const Test = () => {
<CardTitle className="font-satoshi text-4xl"> <CardTitle className="font-satoshi text-4xl">
Ready to begin your test? Ready to begin your test?
</CardTitle> </CardTitle>
{/* <CardDescription>
<section className="flex justify-between gap-6 px-4">
<div className="flex flex-col justify-center items-center gap-4">
<div className="w-fit bg-cyan-100 p-2 rounded-full">
<Clock size={30} color="oklch(60.9% 0.126 221.723)" />
</div>
<div className="flex flex-col justify-center items-center">
<h3 className="text-xl font-satoshi-bold text-black">
{practiceSheet?.time_limit}
</h3>
<p className="text-md font-satoshi ">Minutes</p>
</div>
</div>
<div className="flex flex-col justify-center items-center gap-4">
<div className="w-fit bg-lime-100 p-2 rounded-full">
<CircleQuestionMark
size={30}
color="oklch(64.8% 0.2 131.684)"
/>
</div>
<div className="flex flex-col justify-center items-center">
<h3 className="text-xl font-satoshi-bold text-black">
{practiceSheet?.questions_count}
</h3>
<p className="text-md font-satoshi ">Questions</p>
</div>
</div>
<div className="flex flex-col justify-center items-center gap-4">
<div className="w-fit bg-amber-100 p-2 rounded-full">
<Layers size={30} color="oklch(66.6% 0.179 58.318)" />
</div>
<div className="flex flex-col justify-center items-center">
<h3 className="text-xl font-satoshi-bold text-black">
{practiceSheet?.modules.length}
</h3>
<p className="text-md font-satoshi ">Modules</p>
</div>
</div>
</section>
</CardDescription> */}
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<h2 className="font-satoshi-bold text-2xl">Before you begin:</h2> <h2 className="font-satoshi-bold text-2xl">Before you begin:</h2>
@ -377,12 +369,7 @@ export const Test = () => {
Your progress will be saved automatically Your progress will be saved automatically
</span> </span>
</div> </div>
<div className="flex items-center gap-4">
<Check size={24} color="oklch(62.7% 0.265 303.9)" />
<span className="font-satoshi">
You can take breaks using the "More" menu in the top right
</span>
</div>
<Button <Button
onClick={() => startExam()} onClick={() => startExam()}
variant="outline" variant="outline"

View File

@ -5,6 +5,7 @@ import type { StartExamPayload, ExamMode } from "../types/test";
interface ExamConfigState { interface ExamConfigState {
payload: StartExamPayload | null; payload: StartExamPayload | null;
userXp: number;
setSheetId: (id: string) => void; setSheetId: (id: string) => void;
storeTopics: (ids: string[]) => void; storeTopics: (ids: string[]) => void;
@ -14,6 +15,7 @@ interface ExamConfigState {
storeDuration: (minutes: number) => void; storeDuration: (minutes: number) => void;
setMode: (mode: ExamMode) => void; setMode: (mode: ExamMode) => void;
setSection: (section: string) => void; setSection: (section: string) => void;
setUserXp: (section: number) => void;
clearPayload: () => void; clearPayload: () => void;
} }
@ -22,6 +24,7 @@ export const useExamConfigStore = create<ExamConfigState>()(
persist( persist(
(set, get) => ({ (set, get) => ({
payload: null, payload: null,
userXp: 0,
setSheetId: (sheet_id) => setSheetId: (sheet_id) =>
set({ set({
@ -45,6 +48,13 @@ export const useExamConfigStore = create<ExamConfigState>()(
section, section,
} as StartExamPayload, } as StartExamPayload,
}), }),
setUserXp: (userXp) =>
set({
payload: {
...(get().payload ?? {}),
userXp,
} as StartExamPayload,
}),
setDifficulty: (difficulty) => setDifficulty: (difficulty) =>
set({ set({