'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 (
{imageUrl ? (
) : (
)}
{product.name}
{product.oldPrice != null && product.oldPrice > product.price ? ( {formatCurrency(Number(product.oldPrice))} ) : null} {formatCurrency(Number(product.price))}
{line1 ?

{line1}

: null} {line2 ?

{line2}

: null}
); };