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>
|
||||
);
|
||||
}
|
||||
217
app/cart/page.tsx
Normal file
217
app/cart/page.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
'use client';
|
||||
|
||||
import { useCart } from "@/components/context/cartcontext";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Trash2,
|
||||
ShoppingBag,
|
||||
ChevronLeft,
|
||||
Plus,
|
||||
Minus,
|
||||
ShieldCheck,
|
||||
Truck,
|
||||
CreditCard
|
||||
} from "lucide-react";
|
||||
|
||||
export default function CartPage() {
|
||||
const { cart, clearCart, addToCart, decreaseQuantity } = useCart();
|
||||
|
||||
// تبدیل رشته قیمت به عدد
|
||||
const parsePrice = (priceStr?: string | null) => {
|
||||
if (!priceStr) return 0;
|
||||
return Number(priceStr.toString().replace(/,/g, ''));
|
||||
};
|
||||
|
||||
// محاسبه قیمت کل و تعداد کل
|
||||
const totalPrice = cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
|
||||
const totalItems = cart.reduce((total, item) => total + item.quantity, 0);
|
||||
|
||||
// دیزاین حالت سبد خرید خالی
|
||||
if (cart.length === 0) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50/50 flex flex-col items-center justify-center p-6">
|
||||
<div className="bg-white p-12 rounded-[3rem] shadow-sm border border-gray-100 flex flex-col items-center max-w-md w-full text-center">
|
||||
<div className="bg-blue-50/50 w-32 h-32 rounded-full flex items-center justify-center mb-8 relative">
|
||||
<ShoppingBag size={56} className="text-blue-500 relative z-10" />
|
||||
<div className="absolute inset-0 bg-blue-100 rounded-full animate-ping opacity-20"></div>
|
||||
</div>
|
||||
<h2 className="text-2xl font-black text-gray-800 mb-3">سبد خرید شما خالی است!</h2>
|
||||
<p className="text-gray-500 mb-10 leading-relaxed text-sm">
|
||||
هنوز هیچ محصولی به سبد خرید خود اضافه نکردهاید. برای مشاهده محصولات به صفحه اصلی برگردید.
|
||||
</p>
|
||||
<Link href="/" className="w-full bg-[#ffb900] hover:bg-[#e5a600] text-black font-bold text-lg px-8 py-4 rounded-2xl transition-all shadow-[0_4px_20px_rgba(255,185,0,0.3)] hover:shadow-[0_6px_25px_rgba(255,185,0,0.4)] flex items-center justify-center gap-2">
|
||||
بازگشت به فروشگاه
|
||||
<ChevronLeft size={20} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="bg-gray-50/30 min-h-screen pb-20">
|
||||
<div className="container mx-auto px-4 py-8 max-w-6xl">
|
||||
|
||||
{/* بخش هدر و Breadcrumb */}
|
||||
<div className="mb-10">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<h1 className="text-2xl md:text-3xl font-black text-gray-800 flex items-center gap-3">
|
||||
سبد خرید
|
||||
<span className="bg-[#ffb900]/20 text-[#d99d00] text-sm font-bold py-1 px-3 rounded-xl flex items-center">
|
||||
{totalItems} کالا
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Breadcrumb (مراحل پرداخت) */}
|
||||
<div className="flex items-center justify-center w-full max-w-2xl mx-auto">
|
||||
<div className="flex flex-col items-center w-1/3 relative z-10">
|
||||
<div className="w-10 h-10 bg-[#ffb900] text-black rounded-full flex items-center justify-center font-bold shadow-md mb-2 ring-4 ring-white">
|
||||
<ShoppingBag size={18} />
|
||||
</div>
|
||||
<span className="text-sm font-bold text-gray-800">سبد خرید</span>
|
||||
</div>
|
||||
|
||||
<div className="h-[2px] w-full bg-gray-200 absolute top-[28%] -z-0">
|
||||
<div className="h-full bg-[#ffb900] w-1/6 transition-all"></div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center w-1/3 relative z-10">
|
||||
<div className="w-10 h-10 bg-white border-2 border-gray-200 text-gray-400 rounded-full flex items-center justify-center font-bold mb-2">
|
||||
<Truck size={18} />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-400">اطلاعات ارسال</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center w-1/3 relative z-10">
|
||||
<div className="w-10 h-10 bg-white border-2 border-gray-200 text-gray-400 rounded-full flex items-center justify-center font-bold mb-2">
|
||||
<CreditCard size={18} />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-400">پرداخت</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 lg:gap-8">
|
||||
|
||||
{/* بخش لیست محصولات */}
|
||||
<div className="flex-1">
|
||||
<div className="bg-white rounded-[2rem] p-4 md:p-8 shadow-sm border border-gray-100">
|
||||
<div className="flex justify-between items-center mb-6 pb-6 border-b border-gray-100">
|
||||
<h2 className="text-lg font-bold text-gray-800">محصولات انتخاب شده</h2>
|
||||
<button onClick={clearCart} className="text-sm text-red-500 hover:text-red-700 transition flex items-center gap-1.5 bg-red-50 hover:bg-red-100 px-3 py-1.5 rounded-lg">
|
||||
<Trash2 size={16} />
|
||||
حذف همه
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
{cart.map((item) => {
|
||||
const itemTotal = parsePrice(item.price) * item.quantity;
|
||||
|
||||
return (
|
||||
<div key={item.id} className="group flex flex-col sm:flex-row gap-4 sm:gap-6 items-start sm:items-center border-b border-gray-50 pb-6 last:border-0 last:pb-0">
|
||||
|
||||
{/* تصویر محصول */}
|
||||
<div className="bg-gray-50/80 p-3 rounded-2xl border border-gray-100 shrink-0 relative group-hover:bg-white transition-colors duration-300 w-full sm:w-auto flex justify-center">
|
||||
<Image src={item.image} alt={item.title} width={100} height={100} className="object-contain w-24 h-24 drop-shadow-sm" />
|
||||
</div>
|
||||
|
||||
{/* اطلاعات محصول */}
|
||||
<div className="flex-1 w-full">
|
||||
<p className="text-xs font-medium text-blue-500 mb-1.5">{item.brand}</p>
|
||||
<h3 className="text-base font-bold text-gray-800 line-clamp-2 leading-tight mb-3 group-hover:text-blue-600 transition-colors">{item.title}</h3>
|
||||
|
||||
{/* ویژگیها (Badges) */}
|
||||
<div className="flex flex-wrap gap-2 text-xs font-medium text-gray-600">
|
||||
<span className="bg-gray-100/80 px-2.5 py-1 rounded-md" dir="ltr">L : {item.l}</span>
|
||||
<span className="bg-gray-100/80 px-2.5 py-1 rounded-md" dir="ltr">D : {item.d}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* قیمت و کنترلر */}
|
||||
<div className="flex sm:flex-col justify-between sm:justify-end items-center sm:items-end w-full sm:w-auto gap-4 shrink-0 mt-2 sm:mt-0">
|
||||
<span className="font-black text-lg text-gray-900">
|
||||
{item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'}
|
||||
</span>
|
||||
|
||||
{/* کنترلر تعداد طرح کپسولی */}
|
||||
<div className="flex items-center gap-1 bg-gray-50 border border-gray-200 rounded-full p-1 shadow-sm">
|
||||
<button
|
||||
onClick={() => addToCart(item)}
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full bg-white text-gray-600 hover:text-green-600 hover:shadow-sm transition-all"
|
||||
>
|
||||
<Plus size={16} strokeWidth={2.5} />
|
||||
</button>
|
||||
|
||||
<span className="text-sm font-bold text-gray-800 w-6 text-center">
|
||||
{item.quantity}
|
||||
</span>
|
||||
|
||||
<button
|
||||
onClick={() => decreaseQuantity(item.id)}
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full bg-white text-gray-600 hover:text-red-500 hover:shadow-sm transition-all"
|
||||
>
|
||||
{item.quantity === 1 ? <Trash2 size={16} /> : <Minus size={16} strokeWidth={2.5} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* بخش صورتحساب (Sidebar) */}
|
||||
<div className="w-full lg:w-[340px] shrink-0">
|
||||
<div className="bg-white rounded-[2rem] p-6 md:p-8 shadow-xl shadow-gray-200/20 border border-gray-100 sticky top-6">
|
||||
<h2 className="text-xl font-bold text-gray-800 mb-6">خلاصه صورتحساب</h2>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-gray-500">تعداد کالاها</span>
|
||||
<span className="font-bold text-gray-800">{totalItems} عدد</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-gray-500">هزینه ارسال</span>
|
||||
<span className="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded-md text-xs">در مرحله بعد</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* خطچین جداکننده */}
|
||||
<div className="w-full border-t-2 border-dashed border-gray-200 my-6"></div>
|
||||
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<span className="text-sm font-bold text-gray-600">مبلغ قابل پرداخت</span>
|
||||
<span className="font-black text-2xl text-[#ffb900] tracking-tight">
|
||||
{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')}` : 'استعلام'}
|
||||
{totalPrice > 0 && <span className="text-sm font-medium text-gray-500 mr-1">تومان</span>}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button className="w-full bg-[#ffb900] hover:bg-[#e5a600] text-black py-4 rounded-xl font-bold text-lg transition-all shadow-[0_4px_15px_rgba(255,185,0,0.2)] hover:shadow-[0_6px_20px_rgba(255,185,0,0.3)] flex justify-center items-center gap-2 mb-6">
|
||||
تایید و ادامه
|
||||
<ChevronLeft size={20} />
|
||||
</button>
|
||||
|
||||
{/* بجهای اعتماد (Trust Badges) */}
|
||||
<div className="flex items-center justify-center gap-4 text-xs font-medium text-gray-400 bg-gray-50 py-3 rounded-xl border border-gray-100">
|
||||
<div className="flex items-center gap-1">
|
||||
<ShieldCheck size={16} className="text-green-500" />
|
||||
پرداخت امن
|
||||
</div>
|
||||
<div className="w-1 h-1 bg-gray-300 rounded-full"></div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Truck size={16} className="text-blue-500" />
|
||||
ارسال سریع
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
21
app/globals.css
Normal file
21
app/globals.css
Normal file
@@ -0,0 +1,21 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
/* --background: #ffffff; */
|
||||
/* --foreground: #171717; */
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
/* --background: #0a0a0a; */
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
/* background: var(--background); */
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-site), Arial, Helvetica, sans-serif;
|
||||
}
|
||||
43
app/layout.tsx
Normal file
43
app/layout.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
import "./globals.css";
|
||||
import { Header } from "@/components/header";
|
||||
import Footer from "@/components/footer";
|
||||
import localFont from 'next/font/local';
|
||||
import { CartProvider } from "@/components/context/cartcontext";
|
||||
|
||||
|
||||
const Yekanbakh = localFont({
|
||||
src: [
|
||||
{
|
||||
path: '../public/src/font/YekanBakhFaNum-Regular.woff',
|
||||
weight: '400',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: '../public/src/font/YekanBakhFaNum-SemiBold.woff',
|
||||
weight: '500',
|
||||
style: 'normal',
|
||||
},
|
||||
],
|
||||
variable: '--font-site',
|
||||
display: 'swap',
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="fa" dir="rtl" className={Yekanbakh.variable}>
|
||||
<body>
|
||||
<CartProvider>
|
||||
<Header/>
|
||||
{children}
|
||||
</CartProvider>
|
||||
|
||||
<Footer/>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
445
app/page.tsx
Normal file
445
app/page.tsx
Normal file
@@ -0,0 +1,445 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { Headphones, FileText, Truck, ShieldCheck, Square, MoreVertical, Circle, Target, CookingPot, Plus, Minus, MessageCircleCheckIcon, } from "lucide-react";
|
||||
import ProductCard from "@/components/productcard";
|
||||
import ArticleCard from "@/components/articlecard";
|
||||
import FAQItem from "@/components/faq";
|
||||
import { products } from "@/lib/data";
|
||||
import { articles } from "@/lib/data";
|
||||
|
||||
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Headphones,
|
||||
title: "مشاوره فنی رایگان",
|
||||
desc: "انتخاب بهترین قطعه با توجه به نیاز شما",
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: "فاکتور رسمی",
|
||||
desc: "صدور فاکتور برای شرکتها",
|
||||
},
|
||||
{
|
||||
icon: Truck,
|
||||
title: "ارسال فوری",
|
||||
desc: "ارسال در سریعترین زمان ممکن",
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
title: "ضمانت اصالت کالا",
|
||||
desc: "تضمین اورجینال بودن محصولات",
|
||||
},
|
||||
];
|
||||
|
||||
const categories = [
|
||||
{
|
||||
title: "فیلتر و رگلاتور",
|
||||
icon: CookingPot,
|
||||
},
|
||||
{
|
||||
title: "باتن",
|
||||
icon: Square,
|
||||
},
|
||||
{
|
||||
title: "سوئیچ",
|
||||
icon: MoreVertical,
|
||||
},
|
||||
{
|
||||
title: "شبکه ای",
|
||||
icon: Circle,
|
||||
},
|
||||
{
|
||||
title: "مدیرفشار",
|
||||
icon: Target,
|
||||
},
|
||||
{
|
||||
title: "شیر برقی",
|
||||
icon: Minus,
|
||||
},
|
||||
];
|
||||
|
||||
const tabs = ["پرفروشترینها", "تخفیفدار", "جدیدترینها"];
|
||||
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: "آیا تمامی قطعات دارای ضمانت اصالت هستند؟",
|
||||
answer:
|
||||
"بله، تمامی محصولات ارائه شده دارای ضمانت اصالت کالا بوده و از برندهای معتبر جهانی تأمین میشوند.",
|
||||
},
|
||||
{
|
||||
question: "امکان صدور فاکتور رسمی برای شرکتها وجود دارد؟",
|
||||
answer:
|
||||
"بله، برای تمامی سفارشات امکان صدور فاکتور رسمی وجود دارد.",
|
||||
},
|
||||
{
|
||||
question: "سفارشها چه زمانی ارسال میشوند؟",
|
||||
answer:
|
||||
"سفارشها در سریعترین زمان ممکن پردازش شده و از طریق روشهای ارسال معتبر ارسال میشوند.",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export default function Home() {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const brands = ["NTN", "KOYO", "NACHI", "TIMKEN", "FAG", "SKF"];
|
||||
const latestArticles = articles.slice(-3);
|
||||
const featuredProducts = products.slice(0, 10);
|
||||
const [startIndex, setStartIndex] = useState(0);
|
||||
|
||||
const nextSlide = () => {
|
||||
if (startIndex + 4 < featuredProducts.length) {
|
||||
setStartIndex(startIndex + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
if (startIndex > 0) {
|
||||
setStartIndex(startIndex - 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<main className=" bg-gray-50">
|
||||
|
||||
{/* hero section */}
|
||||
<section className="bg-[#0b1d36] text-white py-16">
|
||||
<div className="container mx-auto text-center">
|
||||
{/* Badge */}
|
||||
<div className="inline-block border mb-4 px-3 py-2 bg-[#443A27] text-[#ffb900] text-xs rounded-full">
|
||||
تأمینکننده برتر قطعات
|
||||
</div>
|
||||
|
||||
{/* Heading */}
|
||||
<h1 className=" leading-snug md:text-5xl font-extrabold mb-4">
|
||||
تخصصیترین مرجع
|
||||
<br></br>
|
||||
بلبرینگ و قطعات صنعتی
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p className=" text-[15px] text-[#9a9a9a] mb-10">
|
||||
|
||||
دسترسی به بیش از 10.000 قطعه با ضمانت اصالت کالا
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
{/* hero search */}
|
||||
<div className="w-full px-4">
|
||||
<div className=" max-w-[70.5rem] mx-auto px-4 bg-white -mt-20 text-gray-800 rounded-2xl shadow-lg p-6 flex flex-col md:flex-row gap-4 md:gap-2 items-center justify-between">
|
||||
{/* Filters */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-6 w-full gap-4 justify-between">
|
||||
|
||||
<div className="flex flex-col md:col-span-2">
|
||||
<label htmlFor="partNumber" className="text-xs mb-3 text-black font-bold">
|
||||
شماره فنی (Part Number)
|
||||
</label>
|
||||
<input
|
||||
id="partNumber"
|
||||
type="text"
|
||||
placeholder="Part Number"
|
||||
className="px-4 py-3 bg-[#f9f9f9] rounded-xl border border-gray-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#e6d3a3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="innerDiameter" className="text-xs mb-3 text-black font-bold">
|
||||
قطر داخل
|
||||
</label>
|
||||
<input
|
||||
id="innerDiameter"
|
||||
type="text"
|
||||
placeholder="mm"
|
||||
className="px-4 bg-[#f9f9f9] py-3 rounded-xl border border-gray-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#e6d3a3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="outerDiameter" className="text-xs mb-3 text-black font-bold">
|
||||
قطر خارج
|
||||
</label>
|
||||
<input
|
||||
id="outerDiameter"
|
||||
type="text"
|
||||
placeholder="mm"
|
||||
className="px-4 bg-[#f9f9f9] py-3 rounded-xl border border-gray-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#e6d3a3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="thickness" className="text-xs mb-3 text-black font-bold">
|
||||
ضخامت
|
||||
</label>
|
||||
<input
|
||||
id="thickness"
|
||||
type="text"
|
||||
placeholder="mm"
|
||||
className="px-4 bg-[#f9f9f9] py-3 rounded-xl border border-gray-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#e6d3a3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Button */}
|
||||
<button
|
||||
className="mt-6 rounded-xl border-2 md:w-auto md:h-auto w-[100px] h-[48px] border-[#e6d3a3] bg-[#ffb900] text-black font-semibold text-xs transition-all hover:bg-[#da9800]">
|
||||
جستجوی قطعه
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* feature cards */}
|
||||
<div className="w-full py-6 mt-12">
|
||||
<div className="max-w-6xl px-4 mx-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{features.map((item, index) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-4 px-4 py-7 border bg-white shadow-sm hover:shadow-md transition-shadow duration-300 border-[#e3e3e3] rounded-xl "
|
||||
>
|
||||
<div className="p-3 bg-gray-100 rounded-lg">
|
||||
<Icon size={24} className="text-gray-700" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-800 text-sm">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500">{item.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* categories card */}
|
||||
<section className="w-full py-12 bg-gray-50">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
|
||||
{/* title */}
|
||||
<div className="w-full mb-10">
|
||||
<div className="flex">
|
||||
<h2 className="text-xl font-bold text-gray-800">
|
||||
دسته بندی های بلبرینگ
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* line */}
|
||||
<div className="relative mt-3 h-[2px] bg-gray-200 w-full">
|
||||
<div className="absolute right-0 top-0 h-[2px] w-24 bg-yellow-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* cards */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
|
||||
{categories.map((cat, index) => {
|
||||
const Icon = cat.icon;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white border border-gray-200 rounded-xl shadow-sm hover:shadow-md transition-all duration-300 flex flex-col items-center justify-center py-8 cursor-pointer"
|
||||
>
|
||||
<Icon size={34} className="text-gray-400 mb-3" />
|
||||
|
||||
<p className="text-sm font-medium text-gray-700">
|
||||
{cat.title}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* products */}
|
||||
<section className="py-12">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
<div className="items-center justify-between mb-8">
|
||||
|
||||
{/* title */}
|
||||
<div className="flex flex-wrap md:flex-nowrap justify-between w-full mb-4">
|
||||
<div className="flex">
|
||||
<h3 className="text-xl font-bold text-gray-800">
|
||||
پیشنهاد ویژه صنعتی
|
||||
</h3>
|
||||
</div>
|
||||
{/* tabs */}
|
||||
<div className="flex mt-6 mb-4 md:mt-0 md:mb-0 gap-8 text-sm">
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setActiveTab(index)}
|
||||
className={`pb-2 cursor-pointer border-b-2 transition
|
||||
${activeTab === index
|
||||
? "border-yellow-500 text-black"
|
||||
: "border-transparent text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* line */}
|
||||
<div className="relative mt-3 h-[2px] bg-gray-200 w-full">
|
||||
<div className="absolute right-0 top-0 h-[2px] w-24 bg-yellow-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* products → فقط ۴ تای آخر */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||
{products.slice(-4).map((product, i) => (
|
||||
<ProductCard key={i} product={product} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* blog */}
|
||||
<section className="py-16 bg-white">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
|
||||
{/* header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between w-full mb-4">
|
||||
<h3 className="text-xl font-bold text-gray-800">
|
||||
مجله فنی و مهندسی
|
||||
</h3>
|
||||
|
||||
<a className="text-[#ffb900] text-sm cursor-pointer">
|
||||
مشاهده آرشیو
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="relative h-[2px] bg-gray-200 w-full">
|
||||
<div className="absolute right-0 top-0 h-[2px] w-24 bg-yellow-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{latestArticles.map((article, i) => (
|
||||
<ArticleCard key={i} article={article} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* search */}
|
||||
<section className="py-16">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
|
||||
<div className="bg-[#0b1e3b] rounded-2xl p-10 flex flex-col lg:flex-row items-center justify-between gap-8 relative overflow-hidden">
|
||||
|
||||
{/* right content */}
|
||||
<div className="text-right text-white max-w-md">
|
||||
<h2 className="text-2xl font-bold text-amber-400 mb-3">
|
||||
قطعه خاصی مد نظرتان است؟
|
||||
</h2>
|
||||
|
||||
<p className="text-sm text-gray-300 leading-7">
|
||||
تیم فنی ما آماده است تا قطعات صنعتی مورد نیاز شما را در سریعترین زمان
|
||||
ممکن تأمین کند. مشخصات قطعه را وارد کنید.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-2 mt-3 text-sm text-gray-300">
|
||||
<MessageCircleCheckIcon size={16} />
|
||||
پاسخگویی سریع در واتساپ
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* search box */}
|
||||
<div className="bg-[#2a3548] p-4 flex-wrap rounded-xl flex items-center gap-3 w-full max-w-xl">
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="شماره تماس"
|
||||
className="flex-1 bg-gray-200 rounded-lg md:px-4 px-0 py-3 outline-none text-xs"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder=" کد فنی قطعه"
|
||||
className="flex-1 bg-gray-200 rounded-lg px-0 md:px-4 py-3 outline-none text-xs"
|
||||
/>
|
||||
|
||||
<button className="bg-amber-500 hover:bg-amber-600 text-black px-3 py-3 rounded-lg flex items-center gap-2 text-xs cursor-pointer">
|
||||
ثبت درخواست
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* faq */}
|
||||
<section className="py-6">
|
||||
|
||||
<div className="max-w-3xl mx-auto px-4">
|
||||
|
||||
<h2 className="text-center text-xl font-bold mb-10">
|
||||
سوالات پرتکرار مشتریان
|
||||
</h2>
|
||||
|
||||
{/* faq list */}
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, index) => (
|
||||
<FAQItem
|
||||
key={index}
|
||||
question={faq.question}
|
||||
answer={faq.answer}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
{/* brands */}
|
||||
<section className="w-full py-20 px-6">
|
||||
<div className="max-w-6xl mx-auto text-center">
|
||||
|
||||
<h2 className="text-gray-500 text-sm font-semibold mb-10">
|
||||
تامین کننده برندهای معتبر جهان
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-wrap justify-center items-center gap-8">
|
||||
{brands.map((brand) => (
|
||||
<div
|
||||
key={brand}
|
||||
className="w-40 h-16 flex items-center justify-center border border-gray-200 rounded-md bg-white"
|
||||
>
|
||||
<span className="text-gray-300 font-semibold tracking-wider text-lg">
|
||||
{brand}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
79
app/products/[id]/[slug]/page.tsx
Normal file
79
app/products/[id]/[slug]/page.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import ScrollToTop from '@/components/scrolltop';
|
||||
import { products } from '@/lib/data';
|
||||
import { notFound } from 'next/navigation';
|
||||
import Image from "next/image";
|
||||
|
||||
// تعریف دقیق پارامترهایی که از URL میآیند
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
slug: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default async function SingleProductPage({ params }: PageProps) {
|
||||
const resolvedParams = await params;
|
||||
|
||||
|
||||
const product = products.find((p) => p.id.toString() === resolvedParams.id);
|
||||
|
||||
// ۳. اگر محصول با این ID در دیتابیس نبود
|
||||
if (!product) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-8 max-w-4xl">
|
||||
<div className="bg-white rounded-xl shadow-lg p-6 flex flex-col md:flex-row gap-8 border border-gray-100">
|
||||
<ScrollToTop />
|
||||
|
||||
<div className="w-full md:w-1/3 flex justify-center bg-gray-50 rounded-lg p-4">
|
||||
<Image
|
||||
src={product.image}
|
||||
width={300}
|
||||
height={300}
|
||||
alt={product.title}
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full md:w-2/3 flex flex-col justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-800 mb-2">{product.title}</h1>
|
||||
<p className="text-gray-500 font-semibold mb-6">برند: {product.brand}</p>
|
||||
|
||||
<div className="space-y-3 bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex justify-between border-b pb-2">
|
||||
<span className="text-gray-600">قطر داخلی (L):</span>
|
||||
<span className="font-bold" dir="ltr">{product.l}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">قطر خارجی (D):</span>
|
||||
<span className="font-bold" dir="ltr">{product.d}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex justify-between items-center border-t pt-4">
|
||||
<span className={`px-4 py-2 rounded-full font-bold text-sm ${product.stock ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}`}>
|
||||
{product.stock ? "موجود در انبار" : "ناموجود"}
|
||||
</span>
|
||||
|
||||
{product.price ? (
|
||||
<span className="text-2xl font-bold text-blue-600">{product.price} تومان</span>
|
||||
) : (
|
||||
<span className="text-xl font-bold text-gray-700 flex items-center gap-2">
|
||||
نیاز به استعلام
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
app/products/page.tsx
Normal file
17
app/products/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
// app/products/page.tsx
|
||||
import ProductCard from '@/components/productcard'; // مسیر را با توجه به ساختار خود اصلاح کنید
|
||||
import { products } from '@/lib/data'; // ایمپورت دیتای مرکزی
|
||||
|
||||
export default function ProductsPage() {
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-2xl font-bold mb-6">همه محصولات</h1>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||
{products.map((product) => (
|
||||
<ProductCard key={product.title} product={product} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
67
app/search/page.tsx
Normal file
67
app/search/page.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
'use client';
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { Suspense } from 'react';
|
||||
import { products } from '@/lib/data';
|
||||
import { Search } from 'lucide-react';
|
||||
import ProductCard from '@/components/productcard';
|
||||
|
||||
|
||||
// کامپوننت داخلی برای هندل کردن جستجو
|
||||
function SearchResults() {
|
||||
const searchParams = useSearchParams();
|
||||
const query = searchParams.get('q') || '';
|
||||
|
||||
// فیلتر کردن محصولات بر اساس جستجو
|
||||
const results = products.filter(p =>
|
||||
p.title.toLowerCase().includes(query.toLowerCase()) ||
|
||||
p.brand?.toLowerCase().includes(query.toLowerCase()) ||
|
||||
p.d?.toLowerCase().includes(query.toLowerCase()) ||
|
||||
p.l?.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-3 mb-8 border-b border-gray-100 pb-6">
|
||||
<div className="bg-blue-50 p-3 rounded-2xl text-blue-600">
|
||||
<Search size={28} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-800">نتایج جستجو برای: "{query}"</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">{results.length} مورد پیدا شد</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{results.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{results.map((product) => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20 bg-gray-50 rounded-3xl">
|
||||
<p className="text-gray-500 text-lg">متاسفیم، کالایی با این مشخصات پیدا نشد.</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// کامپوننت اصلی صفحه که Suspense را پیادهسازی میکند
|
||||
export default function SearchPage() {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-12 min-h-screen">
|
||||
{/*
|
||||
استفاده از Suspense برای جلوگیری از خطای Deopt در Next.js
|
||||
هنگام استفاده از useSearchParams
|
||||
*/}
|
||||
<Suspense fallback={
|
||||
<div className="flex justify-center items-center py-20 text-gray-500">
|
||||
در حال جستجو...
|
||||
</div>
|
||||
}>
|
||||
<SearchResults />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user