diff --git a/frontend/src/app/collect/page.tsx b/frontend/src/app/collect/page.tsx index 204125f..acb7c5c 100644 --- a/frontend/src/app/collect/page.tsx +++ b/frontend/src/app/collect/page.tsx @@ -6,11 +6,13 @@ import { useDropzone } from "react-dropzone"; import toast from "react-hot-toast"; import api from "@/lib/api"; import { useAuthStore } from "@/lib/store"; +import { useAuth } from "@/hooks/useAuth"; import { SUBJECTS } from "@/types"; import type { MistakeOCRResponse } from "@/types"; export default function CollectPage() { const router = useRouter(); + const { ready } = useAuth(); const isLoggedIn = useAuthStore((s) => s.isLoggedIn); const [subject, setSubject] = useState(""); const [uploading, setUploading] = useState(false); @@ -27,9 +29,9 @@ export default function CollectPage() { const onDrop = useCallback( async (acceptedFiles: File[]) => { - if (!isLoggedIn) { + if (!ready) return null; + // isLoggedIn check removed router.push("/login"); - return; } const file = acceptedFiles[0]; if (!file) return; @@ -103,7 +105,8 @@ export default function CollectPage() { } }; - if (!isLoggedIn) { + if (!ready) return null; + // isLoggedIn check removed router.push("/login"); return null; } diff --git a/frontend/src/app/exam/page.tsx b/frontend/src/app/exam/page.tsx index fa3a009..534c292 100644 --- a/frontend/src/app/exam/page.tsx +++ b/frontend/src/app/exam/page.tsx @@ -5,10 +5,12 @@ import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; import api from "@/lib/api"; import { useAuthStore } from "@/lib/store"; +import { useAuth } from "@/hooks/useAuth"; import { SUBJECTS } from "@/types"; export default function ExamPage() { const router = useRouter(); + const { ready } = useAuth(); const isLoggedIn = useAuthStore((s) => s.isLoggedIn); const [title, setTitle] = useState("错题练习试卷"); const [selectedSubjects, setSelectedSubjects] = useState([]); @@ -18,8 +20,8 @@ export default function ExamPage() { const [pdfUrl, setPdfUrl] = useState(null); useEffect(() => { - if (!isLoggedIn) router.push("/login"); - }, [isLoggedIn]); + // redirect handled by useAuth + }, [ready]); const toggleSubject = (s: string) => { setSelectedSubjects((prev) => @@ -53,7 +55,7 @@ export default function ExamPage() { window.open(downloadUrl, "_blank"); }; - if (!isLoggedIn) return null; + if (!ready) return null; return (
diff --git a/frontend/src/app/mistakes/page.tsx b/frontend/src/app/mistakes/page.tsx index 7f36e1a..37bbedc 100644 --- a/frontend/src/app/mistakes/page.tsx +++ b/frontend/src/app/mistakes/page.tsx @@ -5,11 +5,13 @@ import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; import api from "@/lib/api"; import { useAuthStore } from "@/lib/store"; +import { useAuth } from "@/hooks/useAuth"; import { SUBJECTS, SUBJECT_COLORS } from "@/types"; import type { Mistake, MistakeListResponse, AIAnalysis } from "@/types"; export default function MistakesPage() { const router = useRouter(); + const { ready } = useAuth(); const isLoggedIn = useAuthStore((s) => s.isLoggedIn); const [mistakes, setMistakes] = useState([]); const [total, setTotal] = useState(0); @@ -20,12 +22,12 @@ export default function MistakesPage() { const [analyzingId, setAnalyzingId] = useState(null); useEffect(() => { - if (!isLoggedIn) { + if (!ready) return null; + // isLoggedIn check removed router.push("/login"); - return; } loadMistakes(); - }, [isLoggedIn, page, subject]); + }, [ready, page, subject]); const loadMistakes = async () => { setLoading(true); @@ -72,7 +74,7 @@ export default function MistakesPage() { } }; - if (!isLoggedIn) return null; + if (!ready) return null; const totalPages = Math.ceil(total / 20); diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index e2c35b9..0e1f813 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -4,23 +4,22 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { useAuthStore } from "@/lib/store"; +import { useAuth } from "@/hooks/useAuth"; import api from "@/lib/api"; import type { MistakeStats, ReviewProgressResponse } from "@/types"; export default function DashboardPage() { const router = useRouter(); + const { ready } = useAuth(); const isLoggedIn = useAuthStore((s) => s.isLoggedIn); const displayName = useAuthStore((s) => s.displayName); const [stats, setStats] = useState(null); const [progress, setProgress] = useState(null); useEffect(() => { - if (!isLoggedIn) { - router.push("/login"); - return; - } + if (!ready) return; loadData(); - }, [isLoggedIn]); + }, [ready]); const loadData = async () => { try { @@ -33,7 +32,7 @@ export default function DashboardPage() { } catch {} }; - if (!isLoggedIn) return null; + if (!ready) return null; return (
diff --git a/frontend/src/app/review/page.tsx b/frontend/src/app/review/page.tsx index e615aff..e00009d 100644 --- a/frontend/src/app/review/page.tsx +++ b/frontend/src/app/review/page.tsx @@ -5,11 +5,13 @@ import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; import api from "@/lib/api"; import { useAuthStore } from "@/lib/store"; +import { useAuth } from "@/hooks/useAuth"; import type { ReviewCardResponse, ReviewTodayResponse } from "@/types"; import { REVIEW_QUALITY_LABELS } from "@/types"; export default function ReviewPage() { const router = useRouter(); + const { ready } = useAuth(); const isLoggedIn = useAuthStore((s) => s.isLoggedIn); const [data, setData] = useState(null); const [currentIndex, setCurrentIndex] = useState(0); @@ -20,12 +22,9 @@ export default function ReviewPage() { const [analyzing, setAnalyzing] = useState(false); useEffect(() => { - if (!isLoggedIn) { - router.push("/login"); - return; - } + if (!ready) return; loadToday(); - }, [isLoggedIn]); + }, [ready]); const loadToday = async () => { try { @@ -87,7 +86,7 @@ export default function ReviewPage() { } }; - if (!isLoggedIn) return null; + if (!ready) return null; if (loading) { return ( diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx index 91bd180..504b73a 100644 --- a/frontend/src/app/settings/page.tsx +++ b/frontend/src/app/settings/page.tsx @@ -5,10 +5,12 @@ import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; import api from "@/lib/api"; import { useAuthStore } from "@/lib/store"; +import { useAuth } from "@/hooks/useAuth"; import type { ReviewConfigResponse } from "@/types"; export default function SettingsPage() { const router = useRouter(); + const { ready } = useAuth(); const isLoggedIn = useAuthStore((s) => s.isLoggedIn); const logout = useAuthStore((s) => s.logout); const displayName = useAuthStore((s) => s.displayName); @@ -18,12 +20,12 @@ export default function SettingsPage() { const [mastery, setMastery] = useState(3); useEffect(() => { - if (!isLoggedIn) { + if (!ready) return null; + // isLoggedIn check removed router.push("/login"); - return; } loadConfig(); - }, [isLoggedIn]); + }, [ready]); const loadConfig = async () => { try { @@ -54,7 +56,7 @@ export default function SettingsPage() { router.push("/login"); }; - if (!isLoggedIn) return null; + if (!ready) return null; return (
diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts new file mode 100644 index 0000000..cc90357 --- /dev/null +++ b/frontend/src/hooks/useAuth.ts @@ -0,0 +1,24 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { useAuthStore } from "@/lib/store"; + +export function useAuth() { + const router = useRouter(); + const isLoggedIn = useAuthStore((s) => s.isLoggedIn); + const loadFromStorage = useAuthStore((s) => s.loadFromStorage); + const [ready, setReady] = useState(false); + + useEffect(() => { + if (!isLoggedIn) { + loadFromStorage(); + } + const token = localStorage.getItem("token"); + if (!isLoggedIn && !token) { + router.push("/login"); + } else { + setReady(true); + } + }, [isLoggedIn]); + + return { ready }; +}