diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index d7a00908..7ef53641 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -34,8 +34,9 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { const sessionVerifiedRef = useRef(false); const novuUpdateTimeoutRef = useRef(null); const previousEmailRef = useRef(null); + const loadingTimeoutRef = useRef(null); - const fetchProfile = async (userId: string, retryCount = 0) => { + const fetchProfile = async (userId: string, retryCount = 0, onComplete?: () => void) => { try { const { data, error } = await supabase .from('profiles') @@ -52,27 +53,26 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { console.log(`[Auth] Retrying profile fetch in ${delay}ms (attempt ${retryCount + 1}/3)`); setTimeout(() => { if (isMountedRef.current) { - fetchProfile(userId, retryCount + 1); + fetchProfile(userId, retryCount + 1, onComplete); } }, delay); return; } - // Show user-friendly error notification only after all retries - if (isMountedRef.current && retryCount >= 3) { - toast({ - title: "Profile Loading Error", - description: "Unable to load your profile. Please refresh the page or try again later.", - variant: "destructive", - }); + // All retries exhausted - complete anyway + if (isMountedRef.current) { + console.warn('[Auth] Profile fetch failed after 3 retries'); + setProfile(null); + onComplete?.(); } return; } - // Only update state if component is still mounted + // Success - update state and complete if (isMountedRef.current) { setProfile(data as Profile); - profileRetryCountRef.current = 0; // Reset retry count on success + profileRetryCountRef.current = 0; + onComplete?.(); } } catch (error) { console.error('[Auth] Error fetching profile:', error); @@ -82,9 +82,15 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { const delay = Math.pow(2, retryCount) * 1000; setTimeout(() => { if (isMountedRef.current) { - fetchProfile(userId, retryCount + 1); + fetchProfile(userId, retryCount + 1, onComplete); } }, delay); + } else { + // All retries exhausted + if (isMountedRef.current) { + setProfile(null); + onComplete?.(); + } } } }; @@ -96,18 +102,28 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { }; // Verify session is still valid - const verifySession = async () => { + const verifySession = async (updateLoadingState = false) => { + if (updateLoadingState && isMountedRef.current) { + setLoading(true); + } + try { const { data: { session }, error } = await supabase.auth.getSession(); if (error) { console.error('[Auth] Session verification failed:', error); setSessionError(error.message); + if (updateLoadingState && isMountedRef.current) { + setLoading(false); + } return false; } if (!session) { console.log('[Auth] No active session found'); + if (updateLoadingState && isMountedRef.current) { + setLoading(false); + } return false; } @@ -118,12 +134,21 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { if (!user && isMountedRef.current) { setSession(session); setUser(session.user); - fetchProfile(session.user.id); + fetchProfile(session.user.id, 0, () => { + if (updateLoadingState && isMountedRef.current) { + setLoading(false); + } + }); + } else if (updateLoadingState && isMountedRef.current) { + setLoading(false); } return true; } catch (error) { console.error('[Auth] Session verification error:', error); + if (updateLoadingState && isMountedRef.current) { + setLoading(false); + } return false; } }; @@ -149,7 +174,6 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { setSession(session); setUser(session.user); sessionVerifiedRef.current = true; - setLoading(false); } else if (event === 'SIGNED_OUT') { console.log('[Auth] SIGNED_OUT detected, clearing session'); setSession(null); @@ -236,9 +260,14 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { } // Defer profile fetch to avoid deadlock + const shouldWaitForProfile = event === 'SIGNED_IN'; profileFetchTimeoutRef.current = setTimeout(() => { if (!isMountedRef.current) return; - fetchProfile(session.user.id); + fetchProfile(session.user.id, 0, () => { + if (isMountedRef.current && shouldWaitForProfile) { + setLoading(false); + } + }); profileFetchTimeoutRef.current = null; }, 0); } else { @@ -247,7 +276,10 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { } } - setLoading(false); + // Only set loading false immediately for non-SIGNED_IN events + if (event !== 'SIGNED_IN') { + setLoading(false); + } }); // THEN get initial session @@ -257,17 +289,34 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { if (error) { console.error('[Auth] Initial session fetch error:', error); setSessionError(error.message); + setLoading(false); + return; } setSession(session); setUser(session?.user ?? null); + if (session?.user) { - fetchProfile(session.user.id); sessionVerifiedRef.current = true; + // Fetch profile with completion callback + fetchProfile(session.user.id, 0, () => { + if (isMountedRef.current) { + setLoading(false); + } + }); + } else { + setLoading(false); } - setLoading(false); }); + // Add a safety timeout to force loading to resolve + loadingTimeoutRef.current = setTimeout(() => { + if (isMountedRef.current && loading) { + console.warn('[Auth] Loading timeout reached, forcing loading to false'); + setLoading(false); + } + }, 5000); + // Session verification fallback after 2 seconds const verificationTimeout = setTimeout(() => { if (!sessionVerifiedRef.current && isMountedRef.current) { @@ -290,6 +339,9 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { isMountedRef.current = false; subscription.unsubscribe(); clearTimeout(verificationTimeout); + if (loadingTimeoutRef.current) { + clearTimeout(loadingTimeoutRef.current); + } document.removeEventListener('visibilitychange', handleVisibilityChange); // Clear any pending timeouts