Compare commits
4 Commits
d85e2f1d32
...
c9a9a18c83
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9a9a18c83 | ||
|
|
6e60804241 | ||
|
|
242181ab4d | ||
|
|
426334e27f |
@ -4,6 +4,7 @@ import { motion, AnimatePresence } from "motion/react";
|
|||||||
import { X, MapPin, User, Phone, Hash, Save, Check } from "lucide-react";
|
import { X, MapPin, User, Phone, Hash, Save, Check } from "lucide-react";
|
||||||
import { toPersian, cn } from "../lib/utils";
|
import { toPersian, cn } from "../lib/utils";
|
||||||
import { Address } from "../lib/store/userStore";
|
import { Address } from "../lib/store/userStore";
|
||||||
|
import { IRAN_PROVINCES, PROVINCE_CITIES } from "../lib/data/provinces";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface AddressModalProps {
|
interface AddressModalProps {
|
||||||
@ -205,30 +206,55 @@ export default function AddressModal({ isOpen, onClose, onSave, editingAddress }
|
|||||||
{/* Row 3: Province & City */}
|
{/* Row 3: Province & City */}
|
||||||
<div className="space-y-1">
|
<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>
|
<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"
|
id="addr-province"
|
||||||
name="address-level1"
|
name="address-level1"
|
||||||
autoComplete="address-level1"
|
|
||||||
type="text"
|
|
||||||
value={formData.province}
|
value={formData.province}
|
||||||
onChange={e => setFormData({...formData, province: e.target.value})}
|
onChange={e => {
|
||||||
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")}
|
const newProv = e.target.value;
|
||||||
placeholder="نام استان..."
|
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>}
|
{errors.province && <p className="text-[10px] text-red-500 font-bold pr-2">{errors.province}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<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>
|
<label htmlFor="addr-city" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">شهر</label>
|
||||||
<input
|
{formData.province && PROVINCE_CITIES[formData.province] ? (
|
||||||
id="addr-city"
|
<select
|
||||||
name="address-level2"
|
id="addr-city"
|
||||||
autoComplete="address-level2"
|
name="address-level2"
|
||||||
type="text"
|
value={formData.city}
|
||||||
value={formData.city}
|
onChange={e => setFormData({...formData, city: e.target.value})}
|
||||||
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")}
|
||||||
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="نام شهر..."
|
<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>}
|
{errors.city && <p className="text-[10px] text-red-500 font-bold pr-2">{errors.city}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,15 +14,19 @@ import {
|
|||||||
ArrowRight,
|
ArrowRight,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Heart,
|
Heart,
|
||||||
PlusCircle
|
PlusCircle,
|
||||||
|
Copy
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toPersian, cn } from "../lib/utils";
|
import { toPersian, cn } from "../lib/utils";
|
||||||
|
import { Product } from "../lib/data/products";
|
||||||
import { useCartStore } from "../lib/store/cartStore";
|
import { useCartStore } from "../lib/store/cartStore";
|
||||||
import { usePetStore } from "../lib/store/usePetStore";
|
import { usePetStore } from "../lib/store/usePetStore";
|
||||||
import { useUserStore } from "../lib/store/userStore";
|
import { useUserStore } from "../lib/store/userStore";
|
||||||
import { useSettingsStore } from "../lib/store/settingsStore";
|
import { useSettingsStore } from "../lib/store/settingsStore";
|
||||||
import { Product, PRODUCTS } from "../lib/data/products";
|
|
||||||
import { productService } from "../lib/services/productService";
|
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 { toast } from "sonner";
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
@ -36,15 +40,23 @@ export default function CheckoutPage() {
|
|||||||
const [useRoundUp, setUseRoundUp] = useState(false);
|
const [useRoundUp, setUseRoundUp] = useState(false);
|
||||||
const [dbProducts, setDbProducts] = useState<Product[]>([]);
|
const [dbProducts, setDbProducts] = useState<Product[]>([]);
|
||||||
const [paymentMethod, setPaymentMethod] = useState<string>('card');
|
const [paymentMethod, setPaymentMethod] = useState<string>('card');
|
||||||
|
const [showTopUpModal, setShowTopUpModal] = useState(false);
|
||||||
|
const [showAddressModal, setShowAddressModal] = useState(false);
|
||||||
|
|
||||||
const [selectedAddressId, setSelectedAddressId] = useState<string>('');
|
const [selectedAddressId, setSelectedAddressId] = useState<string>('');
|
||||||
const [showNewAddressForm, setShowNewAddressForm] = useState(false);
|
|
||||||
|
|
||||||
// Custom manual address state if guest or not using stored addresses
|
// Custom manual address state if guest or not using stored addresses
|
||||||
const [manualName, setManualName] = useState('');
|
const [manualName, setManualName] = useState('');
|
||||||
const [manualPhone, setManualPhone] = useState('');
|
const [manualPhone, setManualPhone] = useState('');
|
||||||
|
const [manualProvince, setManualProvince] = useState('');
|
||||||
|
const [manualCity, setManualCity] = useState('');
|
||||||
const [manualAddress, setManualAddress] = useState('');
|
const [manualAddress, setManualAddress] = useState('');
|
||||||
|
|
||||||
|
const handleCopy = (text: string, title: string) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
toast.success(`${title} با موفقیت کپی شد!`);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
productService.getProducts({ limit: 999 }).then(res => setDbProducts(res.data));
|
productService.getProducts({ limit: 999 }).then(res => setDbProducts(res.data));
|
||||||
|
|
||||||
@ -106,9 +118,13 @@ export default function CheckoutPage() {
|
|||||||
|
|
||||||
// 2. Register the order in the backend
|
// 2. Register the order in the backend
|
||||||
const selectedAddr = profile.addresses?.find(a => a.id === selectedAddressId);
|
const selectedAddr = profile.addresses?.find(a => a.id === selectedAddressId);
|
||||||
const shippingAddressStr = selectedAddr
|
let shippingAddressStr = '';
|
||||||
? `${selectedAddr.province}، ${selectedAddr.city}، ${selectedAddr.detail} (گیرنده: ${selectedAddr.receptorName} - ${toPersian(selectedAddr.phone)})`
|
if (selectedAddr) {
|
||||||
: manualAddress || undefined;
|
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({
|
const orderId = await addOrder({
|
||||||
items: [...items],
|
items: [...items],
|
||||||
@ -211,9 +227,19 @@ export default function CheckoutPage() {
|
|||||||
<h2 className="text-xl sm:text-2xl font-black text-medical-gray-900 italic">اطلاعات ارسال</h2>
|
<h2 className="text-xl sm:text-2xl font-black text-medical-gray-900 italic">اطلاعات ارسال</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoggedIn && profile.addresses && profile.addresses.length > 0 && !showNewAddressForm ? (
|
{isLoggedIn && profile.addresses && profile.addresses.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<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">
|
<div className="grid gap-3">
|
||||||
{profile.addresses.map((addr) => (
|
{profile.addresses.map((addr) => (
|
||||||
<div
|
<div
|
||||||
@ -244,27 +270,24 @@ export default function CheckoutPage() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</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>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{profile.addresses && profile.addresses.length > 0 && (
|
{isLoggedIn && (
|
||||||
<button
|
<div className="flex justify-end mb-2">
|
||||||
onClick={() => setShowNewAddressForm(false)}
|
<button
|
||||||
className="mb-4 text-xs font-bold text-canina-blue hover:underline"
|
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"
|
||||||
</button>
|
>
|
||||||
|
<PlusCircle className="w-4 h-4" />
|
||||||
|
<span>افزودن و ذخیره آدرس جدید</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid md:grid-cols-2 gap-4 sm:gap-6">
|
<div className="grid md:grid-cols-2 gap-4 sm:gap-6">
|
||||||
<div className="space-y-2">
|
<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
|
<input
|
||||||
id="chk-fullname"
|
id="chk-fullname"
|
||||||
name="name"
|
name="name"
|
||||||
@ -277,7 +300,7 @@ export default function CheckoutPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<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
|
<input
|
||||||
id="chk-tel"
|
id="chk-tel"
|
||||||
name="tel"
|
name="tel"
|
||||||
@ -285,20 +308,64 @@ export default function CheckoutPage() {
|
|||||||
autoComplete="tel"
|
autoComplete="tel"
|
||||||
value={manualPhone}
|
value={manualPhone}
|
||||||
onChange={(e) => setManualPhone(e.target.value.replace(/[^0-9]/g, ''))}
|
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"
|
placeholder="۰۹۱۲XXXXXXX"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<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
|
<textarea
|
||||||
id="chk-street"
|
id="chk-street"
|
||||||
name="street-address"
|
name="street-address"
|
||||||
autoComplete="street-address"
|
autoComplete="street-address"
|
||||||
value={manualAddress}
|
value={manualAddress}
|
||||||
onChange={(e) => setManualAddress(e.target.value)}
|
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"
|
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="استان، شهر، خیابان..."
|
placeholder="خیابان، کوچه، پلاک، واحد..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -332,36 +399,86 @@ export default function CheckoutPage() {
|
|||||||
<div
|
<div
|
||||||
key={pay.id}
|
key={pay.id}
|
||||||
onClick={() => setPaymentMethod(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">
|
<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}
|
{pay.icon}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<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>
|
<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>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{paymentMethod === 'card' && (
|
{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">
|
<div className="flex items-center gap-2 text-canina-blue font-bold text-sm">
|
||||||
<CreditCard className="w-5 h-5" />
|
<CreditCard className="w-5 h-5" />
|
||||||
<span>اطلاعات کارت جهت واریز وجه سفارش:</span>
|
<span>اطلاعات کارت جهت واریز وجه سفارش:</span>
|
||||||
</div>
|
</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 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>
|
{/* Card Number Box */}
|
||||||
<span className="font-mono text-base font-extrabold text-blue-900 tracking-wider dir-ltr block">۶۲۱۹-۸۶۱۹-۷۴۰۱-۲۰۷۱</span>
|
<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>
|
||||||
<div>
|
|
||||||
<span className="text-gray-500 block mb-1">شماره شبا:</span>
|
{/* Sheba Box */}
|
||||||
<span className="font-mono text-xs font-bold text-gray-800 dir-ltr block">IR-65 0560 6118 2800 5725 1015 01</span>
|
<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>
|
||||||
<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>
|
<span className="text-canina-blue text-[11px]">پس از ثبت سفارش، کارشناسان فروش جهت تایید فیش با شما تماس میگیرند.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -546,6 +663,32 @@ export default function CheckoutPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</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