ce2cd6b397
supabase.tsx Se añadieron los roles al contexto de autenticación. Se modificó la interfaz de UserClaims que consiste en la información que se obtiene de los usuarios. Se obtienen los roles desde la base de datos. _authenticated.tsx Ya todos pueden ver el enlace a la página de facultades. login.tsx Se movió el enlace de '¿Olvidaste tu contraseña?' a después del input de la contraseña, para mejorar la usabilidad. usuarios.tsx - La obtención de los usuarios ahora se hace a con el cliente de llave anónima de supabase y se obtiene de tablas en el esquema public a través de una función de PostgreSQL. - La información de los roles se obtiene del contexto de autenticación para mostrarla en la página. - El RolePill se movió a dentro del componente para poder usar la información del contexto. - Se añadieron validaciones para poder crear un usuario. - Se muestra la información para editar los usuarios y se actualiza en la BDD con una función de PostgreSQL.
163 lines
4.2 KiB
TypeScript
163 lines
4.2 KiB
TypeScript
import { createClient, type User, type Session } from '@supabase/supabase-js'
|
|
import { createContext, useContext, useEffect, useState } from 'react'
|
|
|
|
export const supabase = createClient(
|
|
import.meta.env.VITE_SUPABASE_URL,
|
|
import.meta.env.VITE_SUPABASE_ANON_KEY,
|
|
)
|
|
|
|
export interface SupabaseAuthState {
|
|
isAuthenticated: boolean
|
|
user: User | null
|
|
claims: UserClaims | null
|
|
roles: RolCatalogo[] | null
|
|
login: (email: string, password: string) => Promise<void>
|
|
logout: () => Promise<void>
|
|
isLoading: boolean
|
|
}
|
|
|
|
export interface RolCatalogo {
|
|
id: string
|
|
nombre: string
|
|
icono: string
|
|
nombre_clase: string
|
|
label: string
|
|
}
|
|
|
|
export type Role = string;
|
|
|
|
export type UserClaims = {
|
|
id: string | null
|
|
clave?: string
|
|
nombre: string
|
|
apellidos: string
|
|
title?: string
|
|
avatar?: string | null
|
|
carrera_id?: string | null
|
|
facultad_id?: string | null
|
|
facultad_color?: string | null // 🎨 NEW
|
|
role: Role
|
|
}
|
|
|
|
const SupabaseAuthContext = createContext<SupabaseAuthState | undefined>(undefined)
|
|
|
|
export function SupabaseAuthProvider({ children }: { children: React.ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null)
|
|
const [claims, setClaims] = useState<UserClaims | null>(null)
|
|
const [roles, setRoles] = useState<RolCatalogo[] | null>(null)
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
// Función para manejar la sesión
|
|
const handleSession = async (session: Session | null) => {
|
|
const u = session?.user ?? null
|
|
setUser(u)
|
|
setIsAuthenticated(!!u)
|
|
setClaims(await buildClaims(session))
|
|
setIsLoading(false)
|
|
}
|
|
|
|
// Carga inicial
|
|
supabase.auth.getSession().then(({ data: { session } }) => {
|
|
handleSession(session)
|
|
})
|
|
|
|
// Carga roles catálogo
|
|
fetchRoles().then(fetchedRoles => {
|
|
setRoles(fetchedRoles);
|
|
});
|
|
|
|
// Suscripción a cambios de sesión
|
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
|
handleSession(session)
|
|
})
|
|
|
|
return () => subscription.unsubscribe()
|
|
}, [])
|
|
|
|
const login = async (email: string, password: string) => {
|
|
const { error } = await supabase.auth.signInWithPassword({ email, password })
|
|
if (error?.code === 'invalid_credentials') throw new Error('Credenciales inválidas')
|
|
else if (error) throw error
|
|
}
|
|
|
|
const logout = async () => {
|
|
const { error } = await supabase.auth.signOut()
|
|
if (error) throw error
|
|
location.href = "/login"
|
|
}
|
|
|
|
return (
|
|
<SupabaseAuthContext.Provider
|
|
value={{ isAuthenticated, user, claims, roles, login, logout, isLoading }}
|
|
>
|
|
{children}
|
|
</SupabaseAuthContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useSupabaseAuth() {
|
|
const context = useContext(SupabaseAuthContext)
|
|
if (context === undefined) {
|
|
throw new Error('useSupabaseAuth must be used within SupabaseAuthProvider')
|
|
}
|
|
return context
|
|
}
|
|
|
|
/* ===================== *
|
|
* Helpers
|
|
* ===================== */
|
|
|
|
// Obtiene los claims del usuario desde la base de datos a partir de una función en la BDD
|
|
async function buildClaims(session: Session | null): Promise<UserClaims | null> {
|
|
// Validar sesión
|
|
if (!session || !session.user) {
|
|
console.warn('No session or user found');
|
|
return null;
|
|
}
|
|
const u = session.user;
|
|
|
|
|
|
try{
|
|
const result = await supabase.rpc('obtener_claims_usuario', {
|
|
p_user_id: u.id,
|
|
});
|
|
|
|
const data: UserClaims[] | null = result.data;
|
|
const error = result.error;
|
|
|
|
if (error) {
|
|
console.error('Error al obtener la información:', error);
|
|
throw new Error('Error al obtener la información del usuario');
|
|
}
|
|
|
|
console.log(data);
|
|
if (!data || data.length === 0) {
|
|
console.warn('No se encontró información para el usuario');
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
...data[0],
|
|
id: null
|
|
};
|
|
} catch (e) {
|
|
console.error('Error inesperado:', e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchRoles(): Promise<RolCatalogo[]> {
|
|
const { data, error } = await supabase
|
|
.from("roles_catalogo")
|
|
.select("id, nombre, icono, nombre_clase, label");
|
|
|
|
if (error) {
|
|
console.error("Error al obtener los roles:", error.message);
|
|
return [];
|
|
}
|
|
|
|
return data || [];
|
|
}
|