/** * Auth Context - PRODUCTION READY * Provides auth state throughout the app */ import { createContext, useContext, useEffect, useState, ReactNode, useMemo } from 'react'; import { auth } from '../lib/auth-client'; export interface User { id: string; email?: string; user_metadata?: any; app_metadata?: any; [key: string]: any; } export interface Session { access_token: string; refresh_token?: string; expires_in?: number; expires_at?: number; token_type?: string; user: User; } interface AuthState { user: User | null; session: Session | null; loading: boolean; isAuthenticated: boolean; } interface AuthContextType extends AuthState { signUp: (email: string, password: string, metadata?: Record) => Promise; signIn: (email: string, password: string) => Promise; signInWithMagicLink: (email: string) => Promise; signInWithGoogle: () => Promise; signInWithGithub: () => Promise; signOut: () => Promise; } const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: ReactNode }) { const [state, setState] = useState({ user: null, session: null, loading: true, isAuthenticated: false }); useEffect(() => { let isMounted = true; // Get initial session auth.getSession().then(({ data: { session } }) => { if (!isMounted) return; if (session) { setState({ user: session.user, session, loading: false, isAuthenticated: true }); localStorage.setItem('userId', session.user.id); } else { setState(s => ({ ...s, loading: false })); } }); // Listen for auth changes const { data: { subscription } } = auth.onAuthStateChange((_event, session) => { if (!isMounted) return; if (session) { setState({ user: session.user, session, loading: false, isAuthenticated: true }); localStorage.setItem('userId', session.user.id); } else { setState({ user: null, session: null, loading: false, isAuthenticated: false }); localStorage.removeItem('userId'); } }); // Timeout setTimeout(() => { if (isMounted) { setState(s => s.loading ? { ...s, loading: false } : s); } }, 2000); return () => { isMounted = false; subscription.unsubscribe(); }; }, []); const updateAuthState = async () => { const { data: { session } } = await auth.getSession(); if (session) { setState({ user: session.user, session, loading: false, isAuthenticated: true }); localStorage.setItem('userId', session.user.id); } }; const signUp = async (email: string, password: string, metadata?: Record) => { const result = await auth.signUp(email, password, metadata); if (!result.error) await updateAuthState(); return { error: result.error }; }; const signIn = async (email: string, password: string) => { const result = await auth.signIn(email, password); if (!result.error) await updateAuthState(); return { error: result.error }; }; const signInWithMagicLink = async (email: string) => { const result = await auth.signInWithMagicLink(email); return { error: result.error }; }; const signInWithGoogle = async () => { const result = await auth.signInWithOAuth('google'); return { error: result.error }; }; const signInWithGithub = async () => { const result = await auth.signInWithOAuth('github'); return { error: result.error }; }; const signOut = async () => { try { // Sign out from auth service await auth.signOut(); } catch (error) { console.error('Sign out error:', error); } // Clear all user-related localStorage localStorage.removeItem('userId'); localStorage.removeItem('ai-analyst-storage-v2'); // Zustand store // Clear any user profile data const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith('userProfile_') || key.startsWith('userPreferences_')) { localStorage.removeItem(key); } }); // Clear session - force clear any auth storage const authKeys = keys.filter(key => key.startsWith('sb-') || key.includes('auth') ); authKeys.forEach(key => localStorage.removeItem(key)); // Redirect to landing page window.location.href = '/'; }; const value = useMemo(() => ({ ...state, signUp, signIn, signInWithMagicLink, signInWithGoogle, signInWithGithub, signOut }), [state]); return ( {children} ); } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; } export default AuthContext;