canina/frontend/application/lib/store/cartStore.ts
parsa aghaei 7615aa0e51 fix: resolve seed encoding, search param, and wiki data source issues
- Fix seed-products.ts TS error (implicit any) and BOM handling
- Re-run full seed to restore correct Persian encoding in DB
- Fix productService query→search param mismatch
- Fix IngredientWiki hardcoded port 4000→use settingsStore
- Restore seed-products-data.json from git after accidental corruption
2026-07-11 15:40:49 +03:30

171 lines
5.5 KiB
TypeScript

"use client";
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 {
product: Product;
quantity: number;
calculatedDose?: {
quantity: number;
unit: string;
};
}
interface Order {
id: string;
date: string;
items: CartItem[];
total: number;
charityDonation?: number;
status: 'processing' | 'shipped' | 'delivered';
petId?: string;
trackingNumber: string;
}
interface CartStore {
items: CartItem[];
isSubscribed: boolean;
charityDonation: number;
coupon: { code: string; discount: number } | null;
orders: Order[];
addItem: (product: Product, quantity: number, dose?: { quantity: number; unit: string }) => void;
removeItem: (productId: string) => void;
updateQuantity: (productId: string, quantity: number) => void;
toggleSubscription: () => void;
setCharityDonation: (amount: number) => void;
applyCoupon: (code: string) => Promise<boolean>;
addOrder: (order: Omit<Order, 'id' | 'date' | 'status' | 'trackingNumber'>) => Promise<string>;
clearCart: () => void;
removeCoupon: () => void;
setOrders: (orders: any[]) => void;
getTotalItems: () => number;
getSubtotal: () => number;
getDiscount: () => number;
getTotal: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
isSubscribed: false,
charityDonation: 0,
coupon: null,
orders: [],
addItem: (product, quantity, dose) => {
const items = get().items;
const existing = items.find((i) => i.product.id === product.id);
if (existing) {
set({
items: items.map((i) =>
i.product.id === product.id ? { ...i, quantity: i.quantity + quantity } : i
),
});
} else {
set({ items: [...items, { product, quantity, calculatedDose: dose }] });
}
},
removeItem: (productId) => {
set({ items: get().items.filter((i) => i.product.id !== productId) });
},
updateQuantity: (productId, quantity) => {
if (quantity <= 0) {
get().removeItem(productId);
return;
}
set({
items: get().items.map((i) =>
i.product.id === productId ? { ...i, quantity } : i
),
});
},
toggleSubscription: () => set({ isSubscribed: !get().isSubscribed }),
setCharityDonation: (amount) => set({ charityDonation: amount }),
applyCoupon: async (code) => {
try {
const cleanCode = code.toUpperCase().trim();
const subtotal = get().getSubtotal();
const response = await api.post('/orders/validate-coupon', { code: cleanCode, cartTotal: subtotal });
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 = {
petId: orderData.petId,
items: orderData.items.map(i => ({
productId: i.product.id,
quantity: i.quantity
}))
};
try {
const backendOrder = await orderService.createOrder(payload);
const newOrder: Order = {
...orderData,
id: backendOrder.id,
date: backendOrder.createdAt,
total: Number(backendOrder.totalAmount),
charityDonation: Number(backendOrder.charityDonation),
status: backendOrder.status as any,
trackingNumber: backendOrder.trackingNumber || `CN-${Math.floor(Math.random() * 90000) + 10000}`
};
set({ orders: [newOrder, ...get().orders] });
return backendOrder.id;
} catch (error: any) {
console.error("Order creation failed on backend:", error);
throw error;
}
},
clearCart: () => set({ items: [], coupon: null }),
removeCoupon: () => set({ coupon: null }),
setOrders: (backendOrders) => {
const mappedOrders: Order[] = backendOrders.map(bo => ({
id: bo.id,
date: bo.createdAt,
items: bo.orderItems?.map((oi: any) => ({
product: oi.product,
quantity: oi.quantity
})) || [],
total: Number(bo.totalAmount),
charityDonation: Number(bo.charityDonation),
status: bo.status,
trackingNumber: bo.trackingNumber || `CN-${Math.floor(Math.random() * 90000) + 10000}`
}));
set({ orders: mappedOrders });
},
getTotalItems: () => get().items.reduce((acc, item) => acc + item.quantity, 0),
getSubtotal: () => get().items.reduce((acc, item) => acc + item.product.priceValue * item.quantity, 0),
getDiscount: () => {
const subtotal = get().getSubtotal();
let discount = get().isSubscribed ? subtotal * 0.05 : 0;
if (get().coupon) {
const c = get().coupon!;
if (c.discount < 1) {
discount += subtotal * c.discount;
} else {
discount += c.discount;
}
}
return Math.min(discount, subtotal);
},
getTotal: () => get().getSubtotal() - get().getDiscount() + get().charityDonation,
}),
{ name: "canina-cart" }
)
);