diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 880ac88..b6e89ac 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -31,7 +31,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const navigate = useNavigate(); const [user, setUser] = useState(null); const [user_role, setUserRole] = useState(null); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); // true until INITIAL_SESSION fires const [error, setError] = useState(null); const persistSession = useCallback((session: Session | null) => { @@ -42,40 +42,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { } }, []); - const restoreSessionFromStorage = useCallback(async (): Promise => { - const persistedSession = storageService.get(StorageKeys.SUPABASE_SESSION); - - if (!persistedSession) { - return null; - } - - if (!persistedSession.access_token || !persistedSession.refresh_token) { - storageService.remove(StorageKeys.SUPABASE_SESSION); - return null; - } - - const { data: restored, error: restoreError } = await supabase.auth.setSession({ - access_token: persistedSession.access_token, - refresh_token: persistedSession.refresh_token, - }); - - if (restoreError) { - logger.warn('auth-context', '⚠️ Failed to restore persisted Supabase session', { - error: restoreError.message ?? restoreError, - }); - storageService.remove(StorageKeys.SUPABASE_SESSION); - return null; - } - - if (restored.session) { - persistSession(restored.session); - return restored.session; - } - - storageService.remove(StorageKeys.SUPABASE_SESSION); - return null; - }, [persistSession]); - const buildUserFromSupabase = useCallback(async (supabaseUser: User | null): Promise<{ user: CCUser | null; role: string | null }> => { if (!supabaseUser) { return { user: null, role: null }; @@ -109,114 +75,42 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { }, []); useEffect(() => { - let isMounted = true; - - const loadInitialSession = async () => { - setLoading(true); - try { - const { data: { session }, error } = await supabase.auth.getSession(); - if (error) { - throw error; - } - - let activeSession: Session | null = session ?? null; - - if (!activeSession) { - activeSession = await restoreSessionFromStorage(); - } - - if (!isMounted) { - return; - } - - if (activeSession?.user) { - persistSession(activeSession); - try { - const { user: resolvedUser, role } = await buildUserFromSupabase(activeSession.user); - if (!isMounted) { - return; - } - setUser(resolvedUser); - setUserRole(role); - } catch (buildError) { - logger.error('auth-context', '❌ Failed to build user from initial session', { - error: buildError, - }); - if (!isMounted) { - return; - } - setUser(null); - setUserRole(null); - setError(buildError instanceof Error ? buildError : new Error('Failed to load user')); - } - } else { - persistSession(null); - setUser(null); - setUserRole(null); - } - } catch (error) { - logger.error('auth-context', '❌ Failed to load initial session', { error }); - if (isMounted) { - setError(error instanceof Error ? error : new Error('Failed to load user')); - } - } finally { - if (isMounted) { - setLoading(false); - } - } - }; - - loadInitialSession(); - + // Canonical Supabase auth pattern: rely solely on onAuthStateChange. + // INITIAL_SESSION fires immediately with the current session state, + // eliminating the race condition between loadInitialSession + onAuthStateChange. const { data: { subscription } } = supabase.auth.onAuthStateChange( async (event, session) => { - if (!isMounted) { - return; - } + logger.debug('auth-context', '🔄 Auth state change', { event, hasSession: !!session }); switch (event) { + case 'INITIAL_SESSION': case 'SIGNED_IN': - case 'TOKEN_REFRESHED': - case 'INITIAL_SESSION': { + case 'TOKEN_REFRESHED': { persistSession(session ?? null); if (session?.user) { - setLoading(true); try { const { user: resolvedUser, role } = await buildUserFromSupabase(session.user); - if (!isMounted) { - return; - } setUser(resolvedUser); setUserRole(role); } catch (buildError) { - logger.error('auth-context', '❌ Failed to build user from session', { - event, - error: buildError, - }); + logger.error('auth-context', '❌ Failed to build user from session', { event, error: buildError }); setUser(null); setUserRole(null); setError(buildError instanceof Error ? buildError : new Error('Failed to load user')); - } finally { - if (isMounted) { - setLoading(false); - } } } else { setUser(null); setUserRole(null); - if (isMounted) { - setLoading(false); - } } + // Always clear loading after the first auth event resolves + setLoading(false); break; } case 'SIGNED_OUT': { persistSession(null); setUser(null); setUserRole(null); - if (isMounted) { - setLoading(false); - } + setLoading(false); break; } default: @@ -225,11 +119,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { } ); - return () => { - isMounted = false; - subscription.unsubscribe(); - }; - }, [buildUserFromSupabase, persistSession, restoreSessionFromStorage]); + return () => subscription.unsubscribe(); + }, [buildUserFromSupabase, persistSession]); const signIn = async (email: string, password: string) => { try {