// app/builds/[buildId]/page.tsx import Link from "next/link"; import { notFound } from "next/navigation"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? ""; function formatMoney(n?: number | null) { if (n == null) return "—"; return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(n); } function timeAgo(iso?: string | null) { if (!iso) return ""; const d = new Date(iso); const diff = Math.max(0, Date.now() - d.getTime()); const mins = Math.floor(diff / 60000); if (mins < 60) return `${mins}m ago`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h ago`; const days = Math.floor(hrs / 24); return `${days}d ago`; } export default async function BuildBreakdownPage({ params, }: { params: Promise<{ buildId: string }>; }) { const { buildId } = await params; // Public detail endpoint - use Next.js API route const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'; const res = await fetch(`${baseUrl}/api/builds/public/${buildId}`, { cache: "no-store", }); if (res.status === 404) return notFound(); const build = await res.json().catch(() => null); if (!build) return notFound(); const items: any[] = build.items ?? []; const total = items.reduce((sum, it) => { const qty = Number(it.quantity ?? 1); const price = it.bestPrice == null ? 0 : Number(it.bestPrice); return sum + qty * price; }, 0); const images: any[] = build.images ?? build.photos ?? build.media ?? []; return (
{/* Header crumb */}
← Back to Community Builds
Public Build {build.updatedAt && ( updated {timeAgo(build.updatedAt)} )}
{/* Layout */}
{/* Post column */}
{/* “Reddit-ish” Post Card */}
{/* Vote rail */}
{/* Post body */}
{/* Meta */}
r/BattlBuilders posted by u/anonymous {build.createdAt && ( <> {timeAgo(build.createdAt)} )}
{/* Title */}

{build.title ?? "Untitled Build"}

{/* Description */} {build.description ? (

{build.description}

) : (

{/* placeholder copy */} Field notes coming soon. This build breakdown will include purpose, tradeoffs, and why each part made the cut.

)} {/* Action bar */}
Est. Total {formatMoney(total)}
{/* Parts card */}

Parts

{items.length} items
{items.map((it) => (
{/* Thumb */}
{it.productImageUrl ? ( // eslint-disable-next-line @next/next/no-img-element {it.productName ) : (
IMG
)}
{/* Info */}
{it.productName ?? "Part"}
{it.slot ?? "slot"} {it.productBrand && {it.productBrand}} x{it.quantity ?? 1}
{/* Price */}
{formatMoney(it.bestPrice)}
))}
{/* Gallery */}

Gallery

user submitted
{images.length === 0 ? (

No photos yet. Be the first to add a build pic.

(Upload UI coming soon — we’ll store and moderate images per build.)

{/* Mock thumbnails */}
{Array.from({ length: 8 }).map((_, i) => (
IMG
))}
) : (
{/* “Hero” preview */}
{/* eslint-disable-next-line @next/next/no-img-element */} Build photo
{/* Thumbs */}
{images.slice(0, 12).map((img, idx) => (
{/* eslint-disable-next-line @next/next/no-img-element */} {`Build
))}
{images.length} photos
)}
{/* Comments (placeholder) */}

Comments

mocked
{[ { user: "u/gearhead", body: "Solid parts list. Any reason you went with that upper over a BCM?", }, { user: "u/OP", body: "Mostly availability + price. I’ll probably swap once I track deals for a week.", }, { user: "u/boltcarrier", body: "Would love to see this with a pinned/weld option for 14.5 builds.", }, ].map((c, idx) => (
{c.user === "u/OP" ? "OP" : "u"}
{c.user} • 1h ago
{c.body}
))}
Commenting is coming soon. This will become the “breakdown” thread for the build (notes, tradeoffs, deal alerts, revisions).
{/* Sidebar */}
); }