feat(checkout): add copy buttons for card info, province/city dropdowns, and wallet top-up button
Some checks failed
Deploy Canina / deploy (push) Has been cancelled
Some checks failed
Deploy Canina / deploy (push) Has been cancelled
This commit is contained in:
parent
05fe9b5594
commit
426334e27f
@ -4,6 +4,7 @@ import { motion, AnimatePresence } from "motion/react";
|
||||
import { X, MapPin, User, Phone, Hash, Save, Check } from "lucide-react";
|
||||
import { toPersian, cn } from "../lib/utils";
|
||||
import { Address } from "../lib/store/userStore";
|
||||
import { IRAN_PROVINCES, PROVINCE_CITIES } from "../lib/data/provinces";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface AddressModalProps {
|
||||
@ -205,30 +206,55 @@ export default function AddressModal({ isOpen, onClose, onSave, editingAddress }
|
||||
{/* Row 3: Province & City */}
|
||||
<div className="space-y-1">
|
||||
<label htmlFor="addr-province" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">استان</label>
|
||||
<input
|
||||
<select
|
||||
id="addr-province"
|
||||
name="address-level1"
|
||||
autoComplete="address-level1"
|
||||
type="text"
|
||||
value={formData.province}
|
||||
onChange={e => setFormData({...formData, province: e.target.value})}
|
||||
className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20", errors.province ? "border-red-300" : "border-medical-gray-100")}
|
||||
placeholder="نام استان..."
|
||||
/>
|
||||
onChange={e => {
|
||||
const newProv = e.target.value;
|
||||
const cities = PROVINCE_CITIES[newProv] || [];
|
||||
setFormData({
|
||||
...formData,
|
||||
province: newProv,
|
||||
city: cities[0] || ""
|
||||
});
|
||||
}}
|
||||
className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20 bg-white", errors.province ? "border-red-300" : "border-medical-gray-100")}
|
||||
>
|
||||
<option value="">انتخاب استان...</option>
|
||||
{IRAN_PROVINCES.map(prov => (
|
||||
<option key={prov} value={prov}>{prov}</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.province && <p className="text-[10px] text-red-500 font-bold pr-2">{errors.province}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label htmlFor="addr-city" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">شهر</label>
|
||||
<input
|
||||
id="addr-city"
|
||||
name="address-level2"
|
||||
autoComplete="address-level2"
|
||||
type="text"
|
||||
value={formData.city}
|
||||
onChange={e => setFormData({...formData, city: e.target.value})}
|
||||
className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20", errors.city ? "border-red-300" : "border-medical-gray-100")}
|
||||
placeholder="نام شهر..."
|
||||
/>
|
||||
{formData.province && PROVINCE_CITIES[formData.province] ? (
|
||||
<select
|
||||
id="addr-city"
|
||||
name="address-level2"
|
||||
value={formData.city}
|
||||
onChange={e => setFormData({...formData, city: e.target.value})}
|
||||
className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20 bg-white", errors.city ? "border-red-300" : "border-medical-gray-100")}
|
||||
>
|
||||
<option value="">انتخاب شهر...</option>
|
||||
{PROVINCE_CITIES[formData.province].map(c => (
|
||||
<option key={c} value={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
id="addr-city"
|
||||
name="address-level2"
|
||||
type="text"
|
||||
value={formData.city}
|
||||
onChange={e => setFormData({...formData, city: e.target.value})}
|
||||
className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20", errors.city ? "border-red-300" : "border-medical-gray-100")}
|
||||
placeholder="نام شهر..."
|
||||
/>
|
||||
)}
|
||||
{errors.city && <p className="text-[10px] text-red-500 font-bold pr-2">{errors.city}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -14,15 +14,16 @@ import {
|
||||
ArrowRight,
|
||||
CheckCircle2,
|
||||
Heart,
|
||||
PlusCircle
|
||||
PlusCircle,
|
||||
Copy
|
||||
} from "lucide-react";
|
||||
import { toPersian, cn } from "../lib/utils";
|
||||
import { useCartStore } from "../lib/store/cartStore";
|
||||
import { usePetStore } from "../lib/store/usePetStore";
|
||||
import { useUserStore } from "../lib/store/userStore";
|
||||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||||
import { Product, PRODUCTS } from "../lib/data/products";
|
||||
import { productService } from "../lib/services/productService";
|
||||
import TopUpModal from "./TopUpModal";
|
||||
import AddressModal from "./AddressModal";
|
||||
import { IRAN_PROVINCES, PROVINCE_CITIES } from "../lib/data/provinces";
|
||||
import { toast } from "sonner";
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
@ -36,15 +37,23 @@ export default function CheckoutPage() {
|
||||
const [useRoundUp, setUseRoundUp] = useState(false);
|
||||
const [dbProducts, setDbProducts] = useState<Product[]>([]);
|
||||
const [paymentMethod, setPaymentMethod] = useState<string>('card');
|
||||
const [showTopUpModal, setShowTopUpModal] = useState(false);
|
||||
const [showAddressModal, setShowAddressModal] = useState(false);
|
||||
|
||||
const [selectedAddressId, setSelectedAddressId] = useState<string>('');
|
||||
const [showNewAddressForm, setShowNewAddressForm] = useState(false);
|
||||
|
||||
// Custom manual address state if guest or not using stored addresses
|
||||
const [manualName, setManualName] = useState('');
|
||||
const [manualPhone, setManualPhone] = useState('');
|
||||
const [manualProvince, setManualProvince] = useState('');
|
||||
const [manualCity, setManualCity] = useState('');
|
||||
const [manualAddress, setManualAddress] = useState('');
|
||||
|
||||
const handleCopy = (text: string, title: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
toast.success(`${title} با موفقیت کپی شد!`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
productService.getProducts({ limit: 999 }).then(res => setDbProducts(res.data));
|
||||
|
||||
@ -106,9 +115,13 @@ export default function CheckoutPage() {
|
||||
|
||||
// 2. Register the order in the backend
|
||||
const selectedAddr = profile.addresses?.find(a => a.id === selectedAddressId);
|
||||
const shippingAddressStr = selectedAddr
|
||||
? `${selectedAddr.province}، ${selectedAddr.city}، ${selectedAddr.detail} (گیرنده: ${selectedAddr.receptorName} - ${toPersian(selectedAddr.phone)})`
|
||||
: manualAddress || undefined;
|
||||
let shippingAddressStr = '';
|
||||
if (selectedAddr) {
|
||||
shippingAddressStr = `${selectedAddr.province}، ${selectedAddr.city}، ${selectedAddr.detail} (گیرنده: ${selectedAddr.receptorName} - ${toPersian(selectedAddr.phone)})`;
|
||||
} else if (manualAddress) {
|
||||
const provCity = [manualProvince, manualCity].filter(Boolean).join('، ');
|
||||
shippingAddressStr = `${provCity ? provCity + '، ' : ''}${manualAddress}${manualName ? ` (گیرنده: ${manualName}${manualPhone ? ` - ${toPersian(manualPhone)}` : ''})` : ''}`;
|
||||
}
|
||||
|
||||
const orderId = await addOrder({
|
||||
items: [...items],
|
||||
@ -211,9 +224,19 @@ export default function CheckoutPage() {
|
||||
<h2 className="text-xl sm:text-2xl font-black text-medical-gray-900 italic">اطلاعات ارسال</h2>
|
||||
</div>
|
||||
|
||||
{isLoggedIn && profile.addresses && profile.addresses.length > 0 && !showNewAddressForm ? (
|
||||
{isLoggedIn && profile.addresses && profile.addresses.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs font-bold text-medical-gray-500 mb-2">انتخاب از بین آدرسهای ذخیرهشده شما:</p>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<p className="text-xs font-bold text-medical-gray-500">انتخاب آدرس جهت تحویل سفارش:</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddressModal(true)}
|
||||
className="flex items-center gap-1.5 text-canina-blue font-black text-xs hover:underline bg-canina-blue/5 border border-canina-blue/20 px-3 py-1.5 rounded-xl transition-all"
|
||||
>
|
||||
<PlusCircle className="w-4 h-4" />
|
||||
<span>+ افزودن آدرس جدید</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
{profile.addresses.map((addr) => (
|
||||
<div
|
||||
@ -244,27 +267,24 @@ export default function CheckoutPage() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowNewAddressForm(true)}
|
||||
className="mt-4 flex items-center gap-2 text-canina-blue font-black text-xs hover:underline"
|
||||
>
|
||||
<PlusCircle className="w-4 h-4" />
|
||||
<span>استفاده از آدرس جدید</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{profile.addresses && profile.addresses.length > 0 && (
|
||||
<button
|
||||
onClick={() => setShowNewAddressForm(false)}
|
||||
className="mb-4 text-xs font-bold text-canina-blue hover:underline"
|
||||
>
|
||||
← بازگشت به آدرسهای ذخیرهشده
|
||||
</button>
|
||||
{isLoggedIn && (
|
||||
<div className="flex justify-end mb-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddressModal(true)}
|
||||
className="flex items-center gap-1.5 text-canina-blue font-black text-xs hover:underline bg-canina-blue/5 border border-canina-blue/20 px-3 py-1.5 rounded-xl transition-all"
|
||||
>
|
||||
<PlusCircle className="w-4 h-4" />
|
||||
<span>افزودن و ذخیره آدرس جدید</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid md:grid-cols-2 gap-4 sm:gap-6">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="chk-fullname" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">نام و نام خانوادگی</label>
|
||||
<label htmlFor="chk-fullname" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">نام و نام خانوادگی گیرنده *</label>
|
||||
<input
|
||||
id="chk-fullname"
|
||||
name="name"
|
||||
@ -277,7 +297,7 @@ export default function CheckoutPage() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="chk-tel" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">شماره تماس</label>
|
||||
<label htmlFor="chk-tel" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">شماره تماس گیرنده *</label>
|
||||
<input
|
||||
id="chk-tel"
|
||||
name="tel"
|
||||
@ -285,20 +305,64 @@ export default function CheckoutPage() {
|
||||
autoComplete="tel"
|
||||
value={manualPhone}
|
||||
onChange={(e) => setManualPhone(e.target.value.replace(/[^0-9]/g, ''))}
|
||||
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
|
||||
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 dir-ltr text-right"
|
||||
placeholder="۰۹۱۲XXXXXXX"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="chk-province" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">استان *</label>
|
||||
<select
|
||||
id="chk-province"
|
||||
value={manualProvince}
|
||||
onChange={(e) => {
|
||||
const prov = e.target.value;
|
||||
const cities = PROVINCE_CITIES[prov] || [];
|
||||
setManualProvince(prov);
|
||||
setManualCity(cities[0] || '');
|
||||
}}
|
||||
className="w-full bg-white border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 font-bold"
|
||||
>
|
||||
<option value="">انتخاب استان...</option>
|
||||
{IRAN_PROVINCES.map((prov) => (
|
||||
<option key={prov} value={prov}>{prov}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="chk-city" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">شهر *</label>
|
||||
{manualProvince && PROVINCE_CITIES[manualProvince] ? (
|
||||
<select
|
||||
id="chk-city"
|
||||
value={manualCity}
|
||||
onChange={(e) => setManualCity(e.target.value)}
|
||||
className="w-full bg-white border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 font-bold"
|
||||
>
|
||||
<option value="">انتخاب شهر...</option>
|
||||
{PROVINCE_CITIES[manualProvince].map((c) => (
|
||||
<option key={c} value={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
id="chk-city"
|
||||
type="text"
|
||||
value={manualCity}
|
||||
onChange={(e) => setManualCity(e.target.value)}
|
||||
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
|
||||
placeholder="نام شهر..."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="md:col-span-2 space-y-2">
|
||||
<label htmlFor="chk-street" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">آدرس دقیق پستی</label>
|
||||
<label htmlFor="chk-street" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">آدرس دقیق پستی *</label>
|
||||
<textarea
|
||||
id="chk-street"
|
||||
name="street-address"
|
||||
autoComplete="street-address"
|
||||
value={manualAddress}
|
||||
onChange={(e) => setManualAddress(e.target.value)}
|
||||
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 h-28 sm:h-32"
|
||||
placeholder="استان، شهر، خیابان..."
|
||||
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 h-24 sm:h-28"
|
||||
placeholder="خیابان، کوچه، پلاک، واحد..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -332,36 +396,86 @@ export default function CheckoutPage() {
|
||||
<div
|
||||
key={pay.id}
|
||||
onClick={() => setPaymentMethod(pay.id)}
|
||||
className={cn("p-4 sm:p-6 rounded-2xl sm:rounded-3xl border-2 cursor-pointer transition-all group flex sm:flex-col items-center sm:items-start gap-4 sm:gap-0", paymentMethod === pay.id ? "border-canina-blue bg-canina-blue/5" : "border-medical-gray-100 hover:border-canina-blue")}
|
||||
className={cn("p-4 sm:p-6 rounded-2xl sm:rounded-3xl border-2 cursor-pointer transition-all group flex sm:flex-col items-center sm:items-start gap-4 sm:gap-0 relative", paymentMethod === pay.id ? "border-canina-blue bg-canina-blue/5" : "border-medical-gray-100 hover:border-canina-blue")}
|
||||
>
|
||||
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-medical-gray-50 rounded-xl sm:rounded-2xl flex items-center justify-center text-medical-gray-400 group-hover:text-canina-blue group-hover:bg-canina-blue/10 sm:mb-4 transition-all flex-shrink-0">
|
||||
{pay.icon}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h4 className="font-black text-xs sm:text-sm text-medical-gray-900 mb-0.5 sm:mb-1 truncate">{pay.label}</h4>
|
||||
<p className={cn("text-[10px] font-bold truncate", pay.id === 'wallet' ? "text-canina-blue" : "text-medical-gray-400")}>{pay.desc}</p>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className={cn("text-[10px] font-bold truncate", pay.id === 'wallet' ? "text-canina-blue" : "text-medical-gray-400")}>{pay.desc}</p>
|
||||
{pay.id === 'wallet' && (profile.walletBalance || 0) === 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowTopUpModal(true);
|
||||
}}
|
||||
className="text-[10px] bg-canina-blue text-white px-2 py-0.5 rounded-lg font-bold hover:bg-blue-700 transition-colors flex items-center gap-1"
|
||||
>
|
||||
<PlusCircle className="w-3 h-3" />
|
||||
<span>افزایش موجودی</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{paymentMethod === 'card' && (
|
||||
<div className="mt-6 bg-blue-50/80 border border-blue-200 rounded-2xl p-5 space-y-3 font-shabnam">
|
||||
<div className="mt-6 bg-blue-50/80 border border-blue-200 rounded-2xl p-5 space-y-3 font-vazir">
|
||||
<div className="flex items-center gap-2 text-canina-blue font-bold text-sm">
|
||||
<CreditCard className="w-5 h-5" />
|
||||
<span>اطلاعات کارت جهت واریز وجه سفارش:</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs bg-white p-4 rounded-xl border border-blue-100 shadow-sm">
|
||||
<div>
|
||||
<span className="text-gray-500 block mb-1">شماره کارت (بانک سامان):</span>
|
||||
<span className="font-mono text-base font-extrabold text-blue-900 tracking-wider dir-ltr block">۶۲۱۹-۸۶۱۹-۷۴۰۱-۲۰۷۱</span>
|
||||
|
||||
{/* Card Number Box */}
|
||||
<div className="bg-gray-50/80 p-3 rounded-xl border border-gray-200/60 flex items-center justify-between group hover:border-canina-blue transition-all">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[11px] text-gray-500 font-bold block">شماره کارت (بانک سامان):</span>
|
||||
<span
|
||||
onClick={() => handleCopy("6219861974012071", "شماره کارت")}
|
||||
className="font-mono text-sm sm:text-base font-extrabold text-gray-900 tracking-wider dir-ltr block cursor-pointer hover:text-canina-blue transition-colors"
|
||||
>
|
||||
6219 - 8619 - 7401 - 2071
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCopy("6219861974012071", "شماره کارت")}
|
||||
className="p-2 rounded-lg bg-white border border-gray-200 text-gray-500 hover:text-canina-blue hover:border-canina-blue transition-all"
|
||||
title="کپی شماره کارت"
|
||||
>
|
||||
<Copy className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500 block mb-1">شماره شبا:</span>
|
||||
<span className="font-mono text-xs font-bold text-gray-800 dir-ltr block">IR-65 0560 6118 2800 5725 1015 01</span>
|
||||
|
||||
{/* Sheba Box */}
|
||||
<div className="bg-gray-50/80 p-3 rounded-xl border border-gray-200/60 flex items-center justify-between group hover:border-canina-blue transition-all">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[11px] text-gray-500 font-bold block">شماره شبا:</span>
|
||||
<span
|
||||
onClick={() => handleCopy("IR650560611828005725101501", "شماره شبا")}
|
||||
className="font-mono text-xs sm:text-sm font-extrabold text-gray-900 tracking-wider dir-ltr block cursor-pointer hover:text-canina-blue transition-colors"
|
||||
>
|
||||
IR-65 0560 6118 2800 5725 1015 01
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCopy("IR650560611828005725101501", "شماره شبا")}
|
||||
className="p-2 rounded-lg bg-white border border-gray-200 text-gray-500 hover:text-canina-blue hover:border-canina-blue transition-all"
|
||||
title="کپی شماره شبا"
|
||||
>
|
||||
<Copy className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="md:col-span-2 pt-2 border-t border-gray-100 flex items-center justify-between text-gray-700 font-bold">
|
||||
<span>به نام: پارسا آقایی</span>
|
||||
|
||||
<div className="md:col-span-2 pt-2 border-t border-gray-100 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 text-gray-700 font-bold">
|
||||
<span className="text-xs">به نام: <strong className="text-gray-900">پارسا آقایی</strong></span>
|
||||
<span className="text-canina-blue text-[11px]">پس از ثبت سفارش، کارشناسان فروش جهت تایید فیش با شما تماس میگیرند.</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -546,6 +660,32 @@ export default function CheckoutPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TopUpModal
|
||||
isOpen={showTopUpModal}
|
||||
onClose={() => setShowTopUpModal(false)}
|
||||
onConfirm={(amt) => {
|
||||
useUserStore.setState(state => ({
|
||||
profile: {
|
||||
...state.profile,
|
||||
walletBalance: (state.profile.walletBalance || 0) + amt
|
||||
}
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
|
||||
<AddressModal
|
||||
isOpen={showAddressModal}
|
||||
onClose={() => setShowAddressModal(false)}
|
||||
onSave={(newAddr) => {
|
||||
const currentAddresses = profile.addresses || [];
|
||||
const updatedAddresses = [newAddr, ...currentAddresses];
|
||||
updateProfile({ addresses: updatedAddresses });
|
||||
setSelectedAddressId(newAddr.id);
|
||||
setShowAddressModal(false);
|
||||
toast.success('آدرس جدید اضافه شد و به عنوان آدرس تحویل انتخاب گردید.');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
55
frontend/application/lib/data/provinces.ts
Normal file
55
frontend/application/lib/data/provinces.ts
Normal file
@ -0,0 +1,55 @@
|
||||
export const IRAN_PROVINCES = [
|
||||
"آذربایجان شرقی",
|
||||
"آذربایجان غربی",
|
||||
"اردبیل",
|
||||
"اصفهان",
|
||||
"البرز",
|
||||
"ایلام",
|
||||
"بوشهر",
|
||||
"تهران",
|
||||
"چهارمحال و بختیاری",
|
||||
"خراسان جنوبی",
|
||||
"خراسان رضوی",
|
||||
"خراسان شمالی",
|
||||
"خوزستان",
|
||||
"زنجان",
|
||||
"سمنان",
|
||||
"سیستان و بلوچستان",
|
||||
"فارس",
|
||||
"قزوین",
|
||||
"قم",
|
||||
"کردستان",
|
||||
"کرمان",
|
||||
"کرمانشاه",
|
||||
"کهگیلویه و بویراحمد",
|
||||
"گلستان",
|
||||
"گیلان",
|
||||
"لرستان",
|
||||
"مازندران",
|
||||
"مرکزی",
|
||||
"هرمزگان",
|
||||
"همدان",
|
||||
"یزد"
|
||||
];
|
||||
|
||||
export const PROVINCE_CITIES: Record<string, string[]> = {
|
||||
"تهران": ["تهران", "شهریار", "اسلامشهر", "قدس", "ملارد", "ورامین", "ری", "دماوند", "پردیس", "رباطکریم", "شمیرانات", "فشم"],
|
||||
"البرز": ["کرج", "فردیس", "هشتگرد", "نظرآباد", "اشتهارد", "طالقان", "چهارباغ"],
|
||||
"اصفهان": ["اصفهان", "کاشان", "خمینیشهر", "نجفآباد", "شاهینشهر", "شهرضا", "فولادشهر", "مبارکه"],
|
||||
"فارس": ["شیراز", "مرودشت", "جهرم", "فسا", "کازرون", "لار", "داراب", "آباده"],
|
||||
"خراسان رضوی": ["مشهد", "نیشابور", "سبزوار", "تربت حیدریه", "کاشمر", "قوچان", "چناران"],
|
||||
"آذربایجان شرقی": ["تبریز", "مراغه", "مرند", "میانه", "اهر", "بناب", "سهند"],
|
||||
"آذربایجان غربی": ["ارومیه", "خوی", "میاندوآب", "مهاباد", "بوکان", "سلماس", "پیرانشهر"],
|
||||
"مازندران": ["ساری", "بابل", "آمل", "قائمشهر", "بهشهر", "چالوس", "تنکابن", "نوشهر", "رامسر"],
|
||||
"گیلان": ["رشت", "بندر انزلی", "لاهیجان", "لنگرود", "تالش", "آستارا", "فومن", "صومعهسرا"],
|
||||
"خوزستان": ["اهواز", "دزفول", "آبادان", "خرمشهر", "ماهشهر", "اندیمشک", "ایذه", "شوشتر"],
|
||||
"یزد": ["یزد", "میبد", "اردکان", "بافق", "مهریز"],
|
||||
"قم": ["قم"],
|
||||
"قزوین": ["قزوین", "تاکستان", "الوند", "آبیک"],
|
||||
"سمنان": ["سمنان", "شاهرود", "دامغان", "گرمسار"],
|
||||
"کرمان": ["کرمان", "سیرجان", "رفسنجان", "جیرفت", "بم", "زرند"],
|
||||
"کرمانشاه": ["کرمانشاه", "اسلامآباد غرب", "کنگاور", "سنقر", "سرپل ذهاب"],
|
||||
"مرکزی": ["اراک", "ساوه", "خمینی", "محلات", "دلیجان"],
|
||||
"هرمزگان": ["بندرعباس", "قشم", "کیش", "میناب", "بندرلنگه", "حاجیآباد"],
|
||||
"همدان": ["همدان", "ملایر", "نهاوند", "تویسرکان"]
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user