edit/delete address api

This commit is contained in:
haniyeroozmand
2026-04-27 14:23:07 +03:30
parent e735acfff0
commit 333c2bf025
2 changed files with 262 additions and 94 deletions

View File

@@ -15,7 +15,7 @@ import {
} from "lucide-react";
import PaymentMethodsSection from "@/components/PaymentMethods";
import { getCartApi } from "@/public/src/services/cart/api";
import { fetchUserAddresses, type Address, addAddressApi, NewAddressData } from "@/public/src/services/address/api";
import { fetchUserAddresses, type Address, addAddressApi, NewAddressData, updateAddressApi, deleteAddressApi } from "@/public/src/services/address/api";
@@ -133,12 +133,122 @@ export default function CheckoutPage() {
}
};
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
@@ -239,11 +349,13 @@ 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">افزودن آدرس جدید</button>
<button className="text-lg cursor-pointer font-bold text-gray-800">
{editingAddressId ? 'ویرایش آدرس' : 'افزودن آدرس جدید'}
</button>
{addresses.length > 0 && (
<button
type="button"
onClick={() => setShowNewAddressForm(false)}
onClick={handleCancelForm}
className="text-sm cursor-pointer text-red-500 hover:text-red-700"
>
انصراف
@@ -370,10 +482,10 @@ export default function CheckoutPage() {
<div className="pt-4">
<button
type="button"
onClick={handleAddNewAddress}
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>
@@ -386,7 +498,11 @@ export default function CheckoutPage() {
<h2 className="text-lg font-bold text-gray-800">انتخاب آدرس ارسال</h2>
<button
type="button"
onClick={() => setShowNewAddressForm(true)}
onClick={() => {
setEditingAddressId(null);
setNewAddress({ title: "", recipientName: "", phone: "", province: "", city: "", postalCode: "", addressLine: "", plaque: "", unit: "", isDefault: false });
setShowNewAddressForm(true);
}}
className="text-sm cursor-pointer text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1"
>
<span>+</span> افزودن آدرس جدید
@@ -402,118 +518,89 @@ export default function CheckoutPage() {
{addresses.map((address) => (
<label
key={address.id}
className={`block border rounded-xl p-4 cursor-pointer transition-all ${selectedAddressId === address.id
? 'border-[#ffb900] bg-[#ffb900]/5'
: 'border-gray-100 hover:border-gray-200'
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-3">
<div className="pt-1">
<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="w-4 h-4 border-[#ffb900] focus:ring-[#ffb900] cursor-pointer"
// حذف استایل پیش‌فرض و اعمال بوردر و رنگ سفارشی
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">
<div className="flex justify-between items-start">
<p className="font-medium text-gray-900">{address.addressLine}</p>
<div className="flex-1 space-y-3">
{/* عنوان و متن آدرس */}
<div>
{address.title && (
<span className="bg-gray-100 text-gray-600 text-xs px-2 py-1 rounded">
<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="mt-2 text-sm text-gray-600 flex flex-wrap gap-y-1 gap-x-4">
<span className="flex items-center gap-1">
<span className="font-semibold text-gray-500">گیرنده:</span> {address.recipientName}
</span>
<span className="flex items-center gap-1">
<span className="font-semibold text-gray-500">موبایل:</span> {address.phone}
</span>
<span className="flex items-center gap-1">
<span className="font-semibold text-gray-500">کد پستی:</span> {address.postalCode}
</span>
{/* جزئیات آدرس (مینیمال شده) */}
<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>
<span className="font-semibold text-gray-500">پلاک:</span> {address.plaque} {address.unit ? `- واحد: ${address.unit}` : ''}
پلاک {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>
))}
</div>
)}
</div>
)}
</>
)}
</div>
{/* <div className="bg-white rounded-[1rem] p-6 md:p-8 shadow-sm">
<div className="flex items-center gap-3 mb-6 pb-4 border-b border-gray-100">
<div className="bg-[#1A2332]/5 p-2 rounded-lg text-[#1A2332]">
<MapPin size={20} />
</div>
<h2 className="text-base md:text-lg font-bold text-[#1A2332]">آدرس پستی</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5 mb-5">
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600 block">استان</label>
<select className="w-full bg-gray-50 text-[13px] border border-gray-200 text-[#1A2332] rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-[#ffb900]/40 focus:border-[#ffb900] transition-all appearance-none cursor-pointer">
<option value="">انتخاب استان...</option>
<option value="tehran">تهران</option>
<option value="alborz">البرز</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600 block">شهر</label>
<select className="w-full text-[13px] bg-gray-50 border border-gray-200 text-[#1A2332] rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-[#ffb900]/40 focus:border-[#ffb900] transition-all appearance-none cursor-pointer">
<option value="">انتخاب شهر...</option>
<option value="tehran">تهران</option>
<option value="karaj">کرج</option>
</select>
</div>
</div>
<div className="space-y-2 mb-5">
<label className="text-sm font-medium text-gray-600 block">آدرس دقیق پستی</label>
<textarea rows={3} placeholder="خیابان، کوچه، پلاک، واحد..." className="w-full text-[13px] bg-gray-50 border border-gray-200 text-[#1A2332] rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-[#ffb900]/40 focus:border-[#ffb900] transition-all placeholder:text-gray-400 resize-none" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5 mb-5">
<div className=" space-y-2">
<label className="text-sm font-medium text-gray-600 block">کد پستی (۱۰ رقمی)</label>
<input type="text" placeholder="1234567890" className="w-full bg-gray-50 border border-gray-200 text-[#1A2332] rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-[#ffb900]/40 focus:border-[#ffb900] transition-all placeholder:text-gray-400 placeholder:text-[13px]" />
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-600 block">ایمیل</label>
<input type="email" placeholder="test@email.com" className="w-full bg-gray-50 border border-gray-200 text-[#1A2332] rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-[#ffb900]/40 focus:border-[#ffb900] transition-all placeholder:text-gray-400 placeholder:text-[13px]" />
</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 className="bg-white rounded-[1rem] pt-4 pb-4">
<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} />
@@ -549,6 +636,28 @@ export default function CheckoutPage() {
</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 className="w-full border-t-2 border-dashed border-gray-100 my-6"></div>