Fix: Implement complete fix

This commit is contained in:
gpt-engineer-app[bot]
2025-10-11 16:12:45 +00:00
parent 120f68c926
commit 0330fbd1f3
5 changed files with 380 additions and 35 deletions

View File

@@ -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,
};