first commit
12
.gitignore
vendored
Normal 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
@@ -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
@@ -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
@@ -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
|
After Width: | Height: | Size: 25 KiB |
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
78
components/articlecard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
88
components/context/cartcontext.tsx
Normal 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
@@ -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
@@ -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
@@ -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">071‑1234‑5678</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">071‑1234‑5678</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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
25
package.json
Normal 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
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
3
public/src/css/header.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.ac{
|
||||
background-color: #31384a;
|
||||
}
|
||||
1
public/src/file.svg
Normal 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 |
BIN
public/src/font/YekanBakhFaNum-Bold.woff
Normal file
BIN
public/src/font/YekanBakhFaNum-Regular.woff
Normal file
BIN
public/src/font/YekanBakhFaNum-SemiBold.woff
Normal file
1
public/src/globe.svg
Normal 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
|
After Width: | Height: | Size: 37 KiB |
BIN
public/src/img/2.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/src/img/3.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/src/img/4.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/src/img/5.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/src/img/6.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/src/img/7.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
1
public/src/next.svg
Normal 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
@@ -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
@@ -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"]
|
||||
}
|
||||