add cart API's (add , delete , get)

This commit is contained in:
haniyeroozmand
2026-04-07 10:14:56 +03:30
parent 5504e20948
commit 8be715b34b
14 changed files with 528 additions and 185 deletions

View File

@@ -4,7 +4,7 @@ import { useCart } from "@/components/context/cartcontext";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react"; // اضافه شدن useState
import { useState, useEffect } from "react"; // اضافه شدن useEffect
import {
Trash2,
ShoppingBag,
@@ -16,40 +16,140 @@ import {
CreditCard
} from "lucide-react";
// مسیر ایمپورت کامپوننت Notlogin را بر اساس ساختار پوشه‌بندی خود تنظیم کنید
import NotLogin from "@/components/Notlogin";
import { getCartApi } from "@/public/src/services/cart/api"; // ایمپورت API
import { clearServerCartApi } from "@/public/src/services/cart/api";
export default function CartPage() {
const { cart, clearCart, addToCart, decreaseQuantity } = useCart();
const {
cart,
clearCart,
addToCart,
decreaseQuantity,
} = useCart();
// const handleClearAll = async () => {
// // ۱. پاک کردن استیت لوکال
// clearCart();
// const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
// if (token) {
// try {
// // ۲. پاک کردن از سرور
// await clearServerCartApi();
// // ۳. آپدیت کردن صفحه برای دریافت دیتای جدید سرور (سبد خالی)
// router.refresh();
// } catch (error) {
// console.error(error);
// }
// }
// };
const router = useRouter();
// استیت برای مدیریت نمایش مودال/کامپوننت لاگین نشده‌ها
const [showNotLogin, setShowNotLogin] = useState(false);
// --- استیت‌های مربوط به دریافت اطلاعات سرور ---
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [serverCartItems, setServerCartItems] = useState<any[]>([]);
const [serverSummary, setServerSummary] = useState<any>(null);
const [isLoading, setIsLoading] = useState(true);
// --- منطق اصلاح شده دکمه حذف همه ---
const handleClearAll = async () => {
// ۱. پاک کردن استیت‌های لوکال و استیت‌های همین صفحه
clearCart();
setServerCartItems([]);
setServerSummary(null);
// ۲. ارسال دستور مستقیم به هدر برای خالی شدن درجا (بدون درنگ)
window.dispatchEvent(new Event('cartCleared'));
// ۳. فراخوانی API برای پاک کردن دیتابیس در پس‌زمینه
const token = typeof window !== 'undefined' ? localStorage.getItem('accessToken') : null;
if (token) {
try {
await clearServerCartApi(); // متد DELETE
// اینجا دیگر نیازی به cartUpdated نیست چون هدر را به صورت دستی خالی کردیم
} catch (error) {
console.error("خطا در پاک کردن سبد خرید سرور:", error);
}
}
};
// --- useEffect برای بررسی لاگین و دریافت دیتای سرور ---
useEffect(() => {
const fetchServerCart = async () => {
const token = localStorage.getItem('accessToken');
if (token) {
setIsLoggedIn(true);
try {
const data = await getCartApi();
if (data) {
setServerCartItems(data.items || []);
setServerSummary(data.summary || null);
}
} catch (error) {
console.error("خطا در دریافت اطلاعات سبد خرید:", error);
}
} else {
setIsLoggedIn(false);
}
setIsLoading(false);
};
fetchServerCart();
}, []);
// بررسی لاگین کاربر
const handleCheckoutNavigation = () => {
const token = localStorage.getItem('accessToken');
if (token) {
// اگر لاگین بود برود به چک اوت
router.push('/checkout');
} else {
// اگر لاگین نبود، کامپوننت Notlogin نمایش داده شود
setShowNotLogin(true);
}
};
// تبدیل رشته قیمت به عدد
const parsePrice = (priceStr?: number | null) => {
const parsePrice = (priceStr?: number | 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);
// --- منطق یکپارچه‌سازی: اگر لاگین بود دیتای سرور، در غیر این‌صورت دیتای لوکال ---
const displayCart = isLoggedIn
? serverCartItems.map(item => ({
id: item.product?.id || item.productId,
title: item.product?.title || "بدون نام",
brand: item.product?.brand || "متفرقه",
price: item.unitPrice || item.product?.price || 0,
quantity: item.quantity || 1,
image: item.product?.mainImageUrl || item.product?.image || "/placeholder.png"
}))
: cart;
// دیزاین حالت سبد خرید خالی
if (cart.length === 0) {
// محاسبه قیمت کل و تعداد کل
const totalPrice = isLoggedIn && serverSummary
? serverSummary.totalPrice || serverSummary.total || 0
: cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
const totalItems = isLoggedIn && serverSummary
? serverSummary.totalQuantity || serverSummary.itemsCount || 0
: cart.reduce((total, item) => total + item.quantity, 0);
// --- جلوگیری از پرش تصویر قبل از لود شدن دیتا ---
if (isLoading) {
return <div className="min-h-screen bg-gray-50/50 flex items-center justify-center">در حال بارگذاری...</div>;
}
// دیزاین حالت سبد خرید خالی (تغییر cart.length به displayCart.length)
if (displayCart.length === 0) {
return (
<div className="min-h-screen bg-gray-50/50 flex flex-col items-center justify-center p-6">
<div className="bg-white p-12 rounded-[3rem] shadow-sm border border-gray-100 flex flex-col items-center max-w-md w-full text-center">
@@ -61,7 +161,7 @@ export default function CartPage() {
<p className="text-gray-500 mb-10 leading-relaxed text-sm">
هنوز هیچ محصولی به سبد خرید خود اضافه نکردهاید. برای مشاهده محصولات به صفحه اصلی برگردید.
</p>
<Link href="/" className="w-full bg-[#ffb900] hover:bg-[#e5a600] text-black font-bold text-lg px-8 py-4 rounded-2xl transition-all shadow-[0_4px_20px_rgba(255,185,0,0.3)] hover:shadow-[0_6px_25px_rgba(255,185,0,0.4)] flex items-center justify-center gap-2">
<Link href="/products" className="w-full bg-[#ffb900] hover:bg-[#e5a600] text-black font-bold text-lg px-8 py-4 rounded-2xl transition-all shadow-[0_4px_20px_rgba(255,185,0,0.3)] hover:shadow-[0_6px_25px_rgba(255,185,0,0.4)] flex items-center justify-center gap-2">
بازگشت به فروشگاه
<ChevronLeft size={20} />
</Link>
@@ -72,11 +172,9 @@ export default function CartPage() {
return (
<main className="bg-gray-50/30 min-h-screen pb-20">
{/* رندر کردن کامپوننت Notlogin به صورت شرطی */}
{/* اگر نیاز است که کاربر بتواند آن را ببندد، پراپ onClose را به آن پاس بدهید */}
{showNotLogin && (
<NotLogin
buttonText="بازگشت به سبد خرید"
<NotLogin
buttonText="بازگشت به سبد خرید"
onClose={() => setShowNotLogin(false)} />
)}
@@ -106,7 +204,6 @@ export default function CartPage() {
<span className="text-[10px] sm:text-sm font-bold text-[#1A2332] text-center">سبد خرید</span>
</Link>
{/* کلیک روی آیکون اطلاعات ارسال */}
<div onClick={handleCheckoutNavigation} className="flex flex-col items-center w-1/3 cursor-pointer group">
<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] group-hover:border-[#ffb900] transition-colors">
<Truck className="w-4 h-4 sm:w-5 sm:h-5" strokeWidth={2} />
@@ -130,14 +227,15 @@ export default function CartPage() {
<div className="bg-white rounded-[1rem] p-4 md:p-8 shadow-sm ">
<div className="flex justify-between items-center mb-6 pb-6 border-b border-gray-100">
<h2 className="text-base md:text-lg font-bold text-gray-800">محصولات انتخاب شده</h2>
<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">
<button onClick={handleClearAll} className="text-xs cursor-pointer 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} />
حذف همه
</button>
</div>
<div className="flex flex-col gap-6">
{cart.map((item) => {
{/* حلقه مپ روی displayCart اعمال شده است */}
{displayCart.map((item) => {
const itemTotal = parsePrice(item.price) * item.quantity;
return (
<div key={item.id} className="group flex flex-col sm:flex-row gap-4 sm:gap-6 items-start sm:items-center border-b border-gray-50 pb-6 last:border-0 last:pb-0">
@@ -153,7 +251,7 @@ export default function CartPage() {
{item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'}
</span>
<div className="flex items-center gap-1 bg-gray-50 border border-gray-200 rounded-full p-1 shadow-sm">
<button onClick={() => addToCart(item)} className="w-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">
<button onClick={() => addToCart(item as any)} 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={14} strokeWidth={2.5} />
</button>
<span className="text-xs md:text-sm font-bold text-gray-800 w-6 text-center">{item.quantity}</span>
@@ -191,10 +289,9 @@ export default function CartPage() {
</span>
</div>
{/* دکمه تایید و ادامه */}
<button
onClick={handleCheckoutNavigation}
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"
className="w-full bg-[#ffb900] cursor-pointer 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} />

View File

@@ -268,14 +268,13 @@ export default function Home() {
</div>
</div>
{/* 4. نمایش وضعیت لودینگ یا لیست محصولات */}
{loading ? (
<div className="flex justify-center py-10">
<p>در حال بارگذاری محصولات...</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{products.slice(-4).map((product: any) => (
{products.slice(0,4).map((product: any) => (
<ProductCard key={product.id} product={product} />
))}
</div>

View File

@@ -3,6 +3,7 @@ import { notFound } from 'next/navigation';
import Image from "next/image";
import ProductCartAction from '@/components/cartaction';
import { getProductBySlug } from "@/public/src/services/products/api";
import Link from 'next/link';
interface PageProps {
params: Promise<{ slug: string }>;
@@ -10,7 +11,7 @@ interface PageProps {
export default async function SingleProductPage({ params }: PageProps) {
const resolvedParams = await params;
const resolvedParams = await params;
const slug = resolvedParams.slug;
console.log("👉 Extracted slug from URL:", slug);
@@ -47,18 +48,18 @@ export default async function SingleProductPage({ params }: PageProps) {
return (
<div className="bg-[#f8f9fc] min-h-screen py-8" dir="rtl">
<div className="mx-auto px-4 lg:px-8 container max-w-6xl">
<div className="mx-auto px-4 container max-w-6xl">
<ScrollToTop />
{/* مسیر راهنما */}
<div className="text-sm text-gray-500 mb-6 flex items-center gap-2">
<span>خانه</span> &gt; <span>محصولات</span> &gt; <span className="text-gray-800 font-semibold">{product.title}</span>
<Link href={'/'}>خانه</Link> &gt; <Link href={'/products'}>محصولات</Link> &gt; <span className="text-gray-800 font-semibold">{product.title}</span>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8">
{/* بخش 1: معرفی محصول */}
<div className="lg:col-span-8 order-1 lg:order-none">
<div className="lg:col-span-8 col-span-2 order-1 lg:order-none">
<div className="bg-white rounded-3xl shadow-sm p-6 md:p-8 flex flex-col md:flex-row gap-8 items-center border border-gray-100">
{/* باکس تصویر */}
<div className="w-full md:w-[52%] h-72 rounded-2xl bg-[linear-gradient(135deg,#f8f9fc_0%,#e2e8f0_100%)] flex items-center justify-center p-6 relative">
@@ -80,7 +81,7 @@ export default async function SingleProductPage({ params }: PageProps) {
<div className="inline-block px-3 py-1 bg-gray-100 text-gray-700 rounded-lg text-sm font-bold mb-4 tracking-wider">
{product.brand}
</div>
<h1 className="text-3xl font-bold text-gray-800 mb-6 leading-relaxed">
<h1 className="text-2xl font-bold text-gray-800 mb-6 leading-relaxed">
{product.title}
</h1>
@@ -89,20 +90,20 @@ export default async function SingleProductPage({ params }: PageProps) {
</p>
<div className="grid grid-cols-2 gap-4">
{/* رندر داینامیک ۳ ویژگی اول از API */}
{product.attributes?.slice(0, 3).map((attr: any) => (
<div key={attr.id} className="bg-gray-50 p-4 rounded-2xl flex flex-col items-center justify-center text-center border border-gray-100">
<span className="text-gray-500 text-sm mb-2">{attr.name}</span>
<span className="font-bold text-gray-800 text-sm" dir="ltr">{attr.valueText || '-'}</span>
</div>
))}
{/* رندر داینامیک ۳ ویژگی اول از API */}
{product.attributes?.slice(0, 2).map((attr: any) => (
<div key={attr.id} className="bg-gray-50 p-4 rounded-2xl flex flex-col items-center justify-center text-center border border-gray-100">
<span className="text-gray-500 text-sm mb-2">{attr.name}</span>
<span className="font-bold text-gray-800 text-xs" dir="ltr">{attr.valueText || '-'}</span>
</div>
))}
{/* آیتم چهارم: دسته‌بندی (برای حفظ ظاهر گرید ۴ تایی) */}
<div className="bg-gray-50 p-4 rounded-2xl flex flex-col items-center justify-center text-center border border-gray-100">
<span className="text-gray-500 text-sm mb-2">دستهبندی</span>
<span className="font-bold text-gray-800 text-sm" dir="ltr">{product.primaryCategory?.name || '-'}</span>
</div>
</div>
{/* آیتم چهارم: دسته‌بندی (برای حفظ ظاهر گرید ۴ تایی) */}
{/* <div className="bg-gray-50 p-4 rounded-2xl flex flex-col items-center justify-center text-center border border-gray-100">
<span className="text-gray-500 text-sm mb-2">دسته‌بندی</span>
<span className="font-bold text-gray-800 text-xs" dir="ltr">{product.primaryCategory?.name || '-'}</span>
</div> */}
</div>
</div>
</div>
@@ -110,7 +111,7 @@ export default async function SingleProductPage({ params }: PageProps) {
{/* بخش 2: سایدبار و دکمه خرید */}
<div className="lg:col-span-4 col-span-2 lg:row-span-2 order-2 lg:order-none relative h-full">
<div className="flex flex-col gap-4 lg:sticky lg:top-[20px] pb-8">
<div className="flex flex-col gap-4 lg:sticky lg:top-[20px]">
<div className="bg-white rounded-[2rem] shadow-[0_4px_20px_-10px_rgba(0,0,0,0.05)] p-5 border border-gray-100 text-center">
{hasStock ? (
@@ -180,7 +181,7 @@ export default async function SingleProductPage({ params }: PageProps) {
</div>
{/* بخش 3: مشخصات ابعادی */}
<div className="lg:col-span-8 order-3 lg:order-none">
<div className="lg:col-span-8 col-span-2 order-3 lg:order-none">
<div className="bg-white rounded-3xl shadow-sm p-6 md:p-8 border border-gray-100">
<div className="flex justify-between items-center mb-8 border-b border-gray-100 pb-4">
<h2 className="text-xl font-bold text-gray-800">مشخصات ابعادی دقیق</h2>
@@ -206,29 +207,16 @@ export default async function SingleProductPage({ params }: PageProps) {
<div className="w-full md:w-1/2">
<table className="w-full text-right text-sm">
<tbody>
<tr className="border-b border-gray-50">
<td className="py-3 text-gray-500">قطر داخلی ($d$)</td>
<td className="py-3 font-bold text-gray-800 text-left" dir="ltr">{getAttribute('قطر داخلی')}</td>
</tr>
<tr className="border-b border-gray-50">
<td className="py-3 text-gray-500">قطر خارجی ($D$)</td>
<td className="py-3 font-bold text-gray-800 text-left" dir="ltr">{getAttribute('قطر خارجی')}</td>
</tr>
<tr className="border-b border-gray-50">
<td className="py-3 text-gray-500">پهنا ($B$)</td>
<td className="py-3 font-bold text-gray-800 text-left" dir="ltr">{getAttribute('پهنا')}</td>
</tr>
<tr className="border-b border-gray-50">
<td className="py-3 text-gray-500">وزن خالص</td>
<td className="py-3 font-bold text-gray-800 text-left" dir="ltr">{getAttribute('وزن')}</td>
</tr>
<tr className="border-b border-gray-50">
<td className="py-3 text-gray-500">جنس قفسه</td>
<td className="py-3 font-bold text-gray-800 text-left" dir="ltr">{getAttribute('جنس قفسه')}</td>
</tr>
{product.attributes?.map((attr: any) => (
<tr key={attr.id}>
<td className="py-3 text-[0.9em] text-gray-500">{attr.name}:</td>
<td className="py-3 font-bold text-[0.9em] text-gray-800 text-left" dir="ltr">{attr.valueText || '-'}</td>
</tr>
))}
<tr>
<td className="py-3 text-gray-500">کد بینالمللی</td>
<td className="py-3 font-bold text-gray-800 text-left" dir="ltr">{product.technicalCode}</td>
<td className="py-3 text-[0.9em] text-gray-500">کد بینالمللی:</td>
<td className="py-3 font-bold text-[0.9em] text-gray-800 text-left" dir="ltr">{product.technicalCode}</td>
</tr>
</tbody>
</table>

View File

@@ -26,3 +26,4 @@ export default async function ProductsPage() {
</div>
);
}