"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 ?? ""; type ProductDetailDto = { id: string; name: string; brand: string; platform: string; partRole: string; categoryKey: string | null; price: number | null; buyUrl: string | null; imageUrl: string | null; }; function parseIdFromProductSlug(productSlug: string): string | null { // Expected: "1217-some-slug" const m = /^(\d+)(?:-|$)/.exec(productSlug ?? ""); return m?.[1] ?? null; } /** * Attempts to fetch a product detail. * If you don't have a dedicated endpoint yet, it falls back to list + filter by id. */ async function fetchProductDetail(params: { platform: string; partRole: string; productSlug: string; }): Promise { const { platform, partRole, productSlug } = params; const id = parseIdFromProductSlug(productSlug); if (!id) throw new Error(`Invalid product URL slug: '${productSlug}' (could not parse id)`); // ---- Preferred (if you add it): GET /api/v1/products/{id} // If it 404s, we fall back. try { const res = await fetch(`${API_BASE_URL}/api/v1/products/${encodeURIComponent(id)}`, { headers: { Accept: "application/json" }, }); if (res.ok) { return await res.json(); } } catch { // ignore and fall back } // ---- Fallback: use list endpoint + find by id (works right now with your current API) const listRes = await fetch( `${API_BASE_URL}/api/v1/products?platform=${encodeURIComponent(platform)}&partRoles=${encodeURIComponent(partRole)}`, { headers: { Accept: "application/json" } } ); if (!listRes.ok) { const txt = await listRes.text().catch(() => ""); throw new Error(`Failed to load product (fallback) (${listRes.status}): ${txt}`); } const list: ProductDetailDto[] = await listRes.json(); const found = list.find((p) => String(p.id) === String(id)); if (!found) { throw new Error(`Product id=${id} not found in ${platform}/${partRole} list`); } return found; } export default function ProductDetailPageClient(props: { platform: string; partRole: string; productSlug: string; }) { const { platform, partRole, productSlug } = props; const [p, setP] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const id = useMemo(() => parseIdFromProductSlug(productSlug), [productSlug]); useEffect(() => { let cancelled = false; async function load() { try { setLoading(true); setError(null); const data = await fetchProductDetail({ platform, partRole, productSlug }); if (!cancelled) setP(data); } catch (e: any) { if (!cancelled) setError(e?.message ?? "Failed to load product"); } finally { if (!cancelled) setLoading(false); } } load(); return () => { cancelled = true; }; }, [platform, partRole, productSlug]); return (

Battl Builders

Part: {partRole}

{/* temp disabling while I rework the routing. */} {/* */}
← Back to list id: {id ?? "—"}
{loading &&

Loading product…

} {error && (
{error}
)} {!loading && !error && p && (
{/* eslint-disable-next-line @next/next/no-img-element */} {p.imageUrl ? ( {p.name} ) : (
No image
)}

{p.brand} • {p.platform}

{p.name}

Category

{p.categoryKey ?? "—"}

Best price

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

{p.buyUrl ? ( Buy / View offer ) : ( No buy link )}
)}
); }