import { useQuery, useQueryClient } from "@tanstack/react-query"; import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; import { Navigate, useLocation } from "react-router-dom"; import { api, getToken, LOGOUT_EVENT, setToken } from "../api/client"; import type { Me } from "../api/types"; interface AuthState { user: Me | null; isAdmin: boolean; ready: boolean; login: (u: string, p: string) => Promise; logout: () => void; } const Ctx = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const qc = useQueryClient(); const [token, setTok] = useState(() => getToken()); useEffect(() => { const off = () => setTok(null); // client 的 401 拦截在这里回收 React 状态 window.addEventListener(LOGOUT_EVENT, off); return () => window.removeEventListener(LOGOUT_EVENT, off); }, []); const meQ = useQuery({ queryKey: ["me"], queryFn: api.me, enabled: !!token, retry: 0 }); useEffect(() => { if (token && meQ.isError) setTok(null); }, [token, meQ.isError]); const value = useMemo( () => ({ user: meQ.data ?? null, isAdmin: meQ.data?.role === "admin", ready: !token || !meQ.isPending, login: async (u, p) => { const r = await api.login(u, p); setToken(r.token); setTok(r.token); await qc.invalidateQueries({ queryKey: ["me"] }); }, logout: () => { setToken(null); setTok(null); qc.clear(); }, }), [meQ.data, meQ.isPending, meQ.isError, token, qc], ); return {children}; } export function useAuth(): AuthState { const v = useContext(Ctx); if (!v) throw new Error("useAuth 必须在 AuthProvider 内使用"); return v; } export function RequireAuth({ children }: { children: ReactNode }) { const { user, ready } = useAuth(); const loc = useLocation(); if (!getToken()) return ; if (!ready || !user) return
加载中…
; return <>{children}; } export function RequireAdmin({ children }: { children: ReactNode }) { const { isAdmin } = useAuth(); return isAdmin ? <>{children} : ; }