first commit

This commit is contained in:
haniyeroozmand
2026-03-21 18:58:07 +03:30
parent 738b02d376
commit 8b733c817c
40 changed files with 3968 additions and 0 deletions

62
app/blog/[slug]/page.tsx Normal file
View 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
View 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>
);
}