- change refresh icon & apply refreshing logic - apply validation in action btn in menu - preserve on customer info state upon refresh
868 lines
25 KiB
TypeScript
868 lines
25 KiB
TypeScript
import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
import { OrderType } from "pages/checkout/hooks/types";
|
|
import { RootState } from "redux/store";
|
|
import { CartItem, RestaurantDetails, Tax } from "utils/types/appTypes";
|
|
|
|
interface LocationData {
|
|
lat: number;
|
|
lng: number;
|
|
address: string;
|
|
}
|
|
|
|
export interface RoomDetailsType {
|
|
roomNo: string;
|
|
floorNo: string;
|
|
guestName: string;
|
|
note: string;
|
|
}
|
|
|
|
export interface OfficeDetailsType {
|
|
officeNo: string;
|
|
floorNo: string;
|
|
note: string;
|
|
company: string;
|
|
contactPerson: string;
|
|
phone: string;
|
|
}
|
|
|
|
export interface GiftDetailsType {
|
|
amount: number;
|
|
receiverName: string;
|
|
receiverPhone: string;
|
|
message: string;
|
|
senderName: string;
|
|
senderPhone: string;
|
|
senderEmail: string;
|
|
isSecret: boolean;
|
|
cardId: number;
|
|
giftType: "items" | "vouchers" | "itemsAndVouchers";
|
|
}
|
|
|
|
interface DiscountData {
|
|
value: number;
|
|
isGift: boolean;
|
|
isDiscount: boolean;
|
|
}
|
|
|
|
interface CartState {
|
|
restaurant: Partial<RestaurantDetails>;
|
|
items: CartItem[];
|
|
tmp: any;
|
|
specialRequest: string;
|
|
location: LocationData | null;
|
|
roomDetails: RoomDetailsType | null;
|
|
officeDetails: OfficeDetailsType | null;
|
|
giftDetails: GiftDetailsType | null;
|
|
coupon: string;
|
|
tip: string;
|
|
table: string;
|
|
estimateTime: Date;
|
|
estimateTimeDate: Date;
|
|
estimateTimeTime: string;
|
|
collectionMethod: string;
|
|
phone: string;
|
|
paymentMethod: string;
|
|
orderType: OrderType | "";
|
|
useLoyaltyPoints: boolean;
|
|
loyaltyValidationError: string | null;
|
|
scheduledDate: string;
|
|
discount: DiscountData;
|
|
plateCar: string;
|
|
pickupDate: string;
|
|
pickupTime: string;
|
|
pickupType: string;
|
|
estimateWay: string;
|
|
order: any;
|
|
splitBillAmount: number;
|
|
customerName: string;
|
|
totalServices: number;
|
|
hiddenServices: number;
|
|
visibleServices: number;
|
|
fee: number;
|
|
}
|
|
|
|
// localStorage keys
|
|
export const CART_STORAGE_KEYS = {
|
|
ITEMS: "fascano_cart_items",
|
|
SPECIAL_REQUEST: "fascano_special_request",
|
|
COUPON: "fascano_coupon",
|
|
TIP: "fascano_tip",
|
|
TABLES: "fascano_tables",
|
|
LOCATION: "fascano_location",
|
|
ROOM_DETAILS: "fascano_room_details",
|
|
OFFICE_DETAILS: "fascano_office_details",
|
|
GIFT_DETAILS: "fascano_gift_details",
|
|
ESTIMATE_TIME: "fascano_estimate_time",
|
|
ESTIMATE_TIME_DATE: "fascano_estimate_time_date",
|
|
ESTIMATE_TIME_TIME: "fascano_estimate_time_time",
|
|
COLLECTION_METHOD: "fascano_collection_method",
|
|
PHONE: "fascano_phone",
|
|
PAYMENT_METHOD: "fascano_payment_method",
|
|
ORDER_TYPE: "fascano_order_type",
|
|
USE_LOYALTY_POINTS: "fascano_use_loyalty_points",
|
|
LOYALTY_VALIDATION_ERROR: "fascano_loyalty_validation_error",
|
|
RESTAURANT: "fascano_restaurant",
|
|
SCHEDULED_DATE: "fascano_scheduled_date",
|
|
DISCOUNT: "fascano_discount",
|
|
PLATE: "fascano_plate_car",
|
|
PICKUP_DATE: "fascano_pickup_date",
|
|
PICKUP_TIME: "fascano_pickup_time",
|
|
PICKUP_TYPE: "fascano_pickup_type",
|
|
ORDER: "fascano_order",
|
|
TOTAL_SERVICES: "fascano_total_services",
|
|
HIDDEN_SERVICES: "fascano_hidden_services",
|
|
VISIBLE_SERVICES: "fascano_visible_services",
|
|
ESTIMATE_WAY: "fascano_estimate_way",
|
|
CUSTOMER_NAME: "fascano_customer_name",
|
|
} as const;
|
|
|
|
// Utility functions for localStorage
|
|
const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
|
|
if (typeof window === "undefined") return defaultValue;
|
|
|
|
try {
|
|
const item = localStorage.getItem(key);
|
|
return item ? JSON.parse(item) : defaultValue;
|
|
} catch (error) {
|
|
console.warn(`Error reading ${key} from localStorage:`, error);
|
|
return defaultValue;
|
|
}
|
|
};
|
|
|
|
// Generate a unique identifier for cart items based on product ID, variant, extras, and comment
|
|
const generateUniqueId = (
|
|
item: Omit<CartItem, "quantity" | "uniqueId">,
|
|
): string => {
|
|
const variantStr = item.variant || "";
|
|
const extrasStr = item.extras ? item.extras.sort().join(",") : "";
|
|
const extrasGroupStr = item.extrasgroup
|
|
? item.extrasgroup.sort().join(",")
|
|
: "";
|
|
const commentStr = item.comment || "";
|
|
|
|
return `${item.id}-${variantStr}-${extrasStr}-${extrasGroupStr}-${commentStr}`;
|
|
};
|
|
|
|
const initialState: CartState = {
|
|
items: getFromLocalStorage(CART_STORAGE_KEYS.ITEMS, []),
|
|
tmp: null,
|
|
specialRequest: getFromLocalStorage(CART_STORAGE_KEYS.SPECIAL_REQUEST, ""),
|
|
location: getFromLocalStorage(CART_STORAGE_KEYS.LOCATION, null),
|
|
roomDetails: getFromLocalStorage(CART_STORAGE_KEYS.ROOM_DETAILS, null),
|
|
officeDetails: getFromLocalStorage(CART_STORAGE_KEYS.OFFICE_DETAILS, null),
|
|
giftDetails: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_DETAILS, null),
|
|
coupon: getFromLocalStorage(CART_STORAGE_KEYS.COUPON, ""),
|
|
tip: getFromLocalStorage(CART_STORAGE_KEYS.TIP, ""),
|
|
table: getFromLocalStorage(CART_STORAGE_KEYS.TABLES, ""),
|
|
estimateTime: new Date(
|
|
getFromLocalStorage(
|
|
CART_STORAGE_KEYS.ESTIMATE_TIME,
|
|
new Date().toISOString(),
|
|
),
|
|
),
|
|
estimateTimeDate: new Date(
|
|
getFromLocalStorage(
|
|
CART_STORAGE_KEYS.ESTIMATE_TIME_DATE,
|
|
new Date().toISOString(),
|
|
),
|
|
),
|
|
estimateTimeTime: getFromLocalStorage(
|
|
CART_STORAGE_KEYS.ESTIMATE_TIME_TIME,
|
|
"",
|
|
),
|
|
collectionMethod: getFromLocalStorage(
|
|
CART_STORAGE_KEYS.COLLECTION_METHOD,
|
|
"",
|
|
),
|
|
phone: getFromLocalStorage(CART_STORAGE_KEYS.PHONE, ""),
|
|
paymentMethod: getFromLocalStorage(CART_STORAGE_KEYS.PAYMENT_METHOD, ""),
|
|
orderType: getFromLocalStorage(
|
|
CART_STORAGE_KEYS.ORDER_TYPE,
|
|
"" as OrderType | "",
|
|
),
|
|
useLoyaltyPoints: getFromLocalStorage(
|
|
CART_STORAGE_KEYS.USE_LOYALTY_POINTS,
|
|
false,
|
|
),
|
|
loyaltyValidationError: getFromLocalStorage(
|
|
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
|
|
null,
|
|
),
|
|
restaurant: getFromLocalStorage(CART_STORAGE_KEYS.RESTAURANT, { taxes: [] }),
|
|
scheduledDate: getFromLocalStorage(CART_STORAGE_KEYS.SCHEDULED_DATE, ""),
|
|
discount: getFromLocalStorage(CART_STORAGE_KEYS.DISCOUNT, {
|
|
value: 0,
|
|
isGift: false,
|
|
isDiscount: false,
|
|
}),
|
|
plateCar: getFromLocalStorage(CART_STORAGE_KEYS.PLATE, ""),
|
|
pickupDate: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_DATE, ""),
|
|
pickupTime: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_TIME, ""),
|
|
pickupType: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_TYPE, ""),
|
|
estimateWay: getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_WAY, ""),
|
|
order: getFromLocalStorage(CART_STORAGE_KEYS.ORDER, null),
|
|
splitBillAmount: 0,
|
|
customerName: getFromLocalStorage(CART_STORAGE_KEYS.CUSTOMER_NAME, ""),
|
|
totalServices: 8,
|
|
hiddenServices: 0,
|
|
visibleServices: 0,
|
|
fee: 0,
|
|
};
|
|
|
|
const orderSlice = createSlice({
|
|
name: "order",
|
|
initialState,
|
|
reducers: {
|
|
reset() {
|
|
return initialState;
|
|
},
|
|
updateRestaurant(state, action: PayloadAction<Partial<RestaurantDetails>>) {
|
|
state.restaurant = action.payload;
|
|
state.fee = Number(action.payload.delivery_fees || 0);
|
|
state.visibleServices = [
|
|
action.payload.dineIn,
|
|
action.payload.delivery,
|
|
action.payload.pickup,
|
|
action.payload.gift,
|
|
action.payload.toRoom,
|
|
action.payload.toOffice,
|
|
action.payload.is_schedule_order_enabled,
|
|
action.payload.is_booking_enabled,
|
|
].filter(Boolean).length;
|
|
state.hiddenServices = state.totalServices - state.visibleServices;
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.RESTAURANT,
|
|
JSON.stringify(state.restaurant),
|
|
);
|
|
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.TOTAL_SERVICES,
|
|
JSON.stringify(state.totalServices),
|
|
);
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.HIDDEN_SERVICES,
|
|
JSON.stringify(state.hiddenServices),
|
|
);
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.VISIBLE_SERVICES,
|
|
JSON.stringify(state.visibleServices),
|
|
);
|
|
}
|
|
},
|
|
addItem(
|
|
state,
|
|
action: PayloadAction<{
|
|
item: Omit<CartItem, "quantity">;
|
|
quantity: number;
|
|
}>,
|
|
) {
|
|
const { item, quantity } = action.payload;
|
|
|
|
// Generate a unique ID for this item configuration
|
|
const uniqueId = generateUniqueId(item);
|
|
|
|
// Check if an item with the same configuration already exists
|
|
const existingItem = state.items.find((i) => i.uniqueId === uniqueId);
|
|
|
|
if (existingItem) {
|
|
// Update quantity of existing item with same configuration
|
|
state.items = state.items.map((i) =>
|
|
i.uniqueId === uniqueId
|
|
? { ...i, quantity: i.quantity + quantity }
|
|
: i,
|
|
);
|
|
} else {
|
|
// Add new item with its unique identifier
|
|
state.items = [...state.items, { ...item, quantity, uniqueId }];
|
|
}
|
|
|
|
// Validate loyalty points if enabled
|
|
if (state.useLoyaltyPoints) {
|
|
const loyaltyItems = state.items.filter((item) => item.isHasLoyalty);
|
|
if (loyaltyItems.length === 0) {
|
|
state.loyaltyValidationError = "cart.noLoyaltyItemsInCart";
|
|
} else {
|
|
state.loyaltyValidationError = null;
|
|
}
|
|
}
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ITEMS,
|
|
JSON.stringify(state.items),
|
|
);
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
|
|
JSON.stringify(state.loyaltyValidationError),
|
|
);
|
|
}
|
|
},
|
|
updateQuantity(
|
|
state,
|
|
action: PayloadAction<{
|
|
id: number | string;
|
|
uniqueId: string;
|
|
quantity: number;
|
|
}>,
|
|
) {
|
|
const { uniqueId, quantity } = action.payload;
|
|
state.items = state.items.map((item) =>
|
|
item.uniqueId === uniqueId ? { ...item, quantity } : item,
|
|
);
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ITEMS,
|
|
JSON.stringify(state.items),
|
|
);
|
|
}
|
|
},
|
|
removeItem(state, action: PayloadAction<string>) {
|
|
state.items = state.items.filter(
|
|
(item) => item.uniqueId !== action.payload,
|
|
);
|
|
|
|
// Validate loyalty points if enabled
|
|
if (state.useLoyaltyPoints) {
|
|
const loyaltyItems = state.items.filter((item) => item.isHasLoyalty);
|
|
if (loyaltyItems.length === 0) {
|
|
state.loyaltyValidationError = "cart.noLoyaltyItemsInCart";
|
|
} else {
|
|
state.loyaltyValidationError = null;
|
|
}
|
|
}
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ITEMS,
|
|
JSON.stringify(state.items),
|
|
);
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
|
|
JSON.stringify(state.loyaltyValidationError),
|
|
);
|
|
}
|
|
},
|
|
clearCart(state) {
|
|
state.items = [];
|
|
state.specialRequest = "";
|
|
state.phone = "";
|
|
state.coupon = "";
|
|
state.tip = "";
|
|
state.table = "";
|
|
state.location = null;
|
|
state.roomDetails = null;
|
|
state.officeDetails = null;
|
|
state.giftDetails = null;
|
|
state.estimateTime = new Date();
|
|
state.estimateTimeDate = new Date();
|
|
state.estimateTimeTime = "";
|
|
state.collectionMethod = "";
|
|
state.paymentMethod = "";
|
|
state.loyaltyValidationError = null;
|
|
// Clear all cart data from localStorage
|
|
if (typeof window !== "undefined") {
|
|
Object.values(CART_STORAGE_KEYS)
|
|
.filter((key) => key !== CART_STORAGE_KEYS.ORDER_TYPE)
|
|
.forEach((key) => {
|
|
localStorage.removeItem(key);
|
|
});
|
|
}
|
|
},
|
|
updateSpecialRequest(state, action: PayloadAction<string>) {
|
|
state.specialRequest = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.SPECIAL_REQUEST,
|
|
JSON.stringify(state.specialRequest),
|
|
);
|
|
}
|
|
},
|
|
clearSpecialRequest(state) {
|
|
state.specialRequest = "";
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.removeItem(CART_STORAGE_KEYS.SPECIAL_REQUEST);
|
|
}
|
|
},
|
|
updateCoupon(state, action: PayloadAction<string>) {
|
|
state.coupon = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.COUPON,
|
|
JSON.stringify(state.coupon),
|
|
);
|
|
}
|
|
},
|
|
updateTip(state, action: PayloadAction<string>) {
|
|
state.tip = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(CART_STORAGE_KEYS.TIP, JSON.stringify(state.tip));
|
|
}
|
|
},
|
|
updateTables(state, action: PayloadAction<string>) {
|
|
state.table = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.TABLES,
|
|
JSON.stringify(state.table),
|
|
);
|
|
}
|
|
},
|
|
removeTable(state) {
|
|
state.table = "";
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.removeItem(CART_STORAGE_KEYS.TABLES);
|
|
}
|
|
},
|
|
setTmp(state, action: PayloadAction<unknown>) {
|
|
state.tmp = action.payload;
|
|
},
|
|
updateLocation(state, action: PayloadAction<LocationData | null>) {
|
|
state.location = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.LOCATION,
|
|
JSON.stringify(state.location),
|
|
);
|
|
}
|
|
},
|
|
clearLocation(state) {
|
|
state.location = null;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.removeItem(CART_STORAGE_KEYS.LOCATION);
|
|
}
|
|
},
|
|
updateRoomDetails(state, action: PayloadAction<RoomDetailsType | null>) {
|
|
state.roomDetails = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ROOM_DETAILS,
|
|
JSON.stringify(state.roomDetails),
|
|
);
|
|
}
|
|
},
|
|
updateOfficeDetails(
|
|
state,
|
|
action: PayloadAction<OfficeDetailsType | null>,
|
|
) {
|
|
state.officeDetails = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.OFFICE_DETAILS,
|
|
JSON.stringify(state.officeDetails),
|
|
);
|
|
}
|
|
},
|
|
updateGiftDetails(
|
|
state,
|
|
action: PayloadAction<Partial<GiftDetailsType> | null>,
|
|
) {
|
|
state.giftDetails = {
|
|
...state.giftDetails,
|
|
...action.payload,
|
|
} as GiftDetailsType;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.GIFT_DETAILS,
|
|
JSON.stringify(state.giftDetails),
|
|
);
|
|
}
|
|
},
|
|
updateEstimateTime(
|
|
state,
|
|
action: PayloadAction<{ date: Date; time: string }>,
|
|
) {
|
|
state.estimateTime = action.payload.date;
|
|
state.estimateTimeDate = action.payload.date;
|
|
state.estimateTimeTime = action.payload.time;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ESTIMATE_TIME,
|
|
JSON.stringify(state.estimateTime.toISOString()),
|
|
);
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ESTIMATE_TIME_DATE,
|
|
JSON.stringify(state.estimateTimeDate.toISOString()),
|
|
);
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ESTIMATE_TIME_TIME,
|
|
JSON.stringify(state.estimateTimeTime),
|
|
);
|
|
}
|
|
},
|
|
updateCollectionMethod(state, action: PayloadAction<string>) {
|
|
state.collectionMethod = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.COLLECTION_METHOD,
|
|
JSON.stringify(state.collectionMethod),
|
|
);
|
|
}
|
|
},
|
|
updatePhone(state, action: PayloadAction<string>) {
|
|
state.phone = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.PHONE,
|
|
JSON.stringify(state.phone),
|
|
);
|
|
}
|
|
},
|
|
updatePaymentMethod(state, action: PayloadAction<string>) {
|
|
state.paymentMethod = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.PAYMENT_METHOD,
|
|
JSON.stringify(state.paymentMethod),
|
|
);
|
|
}
|
|
},
|
|
updatePlateCar(state, action: PayloadAction<string>) {
|
|
state.plateCar = action.payload;
|
|
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.PLATE,
|
|
JSON.stringify(state.plateCar),
|
|
);
|
|
}
|
|
},
|
|
updateOrderType(state, action: PayloadAction<OrderType>) {
|
|
state.orderType = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ORDER_TYPE,
|
|
JSON.stringify(state.orderType),
|
|
);
|
|
}
|
|
},
|
|
updateUseLoyaltyPoints(state, action: PayloadAction<boolean>) {
|
|
state.useLoyaltyPoints = action.payload;
|
|
|
|
// Validate loyalty points usage
|
|
if (action.payload) {
|
|
const loyaltyItems = state.items.filter((item) => item.isHasLoyalty);
|
|
if (loyaltyItems.length === 0) {
|
|
state.loyaltyValidationError = "cart.noLoyaltyItemsInCart";
|
|
} else {
|
|
state.loyaltyValidationError = null;
|
|
}
|
|
} else {
|
|
state.loyaltyValidationError = null;
|
|
}
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.USE_LOYALTY_POINTS,
|
|
JSON.stringify(state.useLoyaltyPoints),
|
|
);
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
|
|
JSON.stringify(state.loyaltyValidationError),
|
|
);
|
|
}
|
|
},
|
|
validateLoyaltyPoints(state) {
|
|
if (state.useLoyaltyPoints) {
|
|
const loyaltyItems = state.items.filter((item) => item.isHasLoyalty);
|
|
if (loyaltyItems.length === 0) {
|
|
state.loyaltyValidationError = "cart.noLoyaltyItemsInCart";
|
|
} else {
|
|
state.loyaltyValidationError = null;
|
|
}
|
|
} else {
|
|
state.loyaltyValidationError = null;
|
|
}
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
|
|
JSON.stringify(state.loyaltyValidationError),
|
|
);
|
|
}
|
|
},
|
|
clearLoyaltyValidationError(state) {
|
|
state.loyaltyValidationError = null;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
|
|
JSON.stringify(state.loyaltyValidationError),
|
|
);
|
|
}
|
|
},
|
|
updateScheduledDate(state, action: PayloadAction<string>) {
|
|
state.scheduledDate = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.SCHEDULED_DATE,
|
|
JSON.stringify(state.scheduledDate),
|
|
);
|
|
}
|
|
},
|
|
updateDiscount(state, action: PayloadAction<DiscountData>) {
|
|
state.discount = action.payload;
|
|
|
|
// Sync to localStorage
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.DISCOUNT,
|
|
JSON.stringify(state.discount),
|
|
);
|
|
}
|
|
},
|
|
updatePickupDate(state, action: PayloadAction<string>) {
|
|
state.pickupDate = action.payload;
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.PICKUP_DATE,
|
|
JSON.stringify(state.pickupDate),
|
|
);
|
|
}
|
|
},
|
|
updatePickupTime(state, action: PayloadAction<string>) {
|
|
state.pickupTime = action.payload;
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.PICKUP_TIME,
|
|
JSON.stringify(state.pickupTime),
|
|
);
|
|
}
|
|
},
|
|
updatePickUpType(state, action: PayloadAction<string>) {
|
|
state.pickupType = action.payload;
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.PICKUP_TYPE,
|
|
JSON.stringify(state.pickupType),
|
|
);
|
|
}
|
|
},
|
|
updateEstimateWay(state, action: PayloadAction<string>) {
|
|
state.estimateWay = action.payload;
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ESTIMATE_WAY,
|
|
JSON.stringify(state.estimateWay),
|
|
);
|
|
}
|
|
},
|
|
updateOrder(state, action: PayloadAction<any>) {
|
|
state.order = action.payload;
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.ORDER,
|
|
JSON.stringify(state.order),
|
|
);
|
|
}
|
|
},
|
|
updateSplitBillAmount(state, action: PayloadAction<number>) {
|
|
state.splitBillAmount = action.payload;
|
|
},
|
|
updateCustomerName(state, action: PayloadAction<string>) {
|
|
state.customerName = action.payload;
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(
|
|
CART_STORAGE_KEYS.CUSTOMER_NAME,
|
|
JSON.stringify(state.customerName),
|
|
);
|
|
}
|
|
},
|
|
},
|
|
});
|
|
|
|
export const {
|
|
addItem,
|
|
updateQuantity,
|
|
removeItem,
|
|
clearCart,
|
|
updateSpecialRequest,
|
|
clearSpecialRequest,
|
|
updateCoupon,
|
|
updateTip,
|
|
updateTables,
|
|
removeTable,
|
|
setTmp,
|
|
updateLocation,
|
|
clearLocation,
|
|
updateRoomDetails,
|
|
updateOfficeDetails,
|
|
updateGiftDetails,
|
|
updateEstimateTime,
|
|
updateCollectionMethod,
|
|
updatePhone,
|
|
updatePaymentMethod,
|
|
updatePlateCar,
|
|
updateOrderType,
|
|
updateUseLoyaltyPoints,
|
|
validateLoyaltyPoints,
|
|
clearLoyaltyValidationError,
|
|
reset,
|
|
updateRestaurant,
|
|
updateScheduledDate,
|
|
updateDiscount,
|
|
updatePickupDate,
|
|
updatePickupTime,
|
|
updatePickUpType,
|
|
updateEstimateWay,
|
|
updateOrder,
|
|
updateSplitBillAmount,
|
|
updateCustomerName,
|
|
} = orderSlice.actions;
|
|
|
|
// Tax calculation helper functions
|
|
const calculateTaxAmount = (
|
|
state: RootState,
|
|
amount: number,
|
|
tax: Tax,
|
|
): number => {
|
|
const percentage = parseFloat(tax.percentage);
|
|
return (((state.order.restaurant?.vat || 0) + percentage) * amount) / 100;
|
|
};
|
|
|
|
const calculateTotalTax = (
|
|
state: RootState,
|
|
subtotal: number,
|
|
taxes: Tax[],
|
|
): number => {
|
|
return taxes
|
|
.filter((tax) => tax.is_active === 1)
|
|
.reduce(
|
|
(total, tax) => total + calculateTaxAmount(state, subtotal, tax),
|
|
0,
|
|
);
|
|
};
|
|
|
|
// Selectors
|
|
export const selectCart = (state: RootState) => state.order;
|
|
export const selectCartItems = (state: RootState) => state.order.items;
|
|
|
|
export const selectCartTotal = (state: RootState) =>
|
|
state.order.items.reduce(
|
|
// (total, item) => total + item.price * item.quantity,
|
|
(total, item) => {
|
|
const itemTotalPrice = item.price * item.quantity;
|
|
return total + itemTotalPrice;
|
|
},
|
|
0,
|
|
);
|
|
|
|
export const selectCartItemsQuantity =
|
|
(uniqueId: string) => (state: RootState) => {
|
|
const item = state.order.items.find((i) => i.uniqueId === uniqueId);
|
|
return item ? item.quantity : 0;
|
|
};
|
|
|
|
// Keep backward compatibility for components that still use id
|
|
export const selectCartItemsQuantityById =
|
|
(id: number | string) => (state: RootState) => {
|
|
const item = state.order.items.find((i) => i.id === id);
|
|
return item ? item.quantity : 0;
|
|
};
|
|
|
|
// Loyalty selectors
|
|
const selectOrderItems = (state: RootState) => state.order.items;
|
|
|
|
export const selectLoyaltyItems = createSelector([selectOrderItems], (items) =>
|
|
items.filter((item) => item.isHasLoyalty),
|
|
);
|
|
|
|
export const selectHighestPricedLoyaltyItem = (state: RootState) => {
|
|
const loyaltyItems = selectLoyaltyItems(state);
|
|
if (loyaltyItems.length === 0) return null;
|
|
|
|
return loyaltyItems.reduce((highest, current) =>
|
|
current.price > highest.price ? current : highest,
|
|
);
|
|
};
|
|
|
|
export const selectDiscountTotal = (state: RootState) =>
|
|
(state.order.discount.value / 100) * selectCartTotal(state) +
|
|
(state.order.useLoyaltyPoints &&
|
|
state.order.restaurant?.is_loyalty_enabled === 1
|
|
? selectHighestPricedLoyaltyItem(state)?.price || 0
|
|
: 0);
|
|
|
|
export const selectLoyaltyValidation = createSelector(
|
|
[(state: RootState) => state.order.useLoyaltyPoints, selectLoyaltyItems],
|
|
(useLoyaltyPoints, loyaltyItems) => ({
|
|
canUseLoyaltyPoints: loyaltyItems.length > 0,
|
|
hasLoyaltyItems: loyaltyItems.length > 0,
|
|
loyaltyItemsCount: loyaltyItems.length,
|
|
errorMessage:
|
|
useLoyaltyPoints && loyaltyItems.length === 0
|
|
? "cart.noLoyaltyItemsInCart"
|
|
: null,
|
|
}),
|
|
);
|
|
|
|
// Tax selectors
|
|
export const selectTaxes = (state: RootState) => state.order.restaurant.taxes;
|
|
|
|
export const selectTaxAmount = (state: RootState) => {
|
|
const subtotal = selectCartTotal(state) - selectDiscountTotal(state);
|
|
const taxes = selectTaxes(state);
|
|
return calculateTotalTax(state, subtotal, taxes || []);
|
|
};
|
|
|
|
export const selectGrandTotal = (state: RootState) => {
|
|
const totalDiscount = selectDiscountTotal(state);
|
|
const taxAmount = selectTaxAmount(state);
|
|
const subtotal = selectCartTotal(state);
|
|
const deliveryFee =
|
|
state.order.orderType === OrderType.Delivery
|
|
? Number(state.order.restaurant?.delivery_fees) || 0
|
|
: 0;
|
|
|
|
return (
|
|
subtotal +
|
|
taxAmount -
|
|
totalDiscount +
|
|
deliveryFee -
|
|
state.order.splitBillAmount
|
|
);
|
|
};
|
|
|
|
export default orderSlice.reducer;
|