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