"use client"; import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; import { useApi } from "@/lib/api"; type BuildDto = { uuid: string; title: string; description?: string | null; isPublic?: boolean | null; createdAt?: string | null; updatedAt?: string | null; items?: Array<{ slot: string; productId: number; productName?: string | null; bestPrice?: number | string | null; // backend may serialize BigDecimal as string }>; }; function fmt(d?: string | null) { if (!d) return "—"; const dt = new Date(d); return Number.isNaN(dt.getTime()) ? d : dt.toLocaleString(); } export default function VaultBuildEditPage() { const { uuid } = useParams<{ uuid: string }>(); const router = useRouter(); const api = useApi(); const { token, loading: authLoading } = useAuth(); const [build, setBuild] = useState(null); const [form, setForm] = useState({ title: "", description: "", isPublic: false, }); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [savedMsg, setSavedMsg] = useState(null); const authed = !!token; // Load build once auth is ready + token exists useEffect(() => { if (!uuid) return; if (authLoading) return; if (!authed) { setError("Please log in to edit builds."); return; } let cancelled = false; (async () => { try { setError(null); const res = await api.get( `/api/v1/builds/me/${encodeURIComponent(uuid)}` ); if (!res.ok) { const txt = await res.text().catch(() => ""); throw new Error(txt || `Failed to load build (${res.status})`); } const data = (await res.json()) as BuildDto; if (cancelled) return; setBuild(data); setForm({ title: data.title ?? "", description: data.description ?? "", isPublic: Boolean(data.isPublic), }); } catch (e: any) { if (!cancelled) setError(e?.message ?? "Failed to load build"); } })(); return () => { cancelled = true; }; }, [uuid, authLoading, authed, api]); const dirty = useMemo(() => { if (!build) return false; return ( form.title !== (build.title ?? "") || form.description !== (build.description ?? "") || form.isPublic !== Boolean(build.isPublic) ); }, [build, form]); async function onSave() { if (!uuid) return; setBusy(true); setSavedMsg(null); setError(null); try { const payload = { title: form.title.trim() || "Untitled Build", description: form.description.trim() || null, isPublic: form.isPublic, }; const res = await api.put( `/api/v1/builds/me/${encodeURIComponent(uuid)}`, payload ); if (!res.ok) { const txt = await res.text().catch(() => ""); throw new Error(txt || `Save failed (${res.status})`); } const updated = (await res.json()) as BuildDto; setBuild(updated); setForm({ title: updated.title ?? "", description: updated.description ?? "", isPublic: Boolean(updated.isPublic), }); setSavedMsg("Saved."); setTimeout(() => setSavedMsg(null), 2000); } catch (e: any) { setError(e?.message ?? "Save failed"); } finally { setBusy(false); } } async function onDelete() { if (!uuid) return; const ok = window.confirm("Delete this build?\n\nThis cannot be undone."); if (!ok) return; setBusy(true); setError(null); try { const res = await api.del( `/api/v1/builds/me/${encodeURIComponent(uuid)}` ); if (!res.ok) { const txt = await res.text().catch(() => ""); throw new Error(txt || `Delete failed (${res.status})`); } router.replace("/vault"); router.refresh(); } catch (e: any) { setError(e?.message ?? "Delete failed"); } finally { setBusy(false); } } return (

Edit Build

UUID: {uuid}
← Vault
{authLoading &&
Loading…
} {!authLoading && error && (
          {error}
        
)} {!authLoading && build && (
{/* meta */}
Created: {fmt(build.createdAt)} • Updated:{" "} {fmt(build.updatedAt)}
{form.isPublic ? "Published" : "Draft"}
{/* parts */}
Parts in this build
{build.items?.length ? `${build.items.length} items` : "—"}
{!build.items?.length ? (
No parts saved yet.
) : (
{Object.entries( build.items.reduce>( (acc, it) => { (acc[it.slot] ||= []).push(it); return acc; }, {} ) ).map(([slot, items]) => (
{slot}
{items.map((it, idx) => (
{it.productName ?? `Product #${it.productId}`}
ID:{" "} {it.productId}
{typeof it.bestPrice === "number" ? `$${it.bestPrice.toFixed(2)}` : it.bestPrice ? `$${Number(it.bestPrice).toFixed(2)}` : "—"}
Best price
))}
))}
)}
{/* form */}
setForm((f) => ({ ...f, title: e.target.value })) } className="mt-2 w-full rounded-md border border-white/10 bg-black/30 px-3 py-2 text-sm outline-none focus:border-amber-400/50" placeholder="e.g. Suppressed MK2" maxLength={120} />