diff --git a/client/contexts/AuthContext.tsx b/client/contexts/AuthContext.tsx new file mode 100644 index 00000000..86e49c01 --- /dev/null +++ b/client/contexts/AuthContext.tsx @@ -0,0 +1,201 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { User, Session } from '@supabase/supabase-js'; +import { supabase } from '@/lib/supabase'; +import { UserProfile } from '@/lib/database.types'; +import { aethexToast } from '@/lib/aethex-toast'; + +interface AuthContextType { + user: User | null; + profile: UserProfile | null; + session: Session | null; + loading: boolean; + signIn: (email: string, password: string) => Promise; + signUp: (email: string, password: string, userData?: Partial) => Promise; + signOut: () => Promise; + updateProfile: (updates: Partial) => Promise; +} + +const AuthContext = createContext(undefined); + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [user, setUser] = useState(null); + const [profile, setProfile] = useState(null); + const [session, setSession] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Get initial session + supabase.auth.getSession().then(({ data: { session } }) => { + setSession(session); + setUser(session?.user ?? null); + if (session?.user) { + fetchUserProfile(session.user.id); + } + setLoading(false); + }); + + // Listen for auth changes + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange(async (event, session) => { + setSession(session); + setUser(session?.user ?? null); + + if (session?.user) { + await fetchUserProfile(session.user.id); + } else { + setProfile(null); + } + setLoading(false); + + // Show toast notifications for auth events + if (event === 'SIGNED_IN') { + aethexToast.success({ + title: 'Welcome back!', + description: 'Successfully signed in to AeThex OS' + }); + } else if (event === 'SIGNED_OUT') { + aethexToast.info({ + title: 'Signed out', + description: 'Come back soon!' + }); + } + }); + + return () => subscription.unsubscribe(); + }, []); + + const fetchUserProfile = async (userId: string) => { + try { + const { data, error } = await supabase + .from('user_profiles') + .select('*') + .eq('id', userId) + .single(); + + if (error && error.code !== 'PGRST116') { + throw error; + } + + setProfile(data); + } catch (error) { + console.error('Error fetching user profile:', error); + } + }; + + const signIn = async (email: string, password: string) => { + try { + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + if (error) throw error; + } catch (error: any) { + aethexToast.error({ + title: 'Sign in failed', + description: error.message + }); + throw error; + } + }; + + const signUp = async (email: string, password: string, userData?: Partial) => { + try { + const { data, error } = await supabase.auth.signUp({ + email, + password, + }); + + if (error) throw error; + + if (data.user && userData) { + // Create user profile after successful signup + const { error: profileError } = await supabase + .from('user_profiles') + .insert({ + id: data.user.id, + ...userData, + }); + + if (profileError) throw profileError; + + aethexToast.success({ + title: 'Account created!', + description: 'Please check your email to verify your account' + }); + } + } catch (error: any) { + aethexToast.error({ + title: 'Sign up failed', + description: error.message + }); + throw error; + } + }; + + const signOut = async () => { + try { + const { error } = await supabase.auth.signOut(); + if (error) throw error; + } catch (error: any) { + aethexToast.error({ + title: 'Sign out failed', + description: error.message + }); + throw error; + } + }; + + const updateProfile = async (updates: Partial) => { + if (!user) throw new Error('No user logged in'); + + try { + const { data, error } = await supabase + .from('user_profiles') + .update(updates) + .eq('id', user.id) + .select() + .single(); + + if (error) throw error; + + setProfile(data); + aethexToast.success({ + title: 'Profile updated', + description: 'Your profile has been updated successfully' + }); + } catch (error: any) { + aethexToast.error({ + title: 'Update failed', + description: error.message + }); + throw error; + } + }; + + const value = { + user, + profile, + session, + loading, + signIn, + signUp, + signOut, + updateProfile, + }; + + return ( + + {children} + + ); +};