ifixkart-storefront/sections/RecentlyLaunchedSection.tsx

260 lines
9.6 KiB
TypeScript

"use client";
import React, { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, A11y } from 'swiper/modules';
import type { Swiper as SwiperType } from 'swiper';
import { ProductCard } from '../components/ProductCard';
import { ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react';
import { Product } from '../types';
import { getImageUrl } from '../lib/utils';
import { getSectionMeta, getSectionTitle } from '../lib/homepageDefaults';
import 'swiper/css';
import 'swiper/css/navigation';
const DEFAULT_BANNER_IMAGE =
'https://images.unsplash.com/photo-1610945415295-d9bbf067e59c?w=600&auto=format&fit=crop&q=80';
interface RecentlyLaunchedSectionProps {
config?: any;
products?: Product[];
loading?: boolean;
error?: boolean;
}
export const RecentlyLaunchedSection: React.FC<RecentlyLaunchedSectionProps> = ({
config,
products,
error,
}) => {
const [carouselProducts, setCarouselProducts] = useState<Product[]>([]);
const [canSlide, setCanSlide] = useState(false);
const swiperRef = useRef<SwiperType | null>(null);
useEffect(() => {
const pinnedSlugs: string[] =
config?.metadata_json?.pinned_product_slugs ||
config?.metadata?.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[];
setCarouselProducts(pinned.length > 0 ? pinned : products.slice(0, 8));
return;
}
const rawItems = config?.metadata_json?.items || config?.metadata?.items;
if (rawItems && rawItems.length > 0) {
setCarouselProducts(
rawItems.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 || 12,
category: 'Accessories',
brand: 'Tech',
slug: it.slug || it.id,
}))
);
} else if (products && products.length > 0) {
const recent = products.filter((p) => p.isRecentlyLaunched);
setCarouselProducts(recent.length > 0 ? recent : products.slice(0, 8));
} else {
setCarouselProducts([]);
}
}, [config, products, error]);
const title = getSectionTitle(config, 'recently_launched');
const bannerTitle =
config?.metadata_json?.banner_title ||
config?.metadata?.banner_title ||
'iPhone Prodigy';
const bannerSubtitle =
config?.metadata_json?.banner_subtitle ||
config?.metadata?.banner_subtitle ||
'Performance in Motion';
const rawBannerImage =
config?.metadata_json?.banner_image || config?.metadata?.banner_image || '';
const bannerImage = getImageUrl(rawBannerImage) || DEFAULT_BANNER_IMAGE;
const bannerLink =
config?.metadata_json?.banner_link ||
config?.metadata?.banner_link ||
'/shop';
const syncOverflow = (swiper: SwiperType) => {
if (!swiper) return;
setCanSlide(!swiper.isLocked && carouselProducts.length > 0);
};
useEffect(() => {
const swiper = swiperRef.current;
if (!swiper) return;
setTimeout(() => {
if (swiper.navigation) {
try {
swiper.navigation.destroy();
swiper.navigation.init();
swiper.navigation.update();
} catch (e) {}
}
syncOverflow(swiper);
}, 0);
}, [carouselProducts.length]);
if (carouselProducts.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]">
Recently launched products will appear here once they are in the catalog.
</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]">
{/* Header — title + View All, line underneath (not joined to content) */}
<div className="flex items-center justify-between gap-4 pb-3.5 border-b border-[#e5e5e5]">
<div className="text-left">
<h2 className="text-[18px] md:text-[20px] font-bold text-[#222] tracking-tight">
{title}
</h2>
</div>
<Link
href="/shop?highlight=recent"
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>
{/* Banner + product strip */}
<div className="mt-5 flex flex-col lg:flex-row gap-4 lg:gap-5 items-stretch">
{/* Lavender feature card */}
<Link
href={bannerLink}
className="relative w-full lg:w-[260px] xl:w-[280px] shrink-0 rounded-2xl overflow-hidden bg-[#F3E8FF] min-h-[380px] lg:min-h-0 flex flex-col group/banner"
>
<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">
{bannerTitle}
</h3>
<p className="text-[13px] text-[#333] mb-4">{bannerSubtitle}</p>
<span className="text-[13px] font-bold text-[#1a1a1a] underline underline-offset-4 decoration-1 group-hover/banner:text-primary transition-colors">
Shop Now
</span>
</div>
<div className="absolute inset-x-0 bottom-0 top-[38%] flex items-end justify-center pointer-events-none select-none">
<img
src={bannerImage}
alt={bannerTitle}
className="h-[108%] w-auto max-w-[95%] object-contain object-bottom translate-y-[6%]"
/>
</div>
</Link>
{/* Product carousel with vertical dividers */}
<div className="relative group/rl flex-1 min-w-0 border border-[#e5e5e5] bg-white">
{/* Blue circular arrows — between banner & strip / far right */}
<button
type="button"
aria-label="Previous products"
className={`rl-swiper-prev absolute left-0 top-[42%] z-20 -translate-x-1/2 w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center shadow-md transition-opacity duration-200 hover:brightness-95 ${
canSlide
? 'opacity-100'
: 'opacity-0 pointer-events-none invisible'
}`}
>
<ChevronLeft size={20} strokeWidth={2.5} />
</button>
<button
type="button"
aria-label="Next products"
className={`rl-swiper-next absolute right-0 top-[42%] z-20 translate-x-1/2 w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center shadow-md transition-opacity duration-200 hover:brightness-95 ${
canSlide
? 'opacity-100'
: 'opacity-0 pointer-events-none invisible'
}`}
>
<ChevronRight size={20} strokeWidth={2.5} />
</button>
<Swiper
modules={[Navigation, A11y]}
onSwiper={(swiper) => {
swiperRef.current = swiper;
setTimeout(() => {
if (
swiper &&
swiper.params &&
swiper.params.navigation &&
typeof swiper.params.navigation !== 'boolean'
) {
swiper.params.navigation.prevEl = '.rl-swiper-prev';
swiper.params.navigation.nextEl = '.rl-swiper-next';
}
if (swiper?.navigation) {
try {
swiper.navigation.destroy();
swiper.navigation.init();
swiper.navigation.update();
} catch (e) {}
}
syncOverflow(swiper);
}, 0);
}}
onResize={syncOverflow}
onBreakpoint={syncOverflow}
onLock={() => setCanSlide(false)}
onUnlock={() => setCanSlide(true)}
navigation={{
prevEl: '.rl-swiper-prev',
nextEl: '.rl-swiper-next',
}}
watchOverflow
spaceBetween={0}
slidesPerView={2}
breakpoints={{
640: { slidesPerView: 2 },
900: { slidesPerView: 3 },
1200: { slidesPerView: 4 },
}}
className="h-full"
>
{carouselProducts.map((product) => (
<SwiperSlide
key={product.id || product.slug}
className="!h-auto border-r border-[#e5e5e5] last:border-r-0"
>
<ProductCard
product={product}
products={carouselProducts}
/>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</section>
);
};