import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { login as apiLogin, logout as apiLogout, me } from '../lib/api/auth.js'; import type { AuthUser } from '../lib/api/auth.js'; import { ApiRequestError } from '../lib/api/client.js'; interface AuthContextValue { user: AuthUser | null; status: 'loading' | 'authenticated' | 'anonymous'; login: (username: string, password: string) => Promise; logout: () => Promise; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [status, setStatus] = useState('loading'); const qc = useQueryClient(); // Bootstrap: try /me once. If the refresh interceptor revives a dead access token, this // round-trips once; on a hard 401 the server returns anonymous and we show the Login page. useEffect(() => { let cancelled = false; (async () => { try { const u = await me(); if (!cancelled) { setUser(u); setStatus('authenticated'); } } catch (err) { if (cancelled) return; if (err instanceof ApiRequestError && err.status === 401) { setStatus('anonymous'); } else { setStatus('anonymous'); } } })(); return () => { cancelled = true; }; }, []); const login: AuthContextValue['login'] = async (username, password) => { const u = await apiLogin(username, password); setUser(u); setStatus('authenticated'); return u; }; const logout: AuthContextValue['logout'] = async () => { try { await apiLogout(); } catch { // Ignore — tokens are best-effort-revoked server side. } setUser(null); setStatus('anonymous'); qc.clear(); }; return ( {children} ); } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used within '); return ctx; }