import { createContext, useContext, useEffect, useState, useCallback, type ReactNode, } from "react"; import { supabase, isAuthConfigured, SUPABASE_URL, type Session, type User } from "../lib/supabase"; interface AuthState { user: User | null; session: Session | null; isLoading: boolean; isAuthenticated: boolean; isAuthConfigured: boolean; } interface AuthContextValue extends AuthState { /** Get the current access token for API calls */ getAccessToken: () => string | null; /** Sign in with email and password */ signIn: (email: string, password: string) => Promise<{ error: Error | null }>; /** Sign up with email and password */ signUp: (email: string, password: string) => Promise<{ error: Error | null }>; /** Sign out */ signOut: () => Promise; /** Sign in with OAuth provider */ signInWithOAuth: (provider: "github" | "google") => Promise<{ error: Error | null }>; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [state, setState] = useState({ user: null, session: null, isLoading: true, isAuthenticated: false, isAuthConfigured: isAuthConfigured(), }); // Initialize auth state useEffect(() => { // Get initial session supabase.auth.getSession().then(({ data: { session } }) => { setState({ user: session?.user ?? null, session, isLoading: false, isAuthenticated: !!session, isAuthConfigured: true, }); }); // Listen for auth changes const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { setState((prev) => ({ ...prev, user: session?.user ?? null, session, isAuthenticated: !!session, })); }); return () => subscription.unsubscribe(); }, []); const getAccessToken = useCallback((): string | null => { return state.session?.access_token ?? null; }, [state.session]); const signIn = useCallback( async (email: string, password: string): Promise<{ error: Error | null }> => { const { error } = await supabase.auth.signInWithPassword({ email, password }); return { error: error ? new Error(error.message) : null }; }, [] ); const signUp = useCallback( async (email: string, password: string): Promise<{ error: Error | null }> => { const { error } = await supabase.auth.signUp({ email, password }); return { error: error ? new Error(error.message) : null }; }, [] ); const signOut = useCallback(async () => { // Always clear local state first setState((prev) => ({ ...prev, user: null, session: null, isAuthenticated: false, })); // Clear Supabase storage directly in case signOut API fails const storageKey = `sb-${SUPABASE_URL.split('//')[1]?.split('.')[0]}-auth-token`; localStorage.removeItem(storageKey); // Try to call signOut API (may fail if token is invalid, that's OK) await supabase.auth.signOut({ scope: 'local' }).catch(() => {}); }, []); const signInWithOAuth = useCallback( async (provider: "github" | "google"): Promise<{ error: Error | null }> => { const { error } = await supabase.auth.signInWithOAuth({ provider, options: { redirectTo: window.location.origin, }, }); return { error: error ? new Error(error.message) : null }; }, [] ); const value: AuthContextValue = { ...state, getAccessToken, signIn, signUp, signOut, signInWithOAuth, }; return {children}; } export function useAuth(): AuthContextValue { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within an AuthProvider"); } return context; }