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

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
node_modules
# dependencies
/node_modules
/.pnp
.pnp.js
# next.js build output
/.next/
/out/
/build/

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>
);
}

217
app/cart/page.tsx Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

21
app/globals.css Normal file
View 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
View 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
View 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>
);
}

View 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
View 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
View 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>
);
}

View File

@@ -0,0 +1,78 @@
'use client';
import Image from "next/image";
import Link from "next/link";
import { Calendar, ArrowLeft } from 'lucide-react';
// تابع استانداردسازی عنوان (بدون تغییر طبق درخواست شما)
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 Article {
title: string;
image: string;
excerpt: string;
date: string;
category?: string; // اختیاری: اگر در دیتا دارید استفاده می‌شود
}
export default function ArticleCard({ article }: { article: Article }) {
const articleSlug = generateSlug(article.title);
return (
<Link href={`/blog/${articleSlug}`} className="group block h-full">
<div className="bg-white rounded-[2rem] border border-gray-100 shadow-sm transition-all duration-500 flex flex-col h-[95%] overflow-hidden">
{/* بخش تصویر با نسبت ابعاد متناسب (مربعی‌تر) */}
<div className="relative aspect-[16/11] w-full overflow-hidden">
<Image
src={article.image}
alt={article.title}
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
{/* لایه گرادینت روی عکس برای عمق دادن */}
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
</div>
{/* محتوای متنی */}
<div className="p-6 flex flex-col flex-grow">
{/* تاریخ مقاله */}
<div className="flex items-center gap-2 text-gray-400 text-[10px] mb-3 font-semibold uppercase tracking-wider">
<Calendar size={13} className="text-yellow-500" />
<span>{article.date}</span>
</div>
{/* عنوان مقاله */}
<h3 className="text-[15px] font-bold text-gray-800 mb-3 leading-relaxed group-hover:text-yellow-600 transition-colors line-clamp-2">
{article.title}
</h3>
{/* خلاصه متن */}
<p className="text-gray-500 text-xs leading-6 mb-5 line-clamp-2 font-light">
{article.excerpt}
</p>
{/* فوتر کارت - دکمه ادامه مطلب مدرن */}
<div className="mt-auto pt-4 border-t border-gray-50 flex items-center justify-between">
<span className="text-yellow-600 text-xs font-bold flex items-center gap-1.5 transform -translate-x-2 opacity-0 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-300">
مطالعه مقاله
<ArrowLeft size={14} />
</span>
<div className="w-8 h-8 rounded-full bg-gray-50 flex items-center justify-center text-gray-400 group-hover:bg-yellow-500 group-hover:text-white transition-all duration-300">
<ArrowLeft size={16} />
</div>
</div>
</div>
</div>
</Link>
);
}

View File

