category api - dashboard page
This commit is contained in:
@@ -53,41 +53,56 @@ export default function CartPage() {
|
|||||||
<main className="bg-gray-50/30 min-h-screen pb-20">
|
<main className="bg-gray-50/30 min-h-screen pb-20">
|
||||||
<div className="container mx-auto px-4 py-8 max-w-6xl">
|
<div className="container mx-auto px-4 py-8 max-w-6xl">
|
||||||
|
|
||||||
{/* بخش هدر و Breadcrumb */}
|
{/* بخش هدر */}
|
||||||
<div className="mb-10">
|
<div className="mb-10">
|
||||||
<div className="flex items-center justify-between mb-8">
|
<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">
|
<h1 className="text-xl 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">
|
<span className="bg-[#ffb900]/20 text-[#d99d00] text-xs md:text-sm font-bold py-1 px-3 rounded-xl flex items-center">
|
||||||
{totalItems} کالا
|
{totalItems} کالا
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Breadcrumb (مراحل پرداخت) */}
|
{/* Breadcrumb (مراحل پرداخت) - کاملا رسپانسیو شده */}
|
||||||
<div className="flex items-center justify-center w-full max-w-2xl mx-auto">
|
<div className="relative w-full max-w-2xl mx-auto px-2 sm:px-0">
|
||||||
<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>
|
تنظیمات top: در موبایل آیکون 40px است (وسط آن میشود 20px)
|
||||||
<span className="text-sm font-bold text-gray-800">سبد خرید</span>
|
در دسکتاپ آیکون 48px است (وسط آن میشود 24px)
|
||||||
|
*/}
|
||||||
|
<div className="absolute top-[20px] sm:top-[24px] left-[16.5%] right-[16.5%] h-[2px] bg-gray-200 z-0">
|
||||||
|
{/* پر شدن خط مرحله اول (درصد پیشرفت) */}
|
||||||
|
<div className="h-full bg-[#ffb900] w-0 transition-all"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-[2px] w-full bg-gray-200 absolute top-[28%] -z-0">
|
<div className="flex justify-between relative z-10">
|
||||||
<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">
|
{/* مرحله 1 */}
|
||||||
<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">
|
<div className="flex flex-col items-center w-1/3">
|
||||||
<Truck size={18} />
|
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-[#ffb900] text-black rounded-full flex items-center justify-center shadow-md mb-2 sm:mb-3 ring-[6px] ring-[#f8fafc] sm:ring-[#f8fafc]">
|
||||||
|
<ShoppingBag className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] sm:text-sm font-bold text-gray-800 text-center">سبد خرید</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium text-gray-400">اطلاعات ارسال</span>
|
|
||||||
</div>
|
{/* مرحله 2 */}
|
||||||
<div className="flex flex-col items-center w-1/3 relative z-10">
|
<div className="flex flex-col items-center w-1/3">
|
||||||
<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">
|
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-white border-2 border-gray-200 text-gray-400 rounded-full flex items-center justify-center mb-2 sm:mb-3 ring-[6px] ring-[#f8fafc] sm:ring-[#f8fafc]">
|
||||||
<CreditCard size={18} />
|
<Truck className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] sm:text-sm font-medium text-gray-400 text-center">اطلاعات ارسال</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium text-gray-400">پرداخت</span>
|
|
||||||
|
{/* مرحله 3 */}
|
||||||
|
<div className="flex flex-col items-center w-1/3">
|
||||||
|
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-white border-2 border-gray-200 text-gray-400 rounded-full flex items-center justify-center mb-2 sm:mb-3 ring-[6px] ring-[#f8fafc] sm:ring-[#f8fafc]">
|
||||||
|
<CreditCard className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] sm:text-sm font-medium text-gray-400 text-center">پرداخت</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,8 +113,8 @@ export default function CartPage() {
|
|||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="bg-white rounded-[2rem] p-4 md:p-8 shadow-sm border border-gray-100">
|
<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">
|
<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>
|
<h2 className="text-base md: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">
|
<button onClick={clearCart} className="text-xs md: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} />
|
<Trash2 size={16} />
|
||||||
حذف همه
|
حذف همه
|
||||||
</button>
|
</button>
|
||||||
@@ -120,10 +135,10 @@ export default function CartPage() {
|
|||||||
{/* اطلاعات محصول */}
|
{/* اطلاعات محصول */}
|
||||||
<div className="flex-1 w-full">
|
<div className="flex-1 w-full">
|
||||||
<p className="text-xs font-medium text-blue-500 mb-1.5">{item.brand}</p>
|
<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>
|
<h3 className="text-sm md: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) */}
|
{/* ویژگیها (Badges) */}
|
||||||
<div className="flex flex-wrap gap-2 text-xs font-medium text-gray-600">
|
<div className="flex flex-wrap gap-2 text-[10px] md: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">L : {item.l}</span>
|
||||||
<span className="bg-gray-100/80 px-2.5 py-1 rounded-md" dir="ltr">D : {item.d}</span>
|
<span className="bg-gray-100/80 px-2.5 py-1 rounded-md" dir="ltr">D : {item.d}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +146,7 @@ export default function CartPage() {
|
|||||||
|
|
||||||
{/* قیمت و کنترلر */}
|
{/* قیمت و کنترلر */}
|
||||||
<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">
|
<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">
|
<span className="font-black text-base md:text-lg text-gray-900">
|
||||||
{item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'}
|
{item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -139,20 +154,20 @@ export default function CartPage() {
|
|||||||
<div className="flex items-center gap-1 bg-gray-50 border border-gray-200 rounded-full p-1 shadow-sm">
|
<div className="flex items-center gap-1 bg-gray-50 border border-gray-200 rounded-full p-1 shadow-sm">
|
||||||
<button
|
<button
|
||||||
onClick={() => addToCart(item)}
|
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"
|
className="w-7 h-7 md:w-8 md: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} />
|
<Plus size={14} strokeWidth={2.5} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span className="text-sm font-bold text-gray-800 w-6 text-center">
|
<span className="text-xs md:text-sm font-bold text-gray-800 w-6 text-center">
|
||||||
{item.quantity}
|
{item.quantity}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => decreaseQuantity(item.id)}
|
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"
|
className="w-7 h-7 md:w-8 md: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} />}
|
{item.quantity === 1 ? <Trash2 size={14} /> : <Minus size={14} strokeWidth={2.5} />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,17 +181,17 @@ export default function CartPage() {
|
|||||||
{/* بخش صورتحساب (Sidebar) */}
|
{/* بخش صورتحساب (Sidebar) */}
|
||||||
<div className="w-full lg:w-[340px] shrink-0">
|
<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">
|
<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>
|
<h2 className="text-lg md:text-xl font-bold text-gray-800 mb-6">خلاصه صورتحساب</h2>
|
||||||
|
|
||||||
<div className="space-y-4 mb-6">
|
<div className="space-y-4 mb-6">
|
||||||
<div className="flex justify-between items-center text-sm">
|
<div className="flex justify-between items-center text-xs md:text-sm">
|
||||||
<span className="text-gray-500">تعداد کالاها</span>
|
<span className="text-gray-500">تعداد کالاها</span>
|
||||||
<span className="font-bold text-gray-800">{totalItems} عدد</span>
|
<span className="font-bold text-gray-800">{totalItems} عدد</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between items-center text-sm">
|
<div className="flex justify-between items-center text-xs md:text-sm">
|
||||||
<span className="text-gray-500">هزینه ارسال</span>
|
<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>
|
<span className="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded-md text-[10px] md:text-xs">در مرحله بعد</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -184,27 +199,27 @@ export default function CartPage() {
|
|||||||
<div className="w-full border-t-2 border-dashed border-gray-200 my-6"></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">
|
<div className="flex justify-between items-center mb-8">
|
||||||
<span className="text-sm font-bold text-gray-600">مبلغ قابل پرداخت</span>
|
<span className="text-xs md:text-sm font-bold text-gray-600">مبلغ قابل پرداخت</span>
|
||||||
<span className="font-black text-2xl text-[#ffb900] tracking-tight">
|
<span className="font-black text-xl md:text-2xl text-[#ffb900] tracking-tight">
|
||||||
{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')}` : 'استعلام'}
|
{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')}` : 'استعلام'}
|
||||||
{totalPrice > 0 && <span className="text-sm font-medium text-gray-500 mr-1">تومان</span>}
|
{totalPrice > 0 && <span className="text-xs md:text-sm font-medium text-gray-500 mr-1">تومان</span>}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<button className="w-full bg-[#ffb900] hover:bg-[#e5a600] text-black py-3 md:py-4 rounded-xl font-bold text-base md: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} />
|
<ChevronLeft size={20} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* بجهای اعتماد (Trust Badges) */}
|
{/* بجهای اعتماد (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 justify-center gap-2 md:gap-4 text-[10px] md: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">
|
<div className="flex items-center gap-1">
|
||||||
<ShieldCheck size={16} className="text-green-500" />
|
<ShieldCheck size={14} className="text-green-500" />
|
||||||
پرداخت امن
|
پرداخت امن
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1 h-1 bg-gray-300 rounded-full"></div>
|
<div className="w-1 h-1 bg-gray-300 rounded-full"></div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Truck size={16} className="text-blue-500" />
|
<Truck size={14} className="text-blue-500" />
|
||||||
ارسال سریع
|
ارسال سریع
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,28 +11,20 @@ export default async function CategoryPage({ params }: PageProps) {
|
|||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
const rawCategoryName = resolvedParams.categoryName;
|
const rawCategoryName = resolvedParams.categoryName;
|
||||||
|
|
||||||
// چاپ در ترمینال (VSCode)
|
|
||||||
console.log("--- DEBUG START ---");
|
|
||||||
console.log("1. Raw Params:", resolvedParams);
|
|
||||||
|
|
||||||
// ۲. دیکد کردن آدرس
|
// ۲. دیکد کردن آدرس
|
||||||
const decodedUrl = decodeURIComponent(rawCategoryName);
|
const decodedUrl = decodeURIComponent(rawCategoryName);
|
||||||
console.log("2. Decoded URL:", decodedUrl);
|
|
||||||
|
|
||||||
// ۳. آمادهسازی رشته برای مقایسه
|
// ۳. آمادهسازی رشته برای مقایسه
|
||||||
const categoryNameToMatch = decodedUrl.replace(/-/g, " ").trim();
|
const categoryNameToMatch = decodedUrl.replace(/-/g, " ").trim();
|
||||||
console.log(`3. Final String to Match: "${categoryNameToMatch}"`);
|
|
||||||
|
|
||||||
// استخراج تمام دستهبندیهای موجود در دیتابیس (برای بررسی چشمی)
|
// استخراج تمام دستهبندیهای موجود در دیتابیس (برای بررسی چشمی)
|
||||||
const allCategoriesInDb = Array.from(new Set(products.map(p => p.category)));
|
const allCategoriesInDb = Array.from(new Set(products.map(p => p.category)));
|
||||||
console.log("4. Available DB Categories:", allCategoriesInDb);
|
|
||||||
|
|
||||||
// ۴. فیلتر کردن (با حذف فاصلههای اضافی از دو طرف برای اطمینان)
|
// ۴. فیلتر کردن (با حذف فاصلههای اضافی از دو طرف برای اطمینان)
|
||||||
const filteredProducts = products.filter(
|
const filteredProducts = products.filter(
|
||||||
(p) => p.category.trim() === categoryNameToMatch
|
(p) => p.category.trim() === categoryNameToMatch
|
||||||
);
|
);
|
||||||
console.log("5. Found Products:", filteredProducts.length);
|
|
||||||
console.log("--- DEBUG END ---");
|
|
||||||
|
|
||||||
// ۵. اگر پیدا نشد، صفحه دیباگ را در مرورگر نمایش بده
|
// ۵. اگر پیدا نشد، صفحه دیباگ را در مرورگر نمایش بده
|
||||||
if (!filteredProducts || filteredProducts.length === 0) {
|
if (!filteredProducts || filteredProducts.length === 0) {
|
||||||
|
|||||||
37
app/category/page.tsx
Normal file
37
app/category/page.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// src/app/categories/page.tsx
|
||||||
|
import { categoryService } from '@/public/src/services/categories/api';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export default async function CategoriesPage() {
|
||||||
|
// دریافت دادهها در سمت سرور (بدون هیچ هوک یا لودینگی در سمت کلاینت)
|
||||||
|
// به دلیل تنظیمات revalidate، این درخواست بسیار سریع (از کش) خوانده میشود
|
||||||
|
const categories = await categoryService.getCategories();
|
||||||
|
|
||||||
|
// گرفتن دستهبندیهای اصلی (آنهایی که والد ندارند)
|
||||||
|
const rootCategories = categories.filter((cat) => cat.parent === null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="p-4">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">دستهبندی محصولات</h1>
|
||||||
|
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{rootCategories.map((category) => (
|
||||||
|
<li key={category.id} className="border p-3 rounded-md">
|
||||||
|
<Link href={`/category/${category.slug}`}>
|
||||||
|
<strong className="text-blue-600">{category.name}</strong>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* نمایش زیرمجموعهها در صورت وجود */}
|
||||||
|
{category.children.length > 0 && (
|
||||||
|
<ul className="pr-4 mt-2 list-disc text-sm text-gray-600">
|
||||||
|
{category.children.map((child) => (
|
||||||
|
<li key={child.id}>{child.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
649
app/dashboard/page.tsx
Normal file
649
app/dashboard/page.tsx
Normal file
@@ -0,0 +1,649 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
User, Crown, Home, ShoppingBag, Wallet, MapPin,
|
||||||
|
Headphones, Heart, IdCard, LogOut, TrendingUp,
|
||||||
|
Plus, Hash, Clock, AlertCircle, Eye, Reply,
|
||||||
|
CreditCard, Gift, Edit, Trash2, CircleCheck, Info, Box, Camera, Check, Shield, ShieldAlert, Mail
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
// === رابطها و کامپوننتهای کمکی ===
|
||||||
|
interface PriceDisplayProps {
|
||||||
|
amount: number | string | null;
|
||||||
|
currency?: string;
|
||||||
|
unit?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PriceDisplay: React.FC<PriceDisplayProps> = ({ amount, currency = 'تومان', unit }) => {
|
||||||
|
if (amount === null || amount === undefined || amount === 0) {
|
||||||
|
return <span className="text-gray-400">استعلام</span>;
|
||||||
|
}
|
||||||
|
const formattedAmount = typeof amount === 'number'
|
||||||
|
? amount.toLocaleString('fa-IR')
|
||||||
|
: Number(amount.toString().replace(/,/g, '')).toLocaleString('fa-IR');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{formattedAmount} {unit ? unit : currency}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// === دادههای نمونه ===
|
||||||
|
const sampleOrders = [
|
||||||
|
{
|
||||||
|
id: "PR-10452",
|
||||||
|
status: "در حال پردازش",
|
||||||
|
statusColor: "amber",
|
||||||
|
items: [
|
||||||
|
{ name: "پمپ آب صنعتی مدل PX-200", qty: 2, price: "۳۰۰,۰۰۰" },
|
||||||
|
{ name: "فیلتر هوای کابین خودرو", qty: 1, price: "۲۰۰,۰۰۰" },
|
||||||
|
],
|
||||||
|
total: "۵۰۰,۰۰۰",
|
||||||
|
regDate: "۱۵ دی ۱۴۰۴",
|
||||||
|
deliveryDate: "۲۰ دی ۱۴۰۴",
|
||||||
|
delivered: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PR-10451",
|
||||||
|
status: "تحویل شده",
|
||||||
|
statusColor: "green",
|
||||||
|
items: [
|
||||||
|
{ name: "روغن موتور ۵W-30 کاسترول", qty: 4, price: "۱۲۰,۰۰۰" },
|
||||||
|
],
|
||||||
|
total: "۱۲۰,۰۰۰",
|
||||||
|
regDate: "۱۰ دی ۱۴۰۴",
|
||||||
|
deliveryDate: "۱۲ دی ۱۴۰۴",
|
||||||
|
delivered: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const sampleTickets = [
|
||||||
|
{
|
||||||
|
id: "54321",
|
||||||
|
title: "عدم تطابق قطعه ارسالی با فاکتور",
|
||||||
|
date: "۱۴ دی ۱۴۰۴",
|
||||||
|
department: "بخش فروش",
|
||||||
|
priority: "اولویت بالا",
|
||||||
|
status: "در حال بررسی",
|
||||||
|
statusColor: "amber",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const sampleAddresses = [
|
||||||
|
{
|
||||||
|
id: "addr1",
|
||||||
|
text: "شیراز، بلوار معالی آباد، خیابان پزشکان، ساختمان پارس، واحد ۴",
|
||||||
|
receiver: "علی محمدی",
|
||||||
|
phone: "۰۹۱۲۳۴۵۶۷۸۹",
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "addr2",
|
||||||
|
text: "تهران، خیابان سعدی جنوبی، کوچه ناظمالاطباء، پلاک ۲۰",
|
||||||
|
receiver: "دفتر تهران (شرکت پترو صدف)",
|
||||||
|
phone: "۰۲۱-۳۳۹۰۰۰۰",
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Dashboard() {
|
||||||
|
const [activeTab, setActiveTab] = useState('dash');
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ id: 'dash', label: 'پیشخوان', icon: Home },
|
||||||
|
{ id: 'orders', label: 'سفارشهای من', icon: ShoppingBag },
|
||||||
|
{ id: 'wallet', label: 'کیف پول', icon: Wallet },
|
||||||
|
{ id: 'address', label: 'آدرسها', icon: MapPin },
|
||||||
|
{ id: 'tickets', label: 'تیکت پشتیبانی', icon: Headphones },
|
||||||
|
{ id: 'profile', label: 'مشخصات حساب', icon: IdCard },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
|
||||||
|
<div className="container mx-auto px-4 max-w-6xl">
|
||||||
|
<div className="flex flex-col lg:flex-row gap-8">
|
||||||
|
|
||||||
|
<aside className="w-full lg:w-[280px] bg-white rounded-2xl shadow-sm flex-shrink-0 h-fit">
|
||||||
|
<div className="p-6 flex flex-col items-center border-b border-gray-100">
|
||||||
|
<div className="w-20 h-20 bg-gray-50 rounded-full flex items-center justify-center text-gray-400 mb-4">
|
||||||
|
<User size={32} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<div className="text-lg font-bold text-gray-800 mb-1">علی محمدی</div>
|
||||||
|
<div className="text-sm text-gray-500 mb-4 font-mono" dir="ltr">0912 345 6789</div>
|
||||||
|
<div className="w-full flex flex-col items-center">
|
||||||
|
<div className="inline-flex items-center gap-1.5 px-3 py-1 bg-yellow-100 text-yellow-700 rounded-full text-xs font-bold mb-3">
|
||||||
|
<Crown size={14} /> کاربر طلایی
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden mb-1.5">
|
||||||
|
<div className="h-full bg-yellow-400 rounded-full transition-all duration-500" style={{ width: '75%' }}></div>
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] text-gray-400 text-center">۷۵٪ تا سطح پلاتینی</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="p-4 flex flex-col gap-1">
|
||||||
|
{menuItems.map((item) => (
|
||||||
|
<li key={item.id}>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab(item.id)}
|
||||||
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all duration-200 ${activeTab === item.id
|
||||||
|
? 'bg-[#1a2332] text-white shadow-md'
|
||||||
|
: 'text-gray-600 hover:bg-gray-50 hover:text-[#ffb900]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<item.icon size={18} className={activeTab === item.id ? 'text-white' : 'text-gray-400'} />
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<li className="mt-2 pt-2 border-t border-gray-100">
|
||||||
|
<button className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold text-red-500 hover:bg-red-50 transition-colors">
|
||||||
|
<LogOut size={18} /> خروج
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main className="flex-1 overflow-hidden">
|
||||||
|
|
||||||
|
{/* -------------------- تب پیشخوان -------------------- */}
|
||||||
|
{activeTab === 'dash' && (
|
||||||
|
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800 mb-6">خوش آمدید، علی عزیز</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-4 hover:shadow-md transition-shadow">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-blue-50 text-blue-600 flex items-center justify-center flex-shrink-0"><Wallet size={24} /></div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-gray-500 mb-1">موجودی کیف پول</h4>
|
||||||
|
<strong className="text-lg text-gray-800"><PriceDisplay amount={1500000} /></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-4 hover:shadow-md transition-shadow">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-green-50 text-green-600 flex items-center justify-center flex-shrink-0"><ShoppingBag size={24} /></div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-gray-500 mb-1">سفارشات جاری</h4>
|
||||||
|
<strong className="text-lg text-gray-800">۲ <span className="text-xs font-normal text-gray-500">مورد</span></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-4 hover:shadow-md transition-shadow">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-orange-50 text-orange-600 flex items-center justify-center flex-shrink-0"><Headphones size={24} /></div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-gray-500 mb-1">تیکتهای باز</h4>
|
||||||
|
<strong className="text-lg text-gray-800">۱ <span className="text-xs font-normal text-gray-500">مورد</span></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-4 hover:shadow-md transition-shadow">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-purple-50 text-purple-600 flex items-center justify-center flex-shrink-0"><TrendingUp size={24} /></div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-gray-500 mb-1">امتیاز وفاداری</h4>
|
||||||
|
<strong className="text-lg text-gray-800">۲,۳۴۰ <span className="text-xs font-normal text-gray-500">امتیاز</span></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||||
|
<div className="flex justify-between items-center pb-4 border-b border-gray-100 mb-4">
|
||||||
|
<h3 className="font-bold text-gray-800">آخرین سفارشات</h3>
|
||||||
|
<button onClick={() => setActiveTab('orders')} className="text-sm text-blue-600 hover:text-blue-700">مشاهده همه</button>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500 py-4 flex flex-col gap-3">
|
||||||
|
{/* پیشنمایش کوچکی از سفارشات */}
|
||||||
|
<div className="flex justify-between items-center bg-gray-50 p-3 rounded-lg">
|
||||||
|
<span className="font-medium text-gray-700">#PR-10452</span>
|
||||||
|
<span className="bg-amber-100 text-amber-700 px-2 py-1 rounded text-xs">در حال پردازش</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* -------------------- تب تیکتها -------------------- */}
|
||||||
|
{activeTab === 'tickets' && (
|
||||||
|
<div className="animate-in fade-in duration-500">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800 m-0">تیکتهای پشتیبانی</h1>
|
||||||
|
<button onClick={() => alert('فرم تیکت جدید باز شود')} className="bg-[#1a2332] hover:bg-[#ffb900] text-white px-5 py-2.5 rounded-xl flex items-center gap-2 transition-colors text-sm font-medium">
|
||||||
|
<Plus size={18} /> تیکت جدید
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex flex-col items-center justify-center">
|
||||||
|
<span className="text-3xl font-bold text-gray-800 mb-2">۵</span>
|
||||||
|
<div className="text-sm text-gray-500">کل تیکتها</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex flex-col items-center justify-center">
|
||||||
|
<span className="text-3xl font-bold text-amber-600 mb-2">۱</span>
|
||||||
|
<div className="text-sm text-gray-500">در حال بررسی</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex flex-col items-center justify-center">
|
||||||
|
<span className="text-3xl font-bold text-green-700 mb-2">۳</span>
|
||||||
|
<div className="text-sm text-gray-500">پاسخ داده شده</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex flex-col items-center justify-center">
|
||||||
|
<span className="text-3xl font-bold text-red-700 mb-2">۱</span>
|
||||||
|
<div className="text-sm text-gray-500">بسته شده</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6 overflow-x-auto pb-2 scrollbar-hide">
|
||||||
|
<div className="flex gap-2 min-w-max">
|
||||||
|
<button className="bg-gray-800 text-white px-5 py-2 rounded-full text-sm font-medium transition-colors">همه تیکتها</button>
|
||||||
|
<button className="bg-white border border-gray-200 text-gray-600 hover:bg-gray-50 px-5 py-2 rounded-full text-sm font-medium transition-colors">باز</button>
|
||||||
|
<button className="bg-white border border-gray-200 text-gray-600 hover:bg-gray-50 px-5 py-2 rounded-full text-sm font-medium transition-colors">پاسخ داده شده</button>
|
||||||
|
<button className="bg-white border border-gray-200 text-gray-600 hover:bg-gray-50 px-5 py-2 rounded-full text-sm font-medium transition-colors">بسته شده</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{sampleTickets.map(ticket => (
|
||||||
|
<div key={ticket.id} className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 transition-all hover:shadow-md">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-lg font-bold text-gray-800 mb-3">{ticket.title}</h3>
|
||||||
|
<div className="flex flex-wrap items-center gap-4 text-xs sm:text-sm text-gray-500">
|
||||||
|
<span className="flex items-center gap-1"><Hash size={14} /> {ticket.id}</span>
|
||||||
|
<span className="flex items-center gap-1"><Clock size={14} /> {ticket.date}</span>
|
||||||
|
<span className="flex items-center gap-1"><User size={14} /> {ticket.department}</span>
|
||||||
|
<span className="flex items-center gap-1 text-red-600 font-medium"><AlertCircle size={14} /> {ticket.priority}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 w-full lg:w-auto justify-between lg:justify-end border-t lg:border-0 pt-4 lg:pt-0 mt-2 lg:mt-0">
|
||||||
|
<span className="bg-amber-100 text-amber-800 px-3 py-1 rounded-full text-xs font-medium">
|
||||||
|
{ticket.status}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button className="w-10 h-10 rounded-full bg-gray-50 text-gray-600 flex items-center justify-center hover:bg-blue-50 hover:text-[#ffb900] transition-colors">
|
||||||
|
<Eye size={18} />
|
||||||
|
</button>
|
||||||
|
<button className="w-10 h-10 rounded-full bg-gray-50 text-gray-600 flex items-center justify-center hover:bg-blue-50 hover:text-[#ffb900] transition-colors">
|
||||||
|
<Reply size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* -------------------- تب آدرسها -------------------- */}
|
||||||
|
{activeTab === 'address' && (
|
||||||
|
<div className="animate-in fade-in duration-500">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800 m-0">آدرسهای من</h1>
|
||||||
|
<button className="bg-[#1a2332] hover:bg-[#ffb900] text-white px-5 py-2.5 rounded-xl flex items-center gap-2 transition-colors text-sm font-medium">
|
||||||
|
<MapPin size={18} /> افزودن آدرس جدید
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
{sampleAddresses.map((address) => (
|
||||||
|
<div
|
||||||
|
key={address.id}
|
||||||
|
className={`bg-white p-6 rounded-2xl border-2 ${address.isDefault ? 'border-[#ffd230] relative' : 'border-gray-200'} transition-all`}
|
||||||
|
>
|
||||||
|
{address.isDefault && (
|
||||||
|
<span className="absolute -top-3 right-6 bg-[#ffd230] text-white text-[10px] font-bold px-2 py-1 rounded">
|
||||||
|
پیشفرض
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div className="text-gray-800 leading-relaxed font-medium mb-4 text-sm sm:text-base">
|
||||||
|
{address.text}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 text-sm text-gray-500 mb-6">
|
||||||
|
<div className="flex items-center gap-2"><User size={16} /> گیرنده: {address.receiver}</div>
|
||||||
|
<div className="flex items-center gap-2"><Headphones size={16} /> تماس: {address.phone}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-end gap-2 border-t border-gray-100 pt-4 mt-auto">
|
||||||
|
{!address.isDefault && (
|
||||||
|
<button title="انتخاب به عنوان پیشفرض" className="w-10 h-10 rounded-full bg-gray-50 text-gray-600 flex items-center justify-center hover:bg-green-50 hover:text-green-600 transition-colors">
|
||||||
|
<CircleCheck size={18} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button className="w-10 h-10 rounded-full bg-gray-50 text-gray-600 flex items-center justify-center hover:bg-blue-50 hover:text-[#ffb900] transition-colors">
|
||||||
|
<Edit size={18} />
|
||||||
|
</button>
|
||||||
|
<button className="w-10 h-10 rounded-full bg-red-50 text-red-500 flex items-center justify-center hover:bg-red-500 hover:text-white transition-colors">
|
||||||
|
<Trash2 size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* -------------------- تب کیف پول -------------------- */}
|
||||||
|
{activeTab === 'wallet' && (
|
||||||
|
<div className="animate-in fade-in duration-500">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800 mb-6">کیف پول و مدیریت مالی</h1>
|
||||||
|
|
||||||
|
<div className="bg-gray-800 text-white p-6 sm:p-8 rounded-2xl flex flex-col md:flex-row justify-between items-center gap-6 mb-8 bg-[url('/pattern.png')] bg-cover bg-center">
|
||||||
|
<div className="text-center md:text-right">
|
||||||
|
<span className="text-gray-300 text-sm mb-2 block">موجودی فعلی حساب شما:</span>
|
||||||
|
<div className="text-3xl sm:text-4xl font-bold text-yellow-400">
|
||||||
|
۱,۵۰۰,۰۰۰ <span className="text-lg font-normal text-white">تومان</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 text-sm text-gray-400 flex items-center justify-center md:justify-start gap-1">
|
||||||
|
<CreditCard size={14} /> آخرین بروزرسانی: امروز ۱۴:۳۰
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 w-full md:w-auto">
|
||||||
|
<button className="bg-yellow-400 hover:bg-yellow-500 text-gray-900 px-8 py-3 rounded-xl font-bold flex items-center justify-center gap-2 transition-colors">
|
||||||
|
<Plus size={18} /> افزایش موجودی
|
||||||
|
</button>
|
||||||
|
<button className="bg-transparent border border-gray-600 hover:bg-gray-700 text-white px-8 py-2.5 rounded-xl font-medium flex items-center justify-center gap-2 transition-colors">
|
||||||
|
<Plus size={18} className="rotate-180" /> برداشت وجه
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-green-100 text-green-700 flex items-center justify-center"><Plus size={24} /></div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-gray-500 mb-1">کل واریزیها</h4>
|
||||||
|
<strong className="text-lg text-gray-800">۵,۲۰۰,۰۰۰ تومان</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-red-100 text-red-700 flex items-center justify-center"><Plus size={24} className="rotate-180" /></div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-gray-500 mb-1">کل برداشتها</h4>
|
||||||
|
<strong className="text-lg text-gray-800">۳,۷۰۰,۰۰۰ تومان</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-blue-100 text-blue-700 flex items-center justify-center"><TrendingUp size={24} /></div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-gray-500 mb-1">امتیاز وفاداری</h4>
|
||||||
|
<strong className="text-lg text-gray-800">۲,۳۴۰ امتیاز</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center pb-4 border-b border-gray-100 mb-4 gap-4">
|
||||||
|
<div className="text-lg font-bold text-gray-800 flex items-center gap-2">
|
||||||
|
<Clock size={20} className="text-gray-400" /> تاریخچه تراکنشها
|
||||||
|
</div>
|
||||||
|
<select className="bg-gray-50 border border-gray-200 text-gray-700 px-4 py-2 rounded-xl text-sm outline-none focus:border-blue-500">
|
||||||
|
<option>همه تراکنشها</option>
|
||||||
|
<option>واریزیها</option>
|
||||||
|
<option>برداشتها</option>
|
||||||
|
<option>خرید محصول</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 bg-gray-50 rounded-xl gap-4 border border-transparent hover:border-gray-200 transition-colors">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-red-100 text-red-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<ShoppingBag size={18} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-bold text-gray-800 text-sm mb-1">پرداخت بابت سفارش #PR-10452</div>
|
||||||
|
<div className="text-xs text-gray-500 mb-1">۱۵ دی ۱۴۰۴ - ساعت ۱4:۳۰</div>
|
||||||
|
<div className="text-xs text-gray-400">کد پیگیری: TXN-789456123</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-red-600 font-bold bg-red-50 px-3 py-1 rounded-full text-sm">
|
||||||
|
- ۵۰۰,۰۰۰ تومان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 bg-gray-50 rounded-xl gap-4 border border-transparent hover:border-gray-200 transition-colors">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-100 text-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<CreditCard size={18} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-bold text-gray-800 text-sm mb-1">افزایش آنلاین موجودی (درگاه بانکی)</div>
|
||||||
|
<div className="text-xs text-gray-500 mb-1">۱۴ دی ۱۴۰۴ - ساعت ۱۰:۱۵</div>
|
||||||
|
<div className="text-xs text-gray-400">کد پیگیری: TXN-789456122</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-green-600 font-bold bg-green-50 px-3 py-1 rounded-full text-sm">
|
||||||
|
+ ۲,۰۰۰,۰۰۰ تومان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 bg-gray-50 rounded-xl gap-4 border border-transparent hover:border-gray-200 transition-colors">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-100 text-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<Gift size={18} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-bold text-gray-800 text-sm mb-1">استفاده از امتیاز وفاداری</div>
|
||||||
|
<div className="text-xs text-gray-500 mb-1">۱۲ دی ۱۴۰۴ - ساعت ۱۶:۲۰</div>
|
||||||
|
<div className="text-xs text-gray-400">تبدیل ۵۰۰ امتیاز به تومان</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-green-600 font-bold bg-green-50 px-3 py-1 rounded-full text-sm">
|
||||||
|
+ ۵۰,۰۰۰ تومان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* -------------------- تب سفارشها -------------------- */}
|
||||||
|
{activeTab === 'orders' && (
|
||||||
|
<div className="animate-in fade-in duration-500">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800 mb-6">سفارشهای من</h1>
|
||||||
|
|
||||||
|
<div className="mb-6 overflow-x-auto pb-2 scrollbar-hide">
|
||||||
|
<div className="flex gap-2 min-w-max border-b border-gray-200 pb-2">
|
||||||
|
<button className="text-blue-600 border-b-2 border-blue-600 px-4 py-2 text-sm font-bold">همه سفارشات</button>
|
||||||
|
<button className="text-gray-500 hover:text-gray-800 px-4 py-2 text-sm font-medium transition-colors">در حال پردازش</button>
|
||||||
|
<button className="text-gray-500 hover:text-gray-800 px-4 py-2 text-sm font-medium transition-colors">ارسال شده</button>
|
||||||
|
<button className="text-gray-500 hover:text-gray-800 px-4 py-2 text-sm font-medium transition-colors">تحویل شده</button>
|
||||||
|
<button className="text-gray-500 hover:text-gray-800 px-4 py-2 text-sm font-medium transition-colors">لغو شده</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{sampleOrders.map((order) => (
|
||||||
|
<div key={order.id} className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden hover:shadow-md transition-shadow">
|
||||||
|
<div className="bg-gray-50 px-6 py-4 flex justify-between items-center border-b border-gray-100">
|
||||||
|
<div className="font-bold text-gray-800 flex items-center gap-2">
|
||||||
|
<Hash size={16} className="text-gray-400" /> {order.id}
|
||||||
|
</div>
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-bold ${order.statusColor === 'amber' ? 'bg-amber-100 text-amber-700' :
|
||||||
|
order.statusColor === 'green' ? 'bg-green-100 text-green-700' :
|
||||||
|
'bg-gray-200 text-gray-700'
|
||||||
|
}`}>
|
||||||
|
{order.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
|
<div className="flex-1">
|
||||||
|
{order.items.map((item, idx) => (
|
||||||
|
<div key={idx} className="flex justify-between items-center py-3 border-b border-gray-50 last:border-0 text-sm">
|
||||||
|
<span className="font-medium text-gray-700 flex-1">{item.name}</span>
|
||||||
|
<span className="text-gray-500 w-24 text-center">تعداد: {item.qty}</span>
|
||||||
|
<span className="font-bold text-gray-800 w-32 text-left">{item.price} تومان</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="w-full lg:w-64 bg-gray-50 rounded-xl p-4 flex flex-col gap-3 text-sm text-gray-600">
|
||||||
|
<div className="flex items-center gap-2"><Clock size={16} className="text-gray-400" /> تاریخ ثبت: {order.regDate}</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{order.delivered ? <CircleCheck size={16} className="text-green-500" /> : <Box size={16} className="text-blue-500" />}
|
||||||
|
{order.delivered ? 'تحویل شده:' : 'تحویل تا:'} {order.deliveryDate}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 border-t border-gray-200 pt-3 mt-1 font-bold text-gray-800">
|
||||||
|
<CreditCard size={16} className="text-gray-400" /> مبلغ کل: {order.total} تومان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2 mt-6 pt-4 border-t border-gray-100 justify-end">
|
||||||
|
<button className="px-4 py-2 border border-gray-200 text-gray-700 rounded-xl text-sm font-medium hover:bg-gray-50 transition-colors">جزئیات سفارش</button>
|
||||||
|
{!order.delivered && (
|
||||||
|
<button className="px-4 py-2 border border-gray-200 text-gray-700 rounded-xl text-sm font-medium hover:bg-gray-50 transition-colors">پیگیری مرسوله</button>
|
||||||
|
)}
|
||||||
|
{order.delivered ? (
|
||||||
|
<>
|
||||||
|
<button className="px-4 py-2 bg-[#1a2332] text-white rounded-xl text-sm font-medium hover:bg-[#ffb900] transition-colors">خرید مجدد</button>
|
||||||
|
<button className="px-4 py-2 border border-gray-200 text-gray-700 rounded-xl text-sm font-medium hover:bg-gray-50 transition-colors">نظر و امتیاز</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<button className="px-4 py-2 border border-red-200 text-red-500 rounded-xl text-sm font-medium hover:bg-red-50 transition-colors">لغو سفارش</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* -------------------- تب پروفایل -------------------- */}
|
||||||
|
{activeTab === 'profile' && (
|
||||||
|
<div className="animate-in fade-in duration-500">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800 mb-6">پروفایل کاربری</h1>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* اطلاعات شخصی */}
|
||||||
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||||
|
<h2 className="text-xl font-bold text-gray-800 mb-6 border-b border-gray-100 pb-4">اطلاعات شخصی</h2>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
{/* Avatar Section */}
|
||||||
|
<div className="flex flex-col sm:flex-row items-center gap-6 mb-8">
|
||||||
|
<div className="relative">
|
||||||
|
<img
|
||||||
|
src="/avatar-placeholder.png"
|
||||||
|
alt="User Avatar"
|
||||||
|
className="w-24 h-24 rounded-full object-cover border-4 border-gray-50 shadow-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center sm:items-start gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-2 px-4 py-2 border border-blue-600 text-blue-600 rounded-xl hover:bg-blue-50 transition-colors text-sm font-medium"
|
||||||
|
>
|
||||||
|
<Camera size={18} />
|
||||||
|
تغییر تصویر
|
||||||
|
</button>
|
||||||
|
<p className="text-xs text-gray-500">فرمتهای مجاز: JPG, PNG. حداکثر حجم: ۲ مگابایت</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form Fields Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">نام و نام خانوادگی <span className="text-red-500">*</span></label>
|
||||||
|
<input type="text" defaultValue="علی محمدی" required className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all text-gray-800" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">شماره موبایل</label>
|
||||||
|
<input type="text" defaultValue="09123456789" disabled className="w-full px-4 py-2 border border-gray-100 bg-gray-50 text-gray-500 rounded-xl cursor-not-allowed" />
|
||||||
|
<p className="text-xs text-gray-400 mt-1">برای تغییر شماره موبایل با پشتیبانی تماس بگیرید.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">پست الکترونیک</label>
|
||||||
|
<input type="email" defaultValue="ali.m@gmail.com" className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all text-left text-gray-800" dir="ltr" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">کد ملی</label>
|
||||||
|
<input type="text" defaultValue="2280000000" className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all text-gray-800" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">تاریخ تولد</label>
|
||||||
|
<input type="text" placeholder="مثال: ۱۳۷۰/۰۵/۱۲" className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all text-gray-800" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">جنسیت</label>
|
||||||
|
<select className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all appearance-none bg-white text-gray-800">
|
||||||
|
<option value="male">مرد</option>
|
||||||
|
<option value="female">زن</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<div className="flex justify-end pt-4 border-t border-gray-50">
|
||||||
|
<button type="submit" className="flex items-center gap-2 bg-[#1a2332] text-white px-6 py-2.5 rounded-xl hover:bg-[#ffb900] transition-colors font-medium shadow-sm text-sm">
|
||||||
|
<Check size={18} />
|
||||||
|
ثبت تغییرات
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* تنظیمات امنیتی */}
|
||||||
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||||
|
<h2 className="text-xl font-bold text-gray-800 mb-6 border-b border-gray-100 pb-4">تنظیمات امنیتی</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Change Password */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between p-4 border border-gray-100 rounded-xl bg-gray-50 hover:bg-gray-100/50 transition-colors">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<Shield size={20} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-gray-800 text-sm">تغییر رمز عبور</h3>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">آخرین تغییر: ۲ ماه پیش</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className="mt-4 sm:mt-0 flex items-center justify-center px-4 py-2 border border-gray-200 text-gray-700 bg-white rounded-xl hover:bg-gray-50 transition-colors text-sm font-medium shadow-sm">
|
||||||
|
تغییر رمز
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Two-Factor Authentication */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between p-4 border border-gray-100 rounded-xl bg-gray-50 hover:bg-gray-100/50 transition-colors">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-amber-50 text-amber-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<ShieldAlert size={20} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-gray-800 text-sm">احراز هویت دو مرحلهای</h3>
|
||||||
|
<p className="text-xs mt-1 text-gray-500">
|
||||||
|
وضعیت: <span className="text-red-500 font-bold">غیرفعال</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className="mt-4 sm:mt-0 bg-[#1a2332] text-white px-5 py-2 rounded-xl hover:bg-[#ffb900] transition-colors shadow-sm text-sm font-medium">
|
||||||
|
فعالسازی
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Email Notifications */}
|
||||||
|
<div className="flex items-center justify-between p-4 border border-gray-100 rounded-xl bg-gray-50 hover:bg-gray-100/50 transition-colors">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-50 text-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<Mail size={20} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-gray-800 text-sm">اطلاعرسانی ایمیلی</h3>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">دریافت ایمیل برای ورودهای جدید و هشدارهای امنیتی</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Toggle Switch */}
|
||||||
|
<label className="relative inline-flex items-center cursor-pointer">
|
||||||
|
<input type="checkbox" className="sr-only peer" defaultChecked />
|
||||||
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#1a2332]"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { Header } from "@/components/header";
|
|||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import localFont from 'next/font/local';
|
import localFont from 'next/font/local';
|
||||||
import { CartProvider } from "@/components/context/cartcontext";
|
import { CartProvider } from "@/components/context/cartcontext";
|
||||||
|
import { categoryService } from "@/public/src/services/categories/api";
|
||||||
|
|
||||||
const Yekanbakh = localFont({
|
const Yekanbakh = localFont({
|
||||||
src: [
|
src: [
|
||||||
@@ -23,16 +23,19 @@ const Yekanbakh = localFont({
|
|||||||
display: 'swap',
|
display: 'swap',
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
|
||||||
|
const categories = await categoryService.getCategories();
|
||||||
return (
|
return (
|
||||||
<html lang="fa" dir="rtl" className={Yekanbakh.variable}>
|
<html lang="fa" dir="rtl" className={Yekanbakh.variable}>
|
||||||
<body>
|
<body>
|
||||||
<CartProvider>
|
<CartProvider>
|
||||||
<Header/>
|
<Header categories={categories}/>
|
||||||
{children}
|
{children}
|
||||||
</CartProvider>
|
</CartProvider>
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import React, { useState, useEffect, useRef } from 'react';
|
|||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { ShoppingCart, Trash2 } from "lucide-react";
|
|
||||||
|
import { Category } from '@/public/src/types/categories';
|
||||||
|
|
||||||
|
import { ShoppingCart, Trash2, Search, X, ChevronDown } from "lucide-react";
|
||||||
import { useCart } from './context/cartcontext';
|
import { useCart } from './context/cartcontext';
|
||||||
import { Search, X } from 'lucide-react';
|
|
||||||
import { products } from '@/lib/data';
|
import { products } from '@/lib/data';
|
||||||
import '@/public/src/css/header.css';
|
import '@/public/src/css/header.css';
|
||||||
|
|
||||||
@@ -16,13 +18,16 @@ const topBarLinks = [
|
|||||||
|
|
||||||
const mainNavLinks = [
|
const mainNavLinks = [
|
||||||
{ label: "صفحه اصلی", href: "/" },
|
{ label: "صفحه اصلی", href: "/" },
|
||||||
{ label: "خرید بلبرینگ", href: "/products" },
|
{ label: "محصولات", href: "/products" },
|
||||||
{ label: "مقالات", href: "/blog" },
|
{ label: "مقالات", href: "/blog" },
|
||||||
{ label: "درباره ما", href: "/about" },
|
{ label: "درباره ما", href: "/about" },
|
||||||
{ label: "تماس با ما", href: "/contact" },
|
{ label: "تماس با ما", href: "/contact" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Header() {
|
// 1. استخراج دستهبندیهای منحصر به فرد از محصولات
|
||||||
|
const uniqueCategories = Array.from(new Set(products.map(p => p.category)));
|
||||||
|
|
||||||
|
export function Header({ categories }: { categories: Category[] }) {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { cart, removeFromCart } = useCart();
|
const { cart, removeFromCart } = useCart();
|
||||||
@@ -31,8 +36,8 @@ export function Header() {
|
|||||||
const [filteredProducts, setFilteredProducts] = useState<any[]>([]);
|
const [filteredProducts, setFilteredProducts] = useState<any[]>([]);
|
||||||
const searchRef = useRef<HTMLDivElement>(null);
|
const searchRef = useRef<HTMLDivElement>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const safeCategories = categories || [];
|
||||||
// محاسبه قیمت کل برای سبد خرید (اصلاح شده برای حذف ویرگولهای احتمالی)
|
const rootCategories = safeCategories.filter(cat => cat.parent === null);
|
||||||
const parsePrice = (priceStr?: string | null) => {
|
const parsePrice = (priceStr?: string | null) => {
|
||||||
if (!priceStr) return 0;
|
if (!priceStr) return 0;
|
||||||
return Number(priceStr.toString().replace(/,/g, ''));
|
return Number(priceStr.toString().replace(/,/g, ''));
|
||||||
@@ -40,13 +45,6 @@ export function Header() {
|
|||||||
|
|
||||||
const totalPrice = cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
|
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(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
|
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
|
||||||
@@ -57,7 +55,6 @@ export function Header() {
|
|||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// فیلتر کردن محصولات در لحظه تایپ
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchTerm.trim().length > 1) {
|
if (searchTerm.trim().length > 1) {
|
||||||
const results = products.filter(p =>
|
const results = products.filter(p =>
|
||||||
@@ -65,7 +62,7 @@ export function Header() {
|
|||||||
p.brand?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
p.brand?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
p.d?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
p.d?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
p.l?.toLowerCase().includes(searchTerm.toLowerCase())
|
p.l?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
).slice(0, 6); // نمایش فقط ۶ نتیجه اول در دراپداون
|
).slice(0, 6);
|
||||||
setFilteredProducts(results);
|
setFilteredProducts(results);
|
||||||
setShowResults(true);
|
setShowResults(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -82,10 +79,12 @@ export function Header() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ... (بقیه کدهای شما از اینجا تا بخش <nav> دست نخورده باقی میماند)
|
||||||
|
// ... (بخشهای top bar, main bar, search, cart, etc. بدون تغییر هستند)
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<div>
|
||||||
|
{/* ... کدهای مربوط به mobile slider, top bar, main bar ... */}
|
||||||
{/* overlay */}
|
{/* overlay */}
|
||||||
{menuOpen && (
|
{menuOpen && (
|
||||||
<div
|
<div
|
||||||
@@ -244,48 +243,30 @@ export function Header() {
|
|||||||
<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="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">
|
<div className="p-2 flex flex-col">
|
||||||
{filteredProducts.map((product) => {
|
{filteredProducts.map((product) => {
|
||||||
// تبدیل نام محصول به اسلاگ (جایگزینی فاصلهها با خط تیره)
|
|
||||||
const productSlug = product.title.replace(/\s+/g, '-');
|
const productSlug = product.title.replace(/\s+/g, '-');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={product.id}
|
key={product.id}
|
||||||
// اصلاح URL برای تطابق با /products/[id]/[slug]
|
|
||||||
href={`/products/${product.id}/${encodeURIComponent(productSlug)}`}
|
href={`/products/${product.id}/${encodeURIComponent(productSlug)}`}
|
||||||
onClick={() => setShowResults(false)}
|
onClick={() => setShowResults(false)}
|
||||||
className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-xl transition group"
|
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">
|
<div className="bg-gray-100 p-1 rounded-lg shrink-0 w-10 h-10 flex items-center justify-center">
|
||||||
<Image
|
<Image src={product.image} alt={product.title} width={32} height={32} className="object-contain" />
|
||||||
src={product.image}
|
|
||||||
alt={product.title}
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
className="object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0 text-right">
|
<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">
|
<p className="text-[11px] font-bold text-gray-800 truncate group-hover:text-blue-600 transition-colors">{product.title}</p>
|
||||||
{product.title}
|
|
||||||
</p>
|
|
||||||
<p className="text-[9px] text-gray-500">{product.brand}</p>
|
<p className="text-[9px] text-gray-500">{product.brand}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] font-bold text-gray-700 bg-gray-50 px-2 py-1 rounded-md shrink-0">
|
<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')} ت` : 'استعلام'}
|
||||||
{product.price
|
|
||||||
? `${Number(product.price.toString().replace(/,/g, '')).toLocaleString('fa-IR')} ت`
|
|
||||||
: 'استعلام'}
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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">
|
||||||
<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}"
|
مشاهده تمام نتایج برای "{searchTerm}"
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -300,22 +281,16 @@ export function Header() {
|
|||||||
|
|
||||||
<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">
|
<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} />
|
<ShoppingCart className="w-4 h-4 text-gray-500" strokeWidth={1.8} />
|
||||||
|
|
||||||
{/* بج (Badge) تعداد محصولات */}
|
|
||||||
{cart.length > 0 && (
|
{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">
|
<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)}
|
{cart.reduce((total, item) => total + item.quantity, 0)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* یک پل نامرئی برای جلوگیری از قطع شدن هاور */}
|
|
||||||
<div className="absolute top-full left-0 w-full h-3 bg-transparent hidden group-hover:block"></div>
|
<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">
|
<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 ? (
|
{cart.length === 0 ? (
|
||||||
<div className="p-6 text-center text-sm text-gray-500 flex flex-col items-center gap-2">
|
<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" />
|
<ShoppingCart className="w-8 h-8 text-gray-200" />
|
||||||
@@ -325,46 +300,25 @@ export function Header() {
|
|||||||
<>
|
<>
|
||||||
<div className="flex justify-between items-center p-4 border-b border-gray-100 bg-gray-50/50">
|
<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>
|
<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 href="/cart" className="text-xs text-blue-500 hover:text-blue-700 font-medium transition">مشاهده سبد خرید</Link>
|
||||||
مشاهده سبد خرید
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-h-64 overflow-y-auto p-2 flex flex-col gap-1 custom-scrollbar">
|
<div className="max-h-64 overflow-y-auto p-2 flex flex-col gap-1 custom-scrollbar">
|
||||||
{cart.slice(0, 3).map((item) => {
|
{cart.slice(0, 3).map((item) => {
|
||||||
// محاسبه قیمت بر اساس تعداد (قیمت واحد × تعداد)
|
const itemTotal = item.price ? (Number(item.price.toString().replace(/,/g, '')) * item.quantity).toLocaleString('fa-IR') : null;
|
||||||
const itemTotal = item.price
|
|
||||||
? (Number(item.price.toString().replace(/,/g, '')) * item.quantity).toLocaleString('fa-IR')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.id} className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-xl transition group/item relative">
|
<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">
|
<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" />
|
<Image src={item.image} alt={item.title} width={40} height={40} className="object-contain w-10 h-10" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-xs font-semibold text-gray-800 truncate">{item.title}</h4>
|
<h4 className="text-xs font-semibold text-gray-800 truncate">{item.title}</h4>
|
||||||
|
|
||||||
{/* بخش جدید: نمایش قیمت و تعداد در کنار هم */}
|
|
||||||
<div className="flex items-center gap-2 mt-1">
|
<div className="flex items-center gap-2 mt-1">
|
||||||
<span className="text-[10px] text-gray-500 font-medium">
|
<span className="text-[10px] text-gray-500 font-medium">{itemTotal ? `${itemTotal} تومان` : 'استعلام'}</span>
|
||||||
{itemTotal ? `${itemTotal} تومان` : 'استعلام'}
|
<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>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => { e.preventDefault(); e.stopPropagation(); removeFromCart(item.id); }}
|
||||||
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"
|
className="text-gray-300 hover:text-red-500 transition p-1.5 opacity-0 group-hover/item:opacity-100"
|
||||||
title="حذف"
|
title="حذف"
|
||||||
>
|
>
|
||||||
@@ -373,49 +327,70 @@ export function Header() {
|
|||||||
</div>
|
</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>)}
|
||||||
{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>
|
||||||
|
|
||||||
|
|
||||||
<div className="p-4 bg-white border-t border-gray-100">
|
<div className="p-4 bg-white border-t border-gray-100">
|
||||||
<div className="flex justify-between items-center mb-3">
|
<div className="flex justify-between items-center mb-3">
|
||||||
<span className="text-xs text-gray-500">مبلغ قابل پرداخت:</span>
|
<span className="text-xs text-gray-500">مبلغ قابل پرداخت:</span>
|
||||||
<span className="text-sm font-bold text-gray-800">
|
<span className="text-sm font-bold text-gray-800">{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')} تومان` : 'استعلام'}</span>
|
||||||
{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')} تومان` : 'استعلام'}
|
|
||||||
</span>
|
|
||||||
</div>
|
</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 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>
|
||||||
ثبت سفارش
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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">
|
<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">
|
<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>
|
||||||
<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>
|
<span>ورود / عضویت</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* nav */}
|
{/* nav -- این بخش تغییر کرده است */}
|
||||||
<div className='justify-center flex'>
|
<div className='justify-center flex'>
|
||||||
<div className='flex max-w-6xl container mx-auto px-4 justify-between py-2 items-center'>
|
<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">
|
<nav className="hidden md:flex items-center 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 href="/" className="hover:text-blue-600 transition duration-200 font-medium">صفحه اصلی</Link>
|
||||||
|
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="flex items-center gap-1 cursor-pointer hover:text-blue-600 transition duration-200 font-medium">
|
||||||
|
<span>دسته بندی محصولات</span>
|
||||||
|
<ChevronDown size={14} className="transition-transform duration-200 group-hover:rotate-180" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute top-full right-0 w-full h-4 bg-transparent hidden group-hover:block"></div>
|
||||||
|
|
||||||
|
<div className="absolute top-[calc(100%+16px)] right-0 w-56 bg-white border border-gray-200 rounded-xl shadow-lg p-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 flex flex-col gap-1 z-50">
|
||||||
|
|
||||||
|
{rootCategories.length > 0 ? (
|
||||||
|
rootCategories.map((category) => (
|
||||||
|
<Link
|
||||||
|
key={category.id}
|
||||||
|
href={`/category/${category.slug}`}
|
||||||
|
className="block w-full text-right px-3 py-2 text-xs font-medium text-gray-600 hover:bg-gray-100 hover:text-blue-600 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center text-xs text-gray-500 py-2">
|
||||||
|
در حال بارگذاری...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* نمایش بقیه لینکها */}
|
||||||
|
{mainNavLinks.filter(link => link.href !== "/").map((link) => (
|
||||||
|
<Link key={link.label} href={link.href} className="hover:text-blue-600 transition duration-200 font-medium">
|
||||||
{link.label}
|
{link.label}
|
||||||
</a>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { Phone, Plus, ShoppingCart, Minus, Trash2 } from "lucide-react";
|
import { Phone, Plus, ShoppingCart, Minus, Trash2 } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useCart, Product } from "./context/cartcontext"; // مسیر ایمپورت را با پروژه خود چک کنید
|
import { useCart, Product } from "./context/cartcontext";
|
||||||
|
|
||||||
interface ProductCardProps {
|
interface ProductCardProps {
|
||||||
product: Product;
|
product: Product;
|
||||||
@@ -77,50 +77,59 @@ export default function ProductCard({ product }: ProductCardProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* بخش قیمت و دکمه خرید */}
|
||||||
{product.price ? (
|
{product.price ? (
|
||||||
<div className="flex justify-between mt-3 items-center min-h-[32px]">
|
<div className="flex justify-between mt-3 items-center min-h-[32px]">
|
||||||
<span className="text-[0.85em] font-bold">
|
<span className="text-[0.85em] font-bold text-gray-800">
|
||||||
{formattedPrice} تومان
|
{formattedPrice} تومان
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{quantity > 0 ? (
|
{/* بررسی موجودی برای نمایش دکمه سبد خرید */}
|
||||||
<div
|
{product.stock ? (
|
||||||
className="flex items-center gap-3 bg-white border border-gray-200 rounded-lg py-1 px-2 shadow-sm"
|
quantity > 0 ? (
|
||||||
onClick={(e) => {
|
<div
|
||||||
e.preventDefault();
|
className="flex items-center gap-3 bg-white border border-gray-200 rounded-lg py-1 px-2 shadow-sm"
|
||||||
e.stopPropagation();
|
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
|
<button
|
||||||
onClick={handleIncrease}
|
onClick={handleIncrease}
|
||||||
className="text-gray-500 hover:text-green-600 transition-colors"
|
className="rounded-lg border border-[#e6e6e6] bg-gray-50 flex items-center p-2 text-gray-600 hover:bg-gray-200 transition-colors"
|
||||||
|
title="افزودن به سبد خرید"
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<ShoppingCart size={16} />
|
||||||
</button>
|
</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}
|
<span className="text-[10px] text-red-500 font-medium bg-red-50 px-2 py-1 rounded-md">
|
||||||
className="rounded-lg border border-[#e6e6e6] bg-gray-50 flex items-center p-2 text-gray-600 hover:bg-gray-200 transition-colors"
|
عدم موجودی
|
||||||
title="افزودن به سبد خرید"
|
</span>
|
||||||
>
|
|
||||||
<ShoppingCart size={16} />
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="flex justify-between mt-3 text-[0.85em] font-bold items-center gap-1 min-h-[32px]">
|
<span className="flex justify-between mt-3 text-[0.85em] font-bold items-center gap-1 min-h-[32px] text-gray-700">
|
||||||
استعلام
|
استعلام
|
||||||
<Phone className="mb-1" size={13} />
|
<Phone className="mb-1" size={13} />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
// export const slugify = (text: string) => {
|
|
||||||
// return text
|
|
||||||
// .toString()
|
|
||||||
// .trim()
|
|
||||||
// .replace(/\s+/g, "-")
|
|
||||||
// .replace(/[^\u0600-\u06FFa-zA-Z0-9\-]/g, "");
|
|
||||||
// };
|
|
||||||
|
|
||||||
32
public/src/services/categories/api.tsx
Normal file
32
public/src/services/categories/api.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// src/services/categories/categories.service.ts
|
||||||
|
import { ApiResponse } from '../../types/global';
|
||||||
|
import { Category } from '../../types/categories';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://parsshop-back.mugit.ir';
|
||||||
|
|
||||||
|
export const categoryService = {
|
||||||
|
/**
|
||||||
|
* دریافت تمام دستهبندیها با استفاده از کش Next.js
|
||||||
|
*/
|
||||||
|
getCategories: async (): Promise<Category[]> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BASE_URL}/api/categories`, {
|
||||||
|
// بهینهترین حالت: دادهها تا ۱ ساعت کش میشوند (3600 ثانیه)
|
||||||
|
// اگر تغییرات شما لحظهای است، این عدد را کمتر کنید یا از تگهای On-Demand Revalidation استفاده کنید
|
||||||
|
next: { revalidate: 3600 },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: ApiResponse<Category[]> = await response.json();
|
||||||
|
|
||||||
|
// ما فقط به آرایه data نیاز داریم
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching categories:', error);
|
||||||
|
return []; // در صورت خطا، یک آرایه خالی برمیگردانیم تا صفحه کرش نکند
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
49
public/src/services/products/api.tsx
Normal file
49
public/src/services/products/api.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { ProductsResponse } from "../../types/product";
|
||||||
|
|
||||||
|
const BASE_URL = "https://parsshop-back.mugit.ir/api";
|
||||||
|
|
||||||
|
export const productService = {
|
||||||
|
/**
|
||||||
|
* دریافت لیست تمام محصولات
|
||||||
|
* @param page شماره صفحه (پیشفرض: 1)
|
||||||
|
* @param limit تعداد در هر صفحه (پیشفرض: 20)
|
||||||
|
* @returns ProductsResponse
|
||||||
|
*/
|
||||||
|
async getProducts(page: number = 1, limit: number = 20): Promise<ProductsResponse> {
|
||||||
|
try {
|
||||||
|
// استفاده از URLSearchParams برای مدیریت تمیزتر کوئری پارامترها (در صورت نیاز)
|
||||||
|
const url = new URL(`${BASE_URL}/products`);
|
||||||
|
// اگر API شما از کوئری پشتیبانی میکند، میتوانید به این شکل ارسال کنید:
|
||||||
|
// url.searchParams.append('page', page.toString());
|
||||||
|
// url.searchParams.append('limit', limit.toString());
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
// هدرهای ضروری بر اساس مستندات شما (معمولاً کلاینت فقط Accept را میفرستد)
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
// در Next.js برای کشینگ میتوانید از گزینههای زیر استفاده کنید:
|
||||||
|
// cache: 'no-store', // اگر دادهها مدام تغییر میکنند (مثل موجودی)
|
||||||
|
next: { revalidate: 60 } // کش کردن دادهها برای 60 ثانیه (مناسب برای محصولات)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: ProductsResponse = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching products:", error);
|
||||||
|
// مدیریت خطا (بسته به سیاست پروژه شما میتواند throw شود یا مقدار پیشفرض برگردد)
|
||||||
|
throw new Error("مشکلی در دریافت اطلاعات محصولات به وجود آمد.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت محصول بر اساس Slug یا ID (برای استفاده در صفحات سینگل پروداکت در آینده)
|
||||||
|
*/
|
||||||
|
// async getProductBySlug(slug: string) { ... }
|
||||||
|
};
|
||||||
14
public/src/types/categories.tsx
Normal file
14
public/src/types/categories.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// src/services/categories/categories.types.ts
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
imageUrl: string | null;
|
||||||
|
type: string; // مثل "Automotive" یا "Industrial"
|
||||||
|
parent: Category | null; // استفاده بازگشتی برای والد
|
||||||
|
children: Category[]; // استفاده بازگشتی برای فرزندان
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
10
public/src/types/global.tsx
Normal file
10
public/src/types/global.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// src/types/global.types.ts
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
success: boolean;
|
||||||
|
statusCode: number;
|
||||||
|
path: string;
|
||||||
|
timestamp: string;
|
||||||
|
data: T; // دیتای اصلی که در هر API فرق میکند اینجا قرار میگیرد
|
||||||
|
}
|
||||||
|
|
||||||
49
public/src/types/product.tsx
Normal file
49
public/src/types/product.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// ساختار هر محصول (Product) بر اساس بادی ریسپانس
|
||||||
|
export interface Product {
|
||||||
|
id: string;
|
||||||
|
sku: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
technicalCode: string;
|
||||||
|
brand: string;
|
||||||
|
brandEntity: any | null; // اگر ساختار مشخصی دارد، بجای any از تایپ مناسب استفاده کنید
|
||||||
|
basePriceUSD: number;
|
||||||
|
salePriceUSD: number;
|
||||||
|
stock: number;
|
||||||
|
featured: boolean;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
mainImageUrl: string | null;
|
||||||
|
threeDModelUrl: string | null;
|
||||||
|
imageGalleryUrls: string[];
|
||||||
|
tags: string[];
|
||||||
|
averageRating: number;
|
||||||
|
reviewsCount: number;
|
||||||
|
primaryCategory: any | null;
|
||||||
|
categories: any[];
|
||||||
|
meta: any | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
brandInfo: any | null;
|
||||||
|
attributes: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ساختار متادیتا (Paginagtion)
|
||||||
|
export interface PaginationMeta {
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ساختار نهایی ریسپانس (Response Body)
|
||||||
|
export interface ProductsResponse {
|
||||||
|
success: boolean;
|
||||||
|
statusCode: number;
|
||||||
|
path: string;
|
||||||
|
timestamp: string;
|
||||||
|
data: {
|
||||||
|
items: Product[];
|
||||||
|
meta: PaginationMeta;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user