diff --git a/src/components/ProfileSheet.tsx b/src/components/ProfileSheet.tsx index 4211365..55851e6 100644 --- a/src/components/ProfileSheet.tsx +++ b/src/components/ProfileSheet.tsx @@ -32,66 +32,56 @@ export function ProfileSheet({ open, onOpenChange }: ProfileSheetProps) { const { user, updateUser } = useAuth() const [name, setName] = useState('') const [company, setCompany] = useState('') - const [saving, setSaving] = useState(false) - const [error, setError] = useState(null) useEffect(() => { if (open && user) { setName(user.name || user.email || '') setCompany(user.company ?? '') - setError(null) } // Intencional: solo inicializar cuando el sheet se abre, no en cada cambio de `user` // (evita resetear el formulario mientras el usuario está editando). // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]) - async function handleSave() { + function handleSave() { if (!user || !name.trim()) return - setSaving(true) - setError(null) - try { - // Usamos fetch nativo en lugar de supabase.from().update() para evitar que esta - // operación quede en la cola del processLock compartido. En el login, upsertUserOnLogin - // y syncProfileFromDB adquieren ese lock; si el servidor tarda, la cola queda ocupada - // y el save cuelga indefinidamente. El fetch nativo es independiente del lock. - const token = getAccessToken() - if (!token) throw new Error('Sesión no disponible — recargá la página e intentá de nuevo') - const res = await 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: name.trim(), - company: company.trim() || null, - }), - // Timeout de 8s: si Supabase no responde (cold start, red lenta, etc.), - // el catch muestra un mensaje de error y libera el botón. - // Sin esto, la modal queda abierta indefinidamente bloqueando todo el UI. - signal: AbortSignal.timeout(8000), - } - ) + const savedName = name.trim() + const savedCompany = company.trim() || undefined - if (!res.ok) { - const detail = await res.text().catch(() => '') - throw new Error(`Error ${res.status}${detail ? `: ${detail}` : ''}`) + // 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. + updateUser({ name: savedName, company: savedCompany }) + onOpenChange(false) + + // Persistir en Supabase en background — fire & forget. + // Si falla (red lenta, token vencido, cold start de Supabase), el estado local + // ya tiene el nuevo nombre. En el próximo login, syncProfileFromDB recupera el DB. + 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), } - - updateUser({ name: name.trim(), company: company.trim() || undefined }) - onOpenChange(false) - } catch (err) { - console.error('Error al guardar perfil:', err) - setError(err instanceof Error ? err.message : 'Error al guardar perfil') - } finally { - setSaving(false) - } + ) + .then((res) => { + if (!res.ok) console.error('[ProfileSheet] Error guardando perfil:', res.status) + }) + .catch((err) => { + console.error('[ProfileSheet] Error guardando perfil:', err) + }) } return ( @@ -125,17 +115,13 @@ export function ProfileSheet({ open, onOpenChange }: ProfileSheetProps) { - - {error && ( -

{error}

- )} - -