@@ -0,0 +1,88 @@
'use client';
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
export interface Product {
id: string;
title: string;
image: string;
l: string;
d: string;
brand: string;
price?: string | null;
badge?: string;
stock: boolean;
}
export interface CartItem extends Product {
quantity: number;
}
interface CartContextType {
cart: CartItem[];
addToCart: (product: Product) => void;
removeFromCart: (id: string) => void;
decreaseQuantity: (id: string) => void;
clearCart: () => void;
}
const CartContext = createContext<CartContextType | undefined>(undefined);
export function CartProvider({ children }: { children: ReactNode }) {
const [cart, setCart] = useState<CartItem[]>([]);
useEffect(() => {
const savedCart = localStorage.getItem('cart');
if (savedCart) {
setCart(JSON.parse(savedCart));
}
}, []);
useEffect(() => {
localStorage.setItem('cart', JSON.stringify(cart));
}, [cart]);
const addToCart = (product: Product) => {
setCart((prevCart) => {
const existingItem = prevCart.find((item) => item.id === product.id);
if (existingItem) {
return prevCart.map((item) =>
item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item
);
}
return [...prevCart, { ...product, quantity: 1 }];
});
};
const decreaseQuantity = (id: string) => {
setCart((prevCart) => {
const existingItem = prevCart.find((item) => item.id === id);
if (existingItem?.quantity === 1) {
return prevCart.filter((item) => item.id !== id);
}
return prevCart.map((item) =>
item.id === id ? { ...item, quantity: item.quantity - 1 } : item
);
});
};
const removeFromCart = (id: string) => {
setCart((prevCart) => prevCart.filter((item) => item.id !== id));
};
const clearCart = () => setCart([]);
return (
<CartContext.Provider value={{ cart, addToCart, removeFromCart, decreaseQuantity, clearCart }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
}

37
components/faq.tsx Normal file
View File

@@ -0,0 +1,37 @@
"use client";
import { useState } from "react";
import { Plus, Minus } from "lucide-react";
interface FAQItemProps {
question: string;
answer: string;
}
export default function FAQItem({ question, answer }: FAQItemProps) {
const [open, setOpen] = useState(false);
return (
<div className="border border-gray-200 rounded-lg bg-white">
{/* question */}
<button
onClick={() => setOpen(!open)}
className="w-full flex items-center justify-between p-5 text-right cursor-pointer"
>
<span className="text-sm font-medium">{question}</span>
<span className="text-amber-500">
{open ? <Minus size={18} /> : <Plus size={18} />}
</span>
</button>
{/* answer */}
{open && (
<div className="px-5 pb-5 text-sm text-gray-500 leading-7">
{answer}
</div>
)}
</div>
);
}

95
components/footer.tsx Normal file
View File

@@ -0,0 +1,95 @@
import { MapPin, Phone, Mail, Linkedin, Instagram, Send } from "lucide-react";
export default function Footer() {
return (
<footer className="bg-[#0F172A] text-gray-300 pt-16 pb-6" dir="rtl">
<div className="max-w-6xl mx-auto px-4 grid md:grid-cols-[1.9fr_1fr_1fr_1fr] gap-12">
{/* About */}
<div className="flex justify-center flex-col ">
<h3 className="text-white text-lg font-bold mb-4 flex items-center gap-2">
وب سایت پارس
<span className="text-yellow-500"></span>
</h3>
<p className="text-sm leading-7 text-gray-400">
فروش تخصصی انواع بلبرینگ، رولبرینگ و قطعات صنعتی با بهترین
استانداردهای اروپایی و کیفیت بالا. هدف ما ارائه محصولات
با کیفیت و ایجاد رضایت کامل مشتریان میباشد.
</p>
<div className="flex gap-3 mt-4">
<a className="bg-white/10 p-2 rounded hover:bg-white/20">
<Linkedin size={18} />
</a>
<a className="bg-white/10 p-2 rounded hover:bg-white/20">
<Instagram size={18} />
</a>
<a className="bg-white/10 p-2 rounded hover:bg-white/20">
<Send size={18} />
</a>
</div>
</div>
{/* Quick Access */}
<div className="flex flex-col ">
<h4 className="text-yellow-500 font-semibold mb-4">
دسترسی سریع
</h4>
<ul className="space-y-3 text-sm">
<li className="hover:text-white cursor-pointer">صفحه اصلی</li>
<li className="hover:text-white cursor-pointer">محصولات صنعتی</li>
<li className="hover:text-white cursor-pointer">محصولات کشاورزی</li>
<li className="hover:text-white cursor-pointer">دانشنامه فنی</li>
<li className="hover:text-white cursor-pointer">درباره ما</li>
</ul>
</div>
{/* Services */}
<div className="flex flex-col ">
<h4 className="text-yellow-500 font-semibold mb-4">
خدمات مشتریان
</h4>
<ul className="space-y-3 text-sm">
<li className="hover:text-white cursor-pointer">ثبت ارسال سفارش</li>
<li className="hover:text-white cursor-pointer">پیگیری سفارش</li>
<li className="hover:text-white cursor-pointer">درخواست پیش فاکتور</li>
<li className="hover:text-white cursor-pointer">بازگشت و ضمانت</li>
<li className="hover:text-white cursor-pointer">راهنمای خرید</li>
</ul>
</div>
{/* Contact */}
<div className="flex flex-col ">
<h4 className="text-yellow-500 font-semibold mb-4">
اطلاعات تماس
</h4>
<ul className="space-y-4 text-sm">
<li className="flex items-center gap-2">
<MapPin size={16} className="text-yellow-500" />
قزوین، مینودر
</li>
<li className="flex items-center gap-2">
<Phone size={16} className="text-yellow-500" />
09120000000
</li>
<li className="flex items-center gap-2">
<Mail size={16} className="text-yellow-500" />
info@pars-bearing.com
</li>
</ul>
</div>
</div>
{/* Bottom */}
<div className="border-t border-white/10 mt-12 pt-6 text-center text-xs text-gray-400">
تمامی حقوق این وبسایت محفوظ و متعلق به پارس میباشد © 2026
</div>
</footer>
);
}

433
components/header.tsx Normal file
View File

@@ -0,0 +1,433 @@
"use client";
import React, { useState, useEffect, useRef } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import { ShoppingCart, Trash2 } from "lucide-react";
import { useCart } from './context/cartcontext';
import { Search, X } from 'lucide-react';
import { products } from '@/lib/data';
import '@/public/src/css/header.css';
const topBarLinks = [
{ label: "بخش صنعتی", href: "/" },
{ label: "بخش خودرویی", href: "/automotive" },
];
const mainNavLinks = [
{ label: "صفحه اصلی", href: "/" },
{ label: "خرید بلبرینگ", href: "/products" },
{ label: "مقالات", href: "/blog" },
{ label: "درباره ما", href: "/about" },
{ label: "تماس با ما", href: "/contact" },
];
export function Header() {
const [menuOpen, setMenuOpen] = useState(false);
const pathname = usePathname();
const { cart, removeFromCart } = useCart();
const [searchTerm, setSearchTerm] = useState('');
const [showResults, setShowResults] = useState(false);
const [filteredProducts, setFilteredProducts] = useState<any[]>([]);
const searchRef = useRef<HTMLDivElement>(null);
const router = useRouter();
// محاسبه قیمت کل برای سبد خرید (اصلاح شده برای حذف ویرگول‌های احتمالی)
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 isLinkActive = (href: string): boolean => {
if (href === "/") {
return pathname === "/";
}
return pathname === href;
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
setShowResults(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// فیلتر کردن محصولات در لحظه تایپ
useEffect(() => {
if (searchTerm.trim().length > 1) {
const results = products.filter(p =>
p.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.brand?.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.d?.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.l?.toLowerCase().includes(searchTerm.toLowerCase())
).slice(0, 6); // نمایش فقط ۶ نتیجه اول در دراپ‌داون
setFilteredProducts(results);
setShowResults(true);
} else {
setFilteredProducts([]);
setShowResults(false);
}
}, [searchTerm]);
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
if (searchTerm.trim()) {
setShowResults(false);
router.push(`/search?q=${encodeURIComponent(searchTerm)}`);
}
};
return (
<header>
<div>
{/* overlay */}
{menuOpen && (
<div
className="fixed inset-0 bg-black/40 z-40 md:hidden"
onClick={() => setMenuOpen(false)}
/>
)}
{/* mobile slider */}
<div
className={`fixed top-0 right-0 h-full w-[240px] max-w-[90vw] bg-white z-50 transform transition-transform duration-300 md:hidden
${menuOpen ? "translate-x-0" : "translate-x-full"}`}
>
<div className="p-6 flex flex-col gap-6">
{mainNavLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-sm text-gray-700"
onClick={() => setMenuOpen(false)}
>
{link.label}
</a>
))}
<button className="inline-flex items-center justify-center px-6 py-2 rounded-xl border-1 border-[#ffb900] text-[#ffb900] text-xs tracking-wide focus:outline-none focus:ring-2 focus:ring-[#e6d3a3]/50">
استعلام قیمت
</button>
{/* mobile contact */}
<div className="mt-6 border-t pt-4 flex flex-col gap-4 md:hidden">
<div className="flex items-center gap-2 text-gray-700 text-xs">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 11.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 11.5c0 6-7.5 10-7.5 10s-7.5-4-7.5-10a7.5 7.5 0 1115 0z" />
</svg>
<span className="whitespace-nowrap">شیراز، معالیآباد</span>
</div>
<a href="tel:07112345678" className="flex items-center gap-2 text-gray-700 hover:text-amber-500 transition text-xs">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 6.75c0 7.87 6.38 14.25 14.25 14.25.62 0 1.12-.5 1.12-1.12v-2.26c0-.53-.37-.99-.88-1.1l-2.77-.6a1.12 1.12 0 00-1.17.54l-.66 1.16a12.04 12.04 0 01-5.54-5.54l1.16-.66c.4-.23.6-.7.54-1.17l-.6-2.77a1.12 1.12 0 00-1.1-.88H3.37c-.62 0-1.12.5-1.12 1.12z" />
</svg>
<span dir="ltr">07112345678</span>
</a>
</div>
</div>
</div>
{/* top bar */}
<div className="bg-[#1a2332] text-white text-sm ">
<div className="container max-w-6xl mx-auto px-4 flex flex-wrap md:flex-nowrap justify-between items-center gap-2 md:gap-0">
{/* links */}
<div className="flex flex-wrap items-center gap-1 md:gap-4 w-full md:w-auto md:justify-start">
{topBarLinks.map((link) => {
// --- منطق جدید برای فعال بودن ---
let isActive = false;
if (link.href === "/automotive") {
// بخش خودرویی فقط زمانی فعال است که آدرس با /automotive شروع شود
isActive = pathname?.startsWith("/automotive");
} else if (link.href === "/") {
// بخش صنعتی در تمام صفحات (جز خودرویی) همیشه فعال می‌ماند
isActive = !pathname?.startsWith("/automotive");
}
// ---------------------------------
const baseClasses = "hover:text-amber-300 transition duration-200 px-3 md:px-6 py-2 text-xs";
const activeClasses = isActive ? "text-amber-400 border-t-2 border-amber-400 ac" : "text-white";
return (
<a
key={link.href}
href={link.href}
className={`${baseClasses} ${activeClasses}`}
>
{link.label}
</a>
);
})}
</div>
{/* contact */}
<div className="flex flex-wrap hidden md:flex md:flex-nowrap justify-center md:justify-end items-center gap-3 md:gap-6 text-sm text-white w-full md:w-auto">
<div className="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 11.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 11.5c0 6-7.5 10-7.5 10s-7.5-4-7.5-10a7.5 7.5 0 1115 0z" />
</svg>
<span className="whitespace-nowrap text-xs">شیراز، معالیآباد</span>
</div>
<div>
<a href="tel:07112345678" className="flex items-center gap-2 hover:text-amber-300 transition">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 6.75c0 7.87 6.38 14.25 14.25 14.25.62 0 1.12-.5 1.12-1.12v-2.26c0-.53-.37-.99-.88-1.1l-2.77-.6a1.12 1.12 0 00-1.17.54l-.66 1.16a12.04 12.04 0 01-5.54-5.54l1.16-.66c.4-.23.6-.7.54-1.17l-.6-2.77a1.12 1.12 0 00-1.1-.88H3.37c-.62 0-1.12.5-1.12 1.12z" />
</svg>
<span className="whitespace-nowrap font-medium text-xs" dir="ltr">07112345678</span>
</a>
</div>
</div>
</div>
</div>
{/* main bar */}
<div className="bg-white border-b-2 border-b-[#f4f4f4] py-4">
<div className="container max-w-6xl mx-auto px-3 flex justify-between items-center flex-wrap gap-2 min-w-0">
{/* logo */}
<div className="items-center hidden md:flex md:order-1 order-2 space-x-2 rtl:space-x-reverse min-w-[100px] md:min-w-[150px]">
<div className="text-3xl font-bold text-green-500">B</div>
<span className="text-xl font-semibold text-[#1a2332]">Bearing Site</span>
</div>
{/* hamburger */}
<button className="md:hidden order-1" onClick={() => setMenuOpen(true)}>
<svg xmlns="http://www.w3.org/2000/svg" className="w-6 h-6 text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
{/* search */}
<div ref={searchRef} className="relative flex order-4 md:order-2 w-full md:w-[35%] min-w-0">
<form onSubmit={handleSearch} className="w-full">
<div className="flex flex-row-reverse w-full border border-gray-300 rounded-3xl overflow-hidden focus-within:ring-2 focus-within:ring-blue-500 bg-white shadow-sm transition-all">
<button type="submit" className="bg-gray-800 p-2.5 text-white hover:bg-gray-700 transition">
<Search size={20} />
</button>
<input
type="text"
placeholder="جستجوی قطعه، کد، یا دسته‌بندی..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onFocus={() => searchTerm.length > 1 && setShowResults(true)}
className="w-full p-3 text-gray-800 placeholder-gray-400 focus:outline-none text-xs font-medium"
/>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className=" text-gray-400 mr-4 hover:text-gray-600"
>
<X size={14} />
</button>
)}
</div>
</form>
{/* دراپ‌داون نتایج لحظه‌ای */}
{showResults && filteredProducts.length > 0 && (
<div className="absolute top-full left-0 right-0 mt-2 bg-white border border-gray-200 rounded-2xl shadow-xl z-[100] overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200">
<div className="p-2 flex flex-col">
{filteredProducts.map((product) => {
// تبدیل نام محصول به اسلاگ (جایگزینی فاصله‌ها با خط تیره)
const productSlug = product.title.replace(/\s+/g, '-');
return (
<Link
key={product.id}
// اصلاح URL برای تطابق با /products/[id]/[slug]
href={`/products/${product.id}/${encodeURIComponent(productSlug)}`}
onClick={() => setShowResults(false)}
className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-xl transition group"
>
<div className="bg-gray-100 p-1 rounded-lg shrink-0 w-10 h-10 flex items-center justify-center">
<Image
src={product.image}
alt={product.title}
width={32}
height={32}
className="object-contain"
/>
</div>
<div className="flex-1 min-w-0 text-right">
<p className="text-[11px] font-bold text-gray-800 truncate group-hover:text-blue-600 transition-colors">
{product.title}
</p>
<p className="text-[9px] text-gray-500">{product.brand}</p>
</div>
<div className="text-[10px] font-bold text-gray-700 bg-gray-50 px-2 py-1 rounded-md shrink-0">
{/* اعمال منطق حذف کاما و تبدیل به عدد در صورت رشته بودن قیمت */}
{product.price
? `${Number(product.price.toString().replace(/,/g, '')).toLocaleString('fa-IR')} ت`
: 'استعلام'}
</div>
</Link>
);
})}
</div>
{/* دکمه مشاهده همه نتایج */}
<button
onClick={handleSearch}
className="w-full bg-gray-50 py-2.5 text-[11px] text-blue-600 font-bold border-t border-gray-100 hover:bg-blue-50 transition-colors"
>
مشاهده تمام نتایج برای "{searchTerm}"
</button>
</div>
)}
</div>
{/* User/Cart Actions */}
<div className='flex items-center order-3 gap-2'>
{/* دراپ‌داون سبد خرید */}
<div className="relative group z-50 before:absolute before:-inset-x-5 before:-inset-y-3 before:content-['']">
<Link href="/cart" className="flex items-center justify-center p-2.5 bg-white border border-gray-300/60 rounded-xl text-gray-700 hover:bg-gray-50 transition relative z-10">
<ShoppingCart className="w-4 h-4 text-gray-500" strokeWidth={1.8} />
{/* بج (Badge) تعداد محصولات */}
{cart.length > 0 && (
<span className="absolute -top-1.5 -right-1.5 bg-[#f92a35] text-white text-[10px] font-bold w-4 h-4 flex items-center justify-center rounded-full shadow-sm">
{cart.reduce((total, item) => total + item.quantity, 0)}
</span>
)}
</Link>
{/* یک پل نامرئی برای جلوگیری از قطع شدن هاور */}
<div className="absolute top-full left-0 w-full h-3 bg-transparent hidden group-hover:block"></div>
{/* منوی کشویی سبد خرید */}
<div className="absolute top-[calc(100%+12px)] left-0 w-80 bg-white border border-gray-200 rounded-2xl shadow-xl hidden group-hover:flex flex-col overflow-hidden z-50">
{cart.length === 0 ? (
<div className="p-6 text-center text-sm text-gray-500 flex flex-col items-center gap-2">
<ShoppingCart className="w-8 h-8 text-gray-200" />
<span>سبد خرید شما خالی است</span>
</div>
) : (
<>
<div className="flex justify-between items-center p-4 border-b border-gray-100 bg-gray-50/50">
<span className="text-xs font-semibold text-gray-500">{cart.length} کالا</span>
<Link href="/cart" className="text-xs text-blue-500 hover:text-blue-700 font-medium transition">
مشاهده سبد خرید
</Link>
</div>
<div className="max-h-64 overflow-y-auto p-2 flex flex-col gap-1 custom-scrollbar">
{cart.slice(0, 3).map((item) => {
// محاسبه قیمت بر اساس تعداد (قیمت واحد × تعداد)
const itemTotal = item.price
? (Number(item.price.toString().replace(/,/g, '')) * item.quantity).toLocaleString('fa-IR')
: null;
return (
<div key={item.id} className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-xl transition group/item relative">
<div className="bg-white border border-gray-100 p-1 rounded-lg shrink-0">
<Image src={item.image} alt={item.title} width={40} height={40} className="object-contain w-10 h-10" />
</div>
<div className="flex-1 min-w-0">
<h4 className="text-xs font-semibold text-gray-800 truncate">{item.title}</h4>
{/* بخش جدید: نمایش قیمت و تعداد در کنار هم */}
<div className="flex items-center gap-2 mt-1">
<span className="text-[10px] text-gray-500 font-medium">
{itemTotal ? `${itemTotal} تومان` : 'استعلام'}
</span>
{/* بج نمایش تعداد محصول */}
<span className="text-[9px] font-bold text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded-md border border-blue-100">
{item.quantity} عدد
</span>
</div>
</div>
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeFromCart(item.id);
}}
className="text-gray-300 hover:text-red-500 transition p-1.5 opacity-0 group-hover/item:opacity-100"
title="حذف"
>
<Trash2 size={14} />
</button>
</div>
)
})}
{cart.length > 3 && (
<p className="text-center text-[10px] text-gray-400 py-2 border-t border-gray-50 mt-1">
و {cart.length - 3} کالای دیگر...
</p>
)}
</div>
<div className="p-4 bg-white border-t border-gray-100">
<div className="flex justify-between items-center mb-3">
<span className="text-xs text-gray-500">مبلغ قابل پرداخت:</span>
<span className="text-sm font-bold text-gray-800">
{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')} تومان` : 'استعلام'}
</span>
</div>
<Link href="/cart" className="flex items-center justify-center w-full py-2 bg-[#ffb900] hover:bg-[#e6a600] text-black text-xs font-semibold rounded-xl transition-colors">
ثبت سفارش
</Link>
</div>
</>
)}
</div>
</div>
<button className="flex items-center gap-2 px-3 py-2.5 bg-white border border-gray-300/60 rounded-xl text-xs text-gray-700 hover:bg-gray-50 transition">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-4 h-4 text-gray-500">
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.5 20.25a7.5 7.5 0 0115 0" />
</svg>
<span>ورود / عضویت</span>
</button>
</div>
</div>
</div>
{/* nav */}
<div className='justify-center flex'>
<div className='flex max-w-6xl container mx-auto px-4 justify-between py-2 items-center'>
<nav className="flex space-x-6 rtl:space-x-4 text-gray-700 text-xs w-full lg:w-auto lg:order-none">
{mainNavLinks.map((link) => (
<a key={link.label} href={link.href} className="hover:text-blue-600 transition duration-200 font-medium hidden md:block">
{link.label}
</a>
))}
</nav>
<div className="text-xl text-gray-700 cursor-pointer hidden md:block">
<button className="inline-flex items-center justify-center px-6 py-2 rounded-xl border-1 border-[#ffb900] text-[#ffb900] text-xs tracking-wide focus:outline-none focus:ring-2 focus:ring-[#e6d3a3]/50">
استعلام قیمت
</button>
</div>
</div>
</div>
</div>
</header>
);
}

