refactor: integrate Better-Auth for authentication and remove Supabase dependencies
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user