"use client"; import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import PlatformSwitcher from "./PlatformSwitcher"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080"; type ProductListDto = { id: string; // your API returns strings sometimes; keep it flexible name: string; brand: string; platform: string; partRole: string; categoryKey: string | null; price: number | null; buyUrl: string | null; imageUrl: string | null; }; function safeSlugify(input: string) { return (input ?? "") .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, "") .slice(0, 80); } export default function PartsListPageClient(props: { platform: string; partRole: string; }) { const { platform, partRole } = props; const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Simple client-side search (fast + good enough for now) const [q, setQ] = useState(""); useEffect(() => { let cancelled = false; async function load() { try { setLoading(true); setError(null); // Your current list endpoint: // GET /api/products?platform=AR-15&partRoles=upper-receiver const url = `${API_BASE_URL}/api/products?platform=${encodeURIComponent(platform)}&partRoles=${encodeURIComponent(partRole)}`; const res = await fetch(url, { headers: { Accept: "application/json" } }); if (!res.ok) { const txt = await res.text().catch(() => ""); throw new Error(`Failed to load products (${res.status}): ${txt}`); } const data: ProductListDto[] = await res.json(); if (!cancelled) setItems(data); } catch (e: any) { if (!cancelled) setError(e?.message ?? "Failed to load products"); } finally { if (!cancelled) setLoading(false); } } load(); return () => { cancelled = true; }; }, [platform, partRole]); const filtered = useMemo(() => { const needle = q.trim().toLowerCase(); if (!needle) return items; return items.filter((p) => { const blob = `${p.brand ?? ""} ${p.name ?? ""} ${p.categoryKey ?? ""}`.toLowerCase(); return blob.includes(needle); }); }, [items, q]); return (

Battl Builders

Parts: {partRole}

setQ(e.target.value)} placeholder="Search brand, name, category…" className="w-full rounded-md border border-zinc-700 bg-zinc-950/80 px-3 py-2 text-sm text-zinc-100 outline-none focus:border-amber-400" />
{loading &&

Loading parts…

} {error && (
{error}
)} {!loading && !error && (

Results

{filtered.length} items

{filtered.map((p) => { const id = String(p.id); const slug = safeSlugify(p.name); const href = `/parts/${encodeURIComponent(platform)}/${encodeURIComponent(partRole)}/${id}-${slug}`; return (
{/* eslint-disable-next-line @next/next/no-img-element */} {p.imageUrl ? ( {p.name} ) : (
)}

{p.brand}

{p.name}

{p.categoryKey ?? "—"}

{typeof p.price === "number" ? `$${p.price.toFixed(2)}` : "—"}

); })}
)}
); }