diff --git a/.replit b/.replit index bbd59c94..cf054042 100644 --- a/.replit +++ b/.replit @@ -37,3 +37,7 @@ externalPort = 80 [[ports]] localPort = 5001 externalPort = 3000 + +[[ports]] +localPort = 37143 +externalPort = 3001 diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index 63fce858..0df910c0 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -157,21 +157,25 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { }, [loading]); useEffect(() => { + console.log('[Auth] Initializing auth provider'); + // CRITICAL: Set up listener FIRST to catch all events const { data: { subscription }, } = supabase.auth.onAuthStateChange((event, session) => { - console.log('[Auth] State change:', event, 'User:', session?.user?.email || 'none'); - - if (!isMountedRef.current) return; + console.log('[Auth] State change:', event, 'User:', session?.user?.email || 'none', 'Has session:', !!session); + // Extract email info early for cleanup const currentEmail = session?.user?.email; const newEmailPending = session?.user?.new_email; + // CRITICAL: Always clear/update pending email state BEFORE any early returns + setPendingEmail(newEmailPending ?? null); + // Clear any error setSessionError(null); - // Explicitly handle SIGNED_IN and INITIAL_SESSION events + // Update session and user state based on event if (event === 'SIGNED_IN' && session) { console.log('[Auth] SIGNED_IN detected, setting session and user'); setSession(session); @@ -179,33 +183,33 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { sessionVerifiedRef.current = true; } else if (event === 'INITIAL_SESSION') { if (session?.user) { - console.log('[Auth] INITIAL_SESSION detected with session, setting user'); + console.log('[Auth] INITIAL_SESSION with user, setting session'); setSession(session); setUser(session.user); sessionVerifiedRef.current = true; } else { - console.log('[Auth] INITIAL_SESSION detected with NO session - user not logged in'); + console.log('[Auth] INITIAL_SESSION with no user - setting loading to false'); setSession(null); setUser(null); setProfile(null); sessionVerifiedRef.current = false; // CRITICAL: Set loading to false immediately when no session setLoading(false); + return; // Exit early, no need to fetch profile } } else if (event === 'SIGNED_OUT') { - console.log('[Auth] SIGNED_OUT detected, clearing session'); + console.log('[Auth] SIGNED_OUT detected, clearing all state'); setSession(null); setUser(null); setProfile(null); sessionVerifiedRef.current = false; + setLoading(false); + return; // Exit early, no need to fetch profile } else { setSession(session); setUser(session?.user ?? null); } - // Track pending email changes - setPendingEmail(newEmailPending ?? null); - // Detect confirmed email change: email changed AND no longer pending if ( session?.user && @@ -222,8 +226,6 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { // Defer Novu update and notifications to avoid blocking auth const oldEmail = previousEmailRef.current; novuUpdateTimeoutRef.current = setTimeout(async () => { - if (!isMountedRef.current) return; - try { // Update Novu subscriber with confirmed email const { notificationService } = await import('@/lib/notificationService'); @@ -271,19 +273,20 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { previousEmailRef.current = currentEmail; } + // Handle profile fetching for authenticated users if (session?.user) { // Clear any existing profile fetch timeout if (profileFetchTimeoutRef.current) { clearTimeout(profileFetchTimeoutRef.current); } - // Defer profile fetch to avoid deadlock - const shouldWaitForProfile = (event === 'SIGNED_IN' || event === 'INITIAL_SESSION') && session !== null; - console.log('[Auth] Profile fetch deferred, shouldWaitForProfile:', shouldWaitForProfile, 'event:', event); + // Only wait for profile on initial auth events + const shouldWaitForProfile = (event === 'SIGNED_IN' || event === 'INITIAL_SESSION'); + console.log('[Auth] Fetching profile, shouldWaitForProfile:', shouldWaitForProfile); + profileFetchTimeoutRef.current = setTimeout(() => { - if (!isMountedRef.current) return; fetchProfile(session.user.id, 0, () => { - if (isMountedRef.current && shouldWaitForProfile) { + if (shouldWaitForProfile) { console.log('[Auth] Profile fetch complete, setting loading to false'); setLoading(false); } @@ -291,27 +294,15 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { profileFetchTimeoutRef.current = null; }, 0); } else { - if (isMountedRef.current) { - setProfile(null); - // CRITICAL: Always resolve loading when there's no session - if (event === 'INITIAL_SESSION' || event === 'SIGNED_OUT') { - console.log('[Auth] No session for event:', event, '- setting loading to false'); - setLoading(false); - } - } - } - - // CRITICAL: Always set loading false for non-session events - if (event !== 'SIGNED_IN' && event !== 'INITIAL_SESSION') { - console.log('[Auth] Setting loading to false immediately for event:', event); + // No session/user - clear profile and resolve loading + setProfile(null); + console.log('[Auth] No user, setting loading to false'); setLoading(false); } }); - // THEN get initial session + // THEN get initial session (this may trigger INITIAL_SESSION event) supabase.auth.getSession().then(({ data: { session }, error }) => { - if (!isMountedRef.current) return; - if (error) { console.error('[Auth] Initial session fetch error:', error); setSessionError(error.message); @@ -319,41 +310,30 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { return; } - setSession(session); - setUser(session?.user ?? null); - - if (session?.user) { - sessionVerifiedRef.current = true; - // Fetch profile with completion callback - fetchProfile(session.user.id, 0, () => { - if (isMountedRef.current) { - setLoading(false); - } - }); - } else { - setLoading(false); - } + // Note: onAuthStateChange will handle the INITIAL_SESSION event + // This is just a backup in case the event doesn't fire + console.log('[Auth] getSession completed, session exists:', !!session); }); - // Add a safety timeout to force loading to resolve + // Add a STRONG safety timeout to force loading to resolve loadingTimeoutRef.current = setTimeout(() => { - if (isMountedRef.current && loadingStateRef.current) { - console.warn('[Auth] Loading timeout reached after 5 seconds, forcing loading to false'); + if (loadingStateRef.current) { + console.warn('[Auth] ⚠️ SAFETY TIMEOUT: Forcing loading to false after 3 seconds'); setLoading(false); } - }, 5000); + }, 3000); - // Session verification fallback after 2 seconds + // Session verification fallback const verificationTimeout = setTimeout(() => { - if (!sessionVerifiedRef.current && isMountedRef.current) { - console.log('[Auth] Session not verified, attempting manual verification'); + if (!sessionVerifiedRef.current) { + console.log('[Auth] Session not verified after 2s, attempting manual verification'); verifySession(); } }, 2000); // Handle page visibility changes const handleVisibilityChange = () => { - if (document.visibilityState === 'visible' && isMountedRef.current) { + if (document.visibilityState === 'visible') { console.log('[Auth] Tab became visible, verifying session'); verifySession(); } @@ -362,6 +342,7 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { document.addEventListener('visibilitychange', handleVisibilityChange); return () => { + console.log('[Auth] Cleaning up auth provider'); isMountedRef.current = false; subscription.unsubscribe(); clearTimeout(verificationTimeout);