mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 18:31:12 -05:00
Fix: Implement complete fix
This commit is contained in:
@@ -10,8 +10,10 @@ interface AuthContextType {
|
||||
profile: Profile | null;
|
||||
loading: boolean;
|
||||
pendingEmail: string | null;
|
||||
sessionError: string | null;
|
||||
signOut: () => Promise<void>;
|
||||
refreshProfile: () => Promise<void>;
|
||||
verifySession: () => Promise<boolean>;
|
||||
clearPendingEmail: () => void;
|
||||
}
|
||||
|
||||
@@ -23,14 +25,17 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
||||
const [profile, setProfile] = useState<Profile | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [pendingEmail, setPendingEmail] = useState<string | null>(null);
|
||||
const [sessionError, setSessionError] = useState<string | null>(null);
|
||||
|
||||
// Refs for lifecycle and cleanup management
|
||||
const isMountedRef = useRef(true);
|
||||
const profileFetchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const profileRetryCountRef = useRef(0);
|
||||
const sessionVerifiedRef = useRef(false);
|
||||
const novuUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const previousEmailRef = useRef<string | null>(null);
|
||||
|
||||
const fetchProfile = async (userId: string) => {
|
||||
const fetchProfile = async (userId: string, retryCount = 0) => {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
@@ -39,10 +44,22 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
||||
.maybeSingle();
|
||||
|
||||
if (error && error.code !== 'PGRST116') {
|
||||
console.error('Error fetching profile:', error);
|
||||
console.error('[Auth] Error fetching profile:', error);
|
||||
|
||||
// Show user-friendly error notification
|
||||
if (isMountedRef.current) {
|
||||
// Retry up to 3 times with exponential backoff
|
||||
if (retryCount < 3 && isMountedRef.current) {
|
||||
const delay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s
|
||||
console.log(`[Auth] Retrying profile fetch in ${delay}ms (attempt ${retryCount + 1}/3)`);
|
||||
setTimeout(() => {
|
||||
if (isMountedRef.current) {
|
||||
fetchProfile(userId, retryCount + 1);
|
||||
}
|
||||
}, 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.",
|
||||
@@ -55,17 +72,19 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
||||
// Only update state if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
setProfile(data as Profile);
|
||||
profileRetryCountRef.current = 0; // Reset retry count on success
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching profile:', error);
|
||||
console.error('[Auth] Error fetching profile:', error);
|
||||
|
||||
// Show user-friendly error notification
|
||||
if (isMountedRef.current) {
|
||||
toast({
|
||||
title: "Profile Loading Error",
|
||||
description: "An unexpected error occurred while loading your profile.",
|
||||
variant: "destructive",
|
||||
});
|
||||
// Retry logic for network errors
|
||||
if (retryCount < 3 && isMountedRef.current) {
|
||||
const delay = Math.pow(2, retryCount) * 1000;
|
||||
setTimeout(() => {
|
||||
if (isMountedRef.current) {
|
||||
fetchProfile(userId, retryCount + 1);
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -76,20 +95,41 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Get initial session
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
if (!isMountedRef.current) return;
|
||||
// Verify session is still valid
|
||||
const verifySession = async () => {
|
||||
try {
|
||||
const { data: { session }, error } = await supabase.auth.getSession();
|
||||
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
if (session?.user) {
|
||||
if (error) {
|
||||
console.error('[Auth] Session verification failed:', error);
|
||||
setSessionError(error.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
console.log('[Auth] No active session found');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('[Auth] Session verified:', session.user.email);
|
||||
sessionVerifiedRef.current = true;
|
||||
|
||||
// Update state if session was found but not set
|
||||
if (!user && isMountedRef.current) {
|
||||
setSession(session);
|
||||
setUser(session.user);
|
||||
fetchProfile(session.user.id);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Auth] Session verification error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for auth changes
|
||||
useEffect(() => {
|
||||
// CRITICAL: Set up listener FIRST to catch all events
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||
@@ -100,12 +140,22 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
||||
const currentEmail = session?.user?.email;
|
||||
const newEmailPending = session?.user?.new_email;
|
||||
|
||||
// Clear any error
|
||||
setSessionError(null);
|
||||
|
||||
// Explicitly handle SIGNED_IN event for iframe compatibility
|
||||
if (event === 'SIGNED_IN' && session) {
|
||||
console.log('[Auth] SIGNED_IN detected, setting session and user');
|
||||
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);
|
||||
setUser(null);
|
||||
setProfile(null);
|
||||
sessionVerifiedRef.current = false;
|
||||
} else {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
@@ -200,9 +250,47 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
// THEN get initial session
|
||||
supabase.auth.getSession().then(({ data: { session }, error }) => {
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
if (error) {
|
||||
console.error('[Auth] Initial session fetch error:', error);
|
||||
setSessionError(error.message);
|
||||
}
|
||||
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
if (session?.user) {
|
||||
fetchProfile(session.user.id);
|
||||
sessionVerifiedRef.current = true;
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
// Session verification fallback after 2 seconds
|
||||
const verificationTimeout = setTimeout(() => {
|
||||
if (!sessionVerifiedRef.current && isMountedRef.current) {
|
||||
console.log('[Auth] Session not verified, attempting manual verification');
|
||||
verifySession();
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
// Handle page visibility changes
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible' && isMountedRef.current) {
|
||||
console.log('[Auth] Tab became visible, verifying session');
|
||||
verifySession();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
subscription.unsubscribe();
|
||||
clearTimeout(verificationTimeout);
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
// Clear any pending timeouts
|
||||
if (profileFetchTimeoutRef.current) {
|
||||
@@ -234,8 +322,10 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
||||
profile,
|
||||
loading,
|
||||
pendingEmail,
|
||||
sessionError,
|
||||
signOut,
|
||||
refreshProfile,
|
||||
verifySession,
|
||||
clearPendingEmail,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user