// app/(account)/account/page.tsx "use client"; import { useMemo, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080"; export default function AccountPage() { const { user, loading, token, setSession, logout } = useAuth(); const searchParams = useSearchParams(); const router = useRouter(); const welcome = searchParams.get("welcome") === "1"; const needsDisplayName = useMemo(() => { return !user?.displayName || user.displayName.trim().length === 0; }, [user?.displayName]); // Only show welcome panel if they actually need setup const showWelcomeCard = welcome && needsDisplayName; // ---------------------------- // Display Name state (shared) // ---------------------------- const [displayName, setDisplayName] = useState(user?.displayName ?? ""); const [saving, setSaving] = useState(false); const [msg, setMsg] = useState(null); // Inline profile editor const [editingName, setEditingName] = useState(false); // ---------------------------- // Password state // ---------------------------- const [showPassword, setShowPassword] = useState(false); const [pw1, setPw1] = useState(""); const [pw2, setPw2] = useState(""); const [pwSaving, setPwSaving] = useState(false); const [pwMsg, setPwMsg] = useState(null); const canSaveName = useMemo(() => { return displayName.trim().length > 0 && !saving; }, [displayName, saving]); const pwMismatch = useMemo( () => pw1.length > 0 && pw2.length > 0 && pw1 !== pw2, [pw1, pw2] ); const pwTooShort = useMemo(() => pw1.length > 0 && pw1.length < 8, [pw1]); const canSavePassword = useMemo(() => { return !pwSaving && pw1.length >= 8 && pw1 === pw2; }, [pwSaving, pw1, pw2]); if (loading) return
Loading…
; if (!user) { return (
You’re not logged in. Please log in to view your account.
); } const authedUser = user; // user is non-null from here down async function saveDisplayName() { setSaving(true); setMsg(null); try { const nextName = displayName.trim(); const res = await fetch(`${API_BASE_URL}/api/users/me`, { method: "PATCH", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ displayName: nextName }), }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(text || "Failed to update profile"); } const updated = await res.json().catch(() => null); const nextUser = { email: authedUser.email, displayName: updated?.displayName ?? nextName, role: authedUser.role, }; if (token) setSession(token, nextUser); setMsg("Saved ✅"); setEditingName(false); // If we came from the welcome flow, clear the param if (welcome) router.replace("/account"); } catch (e: any) { setMsg(e?.message || "Couldn’t save. Try again."); } finally { setSaving(false); } } async function savePassword() { setPwMsg(null); if (pw1.length < 8) { setPwMsg("Password must be at least 8 characters."); return; } if (pw1 !== pw2) { setPwMsg("Passwords don’t match."); return; } setPwSaving(true); try { const res = await fetch(`${API_BASE_URL}/api/users/me/password`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ password: pw1 }), }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(text || "Failed to set password"); } setPwMsg("Password set ✅ You can now use email + password login."); setPw1(""); setPw2(""); setShowPassword(false); if (welcome) router.replace("/account"); } catch (e: any) { setPwMsg(e?.message || "Couldn’t set password. Try again."); } finally { setPwSaving(false); } } return (
{/* Welcome / Setup card (only if needed) */} {showWelcomeCard && (

Welcome to the beta 👋

Quick setup and you’re ready to cook.

{/* Display name */}
setDisplayName(e.target.value)} placeholder="e.g. Sean / S2Tactical / DirtNinja69" 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" />
{msg && ( {msg} )}
{/* Password */}

Want password login later? Optional (but recommended).

{!showPassword ? ( ) : (
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}
)}
)}
)} {/* Profile header */}

Profile

Basic account info.

{/* Profile card */}
Email:{" "} {user.email}
{/* Display name (inline editable) */}
Display name:{" "} {!editingName ? ( <> {user.displayName || "—"} ) : (
setDisplayName(e.target.value)} placeholder="Your display name" 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" />
{msg && ( {msg} )}
)}
Role:{" "} {user.role}
{/* Optional: quick link to set password later */}
{/* If user clicks "Set/change password" outside welcome flow, show it here */} {!showWelcomeCard && 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}
)}
)}
); }