diff --git a/src/components/ProfileSheet.tsx b/src/components/ProfileSheet.tsx index 1d14a70..f033a50 100644 --- a/src/components/ProfileSheet.tsx +++ b/src/components/ProfileSheet.tsx @@ -4,9 +4,24 @@ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from '@/com import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button' -import { supabase } from '@/lib/supabase' +import { SUPABASE_URL, SUPABASE_ANON_KEY } from '@/lib/supabase' import { useAuth } from '@/auth/AuthContext' +// Lee el access token directamente de localStorage sin pasar por el cliente Supabase. +// Evita contención con processLock — el save de perfil no necesita refresh de token +// porque es una operación rápida y el token está fresco en sesiones activas. +function getAccessToken(): string | null { + try { + const projectRef = new URL(SUPABASE_URL).hostname.split('.')[0] + const raw = localStorage.getItem(`sb-${projectRef}-auth-token`) + if (!raw) return null + const parsed = JSON.parse(raw) as { access_token?: string } + return parsed.access_token ?? null + } catch { + return null + } +} + interface ProfileSheetProps { open: boolean onOpenChange: (open: boolean) => void @@ -36,21 +51,37 @@ export function ProfileSheet({ open, onOpenChange }: ProfileSheetProps) { // Actualización optimista: aplicar cambios localmente y cerrar el Sheet de inmediato. // El Sheet nunca queda abierto esperando la respuesta del servidor. // La modal de Radix bloquea todo el UI mientras está abierta, así que cerramos - // ANTES del await para que el usuario pueda seguir trabajando sin demora. + // ANTES del fetch para que el usuario pueda seguir trabajando sin demora. updateUser({ name: savedName, company: savedCompany }) onOpenChange(false) // Persistir en Supabase en background — fire & forget. - // Usamos el cliente Supabase (no fetch nativo) para que maneje el refresh de token - // automáticamente. Como el Sheet ya cerró, processLock puede tardar lo que necesite - // sin bloquear el UI. lockAcquireTimeout: 30s en supabase.ts garantiza que - // eventualmente libera si hay contención (no queda colgado para siempre). - void supabase - .from('users') - .update({ name: savedName, company: savedCompany ?? null }) - .eq('id', user.id) - .then(({ error }) => { - if (error) console.error('[ProfileSheet] Error guardando perfil:', error) + // Usamos raw fetch (no el cliente Supabase) para evitar contención con processLock. + // getSession() interno de supabase.from().update() adquiere processLock y bloquea + // otras queries de la página mientras espera. Raw fetch lee el token de localStorage + // directamente — sin lock, sin overhead de auth. + const token = getAccessToken() + if (!token) return + + void fetch( + `${SUPABASE_URL}/rest/v1/users?id=eq.${encodeURIComponent(user.id)}`, + { + method: 'PATCH', + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Prefer: 'return=minimal', + }, + body: JSON.stringify({ name: savedName, company: savedCompany ?? null }), + signal: AbortSignal.timeout(10000), + } + ) + .then((res) => { + if (!res.ok) console.error('[ProfileSheet] Error guardando perfil:', res.status) + }) + .catch((err) => { + console.error('[ProfileSheet] Error guardando perfil:', err) }) }