import { useEffect, useRef, useState } from 'react'; import { browserClient } from '@/lib/supabase'; import { avatarInfo } from '@/lib/avatar'; import { toast, toastAfterReload } from '@/lib/toast'; import Avatar from './Avatar'; type Maestro = { id: number; nombre: string }; type Props = { userId: string; email: string; nombre: string | null; initialProfile: { matricula: string | null; semestre: string | null; tutor_id: number | null; foto_path: string | null; }; maestros: Maestro[]; googleAvatarUrl?: string | null; mode?: 'edit' | 'onboarding'; }; const BUCKET = 'avatares'; const SEM_OPTS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']; export default function PerfilForm({ userId, email, nombre, initialProfile, maestros, googleAvatarUrl, mode = 'edit', }: Props) { const [matricula, setMatricula] = useState(initialProfile.matricula ?? ''); const [semestre, setSemestre] = useState(initialProfile.semestre ?? ''); const [tutorId, setTutorId] = useState( initialProfile.tutor_id != null ? String(initialProfile.tutor_id) : '', ); const [fotoPath, setFotoPath] = useState(initialProfile.foto_path); const [file, setFile] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fileInputRef = useRef(null); useEffect(() => { if (!file) { setPreviewUrl(null); return; } const url = URL.createObjectURL(file); setPreviewUrl(url); return () => URL.revokeObjectURL(url); }, [file]); const info = avatarInfo({ email, nombre, fotoPath, googleAvatarUrl, }); const previewInfo = previewUrl ? { ...info, src: previewUrl } : info; async function uploadAvatar(): Promise { if (!file) return null; const ext = (file.name.split('.').pop() ?? 'jpg').toLowerCase().replace(/[^a-z0-9]/g, '') || 'jpg'; const path = `${userId}/${Date.now()}.${ext}`; const { error: upErr } = await browserClient() .storage.from(BUCKET) .upload(path, file, { upsert: true, contentType: file.type || undefined }); if (upErr) { toast({ kind: 'error', title: 'La foto no se subió', description: upErr.message }); return null; } return path; } const submit = async (e: React.FormEvent) => { e.preventDefault(); if (loading) return; setLoading(true); setError(null); try { let nuevoPath: string | null | undefined = undefined; if (file) { nuevoPath = await uploadAvatar(); } const body: Record = { matricula: matricula.trim() || null, semestre: semestre || null, tutor_id: tutorId ? Number(tutorId) : null, }; if (nuevoPath !== undefined) body.foto_path = nuevoPath; const res = await fetch('/api/profile', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const json = await res.json().catch(() => ({})); if (!res.ok) throw new Error(json?.error ?? 'no se pudo guardar'); if (mode === 'onboarding') { toastAfterReload({ kind: 'success', title: 'Perfil guardado', description: 'Ya puedes crear vales.' }); window.location.href = '/'; } else { toastAfterReload({ kind: 'success', title: 'Perfil actualizado' }); window.location.reload(); } } catch (err) { setError(err instanceof Error ? err.message : 'Error inesperado'); setLoading(false); } }; return (
setFile(e.target.files?.[0] ?? null)} /> {(file || fotoPath) && ( )}
{!file && !fotoPath && googleAvatarUrl && (

Mostrando tu foto de Google. Sube una para reemplazarla.

)}
setMatricula(e.target.value)} placeholder="123456" autoComplete="off" maxLength={15} />

Se rellenará por defecto al crear vales.

{error && (

{error}

)}
{mode === 'onboarding' && ( Omitir por ahora )}
); }