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, } 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) } 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) } 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) } return updatedState } export const clearCart = (): CartState => { 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), } 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), } 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) }