178 lines
5.3 KiB
TypeScript
178 lines
5.3 KiB
TypeScript
'use client';
|
|
|
|
import React, { useMemo, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import {
|
|
BLOG_CATEGORIES,
|
|
type BlogArticle,
|
|
type BlogCategory,
|
|
} from '@/lib/blogDefaults';
|
|
|
|
function ArticleCard({
|
|
article,
|
|
featured,
|
|
}: {
|
|
article: BlogArticle;
|
|
featured?: boolean;
|
|
}) {
|
|
return (
|
|
<Link
|
|
href={`/blog/${article.slug}`}
|
|
className={`group block bg-white border border-gray-200 rounded-xl overflow-hidden hover:border-primary/30 hover:shadow-sm transition-all ${
|
|
featured ? '' : ''
|
|
}`}
|
|
>
|
|
<div
|
|
className={`relative bg-gray-100 overflow-hidden ${
|
|
featured ? 'aspect-[16/8] md:aspect-[16/7]' : 'aspect-[16/10]'
|
|
}`}
|
|
>
|
|
{/* 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]"
|
|
/>
|
|
<span className="absolute top-3 left-3 text-[11px] font-bold uppercase tracking-wide bg-white/95 text-primary px-2 py-1 rounded">
|
|
{article.category}
|
|
</span>
|
|
</div>
|
|
<div className={featured ? 'p-5 md:p-6' : 'p-4'}>
|
|
<h2
|
|
className={`font-bold text-[#1E293B] leading-snug group-hover:text-primary transition-colors ${
|
|
featured ? 'text-[20px] md:text-[24px]' : 'text-[15px] line-clamp-2'
|
|
}`}
|
|
>
|
|
{article.title}
|
|
</h2>
|
|
<p
|
|
className={`text-gray-500 mt-2 ${
|
|
featured ? 'text-[14px] line-clamp-3' : 'text-[13px] line-clamp-2'
|
|
}`}
|
|
>
|
|
{article.excerpt}
|
|
</p>
|
|
<p className="text-[12px] text-gray-400 mt-3">
|
|
{article.author} · {article.date} · {article.readTime}
|
|
</p>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export function BlogListing({ articles }: { articles: BlogArticle[] }) {
|
|
const [category, setCategory] = useState<'All' | BlogCategory>('All');
|
|
|
|
const filtered = useMemo(
|
|
() =>
|
|
category === 'All'
|
|
? articles
|
|
: articles.filter((a) => a.category === category),
|
|
[articles, category]
|
|
);
|
|
|
|
const featured = filtered[0];
|
|
const rest = filtered.slice(1);
|
|
|
|
return (
|
|
<div>
|
|
<h1 className="text-[28px] md:text-[32px] font-bold text-[#1E293B]">
|
|
News & Blog
|
|
</h1>
|
|
<p className="text-[14px] text-gray-500 mt-2 max-w-2xl">
|
|
Tech news, buying guides, and repair tips from the iFixKart team.
|
|
</p>
|
|
|
|
<div className="flex flex-wrap gap-2 mt-6 mb-8">
|
|
{BLOG_CATEGORIES.map((cat) => {
|
|
const active = category === cat;
|
|
return (
|
|
<button
|
|
key={cat}
|
|
type="button"
|
|
onClick={() => setCategory(cat)}
|
|
className={`h-9 px-3.5 rounded-full text-[13px] font-semibold border transition-colors cursor-pointer ${
|
|
active
|
|
? 'bg-primary text-white border-primary'
|
|
: 'bg-white text-gray-600 border-gray-200 hover:border-primary/40 hover:text-primary'
|
|
}`}
|
|
>
|
|
{cat}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{filtered.length === 0 ? (
|
|
<p className="text-[14px] text-gray-500">No articles in this category yet.</p>
|
|
) : (
|
|
<>
|
|
{featured ? <ArticleCard article={featured} featured /> : null}
|
|
{rest.length > 0 ? (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 mt-5">
|
|
{rest.map((article) => (
|
|
<ArticleCard key={article.slug} article={article} />
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BlogArticleView({
|
|
article,
|
|
related,
|
|
}: {
|
|
article: BlogArticle;
|
|
related: BlogArticle[];
|
|
}) {
|
|
return (
|
|
<article className="max-w-[820px]">
|
|
<Link
|
|
href="/blog"
|
|
className="text-[13px] font-semibold text-primary hover:underline"
|
|
>
|
|
← All articles
|
|
</Link>
|
|
<span className="ml-3 text-[11px] font-bold uppercase tracking-wide text-primary">
|
|
{article.category}
|
|
</span>
|
|
<h1 className="text-[26px] md:text-[32px] font-bold text-[#1E293B] leading-tight mt-3">
|
|
{article.title}
|
|
</h1>
|
|
<p className="text-[13px] text-gray-500 mt-3">
|
|
By {article.author} · {article.date} · {article.readTime}
|
|
</p>
|
|
|
|
<div className="relative w-full aspect-[16/9] rounded-xl overflow-hidden bg-gray-100 mt-6 mb-8">
|
|
{/* 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"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{article.body.map((para, i) => (
|
|
<p key={i} className="text-[15px] text-gray-600 leading-relaxed">
|
|
{para}
|
|
</p>
|
|
))}
|
|
</div>
|
|
|
|
{related.length > 0 ? (
|
|
<div className="mt-12 pt-8 border-t border-gray-200">
|
|
<h2 className="text-[18px] font-bold text-[#1E293B] mb-4">More stories</h2>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
{related.map((item) => (
|
|
<ArticleCard key={item.slug} article={item} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</article>
|
|
);
|
|
}
|