ifixkart-storefront/sections/ArticlesSection.tsx

176 lines
6.5 KiB
TypeScript

'use client';
import React, { useRef, useState } from 'react';
import Link from 'next/link';
import { ArrowRight, 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 {
DEFAULT_BLOG_ARTICLES,
getSectionTitle,
} from '@/lib/homepageDefaults';
import 'swiper/css';
import 'swiper/css/navigation';
interface ArticleItem {
id: string;
title: string;
excerpt: string;
image: string;
link: string;
}
interface ArticlesSectionProps {
config?: any;
}
function mapArticles(raw: any[]): ArticleItem[] {
return raw
.filter((a) => a && (a.title || a.image))
.map((a: any, idx: number) => {
const fallback = DEFAULT_BLOG_ARTICLES[idx % DEFAULT_BLOG_ARTICLES.length];
const img = a.image || a.image_url || '';
return {
id: String(a.id || `article-${idx + 1}`),
title: String(a.title || fallback.title),
excerpt: String(a.excerpt || a.subtitle || a.description || fallback.excerpt),
image: img ? getImageUrl(img) : fallback.image,
link: String(a.link || a.url || a.href || `/blog`),
};
});
}
export const ArticlesSection: React.FC<ArticlesSectionProps> = ({ config }) => {
const raw =
config?.metadata_json?.articles || config?.metadata?.articles || [];
const articles =
Array.isArray(raw) && raw.length > 0 ? mapArticles(raw) : DEFAULT_BLOG_ARTICLES;
const title = getSectionTitle(config, 'tech_blog');
const viewAllHref =
config?.metadata_json?.view_all_url ||
config?.metadata?.view_all_url ||
'/blog';
const swiperRef = useRef<SwiperType | null>(null);
const [canPrev, setCanPrev] = useState(false);
const [canNext, setCanNext] = useState(false);
const [showNav, setShowNav] = useState(false);
const syncNav = (swiper: SwiperType) => {
const overflow = !swiper.isLocked && swiper.slides.length > 0;
setShowNav(overflow);
setCanPrev(overflow && !swiper.isBeginning);
setCanNext(overflow && !swiper.isEnd);
};
if (articles.length === 0) return null;
return (
<section className="py-10 bg-white font-sans">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1400px]">
{/* Header + full-width rule — TechShop reference */}
<div className="flex items-center justify-between gap-4 pb-3.5 border-b border-[#e5e5e5] mb-7">
<h2 className="text-[20px] md:text-[22px] font-bold text-[#222] tracking-tight text-left">
{title}
</h2>
<Link
href={viewAllHref}
className="shrink-0 text-[13px] font-semibold text-[#333] hover:text-primary transition-colors inline-flex items-center gap-1.5"
>
View All
<ArrowRight size={14} strokeWidth={2.5} />
</Link>
</div>
<div className={`relative ${showNav ? 'md:px-10' : ''}`}>
{showNav ? (
<>
<button
type="button"
aria-label="Previous articles"
disabled={!canPrev}
onClick={() => swiperRef.current?.slidePrev()}
className={`hidden md:flex absolute left-0 top-[28%] -translate-y-1/2 z-20 w-10 h-10 rounded-full items-center justify-center shadow-md transition-colors ${
canPrev
? 'bg-primary text-white hover:bg-primary-hover cursor-pointer'
: 'bg-primary/40 text-white cursor-default'
}`}
>
<ChevronLeft size={20} strokeWidth={2.5} />
</button>
<button
type="button"
aria-label="Next articles"
disabled={!canNext}
onClick={() => swiperRef.current?.slideNext()}
className={`hidden md:flex absolute right-0 top-[28%] -translate-y-1/2 z-20 w-10 h-10 rounded-full items-center justify-center shadow-md transition-colors ${
canNext
? 'bg-primary text-white hover:bg-primary-hover cursor-pointer'
: 'bg-primary/40 text-white cursor-default'
}`}
>
<ChevronRight size={20} strokeWidth={2.5} />
</button>
</>
) : null}
<Swiper
modules={[Navigation, A11y]}
onSwiper={(swiper) => {
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={22}
slidesPerView={1.15}
breakpoints={{
640: { slidesPerView: 2, spaceBetween: 20 },
1024: { slidesPerView: 3, spaceBetween: 22 },
1280: { slidesPerView: 4, spaceBetween: 24 },
}}
className="!overflow-visible"
>
{articles.map((article) => (
<SwiperSlide key={article.id} className="h-auto">
<article className="text-left group">
<Link href={article.link} className="block">
<div className="relative w-full aspect-[16/10] rounded-xl overflow-hidden bg-[#f0f0f0] mb-3.5">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={article.image}
alt={article.title}
className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
loading="lazy"
/>
</div>
<h3 className="text-[15px] md:text-[16px] font-bold text-[#222] leading-snug mb-2 line-clamp-2 group-hover:text-primary transition-colors">
{article.title}
</h3>
</Link>
{article.excerpt ? (
<p className="text-[13px] text-[#777] leading-relaxed line-clamp-2">
{article.excerpt}
</p>
) : null}
</article>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</section>
);
};