"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 */}
) : (
)}
);
}
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) => (
))}
);
};