import React from 'react'; import { authorizedFetch, clearOAuthSession, clearTokens, completeOAuthCallback, isAuthError, loadTokens, registerAuthFailureHandler, startOAuthFlow, } from './tokens'; export type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated'; export interface TenantProfile { id: number; tenant_id: number; name?: string; slug?: string; email?: string | null; event_credits_balance?: number; [key: string]: unknown; } interface AuthContextValue { status: AuthStatus; user: TenantProfile | null; login: (redirectPath?: string) => void; logout: (options?: { redirect?: string }) => void; completeLogin: (params: URLSearchParams) => Promise; refreshProfile: () => Promise; } const AuthContext = React.createContext(undefined); export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [status, setStatus] = React.useState('loading'); const [user, setUser] = React.useState(null); const handleAuthFailure = React.useCallback(() => { clearTokens(); setUser(null); setStatus('unauthenticated'); }, []); React.useEffect(() => { const unsubscribe = registerAuthFailureHandler(handleAuthFailure); return unsubscribe; }, [handleAuthFailure]); const refreshProfile = React.useCallback(async () => { try { const response = await authorizedFetch('/api/v1/tenant/me'); if (!response.ok) { throw new Error('Failed to load profile'); } const profile = (await response.json()) as TenantProfile; setUser(profile); setStatus('authenticated'); } catch (error) { if (isAuthError(error)) { handleAuthFailure(); } else { console.error('[Auth] Failed to refresh profile', error); } throw error; } }, [handleAuthFailure]); React.useEffect(() => { const tokens = loadTokens(); if (!tokens) { setStatus('unauthenticated'); return; } refreshProfile().catch(() => { // refreshProfile already handled failures. }); }, [refreshProfile]); const login = React.useCallback((redirectPath?: string) => { const target = redirectPath ?? window.location.pathname + window.location.search; startOAuthFlow(target); }, []); const logout = React.useCallback(({ redirect }: { redirect?: string } = {}) => { clearTokens(); clearOAuthSession(); setUser(null); setStatus('unauthenticated'); if (redirect) { window.location.href = redirect; } }, []); const completeLogin = React.useCallback( async (params: URLSearchParams) => { setStatus('loading'); try { const redirectTarget = await completeOAuthCallback(params); await refreshProfile(); return redirectTarget; } catch (error) { handleAuthFailure(); throw error; } }, [handleAuthFailure, refreshProfile] ); const value = React.useMemo( () => ({ status, user, login, logout, completeLogin, refreshProfile }), [status, user, login, logout, completeLogin, refreshProfile] ); return {children}; }; export function useAuth(): AuthContextValue { const context = React.useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }