'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

)}
{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 */}
);
};