refactor: integrate Better-Auth for authentication and remove Supabase dependencies

This commit is contained in:
Jose Selesan
2026-04-16 20:30:01 -03:00
parent f158845279
commit e5002ce440
29 changed files with 165 additions and 211 deletions

View File

@@ -29,7 +29,7 @@ import type {
UserProfileResponse,
} from '@repo/api-contract';
import axios, { AxiosError } from 'axios';
import { createAuthClient } from "better-auth/react";
import { createAuthClient } from 'better-auth/react';
const apiBaseUrl =
import.meta.env.VITE_API_BASE_URL?.trim() ||
@@ -37,9 +37,11 @@ const apiBaseUrl =
export const authClient = createAuthClient({
baseURL: apiBaseUrl,
fetchOptions: {
credentials: 'include',
},
});
export class ApiClientError extends Error {
status?: number;
details?: unknown;
@@ -66,6 +68,7 @@ const handlers: ErrorHandlers = {};
const http = axios.create({
baseURL: apiBaseUrl,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
@@ -285,6 +288,6 @@ export function configureApiClient(nextHandlers: ErrorHandlers) {
handlers.onError = nextHandlers.onError;
}
export function setApiAccessToken(token: string | null) {
export function setApiAccessToken(_token: string | null) {
// accessToken = token;
}

View File

@@ -1,14 +1,8 @@
import { setApiAccessToken } from '@/lib/api-client';
import { supabase } from '@/lib/supabase';
import type { Session, User } from '@supabase/supabase-js';
import {
type PropsWithChildren,
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
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;
@@ -48,14 +42,7 @@ const AuthContext = createContext<AuthContextValue | null>(null);
function getDisplayName(user: User | null): string {
if (!user) return 'Usuario';
const fromMetadata =
user.user_metadata?.name ?? user.user_metadata?.full_name ?? user.user_metadata?.user_name;
if (typeof fromMetadata === 'string' && fromMetadata.trim().length > 0) {
return fromMetadata.trim();
}
if (user.name?.trim()) return user.name.trim();
return user.email ?? 'Usuario';
}
@@ -66,47 +53,14 @@ function getInitials(name: string): string {
}
function getAvatarUrl(user: User | null): string | null {
if (!user) return null;
const value = user.user_metadata?.avatar_url ?? user.user_metadata?.picture;
return typeof value === 'string' && value.length > 0 ? value : null;
return user?.image || null;
}
export function AuthProvider({ children }: PropsWithChildren) {
const [session, setSession] = useState<Session | null>(null);
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const { data, isPending } = authClient.useSession();
useEffect(() => {
let mounted = true;
const init = async () => {
const { data } = await supabase.auth.getSession();
if (!mounted) return;
setSession(data.session);
setUser(data.session?.user ?? null);
setApiAccessToken(data.session?.access_token ?? null);
setLoading(false);
};
void init();
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, nextSession) => {
setSession(nextSession);
setUser(nextSession?.user ?? null);
setApiAccessToken(nextSession?.access_token ?? null);
setLoading(false);
});
return () => {
mounted = false;
subscription.unsubscribe();
};
}, []);
const user = data?.user ?? null;
const session = data?.session ?? null;
const value = useMemo<AuthContextValue>(() => {
const displayName = getDisplayName(user);
@@ -116,13 +70,13 @@ export function AuthProvider({ children }: PropsWithChildren) {
return {
user,
session,
loading,
loading: isPending,
isAuthenticated: Boolean(user),
displayName,
initials,
avatarUrl,
signInWithPassword: async ({ email, password }) => {
const { error } = await supabase.auth.signInWithPassword({
const { error } = await authClient.signIn.email({
email,
password,
});
@@ -132,15 +86,10 @@ export function AuthProvider({ children }: PropsWithChildren) {
}
},
signUp: async ({ email, password, fullName }) => {
const { data, error } = await supabase.auth.signUp({
const { data: signUpData, error } = await authClient.signUp.email({
email,
password,
options: {
data: {
full_name: fullName,
name: fullName,
},
},
name: fullName,
});
if (error) {
@@ -148,15 +97,12 @@ export function AuthProvider({ children }: PropsWithChildren) {
}
return {
requiresEmailConfirmation: !data.session,
requiresEmailConfirmation: !signUpData?.session,
};
},
updateProfile: async ({ fullName }) => {
const { error } = await supabase.auth.updateUser({
data: {
full_name: fullName,
name: fullName,
},
const { error } = await authClient.user.update({
name: fullName,
});
if (error) {
@@ -164,8 +110,10 @@ export function AuthProvider({ children }: PropsWithChildren) {
}
},
updatePassword: async ({ password }) => {
const { error } = await supabase.auth.updateUser({
password,
// Better Auth uses changePassword for authenticated users
const { error } = await authClient.changePassword({
newPassword: password,
revokeOtherSessions: true,
});
if (error) {
@@ -173,13 +121,13 @@ export function AuthProvider({ children }: PropsWithChildren) {
}
},
signOut: async () => {
const { error } = await supabase.auth.signOut();
const { error } = await authClient.signOut();
if (error) {
throw error;
}
},
};
}, [loading, session, user]);
}, [isPending, session, user]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

View File

@@ -1,18 +0,0 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
if (!supabaseUrl || !supabaseAnonKey) {
throw new Error(
'Missing Supabase environment variables. Define VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY.'
);
}
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
});