import React from 'react'; import { notFound } from 'next/navigation'; import { AnnouncementBar } from '@/layout/AnnouncementBar'; import { Header } from '@/layout/Header'; import { Navbar } from '@/layout/Navbar'; import { Footer } from '@/layout/Footer'; import { FloatingButtons } from '@/components/FloatingButtons'; import { StickyHeaderSpacer } from '@/components/StickyHeaderSpacer'; import { catalogServerService } from '@/services/api/catalogServerService'; import { ProductInteractiveGrid } from '@/components/pdp/ProductInteractiveGrid'; import { ProductTabsWrapper } from '@/components/pdp/ProductTabsWrapper'; import { QuickComparisonTable } from '@/components/pdp/QuickComparisonTable'; import { YouMayAlsoLike } from '@/components/pdp/YouMayAlsoLike'; interface ProductDetailPageProps { params: Promise<{ slug: string }>; } const HIDDEN_SPEC_KEYS = [ 'cost_price', 'costprice', 'parent_media_key', 'parent_category', 'parent_id', 'media_key', 'sku', 'barcode', 'compare_price', ]; function normalizeSpecKey(code: string): string { return String(code || '').trim().toLowerCase().replace(/[\s-]+/g, '_'); } function humanizeSpecLabel(code: string): string { const key = normalizeSpecKey(code); const labels: Record = { quality: 'Quality', qul: 'Quality', qual: 'Quality', ram: 'RAM', storage: 'Storage', color: 'Color', colour: 'Color', clr: 'Color', size: 'Size', condition: 'Condition', in_box_contents: 'In the box', warranty_months: 'Warranty', country_of_origin: 'Country of origin', device_model: 'Device model', device_series: 'Device series', }; if (labels[key]) return labels[key]; return code .replace(/[_-]+/g, ' ') .replace(/\s+/g, ' ') .trim() .replace(/\b\w/g, (c) => c.toUpperCase()); } function looksLikeId(value?: string | null): boolean { if (!value) return true; if (value.length > 18 && /[0-9]/.test(value)) return true; if (/^[0-9A-Z]{16,}$/i.test(value.replace(/[_-]/g, ''))) return true; return false; } function extractDeviceModel(product: any, specifications: any[] = []): string { if (product.device_model_name && !looksLikeId(product.device_model_name)) { return product.device_model_name; } if (typeof product.device_model === 'object' && product.device_model?.name) { return product.device_model.name; } if (typeof product.device_model === 'string' && product.device_model && !looksLikeId(product.device_model)) { return product.device_model; } if (Array.isArray(specifications)) { const modelRef = specifications.find((s: any) => ['model reference', 'device model', 'model', 'device_model', 'model_reference'].includes( String(s.label || s.code || s.name || '').toLowerCase() ) ); if (modelRef && modelRef.value && modelRef.value !== 'N/A' && !looksLikeId(modelRef.value)) { return modelRef.value; } const seriesRef = specifications.find((s: any) => ['series', 'device series', 'device_series'].includes( String(s.label || s.code || s.name || '').toLowerCase() ) ); if (seriesRef && seriesRef.value && seriesRef.value !== 'N/A' && !looksLikeId(seriesRef.value)) { return seriesRef.value; } } if (product.device_series_name && !looksLikeId(product.device_series_name)) { return product.device_series_name; } if (typeof product.device_series === 'object' && product.device_series?.name) { return product.device_series.name; } if (typeof product.device_series === 'string' && product.device_series && !looksLikeId(product.device_series)) { return product.device_series; } const attrRows = variantAttributeRows(product); const devAttr = attrRows.find((a) => ['device_model', 'device_series', 'compatible_device', 'supported_device', 'model_reference', 'model'].includes(a.key) ); if (devAttr && devAttr.value && !looksLikeId(devAttr.value)) { return devAttr.value; } const title = String(product.name || product.title || '').trim(); if (title) { const cleaned = title .replace(/\s*(?:with\s+touch\s+screen|with\s+touch|display|screen|touch\s+glass|combo|battery|panel|housing|back\s+glass|charging\s+port|flex\s+cable|spare\s+parts|replacement)\b.*/i, '') .replace(/^buy\s+/i, '') .trim(); if (cleaned) return cleaned; return title; } return ''; } function variantAttributeRows(product: any): { key: string; label: string; value: string }[] { const collected = new Map>(); (product.variants || []).forEach((variant: any) => { (variant.attributes || []).forEach((attr: any) => { const code = String(attr.attribute_code || ''); const key = normalizeSpecKey(code); if (!code || HIDDEN_SPEC_KEYS.some((h) => key === h || key.includes(h))) return; if (key.startsWith('parent_') || key.includes('cost')) return; const value = String(attr.attribute_value || '').trim(); if (!value) return; if (!collected.has(key)) collected.set(key, new Set()); collected.get(key)!.add(value); }); }); return Array.from(collected.entries()).map(([key, values]) => ({ key, label: humanizeSpecLabel(key), value: Array.from(values).join(', '), })); } function groupSpecs(specifications: any[], product: any) { const groups: { title: string; rows: { label: string; value: string }[] }[] = []; const attrRows = variantAttributeRows(product); const mapped = (specifications || []).map((spec: any) => ({ group: String(spec.group || spec.section || spec.category || '').trim(), label: String(spec.label || spec.name || spec.key || ''), value: String(spec.value ?? spec.val ?? ''), })).filter((s) => s.label); const brandValue = !looksLikeId(product.brand_name) ? product.brand_name : !looksLikeId(product.brand_id) ? product.brand_id : 'Gallery'; const used = new Set(); const take = (keys: string[]) => attrRows .filter((row) => keys.includes(row.key)) .map((row) => { used.add(row.key); return { label: row.label, value: row.value }; }); const general = [ { label: 'Brand', value: brandValue }, { label: 'Model', value: product.name }, ...take(['device_series', 'device_model', 'condition', 'country_of_origin']), ]; const memory = take(['ram', 'storage', 'memory']); const appearance = take(['color', 'colour']); const pack = [ ...(product.warranty_type ? [{ label: 'Warranty Type', value: product.warranty_type }] : []), ...(product.warranty_summary ? [{ label: 'Warranty Details', value: product.warranty_summary }] : []), ...take(['in_box_contents', 'warranty_months', 'warranty']), ]; const leftover = attrRows .filter((row) => !used.has(row.key)) .map((row) => ({ label: row.label, value: row.value })); groups.push({ title: 'GENERAL INFORMATION', rows: general }); if (memory.length) groups.push({ title: 'MEMORY & STORAGE', rows: memory }); if (appearance.length) groups.push({ title: 'APPEARANCE', rows: appearance }); if (pack.length) groups.push({ title: 'PACKAGE & WARRANTY', rows: pack }); if (leftover.length) groups.push({ title: 'ADDITIONAL DETAILS', rows: leftover }); if (mapped.some((s) => s.group)) { const bucket: Record = {}; mapped.forEach((s) => { const key = (s.group || 'PRODUCT DETAILS').toUpperCase(); if (!bucket[key]) bucket[key] = []; bucket[key].push({ label: s.label, value: s.value }); }); Object.entries(bucket).forEach(([title, rows]) => { if (rows.length) groups.push({ title, rows }); }); return groups; } if (mapped.length) { groups.push({ title: 'PRODUCT DETAILS', rows: mapped.map((s) => ({ label: s.label, value: s.value })), }); } return groups; } export default async function ProductDetailPage({ params }: ProductDetailPageProps) { const { slug } = await params; if (!slug) { return notFound(); } let data: any = null; try { data = await catalogServerService.getProductDetail(slug); } catch (err) { console.error('Failed to load product details on server:', err); return notFound(); } if (!data || !data.product) { return notFound(); } const { product, reviews = [], related_products = [], specifications = [] } = data; const related = related_products.slice(0, 5); const comparisonProducts = [product, ...related_products.filter((p: any) => p.product_id !== product.product_id)].slice(0, 5); const specGroups = groupSpecs(specifications, product); const additionalInfoRows: { label: string; value: string }[] = []; // Supported Brand (auto-fetched) let brandVal = product.brand_name || product.brand?.name || ''; if (looksLikeId(brandVal)) { brandVal = ''; } if (!brandVal) { const brandAttr = variantAttributeRows(product).find((a) => a.key === 'brand' || a.key === 'brand_name'); if (brandAttr && !looksLikeId(brandAttr.value)) { brandVal = brandAttr.value; } } if (brandVal) { additionalInfoRows.push({ label: 'Supported Brand', value: brandVal }); } // Supported Device (auto-fetched exact device model) let deviceVal = extractDeviceModel(product, specifications); if (deviceVal) { additionalInfoRows.push({ label: 'Supported Device', value: deviceVal }); } const wType = product.warranty_type || 'Brand Warranty'; additionalInfoRows.push({ label: 'Warranty Type', value: wType }); if (product.warranty_summary || product.warranty_details) { additionalInfoRows.push({ label: 'Warranty Details', value: product.warranty_summary || product.warranty_details, }); } const attrRowsForPack = variantAttributeRows(product); attrRowsForPack.forEach((attr) => { if (['in_box_contents', 'warranty_months', 'warranty'].includes(attr.key)) { if (!additionalInfoRows.some((p) => p.label.toLowerCase() === attr.label.toLowerCase())) { additionalInfoRows.push({ label: attr.label, value: attr.value }); } } }); const descriptionHtml = (
{product.description ? (
) : (
No description available for this product.
)} {additionalInfoRows.length > 0 && (
{additionalInfoRows.map((row, idx) => { const isLast = idx === additionalInfoRows.length - 1; return ( ); })}
ADDITIONAL INFORMATION
{row.label} {row.value}
)}
); const specsHtml = ( {specGroups.map((group, groupIdx) => ( {group.rows.map((row, rowIdx) => { const isLast = groupIdx === specGroups.length - 1 && rowIdx === group.rows.length - 1; return ( ); })} ))}
0 ? 'border-t border-[#e0e0e0]' : '' }`} > {group.title}
{row.label} {row.value}
); return (
{related.length > 0 && } {comparisonProducts.length > 0 && ( )}
); }