import { BillingCycle, BasketItem, ProductDto } from "@/proxy/order/models"; export interface CartState { items: BasketItem[]; subtotal: number; vatTotal: number; total: number; globalBillingCycle: BillingCycle; globalPeriod: number; } export const initialCartState: CartState = { items: [], subtotal: 0, vatTotal: 0, total: 0, globalBillingCycle: 'yearly', globalPeriod: 1 }; // Key for localStorage const CART_STORAGE_KEY = 'cartState'; export const calculateItemPrice = ( product: ProductDto, quantity: number, billingCycle: BillingCycle, period: number = 1 ): number => { const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!; const vatRate = product.vatRate ?? 0.2; return roundMoney(price * quantity * period * (1 + vatRate)); }; const roundMoney = (value: number): number => Math.round(value * 100) / 100; const calculateItemVatAmount = ( totalPrice: number, vatRate: number = 0.2, ): number => (vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0); const withItemVat = ( product: ProductDto, quantity: number, billingCycle: BillingCycle, period: number, ): BasketItem => { const vatRate = product.vatRate ?? 0.2; const totalPrice = calculateItemPrice(product, quantity, billingCycle, period); return { product, quantity, billingCycle, vatRate, vatAmount: calculateItemVatAmount(totalPrice, vatRate), totalPrice, }; }; const calculateCartTotals = (items: BasketItem[]) => { const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0)); const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0)); return { subtotal: roundMoney(total - vatTotal), vatTotal, total, }; }; export const addItemToCart = ( cartState: CartState, product: ProductDto, quantity: number, billingCycle: BillingCycle ): CartState => { const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle); if (!product.isQuantityBased && isAlreadyInCart) { return cartState; } const existingItemIndex = cartState.items.findIndex( item => item.product.id === product.id && item.billingCycle === billingCycle ); let newItems; if (existingItemIndex > -1) { newItems = [...cartState.items]; newItems[existingItemIndex].quantity += quantity; newItems[existingItemIndex] = withItemVat( product, newItems[existingItemIndex].quantity, billingCycle, cartState.globalPeriod, ); } else { const actualQuantity = !product.isQuantityBased ? 1 : quantity; newItems = [ ...cartState.items, withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod), ]; } const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; export const removeItemFromCart = (cartState: CartState, id: string): CartState => { const newItems = cartState.items.filter(item => item.product.id !== id); const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; export const updateCartItemQuantity = (cartState: CartState, id: string, quantity: number): CartState => { const newItems = cartState.items.map(item => { if (item.product.id === id) { if (quantity <= 0) return null; if (!item.product.isQuantityBased) return item; return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod); } return item; }).filter(Boolean) as BasketItem[]; const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; export const clearCart = (): CartState => { localStorage.removeItem(CART_STORAGE_KEY); return initialCartState; }; export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => { const newItems = cartState.items.map(item => withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod) ); const updatedState = { ...cartState, globalBillingCycle: cycle, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => { const newItems = cartState.items.map(item => withItemVat(item.product, item.quantity, item.billingCycle, period) ); const updatedState = { ...cartState, globalPeriod: period, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; export const isItemInCart = (cartState: CartState, productId: string, billingCycle: BillingCycle): boolean => { return cartState.items.some( item => item.product.id === productId && item.billingCycle === billingCycle ); }; export const isItemInCartState = (items: BasketItem[], productId: string, billingCycle: BillingCycle): boolean => { return items.some( item => item.product.id === productId && item.billingCycle === billingCycle ); }; // Utility: Save to localStorage const saveCartToLocalStorage = (cartState: CartState) => { localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(cartState)); }; // Utility: Load from localStorage export const loadCartFromLocalStorage = (): CartState => { const saved = localStorage.getItem(CART_STORAGE_KEY); if (!saved) return initialCartState; const parsed = JSON.parse(saved) as CartState; const period = parsed.globalPeriod || 1; const items = (parsed.items || []).map(item => withItemVat(item.product, item.quantity, item.billingCycle, period) ); return { ...initialCartState, ...parsed, items, ...calculateCartTotals(items), }; };