address card provider
This commit is contained in:
@@ -3,21 +3,11 @@
|
||||
import { useCart } from "@/components/context/cartcontext";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ShoppingBag,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Truck,
|
||||
CreditCard,
|
||||
MapPin,
|
||||
User,
|
||||
Lock
|
||||
} from "lucide-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 { fetchUserAddresses, type Address, addAddressApi, NewAddressData, updateAddressApi, deleteAddressApi } from "@/public/src/services/address/api";
|
||||
|
||||
|
||||
import AddressCard from "@/components/AddressCard";
|
||||
import { useAddressContext } from "@/components/context/Addresscontext";
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const { cart } = useCart();
|
||||
@@ -25,32 +15,18 @@ export default function CheckoutPage() {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [serverSummary, setServerSummary] = useState<any>(null);
|
||||
const [addresses, setAddresses] = useState<Address[]>([]);
|
||||
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null);
|
||||
const [showNewAddressForm, setShowNewAddressForm] = useState(false);
|
||||
const [isAddressLoading, setIsAddressLoading] = useState(true);
|
||||
const [newAddress, setNewAddress] = useState<NewAddressData>({
|
||||
title: 'خانه',
|
||||
recipientName: '',
|
||||
phone: '',
|
||||
province: '',
|
||||
city: '',
|
||||
postalCode: '',
|
||||
addressLine: '',
|
||||
plaque: '',
|
||||
unit: '',
|
||||
isDefault: false
|
||||
});
|
||||
|
||||
const {
|
||||
addresses, isAddressLoading, showNewAddressForm, setShowNewAddressForm,
|
||||
editingAddressId, newAddress, handleAddressInputChange,
|
||||
handleAddNewAddress, handleUpdateAddress, handleCancelForm
|
||||
} = useAddressContext();
|
||||
|
||||
useEffect(() => {
|
||||
const initializeCheckout = async () => {
|
||||
// توجه: در صفحه Cart از refreshToken استفاده کرده بودید، اگر اینجا accessToken است دقت کنید که یکسان باشند
|
||||
const token = localStorage.getItem('accessToken') || localStorage.getItem('refreshToken');
|
||||
|
||||
if (token) {
|
||||
setIsAuthenticated(true);
|
||||
// ۳. دریافت اطلاعات سبد خرید از سرور
|
||||
try {
|
||||
const data = await getCartApi();
|
||||
if (data && data.summary) {
|
||||
@@ -59,47 +35,8 @@ export default function CheckoutPage() {
|
||||
} catch (error) {
|
||||
console.error("خطا در دریافت اطلاعات سبد خرید:", error);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
initializeCheckout();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const initializeCheckout = async () => {
|
||||
const token = localStorage.getItem('accessToken') || localStorage.getItem('refreshToken');
|
||||
|
||||
if (token) {
|
||||
setIsAuthenticated(true);
|
||||
try {
|
||||
// دریافت همزمان اطلاعات سبد و آدرسها
|
||||
const [cartData, addressData] = await Promise.all([
|
||||
getCartApi(),
|
||||
fetchUserAddresses()
|
||||
]);
|
||||
|
||||
if (cartData && cartData.summary) {
|
||||
setServerSummary(cartData.summary);
|
||||
}
|
||||
|
||||
if (addressData && addressData.length > 0) {
|
||||
setAddresses(addressData);
|
||||
// انتخاب آدرس پیشفرض یا اولین آدرس به عنوان انتخابشده
|
||||
const defaultAddress = addressData.find(addr => addr.isDefault) || addressData[0];
|
||||
setSelectedAddressId(defaultAddress.id);
|
||||
} else {
|
||||
// اگر آدرسی وجود نداشت، فرم افزودن آدرس را نمایش بده
|
||||
setShowNewAddressForm(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("خطا در دریافت اطلاعات صفحه پرداخت:", error);
|
||||
} finally {
|
||||
setIsAddressLoading(false);
|
||||
}
|
||||
} else {
|
||||
setIsAuthenticated(false);
|
||||
setIsAddressLoading(false);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
@@ -107,149 +44,11 @@ export default function CheckoutPage() {
|
||||
initializeCheckout();
|
||||
}, []);
|
||||
|
||||
|
||||
const handleAddressInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setNewAddress(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleAddNewAddress = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// اینجا میتوانید ولیدیشنهای لازم را اضافه کنید
|
||||
try {
|
||||
const addedAddress = await addAddressApi(newAddress);
|
||||
// آدرس جدید را به لیست اضافه کن و آن را به عنوان انتخاب شده قرار بده
|
||||
setAddresses(prev => [...prev, addedAddress]);
|
||||
setSelectedAddressId(addedAddress.id);
|
||||
setShowNewAddressForm(false); // فرم را مخفی کن
|
||||
// فرم را ریست کن
|
||||
setNewAddress({
|
||||
title: 'خانه', recipientName: '', phone: '', province: '', city: '',
|
||||
postalCode: '', addressLine: '', plaque: '', unit: '', isDefault: false
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("خطا در ذخیره آدرس:", error);
|
||||
// اینجا میتوانید به کاربر خطا را نمایش دهید
|
||||
}
|
||||
};
|
||||
|
||||
const [editingAddressId, setEditingAddressId] = useState(null);
|
||||
|
||||
// 2. تابع کلیک روی دکمه ویرایش
|
||||
const handleEditClick = (address: any, e: any) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // برای جلوگیری از انتخاب شدن radio button هنگام کلیک روی ویرایش
|
||||
|
||||
setEditingAddressId(address.id);
|
||||
// پر کردن مقادیر فرم با دیتای آدرس انتخاب شده (فرض بر این است که استیت شما setNewAddress نام دارد)
|
||||
setNewAddress({
|
||||
title: address.title || "",
|
||||
recipientName: address.recipientName || "",
|
||||
phone: address.phone || "",
|
||||
province: address.province || "",
|
||||
city: address.city || "",
|
||||
postalCode: address.postalCode || "",
|
||||
addressLine: address.addressLine || "",
|
||||
plaque: address.plaque || "",
|
||||
unit: address.unit || "",
|
||||
isDefault: address.isDefault || false
|
||||
});
|
||||
setShowNewAddressForm(true);
|
||||
};
|
||||
|
||||
// 3. تابع فراخوانی API ویرایش
|
||||
const handleUpdateAddress = async () => {
|
||||
// اضافه کردن این شرط برای جلوگیری از خطای تایپ و توقف اجرا در صورت null بودن ID
|
||||
if (!editingAddressId) return;
|
||||
|
||||
try {
|
||||
const response = await updateAddressApi(editingAddressId, newAddress);
|
||||
if (response.success) {
|
||||
// آپدیت کردن آدرس ویرایش شده در لیست آدرسها
|
||||
setAddresses(prevAddresses =>
|
||||
prevAddresses.map(addr => addr.id === editingAddressId ? response.data : addr)
|
||||
);
|
||||
|
||||
// خروج از حالت فرم و ریست کردن مقادیر
|
||||
setShowNewAddressForm(false);
|
||||
setEditingAddressId(null);
|
||||
setNewAddress({
|
||||
title: "", recipientName: "", phone: "", province: "", city: "", postalCode: "", addressLine: "", plaque: "", unit: "", isDefault: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// مدیریت خطا (در صورت نیاز آلرت یا توست نمایش دهید)
|
||||
console.error("خطا در بهروزرسانی آدرس:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 4. تابع انصراف یکپارچه شده
|
||||
const handleCancelForm = () => {
|
||||
setShowNewAddressForm(false);
|
||||
setEditingAddressId(null);
|
||||
setNewAddress({
|
||||
title: "", recipientName: "", phone: "", province: "", city: "", postalCode: "", addressLine: "", plaque: "", unit: "", isDefault: false
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteAddress = async (addressId: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation(); // جلوگیری از انتخاب شدن آدرس هنگام کلیک روی دکمه حذف
|
||||
|
||||
// گرفتن تاییدیه از کاربر قبل از حذف
|
||||
const confirmDelete = window.confirm("آیا از حذف این آدرس اطمینان دارید؟");
|
||||
if (!confirmDelete) return;
|
||||
|
||||
try {
|
||||
const response = await deleteAddressApi(addressId);
|
||||
|
||||
if (response.success) {
|
||||
// حذف آدرس از لیست موجود در State
|
||||
setAddresses(prevAddresses =>
|
||||
prevAddresses.filter(addr => addr.id !== addressId)
|
||||
);
|
||||
|
||||
// اگر آدرسی که پاک شد همان آدرس انتخابشده بود، انتخاب را لغو کن
|
||||
if (selectedAddressId === addressId) {
|
||||
setSelectedAddressId(null);
|
||||
}
|
||||
|
||||
// اگر آدرسی که پاک شد در حال ویرایش بود، فرم ویرایش را ببند
|
||||
if (editingAddressId === addressId) {
|
||||
setShowNewAddressForm(false);
|
||||
setEditingAddressId(null);
|
||||
setNewAddress({
|
||||
title: "", recipientName: "", phone: "", province: "", city: "", postalCode: "", addressLine: "", plaque: "", unit: "", isDefault: false
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("خطا در حذف آدرس:", error);
|
||||
// اینجا میتوانید یک Toast یا Alert برای نمایش خطا به کاربر اضافه کنید
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// محاسبه قیمت کل و تعداد
|
||||
const parsePrice = (priceStr?: number | null | string) => {
|
||||
if (!priceStr) return 0;
|
||||
return Number(priceStr.toString().replace(/,/g, ''));
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ۴. استفاده از اطلاعات سرور در صورت لاگین بودن، در غیر این صورت استفاده از Context
|
||||
const totalPrice = isAuthenticated && serverSummary
|
||||
? serverSummary.totalPrice || serverSummary.total || 0
|
||||
: cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
|
||||
@@ -261,7 +60,6 @@ export default function CheckoutPage() {
|
||||
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">
|
||||
@@ -270,7 +68,6 @@ export default function CheckoutPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// اگر کاربر لاگین نبود
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50/30 flex flex-col items-center justify-center p-6" dir="rtl">
|
||||
@@ -291,7 +88,6 @@ export default function CheckoutPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// رندر اصلی کامپوننت Checkout
|
||||
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">
|
||||
@@ -336,8 +132,8 @@ export default function CheckoutPage() {
|
||||
</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 ? (
|
||||
@@ -348,8 +144,8 @@ export default function CheckoutPage() {
|
||||
<>
|
||||
{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 pb-4">
|
||||
<button className="text-lg cursor-pointer font-bold text-gray-800">
|
||||
<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 && (
|
||||
@@ -498,11 +294,7 @@ export default function CheckoutPage() {
|
||||
<h2 className="text-lg font-bold text-gray-800">انتخاب آدرس ارسال</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setEditingAddressId(null);
|
||||
setNewAddress({ title: "", recipientName: "", phone: "", province: "", city: "", postalCode: "", addressLine: "", plaque: "", unit: "", isDefault: false });
|
||||
setShowNewAddressForm(true);
|
||||
}}
|
||||
onClick={() => setShowNewAddressForm(true)}
|
||||
className="text-sm cursor-pointer text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1"
|
||||
>
|
||||
<span>+</span> افزودن آدرس جدید
|
||||
@@ -516,90 +308,18 @@ export default function CheckoutPage() {
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{addresses.map((address) => (
|
||||
<label
|
||||
<AddressCard
|
||||
key={address.id}
|
||||
className={`relative flex flex-col gap-4 p-3 cursor-pointer rounded-2xl border transition-all duration-300 ${selectedAddressId === address.id
|
||||
? 'border-[#ffb900] bg-[#ffb900]/[0.03] ring-1 ring-[#ffb900]/20 shadow-sm'
|
||||
: 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
{/* رادیو باتن */}
|
||||
<div className="pt-0.5 relative flex items-center justify-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedAddress"
|
||||
value={address.id}
|
||||
checked={selectedAddressId === address.id}
|
||||
onChange={() => setSelectedAddressId(address.id)}
|
||||
// حذف استایل پیشفرض و اعمال بوردر و رنگ سفارشی
|
||||
className="peer appearance-none w-[18px] h-[18px] border-[1.5px] border-gray-300 rounded-full checked:border-[#ffb900] checked:bg-[#ffb900] cursor-pointer transition-all focus:outline-none focus:ring-2 focus:ring-[#ffb900]/20 focus:ring-offset-1 bg-white"
|
||||
/>
|
||||
{/* دایره سفید مرکزی که فقط در حالت انتخاب شده نمایش داده میشود */}
|
||||
<div className="pointer-events-none absolute w-[10px] h-[10px] rounded-full bg-white opacity-0 peer-checked:opacity-100 transition-opacity"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="flex-1 space-y-3">
|
||||
{/* عنوان و متن آدرس */}
|
||||
<div>
|
||||
{address.title && (
|
||||
<span className="inline-block mb-2 bg-gray-50 text-gray-500 border border-gray-200 text-[11px] font-medium px-2.5 py-0.5 rounded-full">
|
||||
{address.title}
|
||||
</span>
|
||||
)}
|
||||
<p className="font-medium text-gray-800 leading-relaxed text-sm md:text-base">
|
||||
{address.addressLine}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* جزئیات آدرس (مینیمال شده) */}
|
||||
<div className="flex flex-wrap items-center text-xs md:text-sm text-gray-500 gap-x-2 gap-y-1.5">
|
||||
<span>{address.recipientName}</span>
|
||||
<span className="text-gray-300 text-[10px]">●</span>
|
||||
<span dir="ltr">{address.phone}</span>
|
||||
<span className="text-gray-300 text-[10px]">●</span>
|
||||
<span>کد پستی: {address.postalCode}</span>
|
||||
<span className="text-gray-300 text-[10px]">●</span>
|
||||
<span>
|
||||
پلاک {address.plaque}
|
||||
{address.unit ? `، واحد ${address.unit}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* نوار دکمههای عملیاتی */}
|
||||
<div className="flex items-center justify-end gap-2 pt-3 mt-1 border-t border-gray-100/80">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleEditClick(address, e)}
|
||||
className="px-3 cursor-pointer py-1.5 text-xs font-medium text-gray-500 transition-colors rounded-lg hover:bg-blue-50 hover:text-blue-600 focus:outline-none"
|
||||
>
|
||||
ویرایش آدرس
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleDeleteAddress(address.id, e)}
|
||||
className="px-3 cursor-pointer py-1.5 text-xs font-medium text-gray-500 transition-colors rounded-lg hover:bg-red-50 hover:text-red-600 focus:outline-none"
|
||||
>
|
||||
حذف
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
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]">
|
||||
@@ -637,10 +357,8 @@ export default function CheckoutPage() {
|
||||
</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>
|
||||
@@ -654,13 +372,7 @@ export default function CheckoutPage() {
|
||||
<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 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">
|
||||
|
||||
Reference in New Issue
Block a user