ifixkart-storefront/components/pdp/YouMayAlsoLike.tsx

152 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import Link from 'next/link';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import BlurHashImage from '@/components/ui/BlurHashImage';
import { ProductResponse } from '@/services/api/catalogService';
import { formatCurrency, getCatalogPriceInfo, getImageUrl } from '@/lib/utils';
interface YouMayAlsoLikeProps {
products: ProductResponse[];
}
function priceLabel(product: ProductResponse): { current: string; compare?: string; range: boolean } {
const { price, priceMax, oldPrice } = getCatalogPriceInfo(product);
if (!price) return { current: '—', range: false };
if (priceMax && priceMax > price) {
return {
current: `${formatCurrency(price)} ${formatCurrency(priceMax)}`,
range: true,
};
}
return {
current: formatCurrency(price),
compare: oldPrice && oldPrice > price ? formatCurrency(oldPrice) : undefined,
range: false,
};
}
function discountPct(product: ProductResponse): number {
const { discount } = getCatalogPriceInfo(product);
return discount || 0;
}
function stableSaleSeconds(productId: string): number {
let hash = 0;
for (let i = 0; i < productId.length; i++) hash = (hash * 31 + productId.charCodeAt(i)) >>> 0;
return 36 * 3600 + (hash % (48 * 3600));
}
function SaleCountdown({ productId }: { productId: string }) {
const [remaining, setRemaining] = useState(() => stableSaleSeconds(productId));
useEffect(() => {
const t = setInterval(() => setRemaining((s) => (s > 0 ? s - 1 : 0)), 1000);
return () => clearInterval(t);
}, []);
const days = Math.floor(remaining / 86400);
const hrs = Math.floor((remaining % 86400) / 3600);
const min = Math.floor((remaining % 3600) / 60);
const sec = remaining % 60;
const pad = (n: number) => String(n).padStart(2, '0');
const units = [
{ val: pad(days), label: 'DAYS' },
{ val: pad(hrs), label: 'HRS' },
{ val: pad(min), label: 'MIN' },
{ val: pad(sec), label: 'SEC' },
];
return (
<div className="flex items-center justify-center gap-1 my-2">
{units.map((u) => (
<div key={u.label} className="flex flex-col items-center">
<span className="min-w-[28px] h-7 px-1 bg-white border border-gray-200 rounded-sm text-[12px] font-bold text-[#e53935] flex items-center justify-center">
{u.val}
</span>
<span className="text-[8px] font-semibold text-[#f48fb1] uppercase tracking-wide mt-0.5">{u.label}</span>
</div>
))}
</div>
);
}
function Stars({ rating }: { rating: number }) {
const filled = Math.round(rating || 5);
return (
<div className="flex justify-center gap-0.5 my-1.5" aria-label={`${filled} out of 5 stars`}>
{Array.from({ length: 5 }).map((_, i) => (
<span key={i} className={i < filled ? 'text-orange-400' : 'text-gray-300'}>
</span>
))}
</div>
);
}
export function YouMayAlsoLike({ products }: YouMayAlsoLikeProps) {
if (!products.length) return null;
return (
<section className="mt-4 mb-10">
<h3 className="text-[20px] font-semibold text-gray-800 mb-4 tracking-tight">You may also like...</h3>
<div className="overflow-x-auto rounded-md border border-gray-200 bg-white">
<div
className="grid min-w-[860px]"
style={{ gridTemplateColumns: `repeat(${Math.min(products.length, 5)}, minmax(168px, 1fr))` }}
>
{products.slice(0, 5).map((prod, index) => {
const img = getImageUrl(prod.images?.[0]?.image_url || prod.variants?.[0]?.images?.[0]?.image_url);
const off = discountPct(prod);
const pricing = priceLabel(prod);
const onSale = off > 0;
return (
<Link
key={prod.product_id}
href={`/products/${prod.slug}`}
className={`relative p-5 text-center hover:bg-[#fafafa] transition-colors ${
index > 0 ? 'border-l border-gray-200' : ''
}`}
>
{off > 0 && (
<span className="absolute top-3 right-3 bg-primary text-white text-[10px] font-bold px-1.5 py-0.5 rounded-sm">
-{off}%
</span>
)}
<div className="h-[140px] flex items-center justify-center mb-1">
<BlurHashImage
src={img}
alt={prod.name}
width={140}
height={140}
className="max-h-full max-w-full object-contain"
/>
</div>
{onSale ? <SaleCountdown productId={prod.product_id} /> : <div className="h-[42px]" />}
<p className="text-[13px] font-medium text-gray-800 leading-snug line-clamp-2 min-h-[36px]">
{prod.name}
</p>
<Stars rating={prod.rating || 5} />
<div className="text-[13px]">
{pricing.compare && (
<span className="text-gray-400 line-through mr-1.5 font-normal">{pricing.compare}</span>
)}
<span className="font-bold text-gray-900">{pricing.current}</span>
</div>
</Link>
);
})}
</div>
</div>
</section>
);
}