130
components/productcard.tsx Normal file
View File

@@ -0,0 +1,130 @@
'use client';
import { Phone, Plus, ShoppingCart, Minus, Trash2 } from "lucide-react";
import Image from "next/image";
import Link from 'next/link';
import { useCart, Product } from "./context/cartcontext"; // مسیر ایمپورت را با پروژه خود چک کنید
interface ProductCardProps {
product: Product;
}
export default function ProductCard({ product }: ProductCardProps) {
const { addToCart, decreaseQuantity, cart } = useCart();
// پیدا کردن محصول در سبد خرید
const cartItem = cart.find(item => item.id === product.id);
const quantity = cartItem ? cartItem.quantity : 0;
const generateSlug = (text: string) => {
if (!text) return "";
return text
.trim()
.replace(/[\s\u200c]+/g, '-')
.replace(/[^\w\u0600-\u06FF0-9\-]/g, '')
.replace(/\-\-+/g, '-');
};
const slug = generateSlug(product.title);
const handleIncrease = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
addToCart(product);
};
const handleDecrease = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
decreaseQuantity(product.id);
};
// پاکسازی قیمت از ویرگول و تبدیل به عدد برای نمایش صحیح
const formattedPrice = product.price
? Number(product.price.toString().replace(/,/g, '')).toLocaleString('fa-IR')
: null;
return (
<Link href={`/products/${product.id}/${slug}`} scroll={true} className="bg-white gap-1 flex flex-col justify-between border border-gray-200 rounded-xl shadow-sm p-4 transition-all hover:shadow-md">
<div className="relative flex justify-center mb-4">
<span
className={`absolute top-0 right-0 font-semibold text-[0.6em] px-2 py-1 rounded-2xl ${product.stock ? "bg-[#00c9512e] text-[#035702] border border-[#03570263] " : "bg-[#f92a3521] text-[#cf0000] border border-[#cf000047]"}`}
>
{product.stock ? "موجود" : "ناموجود"}
</span>
<Image
src={product.image}
width={150}
height={150}
alt={product.title}
/>
</div>
<p className="text-[#808080] text-sm">{product.brand}</p>
<h3 className="text-sm font-semibold">{product.title}</h3>
<div className="text-xs text-gray-500 mt-2">
<div className="flex border-b border-[#dfdfdf] mb-2 pb-2 justify-between">
<p>قطر داخلی (L):</p>
<p dir="ltr" className="font-semibold text-black">{product.l}</p>
</div>
<div className="flex justify-between">
<p>قطر خارجی (D):</p>
<p dir="ltr" className="font-semibold text-black">{product.d}</p>
</div>
</div>
{product.price ? (
<div className="flex justify-between mt-3 items-center min-h-[32px]">
<span className="text-[0.85em] font-bold">
{formattedPrice} تومان
</span>
{quantity > 0 ? (
<div
className="flex items-center gap-3 bg-white border border-gray-200 rounded-lg py-1 px-2 shadow-sm"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<button
onClick={handleIncrease}
className="text-gray-500 hover:text-green-600 transition-colors"
>
<Plus size={16} />
</button>
<span className="text-xs font-bold text-gray-800 w-3 text-center">
{quantity}
</span>
<button
onClick={handleDecrease}
className="text-gray-500 hover:text-red-500 transition-colors"
>
{quantity === 1 ? <Trash2 size={16} /> : <Minus size={16} />}
</button>
</div>
) : (
<button
onClick={handleIncrease}
className="rounded-lg border border-[#e6e6e6] bg-gray-50 flex items-center p-2 text-gray-600 hover:bg-gray-200 transition-colors"
title="افزودن به سبد خرید"
>
<ShoppingCart size={16} />
</button>
)}
</div>
) : (
<span className="flex justify-between mt-3 text-[0.85em] font-bold items-center gap-1 min-h-[32px]">
استعلام
<Phone className="mb-1" size={13} />
</span>
)}
</Link>
);
}

