"use client"; import { useMemo, useEffect, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import ReactMarkdown from "react-markdown"; const API_URL = process.env.NEXT_PUBLIC_API_BASE_URL!; const ASSET_BASE = process.env.NEXT_PUBLIC_ASSET_URL || "https://forms.lasereverything.net"; type Score = { id: string | number; cat: string; value: number | string; body?: string }; type LinkItem = { id?: string | number; text?: string; url: string; target?: string }; type Entry = { id: number | string; product_make?: string; product_model?: string; product_price?: string; review_overview_text?: string; review_intro_text?: string; author?: string; rec_text?: string; updates?: string; video_review_url?: string; header?: { id?: string; filename_disk?: string }; date_updated?: string | number; links?: LinkItem[]; scores?: Score[]; }; export default function BuyingGuideProductClient() { const searchParams = useSearchParams(); const router = useRouter(); const productId = searchParams.get("product"); const [entry, setEntry] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); // Fetch the entry when productId changes useEffect(() => { let abort = false; async function run() { if (!productId) return; setLoading(true); setError(null); try { const res = await fetch( `${API_URL}/items/bg_entries/${productId}?fields=*,links.id,links.text,links.url,links.target,scores.id,scores.cat,scores.value,scores.body,header.id,header.filename_disk,date_updated`, { cache: "no-store" } ); if (!res.ok) throw new Error(await res.text()); const { data } = await res.json(); if (!abort) setEntry(data); } catch (e: any) { if (!abort) setError(e?.message || "Failed to load product"); } finally { if (!abort) setLoading(false); } } run(); return () => { abort = true; }; }, [productId]); const avgScore = useMemo(() => { if (!entry?.scores?.length) return "N/A"; const sum = entry.scores.reduce((s, it) => s + Number(it.value ?? 0), 0); return (sum / entry.scores.length).toFixed(1); }, [entry]); // Prefer the public filename_disk path (like the list view). const headerUrl = entry?.header?.filename_disk ? `${ASSET_BASE}/assets/${entry.header.filename_disk}` : entry?.header?.id ? `${ASSET_BASE}/assets/${entry.header.id}?cache-buster=${entry.date_updated}&key=system-large-contain` : null; if (!productId) return null; // switcher guards this, but be defensive if (loading) { return
Loading…
; } if (error) { return (

Error: {error}

); } if (!entry) return null; return (
{/* Header Banner */} {headerUrl && (
Header Image
)} {/* Title */}

{entry.product_make}

{entry.product_model}

{entry.product_price && (

{entry.product_price.startsWith("Starting at") ? entry.product_price : `Starting at ${entry.product_price}`}

)}
{/* Links & Score Summary */} {(Array.isArray(entry.links) || Array.isArray(entry.scores)) && (
{Array.isArray(entry.links) && entry.links.length > 0 && (

Links

)} {Array.isArray(entry.scores) && entry.scores.length > 0 && (

Score Summary

    {entry.scores.map((s: any, idx: number) => (
  • {s.cat} {s.value}/10
  • ))}

Total: {avgScore}

)}
)} {/* Overview */} {entry.review_overview_text && (

Overview

{entry.review_overview_text}
)} {/* Intro */} {entry.review_intro_text && (

{`${entry.product_make}, ${entry.product_model} Review by ${entry.author}`}

{entry.review_intro_text}
)} {/* Detailed Scores */} {Array.isArray(entry.scores) && entry.scores.length > 0 && (
{entry.scores.map((s: any, idx: number) => (

{s.cat} – {s.value}/10

{s.body}
))}
)} {/* Recommendation */} {entry.rec_text && (

Recommendation

{entry.rec_text}
)} {/* Updates */} {entry.updates && (

Updates

{entry.updates}
)} {/* Video */} {entry.video_review_url && (

Video Review