address card provider

This commit is contained in:
haniyeroozmand
2026-04-29 12:44:33 +03:30
parent 333c2bf025
commit 0a6bbf425a
4 changed files with 274 additions and 305 deletions

View File

@@ -3,21 +3,11 @@
import { useCart } from "@/components/context/cartcontext"; import { useCart } from "@/components/context/cartcontext";
import Link from "next/link"; import Link from "next/link";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { import { ShoppingBag, ChevronLeft, ChevronRight, Truck, CreditCard, Lock } from "lucide-react";
ShoppingBag,
ChevronLeft,
ChevronRight,
Truck,
CreditCard,
MapPin,
User,
Lock
} from "lucide-react";
import PaymentMethodsSection from "@/components/PaymentMethods"; import PaymentMethodsSection from "@/components/PaymentMethods";
import { getCartApi } from "@/public/src/services/cart/api"; 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() { export default function CheckoutPage() {
const { cart } = useCart(); const { cart } = useCart();
@@ -25,32 +15,18 @@ export default function CheckoutPage() {
const [isAuthenticated, setIsAuthenticated] = useState(false); const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [serverSummary, setServerSummary] = useState<any>(null); const [serverSummary, setServerSummary] = useState<any>(null);
const [addresses, setAddresses] = useState<Address[]>([]); const {
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null); addresses, isAddressLoading, showNewAddressForm, setShowNewAddressForm,
const [showNewAddressForm, setShowNewAddressForm] = useState(false); editingAddressId, newAddress, handleAddressInputChange,
const [isAddressLoading, setIsAddressLoading] = useState(true); handleAddNewAddress, handleUpdateAddress, handleCancelForm
const [newAddress, setNewAddress] = useState<NewAddressData>({ } = useAddressContext();
title: 'خانه',
recipientName: '',
phone: '',
province: '',
city: '',
postalCode: '',
addressLine: '',
plaque: '',
unit: '',
isDefault: false
});
useEffect(() => { useEffect(() => {
const initializeCheckout = async () => { const initializeCheckout = async () => {
// توجه: در صفحه Cart از refreshToken استفاده کرده بودید، اگر اینجا accessToken است دقت کنید که یکسان باشند
const token = localStorage.getItem('accessToken') || localStorage.getItem('refreshToken'); const token = localStorage.getItem('accessToken') || localStorage.getItem('refreshToken');
if (token) { if (token) {
setIsAuthenticated(true); setIsAuthenticated(true);
// ۳. دریافت اطلاعات سبد خرید از سرور
try { try {
const data = await getCartApi(); const data = await getCartApi();
if (data && data.summary) { if (data && data.summary) {
@@ -59,47 +35,8 @@ export default function CheckoutPage() {
} catch (error) { } catch (error) {
console.error("خطا در دریافت اطلاعات سبد خرید:", 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 { } else {
setIsAuthenticated(false); setIsAuthenticated(false);
setIsAddressLoading(false);
} }
setIsLoading(false); setIsLoading(false);
}; };
@@ -107,149 +44,11 @@ export default function CheckoutPage() {
initializeCheckout(); 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) => { const parsePrice = (priceStr?: number | null | string) => {
if (!priceStr) return 0; if (!priceStr) return 0;
return Number(priceStr.toString().replace(/,/g, '')); return Number(priceStr.toString().replace(/,/g, ''));
}; };
// ۴. استفاده از اطلاعات سرور در صورت لاگین بودن، در غیر این صورت استفاده از Context
const totalPrice = isAuthenticated && serverSummary const totalPrice = isAuthenticated && serverSummary
? serverSummary.totalPrice || serverSummary.total || 0 ? serverSummary.totalPrice || serverSummary.total || 0
: cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 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 shippingCost = shippingMethod === 'post' ? 45000 : 75000;
const finalPrice = totalPrice + shippingCost; const finalPrice = totalPrice + shippingCost;
// حالت در حال بررسی توکن
if (isLoading) { if (isLoading) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50/30"> <div className="min-h-screen flex items-center justify-center bg-gray-50/30">
@@ -270,7 +68,6 @@ export default function CheckoutPage() {
); );
} }
// اگر کاربر لاگین نبود
if (!isAuthenticated) { if (!isAuthenticated) {
return ( return (
<div className="min-h-screen bg-gray-50/30 flex flex-col items-center justify-center p-6" dir="rtl"> <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 ( return (
<main className="bg-gray-50/30 min-h-screen pb-20" dir="rtl"> <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="container mx-auto px-4 py-8 max-w-6xl">
@@ -336,8 +132,8 @@ export default function CheckoutPage() {
</div> </div>
</div> </div>
</div> </div>
<div className="flex flex-col lg:flex-row gap-6 lg:gap-8"> <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">
<div className="flex-1 space-y-6"> <div className="flex-1 space-y-6">
{isAddressLoading ? ( {isAddressLoading ? (
@@ -348,8 +144,8 @@ export default function CheckoutPage() {
<> <>
{showNewAddressForm ? ( {showNewAddressForm ? (
<div className="bg-white rounded-[1rem] p-6 shadow-sm space-y-6 border border-gray-100"> <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"> <div className="flex justify-between items-center border-b border-gray-300 b-4">
<button className="text-lg cursor-pointer font-bold text-gray-800"> <button className="text-lg cursor-pointer mb-4 font-bold text-gray-800">
{editingAddressId ? 'ویرایش آدرس' : 'افزودن آدرس جدید'} {editingAddressId ? 'ویرایش آدرس' : 'افزودن آدرس جدید'}
</button> </button>
{addresses.length > 0 && ( {addresses.length > 0 && (
@@ -498,11 +294,7 @@ export default function CheckoutPage() {
<h2 className="text-lg font-bold text-gray-800">انتخاب آدرس ارسال</h2> <h2 className="text-lg font-bold text-gray-800">انتخاب آدرس ارسال</h2>
<button <button
type="button" type="button"
onClick={() => { onClick={() => setShowNewAddressForm(true)}
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" className="text-sm cursor-pointer text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1"
> >
<span>+</span> افزودن آدرس جدید <span>+</span> افزودن آدرس جدید
@@ -516,88 +308,16 @@ export default function CheckoutPage() {
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{addresses.map((address) => ( {addresses.map((address) => (
<label <AddressCard
key={address.id} key={address.id}
className={`relative flex flex-col gap-4 p-3 cursor-pointer rounded-2xl border transition-all duration-300 ${selectedAddressId === address.id address={address}
? '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>
))} ))}
</div> </div>
)} )}
</div> </div>
)} )}
</> </>
)} )}
<div className="bg-white rounded-[1rem] p-6 shadow-sm space-y-4"> <div className="bg-white rounded-[1rem] p-6 shadow-sm space-y-4">
@@ -637,10 +357,8 @@ export default function CheckoutPage() {
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="w-full lg:w-[400px] shrink-0"> <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"> <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> <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> <span className="font-bold text-[#1A2332]">{shippingCost.toLocaleString('fa-IR')} <span className="text-[10px] font-normal text-gray-500">تومان</span></span>
</div> </div>
</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 className="w-full border-t-2 border-dashed border-gray-100 my-6"></div>
<div> <div>
<PaymentMethodsSection /> <PaymentMethodsSection />
<span className="font-black mt-4 mb-4 justify-center items-center flex gap-4 text-[#1A2332] tracking-tight"> <span className="font-black mt-4 mb-4 justify-center items-center flex gap-4 text-[#1A2332] tracking-tight">

View File

@@ -6,8 +6,11 @@ import localFont from 'next/font/local';
import { CartProvider } from "@/components/context/cartcontext"; import { CartProvider } from "@/components/context/cartcontext";
import { categoryService } from "@/public/src/services/categories/api"; import { categoryService } from "@/public/src/services/categories/api";
import { CategoryProvider } from "@/components/context/categoryprovider"; import { CategoryProvider } from "@/components/context/categoryprovider";
import { AddressProvider } from "@/components/context/Addresscontext";
const Yekanbakh = localFont({ const Yekanbakh = localFont({
src: [ src: [
{ {
path: '../public/src/font/YekanBakhFaNum-Regular.woff', path: '../public/src/font/YekanBakhFaNum-Regular.woff',
@@ -35,6 +38,8 @@ export default async function RootLayout({
return ( return (
<html lang="fa" dir="rtl" className={Yekanbakh.variable}> <html lang="fa" dir="rtl" className={Yekanbakh.variable}>
<body> <body>
<AddressProvider>
<CartProvider> <CartProvider>
<CategoryProvider> <CategoryProvider>
@@ -43,6 +48,8 @@ export default async function RootLayout({
</CategoryProvider> </CategoryProvider>
</CartProvider> </CartProvider>
</AddressProvider>
<Footer/> <Footer/>
</body> </body>

View File

@@ -0,0 +1,95 @@
import React from 'react';
import { type Address } from "@/public/src/services/address/api";
// آدرس ایمپورت کانتکست را بر اساس مسیر فایل خود تنظیم کنید:
import { useAddressContext } from './context/Addresscontext';
interface AddressCardProps {
address: Address;
// فقط پراپ address باقی می‌ماند
}
export default function AddressCard({ address }: AddressCardProps) {
// دریافت وضعیت و توابع از کانتکست
const {
selectedAddressId,
setSelectedAddressId,
handleEditClick,
handleDeleteAddress
} = useAddressContext();
// محاسبه وضعیت انتخاب شدن بر اساس آیدی کارت و آیدی ذخیره شده در کانتکست
const isSelected = selectedAddressId === address.id;
return (
<label
className={`relative flex flex-col gap-4 p-3 cursor-pointer rounded-2xl border transition-all duration-300 ${
isSelected
? '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={isSelected}
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-[13px] 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>
);
}

View File

@@ -0,0 +1,155 @@
'use client';
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { fetchUserAddresses, addAddressApi, updateAddressApi, deleteAddressApi, type Address, type NewAddressData } from "@/public/src/services/address/api";
interface AddressContextType {
addresses: Address[];
selectedAddressId: string | null;
setSelectedAddressId: (id: string | null) => void;
isAddressLoading: boolean;
showNewAddressForm: boolean;
setShowNewAddressForm: (show: boolean) => void;
editingAddressId: string | null;
newAddress: NewAddressData;
handleAddressInputChange: (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void;
handleAddNewAddress: (e: React.FormEvent) => Promise<void>;
handleEditClick: (address: Address, e: any) => void;
handleUpdateAddress: () => Promise<void>;
handleCancelForm: () => void;
handleDeleteAddress: (addressId: string, e: React.MouseEvent) => Promise<void>;
fetchAddresses: () => Promise<void>;
}
export const AddressContext = createContext<AddressContextType | undefined>(undefined);
export const useAddressContext = () => {
const context = useContext(AddressContext);
if (!context) {
throw new Error("useAddressContext must be used within an AddressProvider");
}
return context;
};
export const AddressProvider = ({ children }: { children: ReactNode }) => {
const [addresses, setAddresses] = useState<Address[]>([]);
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null);
const [isAddressLoading, setIsAddressLoading] = useState(true);
const [showNewAddressForm, setShowNewAddressForm] = useState(false);
const [editingAddressId, setEditingAddressId] = useState<string | null>(null);
const initialAddressState: NewAddressData = {
title: 'خانه', recipientName: '', phone: '', province: '', city: '',
postalCode: '', addressLine: '', plaque: '', unit: '', isDefault: false
};
const [newAddress, setNewAddress] = useState<NewAddressData>(initialAddressState);
const fetchAddresses = async () => {
setIsAddressLoading(true);
try {
const token = localStorage.getItem('accessToken') || localStorage.getItem('refreshToken');
if (!token) {
setIsAddressLoading(false);
return;
}
const addressData = await fetchUserAddresses();
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);
}
};
useEffect(() => {
fetchAddresses();
}, []);
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(initialAddressState);
} catch (error) {
console.error("خطا در ذخیره آدرس:", error);
}
};
const handleEditClick = (address: any, e: any) => {
e.preventDefault();
e.stopPropagation();
setEditingAddressId(address.id);
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);
};
const handleUpdateAddress = async () => {
if (!editingAddressId) return;
try {
const response = await updateAddressApi(editingAddressId, newAddress);
if (response.success) {
setAddresses(prev => prev.map(addr => addr.id === editingAddressId ? response.data : addr));
setShowNewAddressForm(false);
setEditingAddressId(null);
setNewAddress(initialAddressState);
}
} catch (error) {
console.error("خطا در به‌روزرسانی آدرس:", error);
}
};
const handleCancelForm = () => {
setShowNewAddressForm(false);
setEditingAddressId(null);
setNewAddress(initialAddressState);
};
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) {
setAddresses(prev => prev.filter(addr => addr.id !== addressId));
if (selectedAddressId === addressId) setSelectedAddressId(null);
if (editingAddressId === addressId) {
setShowNewAddressForm(false);
setEditingAddressId(null);
setNewAddress(initialAddressState);
}
}
} catch (error) {
console.error("خطا در حذف آدرس:", error);
}
};
const value = {
addresses, selectedAddressId, setSelectedAddressId, isAddressLoading,
showNewAddressForm, setShowNewAddressForm, editingAddressId, newAddress,
handleAddressInputChange, handleAddNewAddress, handleEditClick,
handleUpdateAddress, handleCancelForm, handleDeleteAddress, fetchAddresses
};
return <AddressContext.Provider value={value}>{children}</AddressContext.Provider>;
};