16
components/scrolltop.tsx Normal file
View File

@@ -0,0 +1,16 @@
'use client'
import { useEffect } from 'react';
export default function ScrollToTop() {
useEffect(() => {
// این کد به محض لود شدن کامپوننت، صفحه را به بالاترین نقطه می‌برد
window.scrollTo({
top: 0,
left: 0,
behavior: 'auto' // اگر می‌خواهید با انیمیشن برود، 'smooth' بنویسید
});
}, []);
return null; // این کامپوننت هیچ خروجی ظاهری ندارد
}

178
lib/data.tsx Normal file
View File

@@ -0,0 +1,178 @@
export const products = [
{
id: "1",
title: "بلبرینگ شبکه‌ای 22220",
image: "/src/img/1.jpg",
l: "120 mm",
d: "60 mm",
brand: "K",
price: null,
stock: true,
},
{
id: "2",
title: "بلبرینگ تماس زاویه‌ای 7200",
image: "/src/img/2.jpg",
l: "72 mm",
d: "30 mm",
brand: "FAG",
price: "450,000",
badge: "جدید",
stock: false,
},
{
id: "3",
title: "رولبرینگ مخروطی 30205",
image: "/src/img/3.jpg",
l: "20 mm",
d: "52 mm",
brand: "KOYO",
price: "870,000",
badge: "-8%",
stock: true,
},
{
id: "4",
title: "بلبرینگ شیار عمیق 6204",
image: "/src/img/4.jpg",
l: "20 mm",
d: "47 mm",
brand: "SKF",
price: "1,250,000",
badge: "پرفروش",
stock: false,
},
{
id: "5",
title: "بلبرینگ استوانه‌ای NU220",
image: "/src/img/5.jpg",
l: "200 mm",
d: "85 mm",
brand: "NSK",
price: "2,400,000",
badge: "پیشنهادی",
stock: true,
},
{
id: "6",
title: "رولبرینگ سوزنی NK20",
image: "/src/img/6.jpg",
l: "20 mm",
d: "35 mm",
brand: "INA",
price: "690,000",
stock: true,
},
{
id: "7",
title: "بوش بلبرینگ برنزی B1810",
image: "/src/img/7.jpg",
l: "28 mm",
d: "45 mm",
brand: "KBT",
price: "330,000",
stock: true,
},
{
id: "8",
title: "بلبرینگ خودتنظیم 1206",
image: "/src/img/1.jpg",
l: "62 mm",
d: "30 mm",
brand: "NTN",
price: "980,000",
badge: "ویژه",
stock: true,
},
{
id: "9",
title: "رولبرینگ کف‌گرد 51206",
image: "/src/img/2.jpg",
l: "24 mm",
d: "60 mm",
brand: "NACHI",
price: "1,080,000",
stock: false,
},
{
id: "10",
title: "بلبرینگ دوبل ردیفی 5206",
image: "/src/img/3.jpg",
l: "30 mm",
d: "62 mm",
brand: "SKF",
price: "1,550,000",
badge: "جدید",
stock: true,
},
];
export const articles = [
{
title: "راهنمای خواندن شماره فنی بلبرینگ (Suffix)",
image: "/src/img/5.jpg",
date: "۲ خرداد ۱۴۰۳",
category: "آموزشی",
excerpt: "پسوندهای بلبرینگ مانند C3 ،2RS ،ZZ چه معنی دارند و چه کاربردی در صنعت دارند.",
content: "در این بخش متن کامل مقاله قرار می‌گیرد. شما می‌توانید در آینده این بخش را به صورت کدهای مارک‌داون یا اچ‌تی‌ام‌ال ذخیره کنید تا ساختار ظاهری مقاله (تیترها، لیست‌ها) حفظ شود..."
},
{
title: "۵ دلیل خرابی زودرس بلبرینگ‌ها",
image: "/src/img/6.jpg",
date: "۳ خرداد ۱۴۰۳",
category: "علمی",
excerpt: "بررسی مهم‌ترین دلایل خرابی بلبرینگ‌ها مانند روانکاری نامناسب، نصب غلط و آلودگی.",
content: "خرابی بلبرینگ‌ها می‌تواند دلایل متعددی داشته باشد. اولین و مهم‌ترین دلیل، عدم روانکاری صحیح است..."
},
{
title: "تفاوت بلبرینگ‌های سری ZZ و 2RS",
image: "/src/img/7.jpg",
category: "تست",
date: "۵ خرداد ۱۴۰۳",
excerpt: "کدام نوع بلبرینگ برای محیط‌های صنعتی مناسب‌تر است؟ بررسی کامل تفاوت‌ها.",
content: "انتخاب بین سری‌های پوشش‌دار بستگی به محیط کاری دارد. مدل‌های واشر پلاستیکی مقاومت بهتری در برابر رطوبت دارند..."
},
{
title: "چگونه عمر بلبرینگ را افزایش دهیم؟",
image: "/src/img/4.jpg",
category: "آموزشی",
date: "۸ خرداد ۱۴۰۳",
excerpt: "راهکارهای کاربردی برای افزایش دوام بلبرینگ در ماشین‌آلات صنعتی.",
content: "مراقبت منظم، آنالیز ارتعاش و استفاده از روانکار مناسب نقش کلیدی در افزایش عمر بلبرینگ دارند..."
},
{
title: "تفاوت بلبرینگ و رولبرینگ در صنایع سنگین",
image: "/src/img/3.jpg",
category: "علمی",
date: "۹ خرداد ۱۴۰۳",
excerpt: "مقایسه دو نوع اصلی یاتاقان‌ها از نظر تحمل بار محوری و شعاعی.",
content: "بلبرینگ‌ها مناسب سرعت‌های بالا هستند؛ رولبرینگ‌ها برای بار سنگین طراحی شده‌اند..."
},
{
title: "نکات نصب بلبرینگ با پرس هیدرولیک",
image: "/src/img/2.jpg",
category: "تست",
date: "۱۰ خرداد ۱۴۰۳",
excerpt: "آموزش نصب ایمن بلبرینگ بدون صدمه به سطح و شیارها.",
content: "در نصب با پرس، باید نیرو به حلقه‌ی مناسب منتقل شود تا آسیب مکانیکی رخ ندهد..."
},
{
title: "روش‌های تشخیص صدای غیرعادی بلبرینگ",
image: "/src/img/1.jpg",
category: "علمی",
date: "۱۲ خرداد ۱۴۰۳",
excerpt: "استفاده از دستگاه‌های ارتعاش‌سنج برای یافتن علل نویز در یاتاقان‌ها.",
content: "آنالیز فرکانسی صدای بلبرینگ می‌تواند مشکلات روانکاری یا ترک سطحی را آشکار کند..."
},
{
title: "بررسی آلیاژهای استفاده شده در ساخت بلبرینگ‌ها",
image: "/src/img/6.jpg",
category: "آموزشی",
date: "۱۳ خرداد ۱۴۰۳",
excerpt: "معرفی فولادهای کروم‌دار و تفاوت آن‌ها در مقاومت به خوردگی و سختی.",
content: "اصلی‌ترین ماده در تولید بلبرینگ‌ها فولاد کروم‌دار 100Cr6 است که تعادل عالی بین سختی و دوام دارد..."
},
];

6
next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

8
next.config.ts Normal file
View File

@@ -0,0 +1,8 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactCompiler: true,
};
export default nextConfig;

1719
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "parsproject",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"lucide-react": "^0.577.0",
"next": "16.1.6",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4",
"typescript": "^5"
}
}

7
postcss.config.mjs Normal file
View File

@@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

View File

@@ -0,0 +1,3 @@
.ac{
background-color: #31384a;
}

1
public/src/file.svg Normal file
View File

@@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
public/src/globe.svg Normal file
View File

@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/src/img/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
public/src/img/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/src/img/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/src/img/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/src/img/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/src/img/6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/src/img/7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

1
public/src/next.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/src/vercel.svg Normal file
View File

@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

34
tsconfig.json Normal file
View File

@@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}