"use client"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; export default function OnboardingPage() { const router = useRouter(); const { user, loading, token, logout, refreshMeAndSession } = useAuth(); // Step: 1=password, 2=username, 3=optional display name const [step, setStep] = useState<1 | 2 | 3>(1); // Password const [pw1, setPw1] = useState(""); const [pw2, setPw2] = useState(""); const [pwSaving, setPwSaving] = useState(false); const [pwMsg, setPwMsg] = useState(null); // Username const [username, setUsername] = useState(""); const [checking, setChecking] = useState(false); const [available, setAvailable] = useState(null); const [userMsg, setUserMsg] = useState(null); const [savingUsername, setSavingUsername] = useState(false); // Display name (optional) const [displayName, setDisplayName] = useState(""); const [savingName, setSavingName] = useState(false); const [nameMsg, setNameMsg] = 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 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; // Hydrate local inputs when user becomes available useEffect(() => { if (!user) return; setUsername(String((user as any)?.username ?? "")); setDisplayName(String(user.displayName ?? "")); }, [user]); // Route guards + step sync (NO redirects during render) useEffect(() => { if (loading) return; if (!user) return; // If complete, bounce them out if (hasUsername && hasPassword) { router.replace("/builder"); return; } // Choose the right step based on what's missing if (!hasPassword) setStep(1); else if (!hasUsername) setStep(2); else setStep(3); }, [loading, user, hasUsername, hasPassword, router]); if (loading) return
Loading…
; if (!user) return
Not logged in.
; 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 saved ✅"); setPw1(""); setPw2(""); // step will auto-sync via useEffect once user updates, but this makes UI feel instant: setStep(2); } catch (e: any) { setPwMsg(e?.message || "Couldn’t set password."); } finally { setPwSaving(false); } } async function checkUsername() { setChecking(true); setUserMsg(null); setAvailable(null); try { const u = username.trim().toLowerCase(); const res = await fetch( `/api/users/me/username-available?username=${encodeURIComponent(u)}`, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}) }, cache: "no-store", } ); if (!res.ok) { throw new Error((await res.text().catch(() => "")) || "Failed to check username"); } const data = await res.json(); setAvailable(!!data?.available); } catch (e: any) { setUserMsg(e?.message || "Couldn’t check username."); } finally { setChecking(false); } } async function saveUsername() { setSavingUsername(true); setUserMsg(null); try { const u = username.trim().toLowerCase(); const res = await fetch(`/api/users/me`, { method: "PATCH", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ username: u }), }); if (!res.ok) { throw new Error((await res.text().catch(() => "")) || "Failed to save username"); } await refreshMeAndSession(); setAvailable(true); setUserMsg("Username saved ✅"); setStep(3); } catch (e: any) { setUserMsg(e?.message || "Couldn’t save username."); } finally { setSavingUsername(false); } } 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); } } return (

Welcome to the beta 👋

Step {step} of 3. Password + username required.

{step === 1 && (
Set password *
setPw1(e.target.value)} placeholder="Password (min 8 chars)" className="w-full rounded-md border border-white/10 bg-black/40 px-3 py-2" /> setPw2(e.target.value)} placeholder="Confirm password" className="w-full rounded-md border border-white/10 bg-black/40 px-3 py-2" /> {(pwTooShort || pwMismatch) && (
{pwTooShort ? "Password must be at least 8 characters." : "Passwords don’t match."}
)} {pwMsg &&
{pwMsg}
}
)} {step === 2 && (
Choose username *
{ setUsername(e.target.value); setAvailable(null); setUserMsg(null); }} placeholder="e.g. s2tactical" className="w-full rounded-md border border-white/10 bg-black/40 px-3 py-2" />
{available !== null && ( {available ? "Available" : "Taken / invalid"} )}
{userMsg &&
{userMsg}
}
)} {step === 3 && (
Display name (optional)
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" /> {nameMsg &&
{nameMsg}
}
)}
); }