'use client'; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import { auth, clearStoredTokens, getStoredAccessToken, setStoredTokens, setUnauthorizedHandler, type User, } from './api'; import { isJwtExpired } from './jwt'; export interface AuthContextValue { user: User | null; loading: boolean; isAuthenticated: boolean; isAdmin: boolean; login: (email: string, password: string) => Promise; register: ( email: string, password: string, fullName: string, ) => Promise; logout: () => void; refreshUser: () => Promise; } const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: React.ReactNode }) { const router = useRouter(); const pathname = usePathname(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const bootstrapRan = useRef(false); const logout = useCallback(() => { clearStoredTokens(); setUser(null); router.push('/login'); }, [router]); // Wire 401 handler from axios interceptor to context-level logout. useEffect(() => { setUnauthorizedHandler(() => { setUser(null); // Only redirect from protected pages to avoid loops on /login. if ( pathname && !pathname.startsWith('/login') && !pathname.startsWith('/register') && pathname !== '/' ) { router.push('/login'); } }); return () => setUnauthorizedHandler(null); }, [pathname, router]); // Bootstrap: if we have a non-expired token, fetch /auth/me. useEffect(() => { if (bootstrapRan.current) return; bootstrapRan.current = true; const token = getStoredAccessToken(); if (!token || isJwtExpired(token, 5)) { setLoading(false); return; } (async () => { try { const me = await auth.me(); setUser(me); } catch { // 401 handler will clear tokens and redirect if needed. } finally { setLoading(false); } })(); }, []); const login = useCallback( async (email: string, password: string) => { const res = await auth.login(email, password); setStoredTokens(res.access_token, res.refresh_token); const me = res.user ?? (await auth.me()); setUser(me); }, [], ); const register = useCallback( async (email: string, password: string, fullName: string) => { const res = await auth.register(email, password, fullName); setStoredTokens(res.access_token, res.refresh_token); setUser(res.user); }, [], ); const refreshUser = useCallback(async () => { try { const me = await auth.me(); setUser(me); } catch { /* ignore */ } }, []); const value = useMemo( () => ({ user, loading, isAuthenticated: !!user, isAdmin: user?.role === 'admin', login, register, logout, refreshUser, }), [user, loading, login, register, logout, refreshUser], ); return {children}; } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used within AuthProvider'); return ctx; }