ifixkart-storefront/sections/HeroSection.tsx

217 lines
7.3 KiB
TypeScript

'use client';
import React, { useMemo } from 'react';
import Link from 'next/link';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay, Pagination, A11y } from 'swiper/modules';
import { SIDEBAR_WIDTH } from '@/components/CategorySidebar';
import { getImageUrl } from '@/lib/utils';
import 'swiper/css';
import 'swiper/css/pagination';
export interface SlideItem {
id: string;
badge: string;
title: string;
subtitle: string;
image: string;
buttonText: string;
buttonUrl: string;
gradient: string;
}
const DEFAULT_GRADIENTS = [
'from-[#0b1b3a] via-[#12356f] to-[#1976F3]',
'from-slate-950 via-blue-950 to-[#1565C0]',
'from-[#071428] via-[#0d2f66] to-[#1e88e5]',
];
/** TechShop-style fallback slides when CMS has no slides yet */
export const DEFAULT_SLIDES: SlideItem[] = [
{
id: '1',
badge: 'Limited Time Offer',
title: 'Ultimate Virtual Reality\nExperience Box',
subtitle: 'Experience the Future of Entertainment Today',
image: '',
buttonText: 'Shop Now',
buttonUrl: '/shop',
gradient: DEFAULT_GRADIENTS[0],
},
{
id: '2',
badge: 'Exclusive Launch',
title: 'Next-Gen Performance\nPro Gaming Gear',
subtitle: 'Unleash Your Full Creative Power Everywhere',
image: '',
buttonText: 'Shop Now',
buttonUrl: '/shop',
gradient: DEFAULT_GRADIENTS[1],
},
{
id: '3',
badge: 'Hot Deal',
title: 'Smart Wearables\nFor Everyday Life',
subtitle: 'Track fitness, stay connected, look sharp',
image: '',
buttonText: 'Explore Now',
buttonUrl: '/shop',
gradient: DEFAULT_GRADIENTS[2],
},
];
function sanitizeGradient(raw: unknown, fallback: string): string {
if (typeof raw !== 'string' || !raw.trim()) return fallback;
const value = raw.trim();
if (
/^(from|via|to)-[\w\[\]#./%-]+(\s+(from|via|to)-[\w\[\]#./%-]+)*$/.test(
value
)
) {
return value;
}
return fallback;
}
function mapConfigSlides(raw: any[]): SlideItem[] {
return raw
.filter((s) => s && (s.title || s.badge || s.image || s.image_url))
.map((s: any, idx: number) => {
const fallback = DEFAULT_SLIDES[idx % DEFAULT_SLIDES.length];
const imageRaw = s.image || s.image_url || '';
return {
id: String(s.id || `slide-${idx + 1}`),
badge: String(s.badge || s.discount_tag || fallback.badge),
title: String(s.title || fallback.title),
subtitle: String(s.subtitle || fallback.subtitle),
image: imageRaw ? getImageUrl(imageRaw) : '',
buttonText: String(
s.buttonText || s.button_text || s.cta_text || fallback.buttonText
),
buttonUrl: String(
s.buttonUrl ||
s.button_url ||
s.cta_link ||
s.link ||
fallback.buttonUrl
),
gradient: sanitizeGradient(
s.gradient,
DEFAULT_GRADIENTS[idx % DEFAULT_GRADIENTS.length]
),
};
})
.filter((s) => s.title);
}
function slidesFromConfig(config?: any): SlideItem[] {
const raw =
config?.metadata_json?.slides ||
config?.metadata?.slides ||
config?.slides;
if (Array.isArray(raw) && raw.length > 0) {
const mapped = mapConfigSlides(raw);
if (mapped.length > 0) return mapped;
}
return DEFAULT_SLIDES;
}
interface HeroSectionProps {
config?: any;
enabled?: boolean;
}
export const HeroSection: React.FC<HeroSectionProps> = ({
config,
enabled = true,
}) => {
const slides = useMemo(() => slidesFromConfig(config), [config]);
if (!enabled) return null;
return (
<div
className="techshop-hero relative rounded-lg overflow-hidden shadow-soft h-[380px] sm:h-[420px] md:h-[455px] w-full"
style={{ minHeight: SIDEBAR_WIDTH }}
>
<Swiper
modules={[Autoplay, Pagination, A11y]}
slidesPerView={1}
spaceBetween={0}
speed={850}
loop={slides.length > 1}
grabCursor
autoplay={{
delay: 5500,
disableOnInteraction: false,
pauseOnMouseEnter: true,
}}
pagination={{
clickable: true,
el: '.techshop-hero-pagination',
}}
className="h-full w-full"
>
{slides.map((slide) => (
<SwiperSlide key={slide.id} className="techshop-hero-slide">
<div
className={`h-full w-full bg-gradient-to-r ${slide.gradient} relative overflow-hidden`}
>
<div className="absolute -right-16 -top-20 w-72 h-72 rounded-full bg-primary/25 blur-3xl pointer-events-none z-[1]" />
<div className="absolute right-1/4 bottom-0 w-56 h-56 rounded-full bg-cyan-400/10 blur-3xl pointer-events-none z-[1]" />
<div className="relative z-10 h-full flex flex-col md:flex-row items-center justify-between gap-6 px-8 sm:px-12 lg:px-14 py-10">
<div className="text-left flex-1 max-w-xl text-white">
{slide.badge ? (
<span className="techshop-layer techshop-layer-1 inline-block bg-primary text-[11px] font-extrabold uppercase tracking-wider px-3.5 py-1.5 rounded-md mb-4 shadow-sm">
{slide.badge}
</span>
) : null}
<h2 className="techshop-layer techshop-layer-2 text-3xl sm:text-4xl lg:text-[42px] font-extrabold tracking-tight leading-[1.08] whitespace-pre-line mb-4 font-outfit drop-shadow-md">
{slide.title}
</h2>
{slide.subtitle ? (
<p className="techshop-layer techshop-layer-3 text-sm md:text-base text-blue-100/95 font-medium mb-7 max-w-lg">
{slide.subtitle}
</p>
) : null}
{slide.buttonText ? (
<div className="techshop-layer techshop-layer-4">
<Link
href={slide.buttonUrl || '/shop'}
className="inline-flex bg-primary hover:bg-primary-hover text-white text-xs md:text-sm font-semibold px-8 py-3.5 rounded-lg items-center shadow-lg transition-colors"
>
{slide.buttonText}
</Link>
</div>
) : null}
</div>
<div className="techshop-layer techshop-layer-media hidden md:flex flex-1 items-center justify-end h-full max-w-[46%]">
<div className="w-full max-w-sm aspect-square rounded-full bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center p-6">
{slide.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={slide.image}
alt={slide.title.replace(/\n/g, ' ')}
className="w-[88%] h-[88%] object-contain drop-shadow-2xl"
/>
) : (
<div className="w-[72%] h-[72%] rounded-2xl bg-gradient-to-br from-white/15 to-white/5 border border-white/20 shadow-2xl" />
)}
</div>
</div>
</div>
</div>
</SwiperSlide>
))}
</Swiper>
<div className="techshop-hero-pagination absolute bottom-4 left-0 right-0 z-20 flex justify-center gap-1.5" />
</div>
);
};