first commit
This commit is contained in:
62
app/blog/[slug]/page.tsx
Normal file
62
app/blog/[slug]/page.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { articles } from '@/lib/data';// مسیر دیتای خود را تنظیم کنید
|
||||
import { notFound } from 'next/navigation';
|
||||
import Image from "next/image";
|
||||
|
||||
// همان تابع استانداردسازی باید اینجا هم باشد تا بتوانیم مقایسه درستی انجام دهیم
|
||||
const generateSlug = (text: string) => {
|
||||
if (!text) return "";
|
||||
return text
|
||||
.trim()
|
||||
.replace(/[\s\u200c]+/g, '-')
|
||||
.replace(/[^\w\u0600-\u06FF0-9\-]/g, '')
|
||||
.replace(/\-\-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
};
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
slug: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default async function SingleArticlePage({ params }: PageProps) {
|
||||
const resolvedParams = await params;
|
||||
|
||||
// ۱. مرورگر کاراکترهای فارسی در آدرس را به طور پیشفرض کدگذاری میکند، پس ابتدا آن را دیکد (Decode) میکنیم
|
||||
const decodedUrlSlug = decodeURIComponent(resolvedParams.slug);
|
||||
|
||||
// ۲. جستجو در دیتابیس: عنوان هر مقاله را به همان فرمت تبدیل کرده و با مقدار موجود در آدرس مقایسه میکنیم
|
||||
const article = articles.find((a) => generateSlug(a.title) === decodedUrlSlug);
|
||||
|
||||
// ۳. اگر مقالهای با این عنوان پیدا نشد، ارور ۴۰۴ بده
|
||||
if (!article) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="container mx-auto p-8 max-w-3xl">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-3xl font-bold mb-4">{article.title}</h1>
|
||||
<span className="text-gray-500 text-sm">{article.date}</span>
|
||||
</header>
|
||||
|
||||
<div className="relative w-full h-[400px] mb-8">
|
||||
<Image
|
||||
src={article.image}
|
||||
alt={article.title}
|
||||
fill
|
||||
className="object-cover rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-4 border-r-4 border-[#ffb900] mb-8 font-medium">
|
||||
{article.excerpt}
|
||||
</div>
|
||||
|
||||
{/* بخش نمایش محتوای کامل که درخواست کرده بودید */}
|
||||
<div className="prose max-w-none leading-loose">
|
||||
{article.content}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
144
app/blog/page.tsx
Normal file
144
app/blog/page.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import ArticleCard from "@/components/articlecard";
|
||||
import { articles } from "@/lib/data";
|
||||
import { Search, BookOpen, FileText } from "lucide-react";
|
||||
|
||||
// رابط کاربری مقالات (فیلد category اضافه شد)
|
||||
interface Article {
|
||||
title: string;
|
||||
image: string;
|
||||
excerpt: string;
|
||||
date: string;
|
||||
category?: string; // این فیلد را در صورت امکان به دیتای خود در lib/data.ts اضافه کنید
|
||||
}
|
||||
|
||||
export default function BlogListingPage() {
|
||||
// ۱. استیتهای مربوط به فیلتر و جستجو
|
||||
const [activeCategory, setActiveCategory] = useState<string>('همه');
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
// ۲. استخراج خودکار دستهبندیهای یکتا از دیتای مقالات
|
||||
const categories = useMemo(() => {
|
||||
// اگر مقالهای دستهبندی نداشت، آن را در 'سایر' قرار میدهیم
|
||||
const allCategories = articles.map(article => article.category || 'سایر');
|
||||
const uniqueCategories = Array.from(new Set(allCategories));
|
||||
return ['همه', ...uniqueCategories];
|
||||
}, []);
|
||||
|
||||
// ۳. فیلتر کردن مقالات بر اساس دستهبندی و متن جستجو
|
||||
const filteredArticles = useMemo(() => {
|
||||
return articles.filter((article) => {
|
||||
const articleCategory = article.category || 'سایر';
|
||||
const matchesCategory = activeCategory === 'همه' || articleCategory === activeCategory;
|
||||
const matchesSearch =
|
||||
article.title.includes(searchQuery) ||
|
||||
article.excerpt.includes(searchQuery);
|
||||
|
||||
return matchesCategory && matchesSearch;
|
||||
});
|
||||
}, [activeCategory, searchQuery]);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-gray-50/30 pb-20">
|
||||
{/*
|
||||
بخش هدر (Hero Section)
|
||||
استفاده از گرادیانت ملایم و استایلهای مدرن
|
||||
*/}
|
||||
<div className="bg-gradient-to-b from-gray-50/80 to-transparent pt-12 pb-8 mb-8 border-b border-gray-100">
|
||||
<div className="container mx-auto px-4 max-w-7xl">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<div className="text-center md:text-right">
|
||||
<h1 className="text-3xl md:text-4xl font-black text-gray-800 mb-4 flex items-center justify-center md:justify-start gap-3">
|
||||
<BookOpen className="text-[#ffb900]" size={32} />
|
||||
وبلاگ و مقالات آموزشی
|
||||
</h1>
|
||||
<p className="text-gray-500 text-sm md:text-base font-medium max-w-2xl">
|
||||
جدیدترین اخبار، آموزشها، ترفندها و مقالات تخصصی در حوزه صنعت و قطعات را اینجا بخوانید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* باکس آمار مقالات */}
|
||||
<div className="hidden lg:flex items-center gap-4 bg-white p-4 rounded-2xl shadow-sm border border-gray-100">
|
||||
<div className="bg-[#ffb900] p-3 rounded-xl ">
|
||||
<FileText size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-400 font-bold mb-1">تعداد کل مقالات</p>
|
||||
<p className="text-xl font-black text-gray-800">{articles.length} مقاله</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 max-w-7xl">
|
||||
|
||||
{/* بخش فیلترها و جستجو */}
|
||||
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-6 mb-10 bg-white p-4 rounded-3xl shadow-sm border border-gray-100">
|
||||
|
||||
{/* فیلتر دستهبندی (قابلیت اسکرول افقی در موبایل با مخفی کردن نوار اسکرول) */}
|
||||
<div className="flex w-full lg:w-auto overflow-x-auto gap-2 pb-2 lg:pb-0 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setActiveCategory(category)}
|
||||
className={`
|
||||
whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold transition-all
|
||||
${activeCategory === category
|
||||
? 'bg-[#ffb900] text-white '
|
||||
: 'bg-gray-50 text-gray-600 hover:bg-gray-100 border border-gray-100'}
|
||||
`}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* باکس جستجو */}
|
||||
<div className="relative w-full lg:w-80 shrink-0">
|
||||
<Search className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="جستجو در مقالات..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-gray-50 border border-gray-100 rounded-full py-3 pr-11 pl-4 text-sm font-medium text-gray-700 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* گرید مقالات */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 md:gap-8">
|
||||
{filteredArticles.map((article) => (
|
||||
<div key={article.title} className="transform hover:-translate-y-1 transition-transform duration-300">
|
||||
<ArticleCard article={article} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* حالت خالی (وقتی مقالهای با فیلتر یا جستجو پیدا نشد) */}
|
||||
{filteredArticles.length === 0 && (
|
||||
<div className="flex flex-col justify-center items-center py-20 bg-white rounded-3xl border border-gray-100 border-dashed mt-8 text-center px-4">
|
||||
<div className="bg-gray-50 p-6 rounded-full mb-4">
|
||||
<Search size={40} className="text-gray-300" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-gray-700 mb-2">مقالهای پیدا نشد!</h3>
|
||||
<p className="text-gray-500 text-sm">
|
||||
با کلمه جستجو شده یا دستهبندی انتخاب شده، مقالهای وجود ندارد. لطفا فیلترها را تغییر دهید.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => { setActiveCategory('همه'); setSearchQuery(''); }}
|
||||
className="mt-6 px-6 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold rounded-xl transition-colors text-sm"
|
||||
>
|
||||
پاک کردن فیلترها
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user