"use client"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; export default function ProfilePage() { const router = useRouter(); const { user, loading, token, logout, refreshMeAndSession } = useAuth(); const [displayName, setDisplayName] = useState(""); const [savingName, setSavingName] = useState(false); const [nameMsg, setNameMsg] = useState(null); const [showPassword, setShowPassword] = useState(false); const [pw1, setPw1] = useState(""); const [pw2, setPw2] = useState(""); const [pwSaving, setPwSaving] = useState(false); const [pwMsg, setPwMsg] = useState(null); const hasUsername = useMemo(() => { const current = String((user as any)?.username ?? "").trim(); return current.length > 0; }, [user]); const hasPassword = useMemo(() => { return !!(user as any)?.passwordSetAt; }, [user]); const needsOnboarding = !!user && (!hasUsername || !hasPassword); const pwMismatch = pw1.length > 0 && pw2.length > 0 && pw1 !== pw2; const pwTooShort = pw1.length > 0 && pw1.length < 8; const canSavePassword = !pwSaving && pw1.length >= 8 && pw1 === pw2; useEffect(() => { if (!user) return; setDisplayName(String(user.displayName ?? "")); }, [user]); // If missing required setup, bounce to onboarding useEffect(() => { if (loading) return; if (!user) return; if (needsOnboarding) { router.replace("/account/onboarding"); } }, [loading, user, needsOnboarding, router]); if (loading) return
Loading…
; if (!user) return
Not logged in.
; // While redirecting, don't flash profile UI if (needsOnboarding) return null; async function saveDisplayName() { setSavingName(true); setNameMsg(null); try { const next = displayName.trim(); const res = await fetch(`/api/users/me`, { method: "PATCH", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ displayName: next }), }); if (!res.ok) { throw new Error((await res.text().catch(() => "")) || "Failed to save display name"); } await refreshMeAndSession(); setNameMsg("Saved ✅"); } catch (e: any) { setNameMsg(e?.message || "Couldn’t save display name."); } finally { setSavingName(false); } } async function savePassword() { setPwMsg(null); if (pw1.length < 8) return setPwMsg("Password must be at least 8 characters."); if (pw1 !== pw2) return setPwMsg("Passwords don’t match."); setPwSaving(true); try { const res = await fetch(`/api/users/me/password`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ password: pw1 }), }); if (!res.ok) { throw new Error((await res.text().catch(() => "")) || "Failed to set password"); } await refreshMeAndSession(); setPwMsg("Password updated ✅"); setPw1(""); setPw2(""); setShowPassword(false); } catch (e: any) { setPwMsg(e?.message || "Couldn’t set password. Try again."); } finally { setPwSaving(false); } } return (

Profile

Basic account info.

Email:{" "} {user.email}
Username:{" "} {String((user as any)?.username ?? "—")}
Display name
{ setDisplayName(e.target.value); setNameMsg(null); }} placeholder="e.g. Sean / S2Tactical" className="w-full rounded-md border border-white/10 bg-black/40 px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-amber-500/40" />
{nameMsg && ( {nameMsg} )}
Role:{" "} {user.role}
{showPassword && (
Set password
setPw1(e.target.value)} placeholder="New password (min 8 chars)" className="w-full rounded-md border border-white/10 bg-black/40 px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-amber-500/40" /> setPw2(e.target.value)} placeholder="Confirm password" className="w-full rounded-md border border-white/10 bg-black/40 px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-amber-500/40" /> {(pwTooShort || pwMismatch) && (
{pwTooShort ? "Password must be at least 8 characters." : "Passwords don’t match."}
)}
{pwMsg && (
{pwMsg}
)}
)}
); }