import { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react'; import type { ReactNode } from 'react'; import { useNavigate } from 'react-router-dom'; import { authService, type AppUser, type UserRole } from '@/api'; interface AuthSession { user: AppUser; access_token: string; } interface AuthContextType { user: AppUser | null; session: AuthSession | null; userRole: UserRole; loading: boolean; signIn: (username: string, password: string) => Promise<{ error: { message: string } | null }>; signOut: () => Promise; } const AuthContext = createContext(undefined); /** * Maps the API user_id to the application's UserRole. * 1 -> helpdesk * 2 -> bank_customer * 3 -> manufacturing_customer */ const mapUserIdToRole = (userId: string): UserRole => { switch (userId) { case '1': return 'helpdesk'; case '2': return 'bank_customer'; case '3': return 'manufacturing_customer'; default: console.warn(`Unknown user_id: ${userId}. Defaulting to null role.`); return null; } }; export const AuthProvider = ({ children }: { children: ReactNode }) => { // OPTIMIZATION: Initialize from localStorage synchronously to avoid blocking render const getInitialSession = (): AuthSession | null => { try { const storedSession = localStorage.getItem('dealer360_session'); if (storedSession) { return JSON.parse(storedSession) as AuthSession; } } catch (e) { localStorage.removeItem('dealer360_session'); } return null; }; const initialSession = getInitialSession(); const [user, setUser] = useState(initialSession?.user || null); const [session, setSession] = useState(initialSession); const [userRole, setUserRole] = useState(initialSession?.user?.role || null); const [loading, setLoading] = useState(false); // Start as false since we sync synchronously const navigate = useNavigate(); // Helper to sync state from localStorage const syncStateFromStorage = useCallback(() => { const storedSession = localStorage.getItem('dealer360_session'); if (storedSession) { try { const parsedSession = JSON.parse(storedSession) as AuthSession; setSession(parsedSession); setUser(parsedSession.user); setUserRole(parsedSession.user.role); } catch (e) { localStorage.removeItem('dealer360_session'); setSession(null); setUser(null); setUserRole(null); } } else { setSession(null); setUser(null); setUserRole(null); } }, []); useEffect(() => { // Initial sync (already done synchronously, but keep for consistency) // This is mainly for storage event listeners // Listen for storage changes (multi-tab support) const handleStorageChange = (e: StorageEvent) => { if (e.key === 'dealer360_session') { syncStateFromStorage(); // If logged out in another tab, redirect to login if (!e.newValue && !window.location.pathname.includes('/login')) { navigate('/login'); } } }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, [syncStateFromStorage, navigate]); const signIn = async (username: string, password: string): Promise<{ error: { message: string } | null }> => { try { const response = await authService.login({ username, password }); if (response.success && response.data) { const { user_id, email, username: apiUsername, company_name, token } = response.data; const role = mapUserIdToRole(user_id); const appUser: AppUser = { id: user_id, username: apiUsername, email: email, role: role, company_name: company_name, }; const authSession: AuthSession = { user: appUser, access_token: token, }; // Store session in localStorage for persistence localStorage.setItem('dealer360_session', JSON.stringify(authSession)); setUser(appUser); setSession(authSession); setUserRole(role); return { error: null }; } return { error: { message: response.message || 'Login failed' } }; } catch (error: any) { const errorMessage = error.response?.data?.message || error.message || 'An unexpected error occurred during login'; return { error: { message: errorMessage } }; } }; const signOut = async () => { // Clear state localStorage.removeItem('dealer360_session'); setUser(null); setSession(null); setUserRole(null); // Attempt to notify backend try { await authService.logout(); } catch (e) { // Silent fail } navigate('/login'); }; // Memoize context value to prevent unnecessary re-renders const contextValue = useMemo( () => ({ user, session, userRole, loading, signIn, signOut }), [user, session, userRole, loading, signIn, signOut] ); return ( {children} ); }; export const useAuth = () => { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; };