Refactor App component styles and remove highlights section

- Updated background gradient colors for a lighter theme.
- Changed button styles to improve visibility.
- Removed the highlights section to streamline the layout.
- Adjusted text colors for better contrast and readability.
This commit is contained in:
2025-08-26 15:24:53 -06:00
parent 602c5dbb31
commit 56b0dc8a62
12 changed files with 2240 additions and 1188 deletions
+126 -88
View File
@@ -1,109 +1,147 @@
import { createClient, type User } from '@supabase/supabase-js'
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,
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)
export interface SupabaseAuthState {
isAuthenticated: boolean
user: User | null
claims: UserClaims | null
login: (email: string, password: string) => Promise<void>
logout: () => Promise<void>
isLoading: boolean
isAuthenticated: boolean
user: User | null
claims: UserClaims | null
login: (email: string, password: string) => Promise<void>
logout: () => Promise<void>
isLoading: boolean
}
type Role =
| 'lci'
| 'vicerrectoria'
| 'director_facultad' // 👈 NEW
| 'secretario_academico'
| 'jefe_carrera'
| 'planeacion'
type UserClaims = {
claims_admin: boolean,
clave: string,
nombre: string,
apellidos: string,
title: string,
avatar: string | null,
carrera_id?: string,
facultad_id?: string,
role: 'lci' | 'vicerrectoria' | 'secretario_academico' | 'jefe_carrera' | 'planeacion',
claims_admin: boolean
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,
)
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 [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
export function SupabaseAuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [claims, setClaims] = useState<UserClaims | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
setClaims({
...(session?.user?.app_metadata as Partial<UserClaims> ?? {}),
...(session?.user?.user_metadata as Partial<UserClaims> ?? {}),
} as UserClaims | null)
setIsAuthenticated(!!session?.user)
setIsLoading(false)
})
useEffect(() => {
// Carga inicial
supabase.auth.getSession().then(async ({ data: { session } }) => {
const u = session?.user ?? null
setUser(u)
setIsAuthenticated(!!u)
setClaims(await buildClaims(session))
setIsLoading(false)
})
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null)
setClaims({
...(session?.user?.app_metadata as Partial<UserClaims> ?? {}),
...(session?.user?.user_metadata as Partial<UserClaims> ?? {}),
} as UserClaims | null)
setIsAuthenticated(!!session?.user)
setIsLoading(false)
})
// Suscripción a cambios de sesión
const { data: { subscription } } = supabase.auth.onAuthStateChange(async (_event, session) => {
const u = session?.user ?? null
setUser(u)
setIsAuthenticated(!!u)
setClaims(await buildClaims(session))
setIsLoading(false)
})
return () => subscription.unsubscribe()
}, [])
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 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"
}
const logout = async () => {
const { error } = await supabase.auth.signOut()
if (error) throw error
location.href = "/login"
}
return (
<SupabaseAuthContext.Provider
value={{
isAuthenticated,
user,
claims,
login,
logout,
isLoading,
}}
>
{children}
</SupabaseAuthContext.Provider>
)
return (
<SupabaseAuthContext.Provider
value={{ isAuthenticated, user, claims, 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
}
const context = useContext(SupabaseAuthContext)
if (context === undefined) {
throw new Error('useSupabaseAuth must be used within SupabaseAuthProvider')
}
return context
}
/* ===================== *
* Helpers
* ===================== */
// Unifica extracción de metadatos y resuelve facultad_color si hay facultad_id
async function buildClaims(session: Session | null): Promise<UserClaims | null> {
const u = session?.user
if (!u) return null
const app = (u.app_metadata ?? {}) as Partial<UserClaims> & { role?: Role }
const meta = (u.user_metadata ?? {}) as Partial<UserClaims>
// Mezcla segura: app_metadata > user_metadata (para campos de claims)
const base: Partial<UserClaims> = {
claims_admin: !!(app.claims_admin ?? (meta as any).claims_admin),
role: (app.role as Role | undefined) ?? ('lci' as Role),
facultad_id: app.facultad_id ?? meta.facultad_id ?? null,
carrera_id: app.carrera_id ?? meta.carrera_id ?? null,
clave: (meta.clave as string) ?? '',
nombre: (meta.nombre as string) ?? '',
apellidos: (meta.apellidos as string) ?? '',
title: (meta.title as string) ?? '',
avatar: (meta.avatar as string) ?? null,
}
let facultad_color: string | null = null
if (base.facultad_id) {
// Lee color desde public.facultades
const { data, error } = await supabase
.from('facultades')
.select('color')
.eq('id', base.facultad_id)
.maybeSingle()
if (!error && data) facultad_color = (data as any)?.color ?? null
}
return {
claims_admin: !!base.claims_admin,
role: (base.role ?? 'lci') as Role,
clave: base.clave ?? '',
nombre: base.nombre ?? '',
apellidos: base.apellidos ?? '',
title: base.title ?? '',
avatar: base.avatar ?? null,
facultad_id: (base.facultad_id as string | null) ?? null,
carrera_id: (base.carrera_id as string | null) ?? null,
facultad_color, // 🎨
}
}