From 426334e27f7488b45d06d90672dfd57f790141e2 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Sat, 1 Aug 2026 11:37:04 +0330 Subject: [PATCH] feat(checkout): add copy buttons for card info, province/city dropdowns, and wallet top-up button --- .../application/components/AddressModal.tsx | 60 +++-- .../application/components/CheckoutPage.tsx | 222 ++++++++++++++---- frontend/application/lib/data/provinces.ts | 55 +++++ 3 files changed, 279 insertions(+), 58 deletions(-) create mode 100644 frontend/application/lib/data/provinces.ts diff --git a/frontend/application/components/AddressModal.tsx b/frontend/application/components/AddressModal.tsx index 09ce186..2d80c39 100644 --- a/frontend/application/components/AddressModal.tsx +++ b/frontend/application/components/AddressModal.tsx @@ -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 */}
- 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")} + > + + {IRAN_PROVINCES.map(prov => ( + + ))} + {errors.province &&

{errors.province}

}
+
- 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] ? ( + + ) : ( + 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 &&

{errors.city}

}
diff --git a/frontend/application/components/CheckoutPage.tsx b/frontend/application/components/CheckoutPage.tsx index d19c2cd..b731b9b 100644 --- a/frontend/application/components/CheckoutPage.tsx +++ b/frontend/application/components/CheckoutPage.tsx @@ -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([]); const [paymentMethod, setPaymentMethod] = useState('card'); + const [showTopUpModal, setShowTopUpModal] = useState(false); + const [showAddressModal, setShowAddressModal] = useState(false); const [selectedAddressId, setSelectedAddressId] = useState(''); - 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() {

اطلاعات ارسال

- {isLoggedIn && profile.addresses && profile.addresses.length > 0 && !showNewAddressForm ? ( + {isLoggedIn && profile.addresses && profile.addresses.length > 0 ? (
-

انتخاب از بین آدرس‌های ذخیره‌شده شما:

+
+

انتخاب آدرس جهت تحویل سفارش:

+ +
{profile.addresses.map((addr) => (
))}
-
) : (
- {profile.addresses && profile.addresses.length > 0 && ( - + {isLoggedIn && ( +
+ +
)}
- +
- + 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" />
+
+ + +
+
+ + {manualProvince && PROVINCE_CITIES[manualProvince] ? ( + + ) : ( + 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="نام شهر..." + /> + )} +
- +