272 lines
9.3 KiB
TypeScript
272 lines
9.3 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useMemo } from 'react';
|
|
import Link from 'next/link';
|
|
import { ProductCard } from '../components/ProductCard';
|
|
import { ArrowRight } from 'lucide-react';
|
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
|
import { Autoplay, Pagination, A11y } from 'swiper/modules';
|
|
import { Product } from '../types';
|
|
import { getImageUrl } from '../lib/utils';
|
|
import { getSectionMeta, getSectionTitle } from '../lib/homepageDefaults';
|
|
|
|
import 'swiper/css';
|
|
import 'swiper/css/pagination';
|
|
|
|
const DEFAULT_BANNER_IMAGE =
|
|
'https://images.unsplash.com/photo-1585790050230-5dd28404ccb9?w=600&auto=format&fit=crop&q=80';
|
|
|
|
const BANNER_BG = ['#F8EDE6', '#E8F1F8', '#F3EDE8', '#EEF6EC'];
|
|
|
|
type BannerSlide = {
|
|
id: string;
|
|
title: string;
|
|
subtitle: string;
|
|
image: string;
|
|
link: string;
|
|
cta: string;
|
|
bg: string;
|
|
};
|
|
|
|
interface UltimateTechSectionProps {
|
|
config?: any;
|
|
products?: Product[];
|
|
loading?: boolean;
|
|
error?: boolean;
|
|
}
|
|
|
|
function buildBannerSlides(meta: Record<string, any>): BannerSlide[] {
|
|
const raw = Array.isArray(meta.banners) ? meta.banners : [];
|
|
const fromList = raw
|
|
.filter((b: any) => b && (b.title || b.image || b.image_url))
|
|
.map((b: any, idx: number) => ({
|
|
id: String(b.id || `banner-${idx + 1}`),
|
|
title: String(b.title || 'Featured'),
|
|
subtitle: String(b.subtitle || b.description || 'Smooth in Performance'),
|
|
image:
|
|
getImageUrl(b.image || b.image_url) || DEFAULT_BANNER_IMAGE,
|
|
link: String(
|
|
b.buttonUrl || b.button_url || b.link || b.href || '/shop'
|
|
),
|
|
cta: String(b.buttonText || b.button_text || b.cta || 'Shop Now'),
|
|
bg: String(b.bgColor || b.bg || BANNER_BG[idx % BANNER_BG.length]),
|
|
}));
|
|
|
|
if (fromList.length > 0) return fromList;
|
|
|
|
// Legacy single-banner fields
|
|
if (meta.banner_title || meta.banner_image) {
|
|
return [
|
|
{
|
|
id: 'banner-legacy',
|
|
title: String(meta.banner_title || 'Active Laptop'),
|
|
subtitle: String(meta.banner_subtitle || 'Smooth in Performance'),
|
|
image: getImageUrl(meta.banner_image) || DEFAULT_BANNER_IMAGE,
|
|
link: String(meta.banner_link || '/shop'),
|
|
cta: String(meta.banner_cta || 'Shop Now'),
|
|
bg: BANNER_BG[0],
|
|
},
|
|
];
|
|
}
|
|
|
|
return [
|
|
{
|
|
id: 'banner-default',
|
|
title: 'Smart Watches',
|
|
subtitle: 'Smooth in Performance',
|
|
image: DEFAULT_BANNER_IMAGE,
|
|
link: '/shop?category=smart-watches',
|
|
cta: 'Shop Now',
|
|
bg: BANNER_BG[0],
|
|
},
|
|
];
|
|
}
|
|
|
|
/** Full-card promo tile (slides as one unit — not content inside a frame). */
|
|
function BannerCard({ slide }: { slide: BannerSlide }) {
|
|
return (
|
|
<Link
|
|
href={slide.link}
|
|
className="relative block w-full h-full min-h-[380px] rounded-2xl overflow-hidden flex flex-col group/banner"
|
|
style={{ backgroundColor: slide.bg }}
|
|
>
|
|
<div className="relative z-10 pt-7 px-6 md:px-7 text-left">
|
|
<h3 className="text-[22px] md:text-[24px] font-bold text-[#1a1a1a] leading-tight mb-1.5">
|
|
{slide.title}
|
|
</h3>
|
|
{slide.subtitle ? (
|
|
<p className="text-[13px] text-[#333] mb-4">{slide.subtitle}</p>
|
|
) : (
|
|
<div className="mb-4" />
|
|
)}
|
|
<span className="text-[13px] font-bold text-[#1a1a1a] underline underline-offset-4 decoration-1 group-hover/banner:text-primary transition-colors">
|
|
{slide.cta}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="absolute inset-x-0 bottom-0 top-[36%] flex items-end justify-center pointer-events-none select-none">
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
src={slide.image}
|
|
alt={slide.title}
|
|
className="h-[110%] w-auto max-w-[98%] object-contain object-bottom translate-y-[8%]"
|
|
/>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export const UltimateTechSection: React.FC<UltimateTechSectionProps> = ({
|
|
config,
|
|
products,
|
|
error,
|
|
}) => {
|
|
const [techGadgets, setTechGadgets] = useState<Product[]>([]);
|
|
const meta = getSectionMeta(config);
|
|
|
|
const banners = useMemo(() => buildBannerSlides(meta), [config]);
|
|
|
|
useEffect(() => {
|
|
const pinnedSlugs: string[] = meta.pinned_product_slugs || [];
|
|
|
|
if (pinnedSlugs.length > 0 && products && products.length > 0) {
|
|
const pinned = pinnedSlugs
|
|
.map((slug) => products.find((p) => p.slug === slug || p.id === slug))
|
|
.filter(Boolean) as Product[];
|
|
if (pinned.length > 0) {
|
|
setTechGadgets(pinned.slice(0, 4));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const rawItems = meta.items;
|
|
if (rawItems && rawItems.length > 0) {
|
|
setTechGadgets(
|
|
rawItems.slice(0, 4).map((it: any) => ({
|
|
id: it.id || String(Math.random()),
|
|
name: it.title || it.name,
|
|
price: it.price || 0,
|
|
oldPrice: it.oldPrice || it.originalPrice,
|
|
discount: it.discount,
|
|
image: getImageUrl(it.image),
|
|
rating: it.rating || 5.0,
|
|
reviewsCount: it.reviewsCount || 8,
|
|
category: 'Tech Gadgets',
|
|
brand: 'SmartTech',
|
|
slug: it.slug || it.id,
|
|
}))
|
|
);
|
|
} else if (products && products.length > 0) {
|
|
const gadgets = products.filter((p) => p.isTechGadget);
|
|
setTechGadgets(
|
|
(gadgets.length > 0 ? gadgets : products).slice(0, 4)
|
|
);
|
|
} else {
|
|
setTechGadgets([]);
|
|
}
|
|
}, [config, products, error]);
|
|
|
|
const title = getSectionTitle(config, 'ultimate_tech');
|
|
const multiBanner = banners.length > 1;
|
|
|
|
if (techGadgets.length === 0) {
|
|
return (
|
|
<section className="py-8 bg-white font-sans">
|
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1400px]">
|
|
<div className="flex items-center justify-between gap-4 pb-3.5 border-b border-[#e5e5e5]">
|
|
<h2 className="text-[18px] md:text-[20px] font-bold text-[#222] tracking-tight">
|
|
{title}
|
|
</h2>
|
|
</div>
|
|
<div className="mt-5 border border-[#e5e5e5] rounded-lg p-10 text-center text-[13px] text-[#777]">
|
|
Tech gadgets will appear here once products are available.
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="py-8 bg-white font-sans">
|
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1400px]">
|
|
<div className="flex items-center justify-between gap-4 pb-3.5 border-b border-[#e5e5e5]">
|
|
<div className="text-left min-w-0">
|
|
<h2 className="text-[18px] md:text-[20px] font-bold text-[#222] tracking-tight">
|
|
{title}
|
|
</h2>
|
|
</div>
|
|
<Link
|
|
href="/shop?highlight=tech"
|
|
className="text-[13px] text-[#333] hover:text-primary transition-colors flex items-center gap-1.5 shrink-0"
|
|
>
|
|
View All
|
|
<ArrowRight size={14} strokeWidth={2} />
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="mt-5 flex flex-col lg:flex-row gap-4 lg:gap-5 items-stretch">
|
|
<div className="flex-1 min-w-0 border border-[#e5e5e5] bg-white grid grid-cols-2 md:grid-cols-4">
|
|
{techGadgets.map((product, idx) => (
|
|
<div
|
|
key={product.id || product.slug || idx}
|
|
className={[
|
|
'border-[#e5e5e5]',
|
|
idx % 2 === 0 ? 'border-r' : '',
|
|
idx < techGadgets.length - 1 ? 'md:border-r' : '',
|
|
idx < 2 ? 'border-b md:border-b-0' : '',
|
|
].join(' ')}
|
|
>
|
|
<ProductCard product={product} products={techGadgets} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Side banners: whole-card slide when more than one */}
|
|
<div className="relative w-full lg:w-[260px] xl:w-[280px] shrink-0 self-stretch min-h-[380px]">
|
|
{multiBanner ? (
|
|
<Swiper
|
|
modules={[Autoplay, Pagination, A11y]}
|
|
slidesPerView={1}
|
|
spaceBetween={0}
|
|
speed={550}
|
|
loop
|
|
autoplay={{
|
|
delay: 4200,
|
|
disableOnInteraction: false,
|
|
pauseOnMouseEnter: true,
|
|
}}
|
|
pagination={{ clickable: true }}
|
|
className="ultimate-banner-swiper h-full min-h-[380px] !overflow-hidden rounded-2xl"
|
|
>
|
|
{banners.map((slide) => (
|
|
<SwiperSlide key={slide.id} className="!h-auto min-h-[380px]">
|
|
<BannerCard slide={slide} />
|
|
</SwiperSlide>
|
|
))}
|
|
</Swiper>
|
|
) : (
|
|
<BannerCard slide={banners[0]} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style
|
|
dangerouslySetInnerHTML={{
|
|
__html: `
|
|
.ultimate-banner-swiper { height: 100%; }
|
|
.ultimate-banner-swiper .swiper-wrapper { align-items: stretch; }
|
|
.ultimate-banner-swiper .swiper-slide { height: auto; align-self: stretch; }
|
|
.ultimate-banner-swiper .swiper-pagination { bottom: 12px !important; }
|
|
.ultimate-banner-swiper .swiper-pagination-bullet {
|
|
width: 7px; height: 7px; background: #1a1a1a; opacity: 0.25;
|
|
}
|
|
.ultimate-banner-swiper .swiper-pagination-bullet-active {
|
|
opacity: 0.85; background: #1976f3;
|
|
}
|
|
`,
|
|
}}
|
|
/>
|
|
</section>
|
|
);
|
|
};
|