makearmy-app/app/portal/account/AccountClient.tsx
2025-09-30 12:03:15 -04:00

122 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app/portal/account/AccountClient.tsx
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
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 AccountClient() {
const [me, setMe] = useState<Me | null>(null);
const [loading, setLoading] = useState(true);
const [needReauth, setNeedReauth] = useState(false);
const [error, setError] = useState<string | null>(null);
async function load() {
setLoading(true);
setError(null);
setNeedReauth(false);
try {
const res = await fetch("/api/account", {
credentials: "include",
cache: "no-store",
});
if (res.status === 401 || res.status === 403) {
setNeedReauth(true);
setMe(null);
return;
}
if (!res.ok) {
const j = await res.json().catch(() => ({}));
throw new Error(j?.error || `Failed: ${res.status}`);
}
const j = await res.json();
setMe(j as Me);
} catch (e: any) {
setError(e?.message || "Failed to load account");
} finally {
setLoading(false);
}
}
useEffect(() => { load(); }, []);
if (loading) {
return <div className="p-6 text-sm opacity-70">Loading account</div>;
}
if (needReauth) {
return (
<div className="p-6 max-w-xl">
<div className="rounded-md border p-4">
<h2 className="text-lg font-semibold mb-2">Confirm its you</h2>
<p className="text-sm text-muted-foreground mb-3">
For security, please sign in again before changing account details.
</p>
<Link
href={`/auth/sign-in?reauth=1&force=1&next=${encodeURIComponent("/portal/account")}`}
className="inline-block px-3 py-2 rounded-md border bg-accent text-background"
>
Re-authenticate
</Link>
</div>
</div>
);
}
if (error) {
return (
<div className="p-6 max-w-xl">
<div className="rounded-md border p-4">
<div className="text-red-600 mb-2">Error: {error}</div>
<button
onClick={load}
className="px-3 py-2 rounded-md border hover:bg-muted text-sm"
>
Retry
</button>
</div>
</div>
);
}
if (!me) return null;
return (
<div className="p-6 max-w-2xl space-y-6">
{/* View-only header */}
<section className="rounded-md border p-4">
<h1 className="text-xl font-semibold mb-2">Account</h1>
<p className="text-sm">
<span className="font-medium">Username:</span> {me.username}{" "}
<span className="text-muted-foreground">(usernames cant be changed)</span>
</p>
</section>
{/* Your existing edit forms can sit here, wired to:
- PATCH /api/account for first_name, last_name, email (optional), location
- POST /api/account/password for password change (asks current password in the form)
- POST /api/account/avatar for avatar upload (to your folder)
None of these fire automatically; only on submit. */}
{/* Example placeholder showing current profile fields */}
<section className="rounded-md border p-4">
<h2 className="text-lg font-semibold mb-3">Profile</h2>
<div className="grid gap-2 text-sm">
<div><span className="text-muted-foreground">First name:</span> {me.first_name || "—"}</div>
<div><span className="text-muted-foreground">Last name:</span> {me.last_name || "—"}</div>
<div><span className="text-muted-foreground">Email:</span> {me.email || "—"}</div>
<div><span className="text-muted-foreground">Location:</span> {me.location || "—"}</div>
</div>
</section>
</div>
);
}