mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 10:31:13 -05:00
Improve authentication state management and user session handling
Refactors the `AuthProviderComponent` in `useAuth.tsx` to enhance the handling of Supabase authentication state changes. Updates console logs for better debugging, prioritizes setting pending email state before early returns, and refines the logic for `SIGNED_IN`, `INITIAL_SESSION`, and `SIGNED_OUT` events. Also defers Novu updates and profile fetches to prevent blocking the authentication process. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e7030bef-c72b-4948-9378-ee5e345d91ce Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7cdf4e95-3f41-4180-b8e3-8ef56d032c0e/e7030bef-c72b-4948-9378-ee5e345d91ce/zQglahT
This commit is contained in:
4
.replit
4
.replit
@@ -37,3 +37,7 @@ externalPort = 80
|
||||
[[ports]]
|
||||
localPort = 5001
|
||||
externalPort = 3000
|
||||
|
||||
[[ports]]
|
||||
localPort = 37143
|
||||
externalPort = 3001
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user