import { authClient } from '@/lib/api-client'; import { type PropsWithChildren, createContext, useContext, useMemo } from 'react'; export type User = typeof authClient.$Infer.Session.user; export type Session = typeof authClient.$Infer.Session.session; type SignInParams = { email: string; password: string; }; type SignUpParams = { email: string; password: string; fullName: string; }; type UpdateProfileParams = { fullName: string; }; type UpdatePasswordParams = { currentPassword?: string; password: string; }; export type AuthContextValue = { user: User | null; session: Session | null; loading: boolean; isAuthenticated: boolean; displayName: string; initials: string; avatarUrl: string | null; signInWithPassword: (params: SignInParams) => Promise; signUp: (params: SignUpParams) => Promise<{ requiresEmailConfirmation: boolean }>; updateProfile: (params: UpdateProfileParams) => Promise; updatePassword: (params: UpdatePasswordParams) => Promise; signOut: () => Promise; }; const AuthContext = createContext(null); /** * Standard MD5 implementation for Gravatar support * (Self-contained to avoid dependency issues) */ function md5(string: string) { const k = Array.from({ length: 64 }, (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32)); let a = 0x67452301; let b = 0xefcdab89; let c = 0x98badcfe; let d = 0x10325476; const s = [ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, ]; const str = unescape(encodeURIComponent(string)); const msg = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) msg[i] = str.charCodeAt(i); const len = msg.length; const wordLen = ((len + 8) >> 6) + 1; const words = new Uint32Array(wordLen * 16); for (let i = 0; i < len; i++) words[i >> 2] |= msg[i] << ((i % 4) * 8); words[len >> 2] |= 0x80 << ((len % 4) * 8); words[wordLen * 16 - 2] = len * 8; for (let i = 0; i < words.length; i += 16) { const [aa, bb, cc, dd] = [a, b, c, d]; for (let j = 0; j < 64; j++) { let f: number; let g: number; if (j < 16) { f = (b & c) | (~b & d); g = j; } else if (j < 32) { f = (d & b) | (~d & c); g = (5 * j + 1) % 16; } else if (j < 48) { f = b ^ c ^ d; g = (3 * j + 5) % 16; } else { f = c ^ (b | ~d); g = (7 * j) % 16; } const temp = d; d = c; c = b; b = (b + (((a + f + k[j] + words[i + g]) << s[j]) | ((a + f + k[j] + words[i + g]) >>> (32 - s[j])))) | 0; a = temp; } a = (a + aa) | 0; b = (b + bb) | 0; c = (c + cc) | 0; d = (d + dd) | 0; } return [a, b, c, d] .map((v) => Array.from({ length: 4 }, (_, i) => ((v >> (i * 8)) & 0xff).toString(16).padStart(2, '0') ).join('') ) .join(''); } function getGravatarUrl(email: string): string { const hash = md5(email.trim().toLowerCase()); return `https://www.gravatar.com/avatar/${hash}?d=identicon`; } function getDisplayName(user: User | null): string { if (!user) return 'Usuario'; if (user.name?.trim()) return user.name.trim(); return user.email ?? 'Usuario'; } 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: User | null): string | null { if (!user) return null; return user.image || getGravatarUrl(user.email); } export function AuthProvider({ children }: PropsWithChildren) { const { data, isPending } = authClient.useSession(); const user = data?.user ?? null; const session = data?.session ?? null; const value = useMemo(() => { const displayName = getDisplayName(user); const initials = getInitials(displayName); const avatarUrl = getAvatarUrl(user); return { user, session, loading: isPending, isAuthenticated: Boolean(user), displayName, initials, avatarUrl, signInWithPassword: async ({ email, password }) => { const { error } = await authClient.signIn.email({ email, password, }); if (error) { throw error; } }, signUp: async ({ email, password, fullName }) => { const { data: signUpData, error } = await authClient.signUp.email({ email, password, name: fullName, }); if (error) { throw error; } return { requiresEmailConfirmation: !signUpData || !('session' in signUpData), }; }, updateProfile: async ({ fullName }) => { const { error } = await authClient.updateUser({ name: fullName, }); if (error) { throw error; } }, updatePassword: async ({ currentPassword, password }) => { // Better Auth uses changePassword for authenticated users const { error } = await authClient.changePassword({ newPassword: password, currentPassword: currentPassword || '', revokeOtherSessions: true, }); if (error) { throw error; } }, signOut: async () => { const { error } = await authClient.signOut(); if (error) { throw error; } }, }; }, [isPending, session, user]); return {children}; } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; }