'use client'; import React, { useState, useEffect, useMemo } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { Heart, Eye, Star, ChartNoAxesColumn } from 'lucide-react'; import { Product } from '../types'; import { getImageUrl, formatCurrency, parseMoney } from '../lib/utils'; import { useCartStore } from '@/store/cartStore'; import { ProductResponse, ProductVariantResponse } from '@/services/api/catalogService'; import BlurHashImage from './ui/BlurHashImage'; import { toast } from 'sonner'; import { addToCompareAndNavigate } from '@/lib/compare'; interface ProductCardProps { product: Product; products?: Product[]; onAddToCart?: (product: Product) => void; onAddToWishlist?: (product: Product) => void; /** TechShop new-arrivals / grid style */ variant?: 'default' | 'techshop'; } function getProductResponse(product: any): ProductResponse { if (product.product_id && Array.isArray(product.variants) && product.variants.length > 0) { return { ...product, variants: product.variants.map((v: any) => ({ ...v, price: parseMoney(v.price) || parseMoney(product.price), compare_price: v.compare_price != null && v.compare_price !== '' ? parseMoney(v.compare_price) : null, cost_price: parseMoney(v.cost_price), })), }; } const realProductId = product.product_id || null; const realVariantId = product.first_variant_id || product.variant_id || null; const idStr = String(realProductId || product.id || product.slug || Math.random()); const productId = realProductId || (idStr.startsWith('prod-') ? idStr : `prod-${idStr}`); const priceVal = parseMoney(product.price); const variantId = realVariantId || `var-${idStr}`; const primaryVariant: ProductVariantResponse = { variant_id: variantId, product_id: productId, sku: product.sku || `SKU-${idStr}`, barcode: null, price: priceVal, compare_price: parseMoney(product.oldPrice) || null, cost_price: priceVal, low_stock_threshold: 5, status: 'ACTIVE', available_stock: typeof product.totalStock === 'number' ? product.totalStock : undefined, images: [ { image_id: `img-var-${idStr}`, variant_id: variantId, image_url: product.image || '', sort_order: 1, is_primary: true, }, ], }; return { product_id: productId, category_id: product.category || 'uncategorized', brand_id: product.brand || null, device_series_id: null, device_model_id: null, name: product.name, slug: product.slug || idStr, full_path: `/products/${product.slug || idStr}`, description: product.description || null, status: 'ACTIVE', created_at: new Date().toISOString(), images: [ { image_id: `img-${idStr}`, product_id: productId, image_url: product.image || '', alt_text: product.name, sort_order: 1, is_banner: false, }, ], variants: [primaryVariant], rating: product.rating, review_count: product.reviewsCount, badge: product.discount ? 'SALE' : null, }; } function SaleCountdown({ days = 0, hours = 0, minutes = 0, seconds = 0, }: { days?: number; hours?: number; minutes?: number; seconds?: number; }) { const [remaining, setRemaining] = useState( days * 86400 + hours * 3600 + minutes * 60 + seconds ); useEffect(() => { setRemaining(days * 86400 + hours * 3600 + minutes * 60 + seconds); }, [days, hours, minutes, seconds]); useEffect(() => { if (remaining <= 0) return; const t = setInterval(() => setRemaining((s) => (s > 0 ? s - 1 : 0)), 1000); return () => clearInterval(t); }, [remaining]); const d = Math.floor(remaining / 86400); const h = Math.floor((remaining % 86400) / 3600); const m = Math.floor((remaining % 3600) / 60); const s = remaining % 60; const pad = (n: number) => String(n).padStart(2, '0'); const units = [ { val: pad(d), label: 'DAYS' }, { val: pad(h), label: 'HRS' }, { val: pad(m), label: 'MIN' }, { val: pad(s), label: 'SEC' }, ]; return (
{units.map((u) => (
{u.val} {u.label}
))}
); } export const ProductCard: React.FC = ({ product, products, onAddToCart, onAddToWishlist, variant = 'default', }) => { const router = useRouter(); const [isHovered, setIsHovered] = useState(false); const wishlist = useCartStore((state) => state.wishlist) || []; const compareList = useCartStore((state) => state.compareList) || []; const prodId = (product as any).product_id || product.id; const cleanId = String(prodId).replace('prod-', ''); const isWishlisted = wishlist.some( (item) => item.product_id === prodId || item.product_id === `prod-${cleanId}` || item.product_id === cleanId || item.slug === product.slug || item.slug === product.id ); const isCompared = compareList.some( (item) => item.product_id === prodId || item.product_id === `prod-${cleanId}` || item.product_id === cleanId || item.slug === product.slug || item.slug === product.id ); const discountPct = useMemo(() => { if (product.discount && product.discount > 0) return product.discount; if (product.oldPrice && product.oldPrice > product.price) { return Math.round( ((product.oldPrice - product.price) / product.oldPrice) * 100 ); } return 0; }, [product.discount, product.oldPrice, product.price]); const showTimer = Boolean( product.hasTimer && product.timeRemaining && discountPct > 0 ); const href = `/products/${product.slug || product.id}`; const isOutOfStock = typeof product.totalStock === 'number' && product.totalStock <= 0; const handleCartClick = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (isOutOfStock) { toast.message('This item is currently out of stock'); router.push(href); return; } if (onAddToCart) { onAddToCart(product); return; } const prodResp = getProductResponse(product); const primaryVariant = prodResp.variants?.[0]; if (!primaryVariant?.variant_id) { toast.error('Unable to add this product to cart'); router.push(href); return; } const stock = primaryVariant.available_stock; if (typeof stock === 'number' && stock <= 0) { toast.message('This item is currently out of stock', { description: 'Opening product page for details', }); router.push(href); return; } const result = await useCartStore .getState() .addToCart(prodResp, primaryVariant, 1); if (result.ok) { toast.success('Added to cart'); return; } if (result.reason === 'out_of_stock') { toast.message('This item is currently out of stock'); router.push(href); return; } if (result.reason === 'stock_limit') { toast.error('Not enough stock for the selected quantity'); return; } toast.error('Unable to add this product to cart'); }; const handleWishlistClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); const prodResp = getProductResponse(product); useCartStore.getState().toggleWishlist(prodResp); if (onAddToWishlist) onAddToWishlist(product); }; const handleCompareClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); const prodResp = getProductResponse(product); void addToCompareAndNavigate(prodResp, (href) => router.push(href)); }; const handleQuickViewClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); window.dispatchEvent( new CustomEvent('openQuickView', { detail: { product, products: products && products.length > 0 ? products : undefined, }, }) ); }; const rawImage = typeof product.image === 'string' ? product.image : ''; const rawHoverImage = typeof product.imageHover === 'string' ? product.imageHover : undefined; const mainImageUrl = getImageUrl(rawImage); const hoverImageUrl = rawHoverImage ? getImageUrl(rawHoverImage) : undefined; const rating = Math.min(5, Math.max(0, Math.round(Number(product.rating) || 5))); const iconBtn = 'w-8 h-8 rounded-full bg-[#f0f0f0] text-[#444] hover:bg-primary hover:text-white flex items-center justify-center transition-colors cursor-pointer border-0 shadow-sm'; const ctaLabel = isOutOfStock ? 'Out of Stock' : variant === 'techshop' ? 'Buy Product' : 'Add To Cart'; return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} className="bg-white p-4 md:p-5 flex flex-col relative font-sans group/card h-full min-h-[380px]" > {/* Badges — top right */}
{isOutOfStock ? ( Out of Stock ) : null} {!isOutOfStock && discountPct > 0 ? ( -{discountPct}% ) : null}
{/* Hover actions — top left */}
{mainImageUrl ? ( <> {hoverImageUrl && !isOutOfStock && ( // eslint-disable-next-line @next/next/no-img-element {product.name} )} {isOutOfStock ? ( Out of Stock ) : null} ) : (
{isOutOfStock ? ( Out of Stock ) : null}
)}
{showTimer && product.timeRemaining ? ( ) : null}

{product.name}

{/* Price: range OR sale + compare */}
{product.priceMax && product.priceMax > product.price ? ( {formatCurrency(product.price)} – {formatCurrency(product.priceMax)} ) : ( <> {product.oldPrice && product.oldPrice > product.price ? ( {formatCurrency(product.oldPrice)} ) : null} {formatCurrency(product.price)} )}
{/* CTA — Buy Product / Out of Stock */}
); };