400 lines
29 KiB
TypeScript
400 lines
29 KiB
TypeScript
'use client';
|
||
|
||
import { useCart } from "@/components/context/cartcontext";
|
||
import Link from "next/link";
|
||
import { useState, useEffect } from "react";
|
||
import { ShoppingBag, ChevronLeft, ChevronRight, Truck, CreditCard, Lock } from "lucide-react";
|
||
import PaymentMethodsSection from "@/components/PaymentMethods";
|
||
import { getCartApi } from "@/public/src/services/cart/api";
|
||
import AddressCard from "@/components/AddressCard";
|
||
import { useAddressContext } from "@/components/context/Addresscontext";
|
||
|
||
export default function CheckoutPage() {
|
||
const { cart } = useCart();
|
||
const [shippingMethod, setShippingMethod] = useState('post');
|
||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [serverSummary, setServerSummary] = useState<any>(null);
|
||
const {
|
||
addresses, isAddressLoading, showNewAddressForm, setShowNewAddressForm,
|
||
editingAddressId, newAddress, handleAddressInputChange,
|
||
handleAddNewAddress, handleUpdateAddress, handleCancelForm
|
||
} = useAddressContext();
|
||
|
||
useEffect(() => {
|
||
const initializeCheckout = async () => {
|
||
const token = localStorage.getItem('accessToken') || localStorage.getItem('refreshToken');
|
||
|
||
if (token) {
|
||
setIsAuthenticated(true);
|
||
try {
|
||
const data = await getCartApi();
|
||
if (data && data.summary) {
|
||
setServerSummary(data.summary);
|
||
}
|
||
} catch (error) {
|
||
console.error("خطا در دریافت اطلاعات سبد خرید:", error);
|
||
}
|
||
} else {
|
||
setIsAuthenticated(false);
|
||
}
|
||
setIsLoading(false);
|
||
};
|
||
|
||
initializeCheckout();
|
||
}, []);
|
||
|
||
const parsePrice = (priceStr?: number | null | string) => {
|
||
if (!priceStr) return 0;
|
||
return Number(priceStr.toString().replace(/,/g, ''));
|
||
};
|
||
|
||
const totalPrice = isAuthenticated && serverSummary
|
||
? serverSummary.totalPrice || serverSummary.total || 0
|
||
: cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
|
||
|
||
const totalItems = isAuthenticated && serverSummary
|
||
? serverSummary.totalQuantity || serverSummary.itemsCount || 0
|
||
: cart.reduce((total, item) => total + item.quantity, 0);
|
||
|
||
const shippingCost = shippingMethod === 'post' ? 45000 : 75000;
|
||
const finalPrice = totalPrice + shippingCost;
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center bg-gray-50/30">
|
||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#ffb900]"></div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!isAuthenticated) {
|
||
return (
|
||
<div className="min-h-screen bg-gray-50/30 flex flex-col items-center justify-center p-6" dir="rtl">
|
||
<div className="bg-white p-12 rounded-[2rem] shadow-sm border border-gray-100 flex flex-col items-center max-w-md w-full text-center">
|
||
<div className="bg-gray-100 w-24 h-24 rounded-full flex items-center justify-center mb-6">
|
||
<Lock size={40} className="text-gray-400" />
|
||
</div>
|
||
<h2 className="text-xl font-bold text-gray-800 mb-3">دسترسی محدود</h2>
|
||
<p className="text-gray-500 mb-8 leading-relaxed text-sm">
|
||
برای ثبت نهایی سفارش و مشاهده این صفحه، باید ابتدا وارد حساب کاربری خود شوید یا ثبتنام کنید.
|
||
</p>
|
||
<Link href="/cart" className="w-full bg-[#ffb900] hover:bg-[#e5a600] text-[#1A2332] font-bold py-3 rounded-xl transition-all flex items-center justify-center gap-2">
|
||
<ChevronRight size={18} />
|
||
بازگشت به سبد خرید
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<main className="bg-gray-50/30 min-h-screen pb-20" dir="rtl">
|
||
<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-[#1A2332]">
|
||
اطلاعات ارسال
|
||
</h1>
|
||
<Link href="/cart" className="text-sm text-gray-500 hover:text-[#1A2332] flex items-center gap-1 transition-colors">
|
||
<ChevronRight size={16} />
|
||
بازگشت به سبد خرید
|
||
</Link>
|
||
</div>
|
||
|
||
<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-md mb-2 sm:mb-3 ring-[6px] ring-[#f8fafc] sm:ring-[#f8fafc] transition-transform group-hover:scale-110">
|
||
<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 className="flex flex-col items-center w-1/3">
|
||
<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">
|
||
<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 className="flex flex-col lg:flex-row gap-6 lg:gap-8">
|
||
<div className="flex-1 space-y-6">
|
||
<div className="flex-1 space-y-6">
|
||
{isAddressLoading ? (
|
||
<div className="bg-white rounded-[1rem] p-8 shadow-sm flex justify-center items-center">
|
||
<p className="text-gray-500">در حال بارگذاری آدرسها...</p>
|
||
</div>
|
||
) : (
|
||
<>
|
||
{showNewAddressForm ? (
|
||
<div className="bg-white rounded-[1rem] p-6 shadow-sm space-y-6 border border-gray-100">
|
||
<div className="flex justify-between items-center border-b border-gray-300 b-4">
|
||
<button className="text-lg cursor-pointer mb-4 font-bold text-gray-800">
|
||
{editingAddressId ? 'ویرایش آدرس' : 'افزودن آدرس جدید'}
|
||
</button>
|
||
{addresses.length > 0 && (
|
||
<button
|
||
type="button"
|
||
onClick={handleCancelForm}
|
||
className="text-sm cursor-pointer text-red-500 hover:text-red-700"
|
||
>
|
||
انصراف
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
<div className="bg-[#FBFBFB] border border-gray-200 rounded-2xl p-8 shadow-sm space-y-6">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">
|
||
عنوان آدرس (مثلاً: خانه، محل کار)
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="title"
|
||
value={newAddress.title}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all placeholder-gray-400"
|
||
placeholder="خانه"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">
|
||
نام و نام خانوادگی گیرنده
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="recipientName"
|
||
value={newAddress.recipientName}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all placeholder-gray-400"
|
||
placeholder="نام گیرنده"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">شماره موبایل</label>
|
||
<input
|
||
type="tel"
|
||
name="phone"
|
||
value={newAddress.phone}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all placeholder-gray-400"
|
||
placeholder="09123456789"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">استان</label>
|
||
<input
|
||
type="text"
|
||
name="province"
|
||
value={newAddress.province}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all placeholder-gray-400"
|
||
placeholder="تهران"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">شهر</label>
|
||
<input
|
||
type="text"
|
||
name="city"
|
||
value={newAddress.city}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all placeholder-gray-400"
|
||
placeholder="تهران"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">
|
||
کد پستی (۱۰ رقمی)
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="postalCode"
|
||
value={newAddress.postalCode}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all placeholder-gray-400"
|
||
placeholder="1234567890"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">آدرس دقیق پستی</label>
|
||
<textarea
|
||
name="addressLine"
|
||
value={newAddress.addressLine}
|
||
onChange={handleAddressInputChange}
|
||
rows={3}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all placeholder-gray-400"
|
||
placeholder="خیابان اصلی، کوچه فرعی..."
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-6 md:w-1/2">
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">پلاک</label>
|
||
<input
|
||
type="text"
|
||
name="plaque"
|
||
value={newAddress.plaque}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-gray-600 font-medium">واحد (اختیاری)</label>
|
||
<input
|
||
type="text"
|
||
name="unit"
|
||
value={newAddress.unit}
|
||
onChange={handleAddressInputChange}
|
||
className="w-full border border-gray-300 rounded-lg p-3 text-sm bg-white text-gray-800 focus:outline-none focus:border-[#FFB900] focus:ring-1 focus:ring-[#FFB900] transition-all"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="pt-4">
|
||
<button
|
||
type="button"
|
||
onClick={editingAddressId ? handleUpdateAddress : handleAddNewAddress}
|
||
className="w-full cursor-pointer md:w-auto bg-[#FFB900] text-white px-10 py-3 rounded-lg font-semibold shadow-md hover:bg-[#e5a000] transition-colors"
|
||
>
|
||
{editingAddressId ? 'ویرایش و ذخیره آدرس' : 'ثبت و ذخیره آدرس'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
) : (
|
||
/* ================= بخش لیست آدرسهای موجود ================= */
|
||
<div className="bg-white rounded-[1rem] p-6 shadow-sm space-y-4">
|
||
<div className="flex justify-between items-center border-b border-[#dcdbdb] pb-4 mb-4">
|
||
<h2 className="text-lg font-bold text-gray-800">انتخاب آدرس ارسال</h2>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowNewAddressForm(true)}
|
||
className="text-sm cursor-pointer text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1"
|
||
>
|
||
<span>+</span> افزودن آدرس جدید
|
||
</button>
|
||
</div>
|
||
|
||
{addresses.length === 0 ? (
|
||
<div className="text-center py-6 text-gray-500">
|
||
هیچ آدرسی یافت نشد. لطفاً یک آدرس جدید اضافه کنید.
|
||
</div>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{addresses.map((address) => (
|
||
<AddressCard
|
||
key={address.id}
|
||
address={address}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
|
||
<div className="bg-white rounded-[1rem] p-6 shadow-sm space-y-4">
|
||
<div className="flex items-center gap-3 mb-6 pb-4 ">
|
||
<div className="bg-[#1A2332]/5 p-2 rounded-lg text-[#1A2332]">
|
||
<Truck size={20} />
|
||
</div>
|
||
<h2 className="text-base md:text-lg font-bold text-[#1A2332]">نحوه ارسال</h2>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<label className={`flex items-center justify-between p-4 rounded-xl border-1 cursor-pointer transition-all ${shippingMethod === 'post' ? 'border-[#ffb900] bg-[#ffb900]/3' : 'border-gray-100 hover:border-gray-200 bg-white'}`}>
|
||
<div className="flex items-center gap-3">
|
||
<div className={`w-5 h-5 rounded-full border-1 flex items-center justify-center ${shippingMethod === 'post' ? 'border-[#ffb900]' : 'border-gray-300'}`}>
|
||
{shippingMethod === 'post' && <div className="w-2.5 h-2.5 bg-[#ffb900] rounded-full" />}
|
||
</div>
|
||
<div>
|
||
<div className="font-bold text-[#1A2332]">پست پیشتاز</div>
|
||
<div className="text-xs text-gray-500 mt-1">زمان تحویل: ۳ تا ۵ روز کاری</div>
|
||
</div>
|
||
</div>
|
||
<div className="font-bold text-[#1A2332]">۴۵,۰۰۰ <span className="text-xs font-normal text-gray-500">تومان</span></div>
|
||
<input type="radio" name="shipping" value="post" className="hidden" checked={shippingMethod === 'post'} onChange={() => setShippingMethod('post')} />
|
||
</label>
|
||
<label className={`flex items-center justify-between p-4 rounded-xl border-1 cursor-pointer transition-all ${shippingMethod === 'tipax' ? 'border-[#ffb900] bg-[#ffb900]/3' : 'border-gray-100 hover:border-gray-200 bg-white'}`}>
|
||
<div className="flex items-center gap-3">
|
||
<div className={`w-5 h-5 rounded-full border-1 flex items-center justify-center ${shippingMethod === 'tipax' ? 'border-[#ffb900]' : 'border-gray-300'}`}>
|
||
{shippingMethod === 'tipax' && <div className="w-2.5 h-2.5 bg-[#ffb900] rounded-full" />}
|
||
</div>
|
||
<div>
|
||
<div className="font-bold text-[#1A2332]">تیپاکس (پسکرایه)</div>
|
||
<div className="text-xs text-gray-500 mt-1">زمان تحویل: ۱ تا ۲ روز کاری</div>
|
||
</div>
|
||
</div>
|
||
<div className="font-bold text-[#1A2332]">۷۵,۰۰۰ <span className="text-xs font-normal text-gray-500">تومان</span></div>
|
||
<input type="radio" name="shipping" value="tipax" className="hidden" checked={shippingMethod === 'tipax'} onChange={() => setShippingMethod('tipax')} />
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="w-full lg:w-[400px] shrink-0">
|
||
<div className="bg-white rounded-[1rem] p-6 md:p-8 shadow-sm sticky top-6">
|
||
<h2 className="text-lg md:text-xl font-bold text-[#1A2332] 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">مبلغ کالاها ({totalItems})</span>
|
||
<span className="font-bold text-[#1A2332]">{totalPrice > 0 ? totalPrice.toLocaleString('fa-IR') : '۰'} <span className="text-[10px] font-normal text-gray-500">تومان</span></span>
|
||
</div>
|
||
<div className="flex justify-between items-center text-xs md:text-sm">
|
||
<span className="text-gray-500">هزینه ارسال</span>
|
||
<span className="font-bold text-[#1A2332]">{shippingCost.toLocaleString('fa-IR')} <span className="text-[10px] font-normal text-gray-500">تومان</span></span>
|
||
</div>
|
||
</div>
|
||
<div className="w-full border-t-2 border-dashed border-gray-100 my-6"></div>
|
||
<div>
|
||
<PaymentMethodsSection />
|
||
<span className="font-black mt-4 mb-4 justify-center items-center flex gap-4 text-[#1A2332] tracking-tight">
|
||
<span className=" font-bold text-gray-600">مبلغ قابل پرداخت</span>
|
||
<span className="text-base flex items-center md:text-lg font-bold text-[#1A2332]">
|
||
{finalPrice > 0 && finalPrice.toLocaleString('fa-IR')}
|
||
{finalPrice > 0 && (
|
||
<span className="text-xs md:text-sm font-medium text-gray-500 mr-1">تومان</span>
|
||
)}
|
||
</span>
|
||
</span>
|
||
</div>
|
||
|
||
<Link href="/payment" className="w-full bg-[#ffb900] hover:bg-[#e5a600] text-[#1A2332] py-3 md:py-4 rounded-xl text-base text-[1em] 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-4">
|
||
پرداخت و ثبت نهایی
|
||
<ChevronLeft size={20} strokeWidth={2} />
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|