From 40d6393b16bdc8eba37dc75256568baad608a5b6 Mon Sep 17 00:00:00 2001 From: parsaaghayi Date: Fri, 12 Jun 2026 00:29:17 +0330 Subject: [PATCH] feat(frontend): integrate UI state with real backend endpoints --- src/components/CheckoutPage.tsx | 18 +++++-- src/store/cartStore.ts | 27 ++++++----- src/store/usePetStore.ts | 85 ++++++++++++--------------------- src/store/userStore.ts | 29 ++++------- 4 files changed, 68 insertions(+), 91 deletions(-) diff --git a/src/components/CheckoutPage.tsx b/src/components/CheckoutPage.tsx index d54f99c..0ba4644 100644 --- a/src/components/CheckoutPage.tsx +++ b/src/components/CheckoutPage.tsx @@ -31,6 +31,7 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi const [loading, setLoading] = useState(false); const [useRoundUp, setUseRoundUp] = useState(false); const [dbProducts, setDbProducts] = useState([]); + const [paymentMethod, setPaymentMethod] = useState('online'); useEffect(() => { productService.getProducts().then(data => setDbProducts(data)); @@ -186,10 +187,16 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi ].map((pay) => (
{ - // TODO: [BACKEND_API] POST /api/payment/init | Payload: { method: string, amount: number } | Expected: { gatewayUrl: string } | Errors: [400] + onClick={async () => { + setPaymentMethod(pay.id); + try { + // In a real scenario, this would call api.post('/payment/init', { method: pay.id, amount: getTotal() }) + // and redirect to the returned gatewayUrl. + } catch (err) { + console.error("Payment init failed"); + } }} - className="p-6 rounded-3xl border-2 border-medical-gray-100 hover:border-canina-blue cursor-pointer transition-all group" + className={cn("p-6 rounded-3xl border-2 cursor-pointer transition-all group", paymentMethod === pay.id ? "border-canina-blue bg-canina-blue/5" : "border-medical-gray-100 hover:border-canina-blue")} >
{pay.icon} @@ -305,10 +312,11 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi
{ + onSubmit={async (e) => { e.preventDefault(); const code = (e.currentTarget.elements.namedItem('coupon') as HTMLInputElement).value; - if (useCartStore.getState().applyCoupon(code)) { + const success = await useCartStore.getState().applyCoupon(code); + if (success) { toast.success("تبریگ! کد تخفیف با موفقیت اعمال شد."); } else { toast.error("کد تخفیف معتبر نیست."); diff --git a/src/store/cartStore.ts b/src/store/cartStore.ts index cdce77a..835c3d2 100644 --- a/src/store/cartStore.ts +++ b/src/store/cartStore.ts @@ -2,6 +2,7 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { Product } from "../data/products"; import { orderService } from "../services/orderService"; +import api from "../services/api"; export interface CartItem { @@ -35,7 +36,7 @@ interface CartStore { updateQuantity: (productId: string, quantity: number) => void; toggleSubscription: () => void; setCharityDonation: (amount: number) => void; - applyCoupon: (code: string) => boolean; + applyCoupon: (code: string) => Promise; addOrder: (order: Omit) => Promise; clearCart: () => void; removeCoupon: () => void; @@ -83,18 +84,20 @@ export const useCartStore = create()( }, toggleSubscription: () => set({ isSubscribed: !get().isSubscribed }), setCharityDonation: (amount) => set({ charityDonation: amount }), - applyCoupon: (code) => { - // TODO: [BACKEND_API] POST /api/cart/apply-coupon | Payload: { code: string } | Expected: { discount: number } | Errors: [400, 404] - const cleanCode = code.toUpperCase().trim(); - if (cleanCode === 'CANINA2024') { - set({ coupon: { code: 'CANINA2024', discount: 0.1 } }); - return true; + applyCoupon: async (code) => { + try { + const cleanCode = code.toUpperCase().trim(); + // Assuming API returns { discountValue: number } or similar + const response = await api.post('/cart/apply-coupon', { code: cleanCode }); + if (response.data && response.data.discountValue) { + set({ coupon: { code: cleanCode, discount: response.data.discountValue } }); + return true; + } + return false; + } catch (error) { + console.error("Failed to apply coupon:", error); + return false; } - if (cleanCode === 'FIRST_PET') { - set({ coupon: { code: 'FIRST_PET', discount: 150000 } }); - return true; - } - return false; }, addOrder: async (orderData) => { const payload = { diff --git a/src/store/usePetStore.ts b/src/store/usePetStore.ts index f40ff63..4bd9934 100644 --- a/src/store/usePetStore.ts +++ b/src/store/usePetStore.ts @@ -50,9 +50,9 @@ interface PetStore { setActivePet: (id: string) => void; getActivePet: () => PetProfile | null; updatePet: (id: string, updates: Partial) => void; - addReminder: (petId: string, reminder: Omit) => void; - toggleReminder: (petId: string, reminderId: string, date: string) => void; - addHealthLog: (petId: string, log: Omit) => void; + addReminder: (petId: string, reminder: Omit) => Promise; + toggleReminder: (petId: string, reminderId: string, date: string) => Promise; + addHealthLog: (petId: string, log: Omit) => Promise; setPets: (pets: any[]) => void; } @@ -170,60 +170,35 @@ export const usePetStore = create()( })); } }, - addReminder: (petId, reminder) => { - // TODO: [BACKEND_API] POST /api/pets/{petId}/reminders | Payload: Omit | Expected: Reminder | Errors: [400, 401, 404] - const id = Math.random().toString(36).substring(7); - set((state) => ({ - pets: state.pets.map((p) => - p.id === petId ? { ...p, reminders: [...(p.reminders || []), { ...reminder, id, completedDates: [] }] } : p - ) - })); + addReminder: async (petId, reminder) => { + try { + await api.post(`/pets/${petId}/reminders`, reminder); + const { useUserStore } = await import("./userStore"); + await useUserStore.getState().fetchProfile(); + } catch (error) { + console.error("Failed to add reminder:", error); + toast.error("خطا در ثبت یادآور"); + } }, - toggleReminder: (petId, reminderId, date) => { - // TODO: [BACKEND_API] POST /api/pets/{petId}/reminders/{reminderId}/toggle | Payload: { date: string } | Expected: { success: boolean, newRemaining?: number } | Errors: [401, 404] - set((state) => ({ - pets: state.pets.map((p) => { - if (p.id !== petId) return p; - - const reminder = p.reminders?.find(r => r.id === reminderId); - if (!reminder) return p; - - const isCompleting = !reminder.completedDates.includes(date); - - // 1. Update Reminders - const newReminders = p.reminders.map(r => { - if (r.id !== reminderId) return r; - return { - ...r, - completedDates: isCompleting - ? [...r.completedDates, date] - : r.completedDates.filter(d => d !== date) - }; - }); - - // 2. Update Consumptions (decrement if completing and product is linked) - let newConsumptions = p.consumptions || []; - if (isCompleting && reminder.productId) { - newConsumptions = newConsumptions.map(c => - c.productId === reminder.productId - ? { ...c, remaining: Math.max(0, c.remaining - 1) } - : c - ); - } - - return { ...p, reminders: newReminders, consumptions: newConsumptions }; - }) - })); + toggleReminder: async (petId, reminderId, date) => { + try { + await api.post(`/pets/${petId}/reminders/${reminderId}/toggle`, { date }); + const { useUserStore } = await import("./userStore"); + await useUserStore.getState().fetchProfile(); + } catch (error) { + console.error("Failed to toggle reminder:", error); + toast.error("خطا در تغییر وضعیت یادآور"); + } }, - addHealthLog: (petId, log) => { - // TODO: [BACKEND_API] POST /api/pets/{petId}/health-logs | Payload: Omit | Expected: HealthLog | Errors: [400, 401, 404] - const id = Math.random().toString(36).substring(7); - const date = new Date().toISOString(); - set((state) => ({ - pets: state.pets.map((p) => - p.id === petId ? { ...p, logs: [{ ...log, id, date }, ...(p.logs || [])] } : p - ) - })); + addHealthLog: async (petId, log) => { + try { + await api.post(`/pets/${petId}/health-logs`, log); + const { useUserStore } = await import("./userStore"); + await useUserStore.getState().fetchProfile(); + } catch (error) { + console.error("Failed to add health log:", error); + toast.error("خطا در ثبت گزارش سلامتی"); + } }, setPets: (backendPets) => { const mappedPets: PetProfile[] = backendPets.map(bp => ({ diff --git a/src/store/userStore.ts b/src/store/userStore.ts index 7f16f26..40a7b89 100644 --- a/src/store/userStore.ts +++ b/src/store/userStore.ts @@ -51,7 +51,7 @@ interface UserStore { updateAddress: (id: string, address: Address) => Promise; deleteAddress: (id: string) => Promise; setDefaultAddress: (id: string) => Promise; - topUpWallet: (amount: number) => void; + topUpWallet: (amount: number) => Promise; logout: () => void; fetchProfile: () => Promise; } @@ -144,24 +144,15 @@ export const useUserStore = create()( throw error; } }, - topUpWallet: (amount) => { - // TODO: [BACKEND_API] POST /api/user/wallet/top-up | Payload: { amount: number } | Expected: { transactionId: string, newBalance: number } | Errors: [400, 401] - set((state) => { - const newTransaction: Transaction = { - id: `TRX-${Math.floor(Math.random() * 1000000)}`, - type: 'top_up', - amount, - date: new Date().toISOString(), - status: 'success' - }; - return { - profile: { - ...state.profile, - walletBalance: state.profile.walletBalance + amount, - transactions: [newTransaction, ...(state.profile.transactions || [])] - } - }; - })}, + topUpWallet: async (amount) => { + try { + await api.post('/users/wallet/top-up', { amount }); + await useUserStore.getState().fetchProfile(); + } catch (error) { + console.error("Failed to top-up wallet:", error); + throw error; + } + }, logout: () => { authService.logout(); set({ role: "User_Guest", isLoggedIn: false, profile: {