import { setApiAccessToken } from '@/lib/api-client'; import { createAuthClient } from 'better-auth/react'; import { emailOTPClient } from 'better-auth/client/plugins'; import type { User, Session } from 'better-auth/types'; import type { PropsWithChildren } from 'react'; import { createContext, useContext, useEffect, useMemo, useState } from 'react'; const authClient = createAuthClient({ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000', plugins: [emailOTPClient()], }); type SignInParams = { email: string; password: string; }; type SignUpParams = { email: string; password: string; name: string; }; type UpdateProfileParams = { fullName: string; }; type UpdatePasswordParams = { password: string; }; type AuthUser = User | null; type AuthSession = Session | null; export type AuthContextValue = { user: AuthUser; session: AuthSession; loading: boolean; isAuthenticated: boolean; displayName: string; initials: string; avatarUrl: string | null; signInWithPassword: (params: SignInParams) => Promise; signUp: (params: SignUpParams) => Promise; updateProfile: (params: UpdateProfileParams) => Promise; updatePassword: (params: UpdatePasswordParams) => Promise; signOut: () => Promise; }; const AuthContext = createContext(null); function getDisplayName(user: AuthUser): string { if (!user) return 'Usuario'; const name = user.name ?? user.email ?? 'Usuario'; return name; } function getInitials(name: string): string { const words = name.trim().split(/\s+/).slice(0, 2); const initials = words.map((word) => word[0]?.toUpperCase() ?? '').join(''); return initials || 'U'; } function getAvatarUrl(user: AuthUser): string | null { if (!user) return null; return user.image ?? null; } export function AuthProvider({ children }: PropsWithChildren) { const sessionStore = authClient.useSession(); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(sessionStore.isPending); }, [sessionStore.isPending]); const user = sessionStore.data?.user ?? null; const session = sessionStore.data?.session ?? null; const isAuthenticated = !!session; useEffect(() => { if (sessionStore.data?.session?.token) { setApiAccessToken(sessionStore.data.session.token); } else { setApiAccessToken(null); } }, [sessionStore.data?.session?.token]); const value = useMemo(() => { const displayName = getDisplayName(user); const initials = getInitials(displayName); const avatarUrl = getAvatarUrl(user); return { user, session, loading, isAuthenticated, displayName, initials, avatarUrl, signInWithPassword: async ({ email, password }) => { const result = await authClient.signIn.email({ email, password, }); if (result.error) { throw new Error(result.error.message); } }, signUp: async ({ email, password, name }) => { const result = await authClient.signUp.email({ email, password, name, }); if (result.error) { throw new Error(result.error.message); } }, updateProfile: async ({ fullName }) => { const result = await (authClient as any).user.update({ name: fullName, }); if (result?.error) { throw new Error(result.error.message); } }, updatePassword: async ({ password }) => { const result = await (authClient as any).user.update({ password, }); if (result?.error) { throw new Error(result.error.message); } }, signOut: async () => { const result = await authClient.signOut(); if (result.error) { throw new Error(result.error.message); } }, }; }, [loading, session, user, isAuthenticated, sessionStore]); return {children}; } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; } export { authClient };