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 [useRoundUp, setUseRoundUp] = useState(false);
const [dbProducts, setDbProducts] = useState<Product[]>([]);
const [paymentMethod, setPaymentMethod] = useState<string>('online');
useEffect(() => {
productService.getProducts().then(data => setDbProducts(data));
@ -186,10 +187,16 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi
].map((pay) => (
<div
key={pay.id}
onClick={() => {
// 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")}
>
<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}
@ -305,10 +312,11 @@ export default function CheckoutPage({ onBack, onComplete }: { onBack: () => voi
<div className="mt-8 pt-8 border-t border-white/10">
<form
className="flex gap-2"
onSubmit={(e) => {
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("کد تخفیف معتبر نیست.");

View File

@ -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<boolean>;
addOrder: (order: Omit<Order, 'id' | 'date' | 'status' | 'trackingNumber'>) => Promise<string>;
clearCart: () => void;
removeCoupon: () => void;
@ -83,18 +84,20 @@ export const useCartStore = create<CartStore>()(
},
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]
applyCoupon: async (code) => {
try {
const cleanCode = code.toUpperCase().trim();
if (cleanCode === 'CANINA2024') {
set({ coupon: { code: 'CANINA2024', discount: 0.1 } });
return true;
}
if (cleanCode === 'FIRST_PET') {
set({ coupon: { code: 'FIRST_PET', discount: 150000 } });
// 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;
}
},
addOrder: async (orderData) => {
const payload = {

View File

@ -50,9 +50,9 @@ interface PetStore {
setActivePet: (id: string) => void;
getActivePet: () => PetProfile | null;
updatePet: (id: string, updates: Partial<PetProfile>) => void;
addReminder: (petId: string, reminder: Omit<Reminder, "id" | "completedDates">) => void;
toggleReminder: (petId: string, reminderId: string, date: string) => void;
addHealthLog: (petId: string, log: Omit<HealthLog, "id" | "date">) => void;
addReminder: (petId: string, reminder: Omit<Reminder, "id" | "completedDates">) => Promise<void>;
toggleReminder: (petId: string, reminderId: string, date: string) => Promise<void>;
addHealthLog: (petId: string, log: Omit<HealthLog, "id" | "date">) => Promise<void>;
setPets: (pets: any[]) => void;
}
@ -170,60 +170,35 @@ export const usePetStore = create<PetStore>()(
}));
}
},
addReminder: (petId, reminder) => {
// TODO: [BACKEND_API] POST /api/pets/{petId}/reminders | Payload: Omit<Reminder, "id"> | 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
)
}));
},
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
);
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("خطا در ثبت یادآور");
}
return { ...p, reminders: newReminders, consumptions: newConsumptions };
})
}));
},
addHealthLog: (petId, log) => {
// TODO: [BACKEND_API] POST /api/pets/{petId}/health-logs | Payload: Omit<HealthLog, "id" | "date"> | 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
)
}));
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: 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 => ({

View File

@ -51,7 +51,7 @@ interface UserStore {
updateAddress: (id: string, address: Address) => Promise<void>;
deleteAddress: (id: string) => Promise<void>;
setDefaultAddress: (id: string) => Promise<void>;
topUpWallet: (amount: number) => void;
topUpWallet: (amount: number) => Promise<void>;
logout: () => void;
fetchProfile: () => Promise<void>;
}
@ -144,24 +144,15 @@ export const useUserStore = create<UserStore>()(
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: {