Files
parsshop/app/cart/page.tsx

335 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useCart } from "@/components/context/cartcontext";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState, useEffect, useRef } from "react";
import {
Trash2,
ShoppingBag,
ChevronLeft,
ShieldCheck,
Truck,
CreditCard
} from "lucide-react";
import NotLogin from "@/components/Notlogin";
import { getCartApi, addToCartApi, clearServerCartApi } from "@/public/src/services/cart/api";
import CartControls from "@/components/CartControls";
export default function CartPage() {
const {
cart,
clearCart,
} = useCart();
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);
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 Promise.all(
cart.map(item => addToCartApi(item.id, item.quantity))
);
clearCart();
window.dispatchEvent(new Event('cartUpdated'));
} catch (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);
}
};
fetchAndMergeCart();
}, [cart.length, clearCart]);
// --- لیسنر برای آپدیت لحظه‌ای وقتی CartControls مقادیر را تغییر می‌دهد ---
useEffect(() => {
const updateServerData = async () => {
const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (token) {
try {
const data = await getCartApi();
if (data) {
setServerCartItems(data.items || []);
setServerSummary(data.summary || null);
}
} catch (error) {
console.error("خطا در آپدیت سبد خرید سرور:", error);
}
}
};
// هر بار CartControls رویداد را فایر کرد، استیت‌های این صفحه رفرش می‌شوند
window.addEventListener('cartUpdated', updateServerData);
return () => {
window.removeEventListener('cartUpdated', updateServerData);
};
}, []);
const handleCheckoutNavigation = () => {
const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (token) {
router.push('/checkout');
} else {
setShowNotLogin(true);
}
};
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.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.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);
const totalItems = isLoggedIn && serverSummary
? serverSummary.totalQuantity || serverSummary.itemsCount || 0
: cart.reduce((total, item) => total + item.quantity, 0);
// دیزاین حالت سبد خرید خالی
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">
<div className="bg-blue-50/50 w-32 h-32 rounded-full flex items-center justify-center mb-8 relative">
<ShoppingBag size={56} className="text-blue-500 relative z-10" />
<div className="absolute inset-0 bg-blue-100 rounded-full animate-ping opacity-20"></div>
</div>
<h2 className="text-2xl font-black text-gray-800 mb-3">سبد خرید شما خالی است!</h2>
<p className="text-gray-500 mb-10 leading-relaxed text-sm">
هنوز هیچ محصولی به سبد خرید خود اضافه نکردهاید. برای مشاهده محصولات به صفحه اصلی برگردید.
</p>
<Link href="/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>
</div>
</div>
);
}
return (
<main className="bg-gray-50/30 min-h-screen pb-20">
{showNotLogin && (
<NotLogin
buttonText="بازگشت به سبد خرید"
onClose={() => setShowNotLogin(false)} />
)}
<div className="container mx-auto px-4 py-8 max-w-6xl">
<div className="mb-10">
<div className="flex items-center justify-between mb-8">
<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-xs md:text-sm font-bold py-1 px-3 rounded-xl flex items-center">
{totalItems} کالا
</span>
</h1>
</div>
<div className="mb-10">
<div className="relative w-full max-w-2xl mx-auto px-2 sm:px-0 mb-12">
<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-[50%] transition-all duration-500 ease-in-out"></div>
</div>
<div className="flex justify-between relative z-10">
<Link href="/cart" className="flex flex-col items-center w-1/3 group cursor-pointer">
<div className=" w-10 h-10 sm:w-12 sm:h-12 bg-[#ffb900] text-[#1A2332] rounded-full flex items-center justify-center shadow-[0_0_15px_rgba(255,185,0,0.4)] mb-2 sm:mb-3 ring-[6px] ring-[#ffb900]/20 transition-all">
<ShoppingBag className="w-5 h-5 sm:w-6 sm:h-6" strokeWidth={2} />
</div>
<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} />
</div>
<span className="text-[10px] sm:text-sm font-bold text-[#1A2332] text-center">اطلاعات ارسال</span>
</div>
<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 className="flex flex-col lg:flex-row gap-6 lg:gap-8">
<div className="flex-1">
<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={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">
{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">
<div className="bg-gray-50/80 p-3 rounded-2xl border border-gray-100 shrink-0 relative group-hover:bg-white transition-colors duration-300 w-full sm:w-auto flex justify-center">
<Image src={item.image} alt={item.title} width={100} height={100} className="object-contain w-24 h-24 drop-shadow-sm" />
</div>
<div className="flex-1 w-full">
<p className="text-xs font-medium text-blue-500 mb-1.5">{item.brand}</p>
<h3 className="text-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>
</div>
<div className="flex sm:flex-col justify-between sm:justify-end items-center sm:items-end w-full sm:w-auto gap-4 shrink-0 mt-2 sm:mt-0">
<span className="font-black text-base md:text-lg text-gray-900">
{item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'}
</span>
<CartControls
product={item.product as any}
cartItemId={isLoggedIn ? item.id : null}
quantity={item.quantity}
/>
</div>
</div>
);
})}
</div>
</div>
</div>
<div className="w-full lg:w-[340px] shrink-0">
<div className="bg-white rounded-[1rem] p-4 md:p-8 shadow-sm sticky top-6">
<h2 className="text-lg md:text-xl font-bold text-gray-800 mb-6">خلاصه صورتحساب</h2>
<div className="space-y-4 mb-6">
<div className="flex justify-between items-center text-xs md:text-sm">
<span className="text-gray-500">تعداد کالاها</span>
<span className="font-bold text-gray-800">{totalItems} عدد</span>
</div>
<div className="flex justify-between items-center text-xs md:text-sm">
<span className="text-gray-500">هزینه ارسال</span>
<span className="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded-md text-[10px] md:text-xs">در مرحله بعد</span>
</div>
</div>
<div className="w-full border-t-2 border-dashed border-gray-200 my-6"></div>
<div className="flex justify-between items-center mb-8">
<span className="text-xs md:text-sm font-bold text-gray-600">مبلغ قابل پرداخت</span>
<span className="font-black text-xl md:text-2xl text-[#ffb900] tracking-tight">
{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')}` : 'استعلام'}
{totalPrice > 0 && <span className="text-xs md:text-sm font-medium text-gray-500 mr-1">تومان</span>}
</span>
</div>
<button
onClick={handleCheckoutNavigation}
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} />
</button>
<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"><ShieldCheck size={14} className="text-green-500" /> پرداخت امن</div>
<div className="w-1 h-1 bg-gray-300 rounded-full"></div>
<div className="flex items-center gap-1"><Truck size={14} className="text-blue-500" /> ارسال سریع</div>
</div>
</div>
</div>
</div>
</div>
</main>
);
}