// components/account/ProfileEditor.tsx "use client"; import { useEffect, useState } from "react"; type Me = { id: string; username: string; first_name?: string | null; last_name?: string | null; email?: string | null; location?: string | null; avatar?: { id: string; filename_download?: string } | null; }; export default function ProfileEditor({ me: meProp, onUpdated, }: { me?: Me | null; onUpdated?: () => void; }) { const [me, setMe] = useState(meProp ?? null); const [first_name, setFirst] = useState(""); const [last_name, setLast] = useState(""); const [email, setEmail] = useState(""); const [profileLocation, setProfileLocation] = useState(""); const [msg, setMsg] = useState(null); const [busy, setBusy] = useState(false); const [loading, setLoading] = useState(!meProp); const nextAccount = "/portal/account"; // Load profile if not provided via props useEffect(() => { if (meProp) { setMe(meProp); setLoading(false); return; } let alive = true; (async () => { try { const r = await fetch("/api/account", { credentials: "include", cache: "no-store" }); if (!r.ok) throw new Error(String(r.status)); const j = await r.json(); const user: Me | undefined = j?.user ?? j?.data ?? undefined; if (!user) throw new Error("Bad response"); if (alive) setMe(user); } catch (e: any) { if (alive) setMsg(`Failed to load: ${e?.message || e}`); } finally { if (alive) setLoading(false); } })(); return () => { alive = false; }; }, [meProp]); useEffect(() => { if (!me) return; setFirst(me.first_name || ""); setLast(me.last_name || ""); setEmail(me.email || ""); setProfileLocation(me.location || ""); }, [me]); // Auto-retry a pending sensitive update after coming back from reauth useEffect(() => { const raw = typeof window !== "undefined" ? sessionStorage.getItem("pendingProfileUpdate") : null; if (!raw) return; let pending: Record | null = null; try { pending = JSON.parse(raw); } catch { pending = null; } if (!pending) { sessionStorage.removeItem("pendingProfileUpdate"); return; } (async () => { try { const r = await fetch("/api/account/profile", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(pending), }); const j = await r.json().catch(() => ({})); if (!r.ok) { setMsg(j?.error || "Update after re-auth failed"); } else { setMsg("Saved after re-authentication."); onUpdated?.(); } } finally { sessionStorage.removeItem("pendingProfileUpdate"); } })(); }, [onUpdated]); const onSave = async () => { setMsg(null); setBusy(true); try { const payload: Record = { first_name: first_name.trim(), last_name: last_name.trim(), email: email.trim() || null, // allow clearing email location: profileLocation.trim(), }; const r = await fetch("/api/account/profile", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (r.status === 428) { // Need reauth for sensitive change (email) if (typeof window !== "undefined") { // Stash the pending payload so we can retry after reauth sessionStorage.setItem("pendingProfileUpdate", JSON.stringify(payload)); window.location.assign( `/auth/sign-in?reauth=1&next=${encodeURIComponent(nextAccount + "#security")}` ); } return; } if (r.status === 401) { if (typeof window !== "undefined") { window.location.assign(`/auth/sign-in?reauth=1&next=${encodeURIComponent(nextAccount)}`); } return; } const j = await r.json().catch(() => ({})); if (!r.ok) { setMsg(j?.error || "Update failed"); return; } setMsg("Saved."); onUpdated?.(); } catch (e: any) { setMsg(e?.message || "Update failed"); } finally { setBusy(false); } }; if (loading) return
Loading editor…
; return (

Edit Profile

{msg &&
{msg}
}
); }