// app/portal/account/AccountPanel.tsx "use client"; import { useEffect, useMemo, useState, useCallback } from "react"; // Use relative paths to avoid alias resolution issues in this route import ProfileEditor from "../../../components/account/ProfileEditor"; import PasswordChange from "../../../components/account/PasswordChange"; import AvatarUploader from "../../../components/account/AvatarUploader"; import SupporterBadges from "../../../components/account/SupporterBadges"; import ConnectKofi from "../../../components/account/ConnectKofi"; import LinkStatus from "../../../components/account/LinkStatus"; type Avatar = { id: string; filename_download?: string } | null; type Me = { id: string; username: string; first_name?: string | null; last_name?: string | null; email?: string | null; location?: string | null; avatar?: Avatar; }; export default function AccountPanel() { const [me, setMe] = useState(null); const [err, setErr] = useState(null); const [loading, setLoading] = useState(true); // Precompute API base once on the client const API_BASE = useMemo( () => (process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""), [] ); const refetchMe = useCallback(async () => { try { const r = await fetch("/api/account", { credentials: "include", cache: "no-store", }); if (!r.ok) throw new Error(`Load failed (${r.status})`); const j = await r.json(); const user: Me | undefined = j?.user ?? j?.data ?? undefined; if (!user) throw new Error("Malformed response"); setMe(user); } catch (e: any) { setErr(e?.message || "Failed to load account"); } }, []); useEffect(() => { let alive = true; (async () => { try { setErr(null); await refetchMe(); } finally { if (alive) setLoading(false); } })(); return () => { alive = false; }; }, [refetchMe]); if (loading) return
Loading…
; if (err) return
Error: {err}
; if (!me) return null; const avatarUrl = me.avatar?.id ? `${API_BASE}/assets/${me.avatar.id}` : null; return (

Account

{/* Shows success/failure after Ko-fi verify redirect */}
{avatarUrl ? ( // eslint-disable-next-line @next/next/no-img-element Avatar ) : ( No Avatar )}
Usernames can’t be changed.
Username
{me.username}
First Name
{me.first_name || "—"}
Last Name
{me.last_name || "—"}
Email
{me.email || "—"}
Location
{me.location || "—"}
{/* Badges (derive-on-read, no cron) */}
{/* Link Ko-fi */} {/* Editable sections */}
); }