/** * @page Compare Page (`app/compare/page.tsx`) * @purpose Product specification comparison matrix comparing up to 4 selected products. */ 'use client'; import { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { useCartStore } from '@/store/cartStore'; import { catalogService, ProductResponse } from '@/services/api/catalogService'; import { mapProductCardToProductResponse } from '@/utils/productMapper'; import { formatCatalogPriceLabel, getImageUrl } from '@/lib/utils'; import { ArrowLeftRight, Trash2 } from 'lucide-react'; import { AnnouncementBar } from '@/layout/AnnouncementBar'; import { Header } from '@/layout/Header'; import { Navbar } from '@/layout/Navbar'; import { Footer } from '@/layout/Footer'; import { StickyHeaderSpacer } from '@/components/StickyHeaderSpacer'; import { FloatingButtons } from '@/components/FloatingButtons'; import { CompareProductPicker } from '@/components/compare/CompareProductPicker'; import { COMPARE_LIMIT } from '@/lib/compare'; import { buildTypeCompareRows, sameTypeCompareList, type ProductSpecItem, } from '@/lib/compareSpecs'; export default function ComparePage() { const compareList = useCartStore((state) => state.compareList) || []; const toggleCompare = useCartStore((state) => state.toggleCompare); const addToCart = useCartStore((state) => state.addToCart); const hasHydrated = useCartStore((state) => state._hasHydrated); const [ready, setReady] = useState(false); const [enriched, setEnriched] = useState>({}); const [specMap, setSpecMap] = useState>({}); useEffect(() => { if (hasHydrated) { const list = useCartStore.getState().compareList; const pruned = sameTypeCompareList(list); if (pruned.length !== list.length) { useCartStore.setState({ compareList: pruned }); } setReady(true); return; } const t = window.setTimeout(() => setReady(true), 250); return () => window.clearTimeout(t); }, [hasHydrated]); const productKey = compareList.map((p) => p.product_id).join('|'); useEffect(() => { let cancelled = false; if (compareList.length === 0) { setEnriched({}); setSpecMap({}); return; } Promise.all( compareList.map(async (product) => { if (!product.slug) return null; try { const data = await catalogService.getProductDetail(product.slug); const raw = data?.product || data; const full = mapProductCardToProductResponse(raw); const specs = Array.isArray(data?.specifications) ? data.specifications .map((item: { label?: string; value?: string }) => ({ label: String(item?.label || '').trim(), value: String(item?.value || '').trim(), })) .filter((item: ProductSpecItem) => item.label && item.value) : []; return { id: product.product_id, product: { ...product, ...full, product_id: product.product_id, slug: product.slug || full.slug, brand_name: full.brand_name || product.brand_name, images: full.images?.length ? full.images : product.images, variants: full.variants?.length ? full.variants : product.variants, } as ProductResponse, specs, }; } catch { return null; } }) ).then((rows) => { if (cancelled) return; const nextProducts: Record = {}; const nextSpecs: Record = {}; rows.forEach((row) => { if (!row) return; nextProducts[row.id] = row.product; nextSpecs[row.id] = row.specs; }); setEnriched(nextProducts); setSpecMap(nextSpecs); }); return () => { cancelled = true; }; }, [productKey]); const products = useMemo( () => compareList.map((p) => enriched[p.product_id] || p), [compareList, enriched] ); const specRows = useMemo( () => buildTypeCompareRows(products, specMap), [products, specMap] ); return (
{ready && compareList.length < COMPARE_LIMIT ? (
) : null} {!ready ? (
) : compareList.length === 0 ? (

No Products Selected for Comparison

Click the compare icon on product cards to compare specifications side-by-side.

Browse Catalog
) : (
{products.map((product) => { const img = getImageUrl(product.images?.[0]?.image_url); return ( ); })} {products.map((p) => ( ))} {specRows.map((row) => ( {row.values.map((value, index) => ( ))} ))} {products.map((p) => ( ))} {products.map((p) => ( ))}
Feature
{img ? ( {product.name} ) : null}
{product.name} {formatCatalogPriceLabel(product)}
Price {formatCatalogPriceLabel(p)}
{row.label} {value.text}
Rating ★ {p.rating || 4.9} / 5.0
Action
)}
); }