feat(frontend): integrate UI state with real backend endpoints

This commit is contained in:
پارسا آقایی 2026-06-12 00:29:17 +03:30
parent 4b69932409
commit 40d6393b16
4 changed files with 68 additions and 91 deletions

View File

@ -31,6 +31,7 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
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>('online');
useEffect(() => { useEffect(() => {
productService.getProducts().then(data => setDbProducts(data)); productService.getProducts().then(data => setDbProducts(data));
@ -186,10 +187,16 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi
].map((pay) => ( ].map((pay) => (
<div <div
key={pay.id} key={pay.id}
onClick={() => { onClick={async () => {
// TODO: [BACKEND_API] POST /api/payment/init | Payload: { method: string, amount: number } | Expected: { gatewayUrl: string } | Errors: [400] 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")}
> >
<div className="w-12 h-12 bg-medical-gray-50 rounded-2xl flex items-center justify-center text-medical-gray-400 group-hover:text-canina-blue group-hover:bg-canina-blue/10 mb-4 transition-all"> <div className="w-12 h-12 bg-medical-gray-50 rounded-2xl flex items-center justify-center text-medical-gray-400 group-hover:text-canina-blue group-hover:bg-canina-blue/10 mb-4 transition-all">
{pay.icon} {pay.icon}
@ -305,10 +312,11 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi
<div className="mt-8 pt-8 border-t border-white/10"> <div className="mt-8 pt-8 border-t border-white/10">
<form <form
className="flex gap-2" className="flex gap-2"
onSubmit={(e) => { onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
const code = (e.currentTarget.elements.namedItem('coupon') as HTMLInputElement).value; 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("تبریگ! کد تخفیف با موفقیت اعمال شد."); toast.success("تبریگ! کد تخفیف با موفقیت اعمال شد.");
} else { } else {
toast.error("کد تخفیف معتبر نیست."); toast.error("کد تخفیف معتبر نیست.");

View File

@ -2,6 +2,7 @@ import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import { Product } from "../data/products"; import { Product } from "../data/products";
import { orderService } from "../services/orderService"; import { orderService } from "../services/orderService";
import api from "../services/api";
export interface CartItem { export interface CartItem {
@ -35,7 +36,7 @@ interface CartStore {
updateQuantity: (productId: string, quantity: number) => void; updateQuantity: (productId: string, quantity: number) => void;
toggleSubscription: () => void; toggleSubscription: () => void;
setCharityDonation: (amount: number) => void; setCharityDonation: (amount: number) => void;
applyCoupon: (code: string) => boolean; applyCoupon: (code: string) => Promise<boolean>;
addOrder: (order: Omit<Order, 'id' | 'date' | 'status' | 'trackingNumber'>) => Promise<string>; addOrder: (order: Omit<Order, 'id' | 'date' | 'status' | 'trackingNumber'>) => Promise<string>;
clearCart: () => void; clearCart: () => void;
removeCoupon: () => void; removeCoupon: () => void;
@ -83,18 +84,20 @@ export const useCartStore = create<CartStore>()(
}, },
toggleSubscription: () => set({ isSubscribed: !get().isSubscribed }), toggleSubscription: () => set({ isSubscribed: !get().isSubscribed }),
setCharityDonation: (amount) => set({ charityDonation: amount }), setCharityDonation: (amount) => set({ charityDonation: amount }),
applyCoupon: (code) => { applyCoupon: async (code) => {
// TODO: [BACKEND_API] POST /api/cart/apply-coupon | Payload: { code: string } | Expected: { discount: number } | Errors: [400, 404] try {
const cleanCode = code.toUpperCase().trim(); const cleanCode = code.toUpperCase().trim();
if (cleanCode === 'CANINA2024') { // Assuming API returns { discountValue: number } or similar
set({ coupon: { code: 'CANINA2024', discount: 0.1 } }); const response = await api.post('/cart/apply-coupon', { code: cleanCode });
return true; 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) => { addOrder: async (orderData) => {
const payload = { const payload = {

View File

@ -50,9 +50,9 @@ interface PetStore {
setActivePet: (id: string) => void; setActivePet: (id: string) => void;
getActivePet: () => PetProfile | null; getActivePet: () => PetProfile | null;
updatePet: (id: string, updates: Partial<PetProfile>) => void; updatePet: (id: string, updates: Partial<PetProfile>) => void;
addReminder: (petId: string, reminder: Omit<Reminder, "id" | "completedDates">) => void; addReminder: (petId: string, reminder: Omit<Reminder, "id" | "completedDates">) => Promise<void>;
toggleReminder: (petId: string, reminderId: string, date: string) => void; toggleReminder: (petId: string, reminderId: string, date: string) => Promise<void>;
addHealthLog: (petId: string, log: Omit<HealthLog, "id" | "date">) => void; addHealthLog: (petId: string, log: Omit<HealthLog, "id" | "date">) => Promise<void>;
setPets: (pets: any[]) => void; setPets: (pets: any[]) => void;
} }
@ -170,60 +170,35 @@ export const usePetStore = create<PetStore>()(
})); }));
} }
}, },
addReminder: (petId, reminder) => { addReminder: async (petId, reminder) => {
// TODO: [BACKEND_API] POST /api/pets/{petId}/reminders | Payload: Omit<Reminder, "id"> | Expected: Reminder | Errors: [400, 401, 404] try {
const id = Math.random().toString(36).substring(7); await api.post(`/pets/${petId}/reminders`, reminder);
set((state) => ({ const { useUserStore } = await import("./userStore");
pets: state.pets.map((p) => await useUserStore.getState().fetchProfile();
p.id === petId ? { ...p, reminders: [...(p.reminders || []), { ...reminder, id, completedDates: [] }] } : p } catch (error) {
) console.error("Failed to add reminder:", error);
})); toast.error("خطا در ثبت یادآور");
}
}, },
toggleReminder: (petId, reminderId, date) => { toggleReminder: async (petId, reminderId, date) => {
// TODO: [BACKEND_API] POST /api/pets/{petId}/reminders/{reminderId}/toggle | Payload: { date: string } | Expected: { success: boolean, newRemaining?: number } | Errors: [401, 404] try {
set((state) => ({ await api.post(`/pets/${petId}/reminders/${reminderId}/toggle`, { date });
pets: state.pets.map((p) => { const { useUserStore } = await import("./userStore");
if (p.id !== petId) return p; await useUserStore.getState().fetchProfile();
} catch (error) {
const reminder = p.reminders?.find(r => r.id === reminderId); console.error("Failed to toggle reminder:", error);
if (!reminder) return p; toast.error("خطا در تغییر وضعیت یادآور");
}
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 };
})
}));
}, },
addHealthLog: (petId, log) => { addHealthLog: async (petId, log) => {
// TODO: [BACKEND_API] POST /api/pets/{petId}/health-logs | Payload: Omit<HealthLog, "id" | "date"> | Expected: HealthLog | Errors: [400, 401, 404] try {
const id = Math.random().toString(36).substring(7); await api.post(`/pets/${petId}/health-logs`, log);
const date = new Date().toISOString(); const { useUserStore } = await import("./userStore");
set((state) => ({ await useUserStore.getState().fetchProfile();
pets: state.pets.map((p) => } catch (error) {
p.id === petId ? { ...p, logs: [{ ...log, id, date }, ...(p.logs || [])] } : p console.error("Failed to add health log:", error);
) toast.error("خطا در ثبت گزارش سلامتی");
})); }
}, },
setPets: (backendPets) => { setPets: (backendPets) => {
const mappedPets: PetProfile[] = backendPets.map(bp => ({ const mappedPets: PetProfile[] = backendPets.map(bp => ({

View File

@ -51,7 +51,7 @@ interface UserStore {
updateAddress: (id: string, address: Address) => Promise<void>; updateAddress: (id: string, address: Address) => Promise<void>;
deleteAddress: (id: string) => Promise<void>; deleteAddress: (id: string) => Promise<void>;
setDefaultAddress: (id: string) => Promise<void>; setDefaultAddress: (id: string) => Promise<void>;
topUpWallet: (amount: number) => void; topUpWallet: (amount: number) => Promise<void>;
logout: () => void; logout: () => void;
fetchProfile: () => Promise<void>; fetchProfile: () => Promise<void>;
} }
@ -144,24 +144,15 @@ export const useUserStore = create<UserStore>()(
throw error; throw error;
} }
}, },
topUpWallet: (amount) => { topUpWallet: async (amount) => {
// TODO: [BACKEND_API] POST /api/user/wallet/top-up | Payload: { amount: number } | Expected: { transactionId: string, newBalance: number } | Errors: [400, 401] try {
set((state) => { await api.post('/users/wallet/top-up', { amount });
const newTransaction: Transaction = { await useUserStore.getState().fetchProfile();
id: `TRX-${Math.floor(Math.random() * 1000000)}`, } catch (error) {
type: 'top_up', console.error("Failed to top-up wallet:", error);
amount, throw error;
date: new Date().toISOString(), }
status: 'success' },
};
return {
profile: {
...state.profile,
walletBalance: state.profile.walletBalance + amount,
transactions: [newTransaction, ...(state.profile.transactions || [])]
}
};
})},
logout: () => { logout: () => {
authService.logout(); authService.logout();
set({ role: "User_Guest", isLoggedIn: false, profile: { set({ role: "User_Guest", isLoggedIn: false, profile: {