From f3173fc429ac84d851c8acfdbec8043ae86b8260 Mon Sep 17 00:00:00 2001 From: Chandan Giri Date: Sun, 7 Jun 2026 11:10:17 +0530 Subject: [PATCH] Fix: Controller.abort()- abort signal fix --- src/app/context/AuthContext.tsx | 37 +- src/app/dashboard/exams/page.tsx | 1324 +++++++++++++++--------------- 2 files changed, 677 insertions(+), 684 deletions(-) diff --git a/src/app/context/AuthContext.tsx b/src/app/context/AuthContext.tsx index b686e4d..4df4111 100644 --- a/src/app/context/AuthContext.tsx +++ b/src/app/context/AuthContext.tsx @@ -7,7 +7,8 @@ const API_BASE = process.env.NEXT_PUBLIC_API_URL || "https://edeziav2.onrender.c const safeFetch = async (url: string, options: RequestInit = {}): Promise => { const isHeavyRequest = url.includes("/upload") || url.includes("/submissions") || url.includes("/runs") || url.includes("/grade"); - const defaultTimeout = isHeavyRequest ? 120000 : 8000; // 120 seconds for AI/upload tasks, 8 seconds for fast auth + const isListRequest = url.includes("/exam-cycles") || url.includes("/question-papers") || url.includes("/classrooms") || url.includes("/schools"); + const defaultTimeout = isHeavyRequest ? 120000 : isListRequest ? 30000 : 8000; // 120s for AI/upload, 30s for list endpoints (cold starts), 8s for auth const timeoutMs = (options as any).timeout !== undefined ? (options as any).timeout : defaultTimeout; const controller = new AbortController(); @@ -23,7 +24,7 @@ const safeFetch = async (url: string, options: RequestInit = {}): Promise 0 ? setTimeout(() => fallbackController.abort(), timeoutMs) : null; @@ -113,7 +114,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (storedToken) { setToken(storedToken); setRefreshToken(typeof window !== "undefined" ? localStorage.getItem("ozymorlab_refresh_token") : null); - + safeFetch(`${API_BASE}/auth/me`, { headers: { Authorization: `Bearer ${storedToken}` }, }) @@ -145,7 +146,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (session) { setToken(session.access_token); setRefreshToken(session.refresh_token || null); - + // Validate token and sync profile with Edexia backend safeFetch(`${API_BASE}/auth/me`, { headers: { Authorization: `Bearer ${session.access_token}` }, @@ -180,7 +181,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (session) { setToken(session.access_token); setRefreshToken(session.refresh_token || null); - + try { const res = await safeFetch(`${API_BASE}/auth/me`, { headers: { Authorization: `Bearer ${session.access_token}` }, @@ -224,7 +225,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (!res.ok) { return { success: false, error: json.detail || "Invalid email or password" }; } - + const { access_token, refresh_token } = json.data; setToken(access_token); setRefreshToken(refresh_token); @@ -232,7 +233,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { localStorage.setItem("ozymorlab_token", access_token); localStorage.setItem("ozymorlab_refresh_token", refresh_token); } - + // Fetch profile const profileRes = await safeFetch(`${API_BASE}/auth/me`, { headers: { Authorization: `Bearer ${access_token}` }, @@ -306,7 +307,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (!res.ok) { return { success: false, error: json.detail || "Account creation failed" }; } - + const { tokens, user: userProfile } = json.data; setToken(tokens.access_token); setRefreshToken(tokens.refresh_token); @@ -446,9 +447,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ gemini_api_key: key }), }); - + console.log("➡️ Response status code:", res.status, res.statusText); - + let responseBody = ""; try { responseBody = await res.text(); @@ -474,9 +475,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { return { success: true }; } catch (err: any) { console.error("❌ hard network failure inside setGeminiKey fetch block:", err); - return { - success: false, - error: `Network error: ${err.message || "Failed to reach backend"}. Please check your browser DevTools Console for CORS preflight blocks or connection drops.` + return { + success: false, + error: `Network error: ${err.message || "Failed to reach backend"}. Please check your browser DevTools Console for CORS preflight blocks or connection drops.` }; } }; @@ -487,7 +488,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { const res = await fetchWithAuth(`${API_BASE}/auth/gemini-key`, { method: "DELETE", }); - + console.log("➡️ Response status code:", res.status); let responseBody = ""; try { @@ -521,7 +522,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { console.log("➡️ refreshUser response status:", res.status); const text = await res.text(); console.log("➡️ refreshUser raw response:", text); - + if (res.ok) { const json = JSON.parse(text); setUser(json.data); diff --git a/src/app/dashboard/exams/page.tsx b/src/app/dashboard/exams/page.tsx index 74405b1..97a5470 100644 --- a/src/app/dashboard/exams/page.tsx +++ b/src/app/dashboard/exams/page.tsx @@ -2,9 +2,9 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; -import { - UploadCloud, CheckCircle2, AlertTriangle, FileText, - Activity, BrainCircuit, ArrowRight, ArrowLeft, Plus, +import { + UploadCloud, CheckCircle2, AlertTriangle, FileText, + Activity, BrainCircuit, ArrowRight, ArrowLeft, Plus, Trash2, FileSpreadsheet, Loader2, Sparkles, Check, Play, Calendar, Layers, ShieldCheck, UserCheck, RefreshCw } from "lucide-react"; @@ -40,7 +40,7 @@ const getThirtyDaysLaterDate = () => { export default function ExamsPage() { const { user, fetchWithAuth } = useAuth(); const router = useRouter(); - + // Student view state const [studentSubject, setStudentSubject] = useState("Physics"); const [studentQPaper, setStudentQPaper] = useState(null); @@ -197,13 +197,13 @@ export default function ExamsPage() { }; // Phase 4: Wizard steps (0: Cycle Selection, 1: Paper Upload, 2: Rubric Edit & State Machine, 3: Bulk Answer Upload, 4: Live Progress) - const [step, setStep] = useState(0); + const [step, setStep] = useState(0); // Exam Cycles State const [cycles, setCycles] = useState([]); const [selectedCycleId, setSelectedCycleId] = useState(""); const [isLoadingCycles, setIsLoadingCycles] = useState(false); - + // Create Cycle Modal State const [newCycleName, setNewCycleName] = useState(""); const [newCycleStart, setNewCycleStart] = useState(""); @@ -278,8 +278,10 @@ export default function ExamsPage() { } else { setSelectedCycleTasks([]); } - } catch (e) { - console.error("Failed to fetch cycle tasks", e); + } catch (e: any) { + if (e?.name !== "AbortError") { + console.error("Failed to fetch cycle tasks", e); + } setSelectedCycleTasks([]); } finally { setIsLoadingTasks(false); @@ -297,8 +299,10 @@ export default function ExamsPage() { setSelectedCycleId(json.data[0].id); } } - } catch (e) { - console.error("Failed to fetch exam cycles", e); + } catch (e: any) { + if (e?.name !== "AbortError") { + console.error("Failed to fetch exam cycles", e); + } } finally { setIsLoadingCycles(false); } @@ -395,7 +399,7 @@ export default function ExamsPage() { setRubricSteps(data.draft_rubric.steps || []); setGradingNotes(data.draft_rubric.grading_notes || ""); setAiConfidence(data.ai_confidence); - + setStep(2); } catch (e: any) { alert(e.message || "An error occurred while processing the question paper."); @@ -560,7 +564,7 @@ export default function ExamsPage() { }; setStep(4); - + // Poll for grading start: retry every 10s until PARSED submissions exist (up to 5 min) const MAX_RETRIES = 30; let attempt = 0; @@ -643,7 +647,7 @@ export default function ExamsPage() { if (user?.role === "student") { return (
- + {/* Page Header */}
- + {/* Subject Select */}