// app/builds/page.tsx import Link from "next/link"; type BuildClass = "Rifle" | "Pistol" | "NFA"; type Caliber = string; type BuildCard = { id: string; title: string; slug: string; creator: string; caliber: Caliber; buildClass: BuildClass; price: number; votes: number; tags: string[]; coverImageUrl?: string | null; }; type BuildFeedCardDto = { uuid: string; title?: string | null; slug?: string | null; creator?: string | null; caliber?: string | null; buildClass?: BuildClass | null; price?: number | null; votes?: number | null; tags?: string[] | null; coverImageUrl?: string | null; }; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? ""; function safeArray(v: unknown): string[] { return Array.isArray(v) ? v.filter(Boolean).map(String) : []; } function fallbackSlug(dto: BuildFeedCardDto) { return dto.slug?.trim() || dto.uuid; } function normalizeCard(dto: BuildFeedCardDto): BuildCard { const title = (dto.title ?? "Untitled Build").trim() || "Untitled Build"; return { id: dto.uuid, title, slug: fallbackSlug(dto), creator: (dto.creator ?? "anonymous").trim() || "anonymous", caliber: (dto.caliber ?? "—").trim() || "—", buildClass: (dto.buildClass ?? "Rifle") as BuildClass, price: typeof dto.price === "number" ? dto.price : 0, votes: typeof dto.votes === "number" ? dto.votes : 0, tags: safeArray(dto.tags), coverImageUrl: dto.coverImageUrl ?? null, }; } // Import the client component that will handle filters & voting import BuildsClient from "./BuildsClient"; export default async function BuildsPage() { let cards: BuildCard[] = []; let error: string | null = null; try { const res = await fetch(`${API_BASE_URL}/api/v1/builds?limit=50`, { method: "GET", // Disable full-page static caching; you can adjust this per your needs: cache: "no-store", headers: { "Content-Type": "application/json" }, }); if (!res.ok) { const txt = await res.text().catch(() => ""); throw new Error(txt || `Failed to load builds (${res.status})`); } const data = (await res.json()) as BuildFeedCardDto[]; cards = (Array.isArray(data) ? data : []).map(normalizeCard); } catch (e: any) { error = e?.message || "Failed to load builds feed"; } return (
{/* Header */}

Battl Builder · Build Book

Community Builds

Browse community builds, vote them up or down, and steal good ideas without shame. This feed is backed by public builds (isPublic=true).

Open Builder + Submit Build
{/* Pass initial data + any load error into the client component */}
); }