Auth Modal / All Cart API's Done and Local storage Logic handle / Add cart Control button

This commit is contained in:
haniyeroozmand
2026-04-08 22:43:40 +03:30
parent 8be715b34b
commit 411109ee7e
8 changed files with 959 additions and 291 deletions

View File

@@ -4,52 +4,27 @@ import { useCart } from "@/components/context/cartcontext";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState, useEffect } from "react"; // اضافه شدن useEffect
import { useState, useEffect, useRef } from "react";
import {
Trash2,
ShoppingBag,
ChevronLeft,
Plus,
Minus,
ShieldCheck,
Truck,
CreditCard
} from "lucide-react";
import NotLogin from "@/components/Notlogin";
import { getCartApi } from "@/public/src/services/cart/api"; // ایمپورت API
import { clearServerCartApi } from "@/public/src/services/cart/api";
import { getCartApi, addToCartApi, clearServerCartApi } from "@/public/src/services/cart/api";
import CartControls from "@/components/CartControls";
export default function CartPage() {
const {
cart,
clearCart,
addToCart,
decreaseQuantity,
const {
cart,
clearCart,
} = 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);
// --- استیت‌های مربوط به دریافت اطلاعات سرور ---
@@ -58,35 +33,84 @@ export default function CartPage() {
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) {
const handleClearAll = async () => {
clearCart();
setServerCartItems([]);
setServerSummary(null);
const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (token) {
try {
await clearServerCartApi();
window.dispatchEvent(new Event('cartUpdated'));
} catch (error) {
console.error("خطا در پاک کردن سبد خرید سرور:", error);
}
}
};
// ۱. ایجاد قفل برای جلوگیری از اجرای تکراری
const isSyncingRef = useRef(false);
const hasFetchedRef = useRef(false);
// --- منطق ادغام (Merge) و دریافت اطلاعات سرور ---
useEffect(() => {
const fetchAndMergeCart = async () => {
const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (!token) {
setIsLoggedIn(false);
setIsLoading(false);
return;
}
setIsLoggedIn(true);
// ۲. بررسی قفل: اگر توکن داریم، سبد لوکال پر است و قبلا در حال سینک نبودیم
if (cart.length > 0 && !isSyncingRef.current) {
isSyncingRef.current = true;
setIsLoading(true);
try {
await clearServerCartApi(); // متد DELETE
// اینجا دیگر نیازی به cartUpdated نیست چون هدر را به صورت دستی خالی کردیم
await Promise.all(
cart.map(item => addToCartApi(item.id, item.quantity))
);
clearCart();
window.dispatchEvent(new Event('cartUpdated'));
} catch (error) {
console.error("خطا در پاک کردن سبد خرید سرور:", error);
console.error("خطا در انتقال محصولات لوکال به سرور:", error);
}
}
// ۳. دریافت اطلاعات از سرور
if (cart.length === 0 && !hasFetchedRef.current || isSyncingRef.current) {
try {
const data = await getCartApi();
if (data) {
setServerCartItems(data.items || []);
setServerSummary(data.summary || null);
hasFetchedRef.current = true;
}
} catch (error) {
console.error("خطا در دریافت اطلاعات سبد خرید:", error);
} finally {
setIsLoading(false);
}
} else {
setIsLoading(false);
}
};
// --- useEffect برای بررسی لاگین و دریافت دیتای سرور ---
fetchAndMergeCart();
}, [cart.length, clearCart]);
// --- لیسنر برای آپدیت لحظه‌ای وقتی CartControls مقادیر را تغییر می‌دهد ---
useEffect(() => {
const fetchServerCart = async () => {
const token = localStorage.getItem('accessToken');
const updateServerData = async () => {
const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (token) {
setIsLoggedIn(true);
try {
const data = await getCartApi();
if (data) {
@@ -94,21 +118,20 @@ export default function CartPage() {
setServerSummary(data.summary || null);
}
} catch (error) {
console.error("خطا در دریافت اطلاعات سبد خرید:", error);
console.error("خطا در آپدیت سبد خرید سرور:", error);
}
} else {
setIsLoggedIn(false);
}
setIsLoading(false);
};
fetchServerCart();
// هر بار CartControls رویداد را فایر کرد، استیت‌های این صفحه رفرش می‌شوند
window.addEventListener('cartUpdated', updateServerData);
return () => {
window.removeEventListener('cartUpdated', updateServerData);
};
}, []);
// بررسی لاگین کاربر
const handleCheckoutNavigation = () => {
const token = localStorage.getItem('accessToken');
const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (token) {
router.push('/checkout');
} else {
@@ -116,25 +139,34 @@ export default function CartPage() {
}
};
// تبدیل رشته قیمت به عدد
const parsePrice = (priceStr?: number | string | null) => {
if (!priceStr) return 0;
return Number(priceStr.toString().replace(/,/g, ''));
};
// --- منطق یکپارچه‌سازی: اگر لاگین بود دیتای سرور، در غیر این‌صورت دیتای لوکال ---
// آماده‌سازی لیست سبد خرید برای نمایش + اضافه کردن کلید product برای کامپوننت CartControls
const displayCart = isLoggedIn
? serverCartItems.map(item => ({
id: item.product?.id || item.productId,
id: item.id,
productId: item.product?.id || item.productId,
product: item.product, // <-- اضافه شد برای پاس دادن به CartControls
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;
: cart.map(item => ({
id: item.id,
productId: item.id,
product: item, // <-- در حالت لوکال، خود آیتم همان محصول است
title: item.title,
brand: item.brand,
price: item.price,
quantity: item.quantity,
image: item.image || "/placeholder.png"
}));
// محاسبه قیمت کل و تعداد کل
const totalPrice = isLoggedIn && serverSummary
? serverSummary.totalPrice || serverSummary.total || 0
: cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
@@ -143,12 +175,7 @@ export default function CartPage() {
? 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">
@@ -234,7 +261,6 @@ export default function CartPage() {
</div>
<div className="flex flex-col gap-6">
{/* حلقه مپ روی displayCart اعمال شده است */}
{displayCart.map((item) => {
const itemTotal = parsePrice(item.price) * item.quantity;
return (
@@ -250,15 +276,12 @@ export default function CartPage() {
<span className="font-black text-base md:text-lg text-gray-900">
{item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'}
</span>
<div className="flex items-center gap-1 bg-gray-50 border border-gray-200 rounded-full p-1 shadow-sm">
<button onClick={() => addToCart(item 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>
<button onClick={() => decreaseQuantity(item.id)} 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={14} /> : <Minus size={14} strokeWidth={2.5} />}
</button>
</div>
<CartControls
product={item.product as any}
cartItemId={isLoggedIn ? item.id : null}
quantity={item.quantity}
/>
</div>
</div>
);