Fix: Resolve authentication loading state issues

This commit is contained in:
gpt-engineer-app[bot]
2025-10-11 16:26:10 +00:00
parent 0330fbd1f3
commit c0a5c3479d

View File

@@ -34,8 +34,9 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
const sessionVerifiedRef = useRef(false); const sessionVerifiedRef = useRef(false);
const novuUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null); const novuUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const previousEmailRef = useRef<string | null>(null); const previousEmailRef = useRef<string | null>(null);
const loadingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const fetchProfile = async (userId: string, retryCount = 0) => { const fetchProfile = async (userId: string, retryCount = 0, onComplete?: () => void) => {
try { try {
const { data, error } = await supabase const { data, error } = await supabase
.from('profiles') .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)`); console.log(`[Auth] Retrying profile fetch in ${delay}ms (attempt ${retryCount + 1}/3)`);
setTimeout(() => { setTimeout(() => {
if (isMountedRef.current) { if (isMountedRef.current) {
fetchProfile(userId, retryCount + 1); fetchProfile(userId, retryCount + 1, onComplete);
} }
}, delay); }, delay);
return; return;
} }
// Show user-friendly error notification only after all retries // All retries exhausted - complete anyway
if (isMountedRef.current && retryCount >= 3) { if (isMountedRef.current) {
toast({ console.warn('[Auth] Profile fetch failed after 3 retries');
title: "Profile Loading Error", setProfile(null);
description: "Unable to load your profile. Please refresh the page or try again later.", onComplete?.();
variant: "destructive",
});
} }
return; return;
} }
// Only update state if component is still mounted // Success - update state and complete
if (isMountedRef.current) { if (isMountedRef.current) {
setProfile(data as Profile); setProfile(data as Profile);
profileRetryCountRef.current = 0; // Reset retry count on success profileRetryCountRef.current = 0;
onComplete?.();
} }
} catch (error) { } catch (error) {
console.error('[Auth] Error fetching profile:', 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; const delay = Math.pow(2, retryCount) * 1000;
setTimeout(() => { setTimeout(() => {
if (isMountedRef.current) { if (isMountedRef.current) {
fetchProfile(userId, retryCount + 1); fetchProfile(userId, retryCount + 1, onComplete);
} }
}, delay); }, 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 // Verify session is still valid
const verifySession = async () => { const verifySession = async (updateLoadingState = false) => {
if (updateLoadingState && isMountedRef.current) {
setLoading(true);
}
try { try {
const { data: { session }, error } = await supabase.auth.getSession(); const { data: { session }, error } = await supabase.auth.getSession();
if (error) { if (error) {
console.error('[Auth] Session verification failed:', error); console.error('[Auth] Session verification failed:', error);
setSessionError(error.message); setSessionError(error.message);
if (updateLoadingState && isMountedRef.current) {
setLoading(false);
}
return false; return false;
} }
if (!session) { if (!session) {
console.log('[Auth] No active session found'); console.log('[Auth] No active session found');
if (updateLoadingState && isMountedRef.current) {
setLoading(false);
}
return false; return false;
} }
@@ -118,12 +134,21 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
if (!user && isMountedRef.current) { if (!user && isMountedRef.current) {
setSession(session); setSession(session);
setUser(session.user); 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; return true;
} catch (error) { } catch (error) {
console.error('[Auth] Session verification error:', error); console.error('[Auth] Session verification error:', error);
if (updateLoadingState && isMountedRef.current) {
setLoading(false);
}
return false; return false;
} }
}; };
@@ -149,7 +174,6 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
setSession(session); setSession(session);
setUser(session.user); setUser(session.user);
sessionVerifiedRef.current = true; sessionVerifiedRef.current = true;
setLoading(false);
} else if (event === 'SIGNED_OUT') { } else if (event === 'SIGNED_OUT') {
console.log('[Auth] SIGNED_OUT detected, clearing session'); console.log('[Auth] SIGNED_OUT detected, clearing session');
setSession(null); setSession(null);
@@ -236,9 +260,14 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
} }
// Defer profile fetch to avoid deadlock // Defer profile fetch to avoid deadlock
const shouldWaitForProfile = event === 'SIGNED_IN';
profileFetchTimeoutRef.current = setTimeout(() => { profileFetchTimeoutRef.current = setTimeout(() => {
if (!isMountedRef.current) return; if (!isMountedRef.current) return;
fetchProfile(session.user.id); fetchProfile(session.user.id, 0, () => {
if (isMountedRef.current && shouldWaitForProfile) {
setLoading(false);
}
});
profileFetchTimeoutRef.current = null; profileFetchTimeoutRef.current = null;
}, 0); }, 0);
} else { } 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 // THEN get initial session
@@ -257,17 +289,34 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
if (error) { if (error) {
console.error('[Auth] Initial session fetch error:', error); console.error('[Auth] Initial session fetch error:', error);
setSessionError(error.message); setSessionError(error.message);
setLoading(false);
return;
} }
setSession(session); setSession(session);
setUser(session?.user ?? null); setUser(session?.user ?? null);
if (session?.user) { if (session?.user) {
fetchProfile(session.user.id);
sessionVerifiedRef.current = true; 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 // Session verification fallback after 2 seconds
const verificationTimeout = setTimeout(() => { const verificationTimeout = setTimeout(() => {
if (!sessionVerifiedRef.current && isMountedRef.current) { if (!sessionVerifiedRef.current && isMountedRef.current) {
@@ -290,6 +339,9 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
isMountedRef.current = false; isMountedRef.current = false;
subscription.unsubscribe(); subscription.unsubscribe();
clearTimeout(verificationTimeout); clearTimeout(verificationTimeout);
if (loadingTimeoutRef.current) {
clearTimeout(loadingTimeoutRef.current);
}
document.removeEventListener('visibilitychange', handleVisibilityChange); document.removeEventListener('visibilitychange', handleVisibilityChange);
// Clear any pending timeouts // Clear any pending timeouts