"use client"; import React, { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; import { ChevronLeft, ChevronRight } from 'lucide-react'; import { Swiper, SwiperSlide } from 'swiper/react'; import { Navigation, A11y } from 'swiper/modules'; import type { Swiper as SwiperType } from 'swiper'; import { getImageUrl } from '../lib/utils'; import 'swiper/css'; import 'swiper/css/navigation'; interface PromoCard { id: string; badge: string; title: string; subtitle: string; priceLabel: string; price: string; image: string; bgColor: string; href: string; } /** TechShop-style promo tiles — used when CMS cards are empty */ const DEFAULT_CARDS: PromoCard[] = []; interface PromoCardsSectionProps { config?: any; } function mapConfigCards(raw: any[]): PromoCard[] { return raw.map((card: any, idx: number) => { const fallback = DEFAULT_CARDS[idx % DEFAULT_CARDS.length]; const priceText = String(card.priceText || card.price_text || ''); const priceMatch = priceText.match(/from\s*(.+)$/i); const bg = card.bgColor || card.bg_color || fallback.bgColor; return { id: String(card.id || `promo-${idx + 1}`), badge: String(card.badge || fallback.badge), title: String(card.title || fallback.title), subtitle: String(card.subtitle || fallback.subtitle), priceLabel: priceMatch ? 'from' : card.priceLabel || fallback.priceLabel, price: priceMatch ? priceMatch[1].trim() : String(card.price || priceText || fallback.price), image: getImageUrl(card.image || card.image_url) || fallback.image, bgColor: String(bg), href: String( card.link || card.buttonUrl || card.button_url || fallback.href ), }; }); } function PromoCardTile({ card }: { card: PromoCard }) { const solidBg = card.bgColor.startsWith('#') || card.bgColor.startsWith('rgb'); return ( {!solidBg ? ( ) : null}
{card.badge ? ( {card.badge} ) : null}

{card.title}

{card.subtitle ? (

{card.subtitle}

) : null} {(card.priceLabel || card.price) && (

{card.priceLabel ? ( {card.priceLabel} ) : null} {card.price}

)}
{card.image ? (
{/* eslint-disable-next-line @next/next/no-img-element */} {card.title}
) : (
)} ); } export const PromoCardsSection: React.FC = ({ config, }) => { const fromConfig = config?.metadata_json?.cards || config?.metadata?.cards || []; const cards = Array.isArray(fromConfig) && fromConfig.length > 0 ? mapConfigCards(fromConfig) : DEFAULT_CARDS; if (cards.length === 0) return null; const [canPrev, setCanPrev] = useState(false); const [canNext, setCanNext] = useState(false); const [showNav, setShowNav] = useState(false); const swiperRef = useRef(null); const syncNav = (swiper: SwiperType) => { const overflow = !swiper.isLocked && swiper.slides.length > 0; setShowNav(overflow); setCanPrev(overflow && !swiper.isBeginning); setCanNext(overflow && !swiper.isEnd); }; useEffect(() => { const swiper = swiperRef.current; if (!swiper) return; requestAnimationFrame(() => syncNav(swiper)); }, [cards.length]); // 3 or fewer: keep the original static 3-column grid if (cards.length <= 3) { return (
{cards.map((card) => ( ))}
); } return (
{showNav && canPrev ? ( ) : null} {showNav && canNext ? ( ) : null} { swiperRef.current = swiper; requestAnimationFrame(() => syncNav(swiper)); }} onSlideChange={syncNav} onResize={syncNav} onBreakpoint={syncNav} onLock={() => { setShowNav(false); setCanPrev(false); setCanNext(false); }} onUnlock={(swiper) => syncNav(swiper)} watchOverflow spaceBetween={16} slidesPerView={1} breakpoints={{ 640: { slidesPerView: 2, spaceBetween: 16 }, 1024: { slidesPerView: 3, spaceBetween: 20 }, }} > {cards.map((card) => ( ))}
); };