diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..079d515 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules +# dependencies +/node_modules +/.pnp +.pnp.js + +# next.js build output +/.next/ +/out/ +/build/ + + diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..3b78c45 --- /dev/null +++ b/app/blog/[slug]/page.tsx @@ -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.title}

+ {article.date} +
+ +
+ {article.title} +
+ +
+ {article.excerpt} +
+ + {/* بخش نمایش محتوای کامل که درخواست کرده بودید */} +
+ {article.content} +
+
+ ); +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000..1f2894d --- /dev/null +++ b/app/blog/page.tsx @@ -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('همه'); + const [searchQuery, setSearchQuery] = useState(''); + + // ۲. استخراج خودکار دسته‌بندی‌های یکتا از دیتای مقالات + 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 ( +
+ {/* + بخش هدر (Hero Section) + استفاده از گرادیانت ملایم و استایل‌های مدرن + */} +
+
+
+
+

+ + وبلاگ و مقالات آموزشی +

+

+ جدیدترین اخبار، آموزش‌ها، ترفندها و مقالات تخصصی در حوزه صنعت و قطعات را اینجا بخوانید. +

+
+ + {/* باکس آمار مقالات */} +
+
+ +
+
+

تعداد کل مقالات

+

{articles.length} مقاله

+
+
+
+
+
+ +
+ + {/* بخش فیلترها و جستجو */} +
+ + {/* فیلتر دسته‌بندی (قابلیت اسکرول افقی در موبایل با مخفی کردن نوار اسکرول) */} +
+ {categories.map((category) => ( + + ))} +
+ + {/* باکس جستجو */} +
+ + 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" + /> +
+ +
+ + {/* گرید مقالات */} +
+ {filteredArticles.map((article) => ( +
+ +
+ ))} +
+ + {/* حالت خالی (وقتی مقاله‌ای با فیلتر یا جستجو پیدا نشد) */} + {filteredArticles.length === 0 && ( +
+
+ +
+

مقاله‌ای پیدا نشد!

+

+ با کلمه جستجو شده یا دسته‌بندی انتخاب شده، مقاله‌ای وجود ندارد. لطفا فیلترها را تغییر دهید. +

+ +
+ )} + +
+
+ ); +} diff --git a/app/cart/page.tsx b/app/cart/page.tsx new file mode 100644 index 0000000..80283ca --- /dev/null +++ b/app/cart/page.tsx @@ -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 ( +
+
+
+ +
+
+

سبد خرید شما خالی است!

+

+ هنوز هیچ محصولی به سبد خرید خود اضافه نکرده‌اید. برای مشاهده محصولات به صفحه اصلی برگردید. +

+ + بازگشت به فروشگاه + + +
+
+ ); + } + + return ( +
+
+ + {/* بخش هدر و Breadcrumb */} +
+
+

+ سبد خرید + + {totalItems} کالا + +

+
+ + {/* Breadcrumb (مراحل پرداخت) */} +
+
+
+ +
+ سبد خرید +
+ +
+
+
+ +
+
+ +
+ اطلاعات ارسال +
+
+
+ +
+ پرداخت +
+
+
+ +
+ + {/* بخش لیست محصولات */} +
+
+
+

محصولات انتخاب شده

+ +
+ +
+ {cart.map((item) => { + const itemTotal = parsePrice(item.price) * item.quantity; + + return ( +
+ + {/* تصویر محصول */} +
+ {item.title} +
+ + {/* اطلاعات محصول */} +
+

{item.brand}

+

{item.title}

+ + {/* ویژگی‌ها (Badges) */} +
+ L : {item.l} + D : {item.d} +
+
+ + {/* قیمت و کنترلر */} +
+ + {item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'} + + + {/* کنترلر تعداد طرح کپسولی */} +
+ + + + {item.quantity} + + + +
+
+
+ ); + })} +
+
+
+ + {/* بخش صورتحساب (Sidebar) */} +
+
+

خلاصه صورتحساب

+ +
+
+ تعداد کالاها + {totalItems} عدد +
+ +
+ هزینه ارسال + در مرحله بعد +
+
+ + {/* خط‌چین جداکننده */} +
+ +
+ مبلغ قابل پرداخت + + {totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')}` : 'استعلام'} + {totalPrice > 0 && تومان} + +
+ + + + {/* بج‌های اعتماد (Trust Badges) */} +
+
+ + پرداخت امن +
+
+
+ + ارسال سریع +
+
+
+
+
+
+
+ ); +} diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..fa56748 --- /dev/null +++ b/app/globals.css @@ -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; +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..ac7d978 --- /dev/null +++ b/app/layout.tsx @@ -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 ( + + + +
+ {children} + + +