'use client';
import React, { useState, useEffect, useMemo, useRef } from 'react';
import Link from 'next/link';
import { ChevronLeft, ChevronRight, Star } from 'lucide-react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay, A11y, Navigation } from 'swiper/modules';
import type { Swiper as SwiperType } from 'swiper';
import { Product } from '../types';
import { getHotDealProduct } from '../utils/productMapper';
import { formatCurrency, getImageUrl } from '../lib/utils';
import { getSectionMeta, getSectionTitle } from '../lib/homepageDefaults';
import BlurHashImage from '../components/ui/BlurHashImage';
import 'swiper/css';
import 'swiper/css/navigation';
type TimerShape = { days: number; hours: number; minutes: number; seconds: number };
/** Remaining time until local end of today (true “deal of the day” window). */
function endOfTodayTimer(): TimerShape {
const now = new Date();
const end = new Date(now);
end.setHours(23, 59, 59, 999);
const diff = Math.max(0, Math.floor((end.getTime() - now.getTime()) / 1000));
return {
days: 0,
hours: Math.floor(diff / 3600),
minutes: Math.floor((diff % 3600) / 60),
seconds: diff % 60,
};
}
/**
* Deal of the Day countdown:
* - Prefer end of today (same-day deal).
* - Honor CMS `countdown_end` only when it falls within the next 24 hours.
* - Ignore far-future CMS dates (e.g. year-end) that produced 100+ day timers.
*/
function resolveDealOfDayTimer(iso?: string | null): TimerShape {
const today = endOfTodayTimer();
if (!iso) return today;
const endMs = new Date(iso).getTime();
if (Number.isNaN(endMs)) return today;
const diffSec = Math.floor((endMs - Date.now()) / 1000);
if (diffSec <= 0) return today;
// More than one day away → not a same-day deal; clamp to today
if (diffSec > 86400) return today;
return {
days: 0,
hours: Math.floor(diffSec / 3600),
minutes: Math.floor((diffSec % 3600) / 60),
seconds: diffSec % 60,
};
}
/** TechShop proportions — Hot Deals rail + 2×3 featured grid */
const FEAT_CARD_H = 148;
const FEAT_GAP = 14;
const FEAT_HEADER = 40;
const FEAT_TOP_GAP = 14;
const BLOCK_H = FEAT_HEADER + FEAT_TOP_GAP + FEAT_CARD_H * 2 + FEAT_GAP;
const HOT_IMG = 140;
function slugFromCmsProduct(p: any, idx: number): string {
if (p?.slug) return String(p.slug);
const link = String(p?.link || p?.url || p?.href || '');
const match = link.match(/\/products\/([^/?#]+)/i);
if (match?.[1]) return decodeURIComponent(match[1]);
return String(p?.id || `deal-${idx + 1}`);
}
interface HotDealsSectionProps {
config?: any;
products?: Product[];
loading?: boolean;
error?: boolean;
}
function SectionNav({
onPrev,
onNext,
}: {
onPrev: () => void;
onNext: () => void;
}) {
return (
);
}
function Stars({ rating }: { rating: number }) {
const filled = Math.min(5, Math.max(0, Math.round(rating)));
return (
{Array.from({ length: 5 }).map((_, i) => (
))}
);
}
/** Pink box countdown — TechShop Hot Deals style (no colon separators) */
function HotCountdown({
days = 0,
hours = 0,
minutes = 0,
seconds = 0,
resetKey,
}: {
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
resetKey: string;
}) {
const [remaining, setRemaining] = useState(
days * 86400 + hours * 3600 + minutes * 60 + seconds
);
useEffect(() => {
setRemaining(days * 86400 + hours * 3600 + minutes * 60 + seconds);
}, [days, hours, minutes, seconds, resetKey]);
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}
))}
);
}
/** Full deal card — slides as one unit when multiple deals exist */
function DealCard({
product,
title,
timer,
}: {
product: Product;
title: string;
timer: TimerShape;
}) {
const discount =
product.discount ||
(product.oldPrice && product.oldPrice > product.price
? Math.round(
((product.oldPrice - product.price) / product.oldPrice) * 100
)
: 0);
return (
{title}
{discount > 0 ? (
-{discount}%
) : null}
{product.image ? (
) : (
)}
{product.name}
{product.oldPrice != null && product.oldPrice > product.price ? (
{formatCurrency(Number(product.oldPrice))}
) : null}
{formatCurrency(Number(product.price))}
);
}
export const HotDealsSection: React.FC = ({
config,
products,
error,
}) => {
const [hotPool, setHotPool] = useState([]);
const [featuredPool, setFeaturedPool] = useState([]);
const [featuredPage, setFeaturedPage] = useState(0);
const dealSwiperRef = useRef(null);
const meta = getSectionMeta(config);
const sectionTitle = getSectionTitle(config, 'deal_of_the_day', 'Hot Deals');
const featuredTitle =
(typeof meta.featured_title === 'string' && meta.featured_title.trim()) ||
'Monthly Featured Item';
const dealTimer = resolveDealOfDayTimer(meta.countdown_end);
useEffect(() => {
const pinnedSlugs: string[] = meta.pinned_product_slugs || [];
const featuredSlugs: string[] =
meta.featured_product_slugs || meta.featured_slugs || [];
const cmsProducts: any[] = Array.isArray(meta.products) ? meta.products : [];
const rawFeaturedItems: any[] = Array.isArray(meta.featuredItems)
? meta.featuredItems
: Array.isArray(meta.featured_items)
? meta.featured_items
: [];
const mapCmsProduct = (p: any, idx: number): Product => {
const oldVal = Number(p.oldPrice || p.originalPrice || 0);
const newVal = Number(p.dealPrice ?? p.price ?? 0);
const slug = slugFromCmsProduct(p, idx);
const catalogMatch = products?.find(
(c) => c.slug === slug || c.id === slug || c.id === p.id
);
return {
id: String(p.id || catalogMatch?.id || `deal-${idx + 1}`),
name: p.title || p.name || catalogMatch?.name || 'Deal item',
price: newVal || Number(catalogMatch?.price) || 0,
oldPrice: oldVal || catalogMatch?.oldPrice || undefined,
discount:
p.discount ||
(oldVal > newVal
? Math.round(((oldVal - newVal) / oldVal) * 100)
: catalogMatch?.discount || 0),
image:
getImageUrl(p.image || p.image_url) || catalogMatch?.image || '',
rating: p.rating || catalogMatch?.rating || 5.0,
reviewsCount: p.reviewsCount || catalogMatch?.reviewsCount || 20,
soldCount: p.soldCount || catalogMatch?.soldCount || 45,
totalStock: p.totalStock || catalogMatch?.totalStock || 100,
timeRemaining: dealTimer,
hasTimer: true,
category: catalogMatch?.category || 'Accessories',
slug,
brand: p.brand || catalogMatch?.brand || 'iFixKart',
features: catalogMatch?.features,
};
};
const resolveFromSlugs = (slugs: string[]): Product[] => {
if (!slugs.length || !products?.length) return [];
return slugs
.map((slug) => products.find((p) => p.slug === slug || p.id === slug))
.filter(Boolean) as Product[];
};
/** Deal timer only on the Deals Of The Day card — never mutate shared catalog products. */
const asDealCards = (list: Product[]) =>
list.map((p) => ({
...p,
hasTimer: true,
timeRemaining: dealTimer,
}));
/** Monthly Featured / other grids: no deal countdown. */
const withoutDealTimer = (list: Product[]) =>
list.map((p) => ({
...p,
hasTimer: false,
timeRemaining: undefined,
}));
const backfillFeatured = (
seed: Product[],
excludeIds: Set = new Set()
): Product[] => {
if (seed.length >= 6) return seed.slice(0, 12);
if (!products || products.length === 0) return seed;
const taken = new Set([
...excludeIds,
...seed.map((p) => p.id || p.slug),
]);
const extras = products.filter(
(p) => !taken.has(p.id) && !taken.has(p.slug || '')
);
return [...seed, ...extras].slice(0, 12);
};
// ── Deal cards (slider when > 1) ──
let nextHot: Product[] = [];
const pinnedDeals = resolveFromSlugs(pinnedSlugs);
const hasSeparateFeatured = featuredSlugs.length > 0;
if (cmsProducts.length > 0) {
nextHot = asDealCards(cmsProducts.map(mapCmsProduct).slice(0, 5));
} else if (pinnedDeals.length > 0 && hasSeparateFeatured) {
nextHot = asDealCards(pinnedDeals.slice(0, 5));
} else if (pinnedDeals.length > 0) {
nextHot = asDealCards([pinnedDeals[0]]);
} else if (meta.hotProduct) {
nextHot = asDealCards([mapCmsProduct(meta.hotProduct, 0)]);
} else if (products && products.length > 0) {
const discounted = products.filter(
(p) => p.discount || (p.oldPrice && p.oldPrice > p.price) || p.isHot
);
const hot = getHotDealProduct(products);
const pool =
discounted.length > 0
? discounted
: hot
? [hot, ...products.filter((p) => p.id !== hot.id)].slice(0, 8)
: products.slice(0, 8);
nextHot = asDealCards(pool.slice(0, 5));
}
setHotPool(nextHot);
// ── Featured grid ──
const hotIds = new Set(
nextHot.map((p) => String(p.id || p.slug || '')).filter(Boolean)
);
let nextFeatured: Product[] = [];
const fromFeaturedSlugs = resolveFromSlugs(featuredSlugs);
if (fromFeaturedSlugs.length > 0) {
nextFeatured = fromFeaturedSlugs;
} else if (pinnedDeals.length > 1) {
nextFeatured = pinnedDeals.slice(1);
} else if (rawFeaturedItems.length > 0) {
nextFeatured = rawFeaturedItems.map((p: any, idx: number) =>
mapCmsProduct(p, idx)
);
} else if (cmsProducts.length > 1 && nextHot.length <= 1) {
nextFeatured = cmsProducts.map(mapCmsProduct).slice(1);
} else if (products && products.length > 0) {
const featured = products.filter((p) => p.isFeatured);
nextFeatured = featured.length > 0 ? featured : [];
}
setFeaturedPool(
withoutDealTimer(backfillFeatured(nextFeatured, hotIds))
);
setFeaturedPage(0);
}, [config, products, error]);
const featuredPageCount = Math.max(1, Math.ceil(featuredPool.length / 6));
const featuredProducts = useMemo(() => {
const start = featuredPage * 6;
return featuredPool.slice(start, start + 6);
}, [featuredPool, featuredPage]);
const defaultTimer = dealTimer;
const multiDeal = hotPool.length > 1;
const canSwitchFeatured = featuredPool.length > 6;
if (hotPool.length === 0) {
return (
{sectionTitle}
Deal products will appear here once they are available in the catalog.
);
}
return (
{/* ════════ Hot Deals — whole-card slider when > 1 ════════ */}
{multiDeal ? (
<>
{
dealSwiperRef.current = sw;
}}
slidesPerView={1}
spaceBetween={0}
speed={550}
loop={hotPool.length > 1}
autoplay={{
delay: 4500,
disableOnInteraction: false,
pauseOnMouseEnter: true,
}}
className="hot-deal-swiper !overflow-hidden rounded-[4px]"
style={{ minHeight: BLOCK_H }}
>
{hotPool.map((product) => {
const timer =
(product.timeRemaining as TimerShape | undefined) ||
defaultTimer;
return (
);
})}
>
) : (
)}
{/* ════════ Monthly Featured Item ════════ */}
{featuredTitle}
{canSwitchFeatured ? (
setFeaturedPage((p) =>
p === 0 ? featuredPageCount - 1 : p - 1
)
}
onNext={() =>
setFeaturedPage((p) =>
p === featuredPageCount - 1 ? 0 : p + 1
)
}
/>
) : null}
{featuredProducts.map((product) => (
))}
);
};
const FeaturedCard: React.FC<{ product: Product }> = ({ product }) => {
const imageUrl = getImageUrl(product.image);
const features =
product.features && product.features.length > 0
? product.features
: [
product.brand && product.brand !== 'iFixKart'
? String(product.brand)
: 'Genuine quality parts',
'Fast dispatch available',
];
const line1 = features[0] || '';
const line2 = features.slice(1, 3).join(' ') || features[1] || '';
return (
{product.name}
{product.oldPrice != null && product.oldPrice > product.price ? (
{formatCurrency(Number(product.oldPrice))}
) : null}
{formatCurrency(Number(product.price))}
{line1 ?
{line1}
: null}
{line2 ?
{line2}
: null}
);
};