apply loyalty discount logic

This commit is contained in:
2025-10-21 23:42:10 +03:00
parent 3c7d609924
commit 37a7c28c56
8 changed files with 393 additions and 118 deletions

View File

@@ -228,7 +228,10 @@
"pleaseSelectEstimateTime": "يرجى اختيار وقت التقديم", "pleaseSelectEstimateTime": "يرجى اختيار وقت التقديم",
"pleaseSelectTable": "يرجى اختيار رقم الطاولة", "pleaseSelectTable": "يرجى اختيار رقم الطاولة",
"pleaseSelectCollectionMethod": "يرجى اختيار طريقة الاستلام", "pleaseSelectCollectionMethod": "يرجى اختيار طريقة الاستلام",
"useLoyaltyPoints": "استخدام نقاط الولاء" "useLoyaltyPoints": "استخدام نقاط الولاء",
"noLoyaltyItemsInCart": "لا توجد عناصر ولاء في سلة المشتريات",
"pleaseAddLoyaltyItems": "يرجى إضافة عناصر ولاء إلى سلة المشتريات لاستخدام نقاط الولاء",
"loyaltyDiscountApplied": "تم تطبيق خصم الولاء: {{itemName}} (خصم {{amount}})"
}, },
"checkout": { "checkout": {
"title": "الدفع", "title": "الدفع",

View File

@@ -238,7 +238,10 @@
"pleaseSelectEstimateTime": "Please select estimate time", "pleaseSelectEstimateTime": "Please select estimate time",
"pleaseSelectTable": "Please select table", "pleaseSelectTable": "Please select table",
"pleaseSelectCollectionMethod": "Please select collection method", "pleaseSelectCollectionMethod": "Please select collection method",
"useLoyaltyPoints": "Use Loyalty Points" "useLoyaltyPoints": "Use Loyalty Points",
"noLoyaltyItemsInCart": "No loyalty items found in your cart",
"pleaseAddLoyaltyItems": "Please add loyalty items to your cart to use loyalty points",
"loyaltyDiscountApplied": "Loyalty discount applied: {{itemName}} ({{amount}} off)"
}, },
"checkout": { "checkout": {
"title": "Checkout", "title": "Checkout",

View File

@@ -3,9 +3,13 @@ import ArabicPrice from "components/ArabicPrice";
import { import {
selectCart, selectCart,
selectCartTotal, selectCartTotal,
selectCartTotalWithLoyaltyDiscount,
selectHighestPricedLoyaltyItem,
selectLoyaltyValidation,
updateUseLoyaltyPoints, updateUseLoyaltyPoints,
} from "features/order/orderSlice"; } from "features/order/orderSlice";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { useAppDispatch, useAppSelector } from "redux/hooks"; import { useAppDispatch, useAppSelector } from "redux/hooks";
import ProText from "../ProText"; import ProText from "../ProText";
import ProTitle from "../ProTitle"; import ProTitle from "../ProTitle";
@@ -16,8 +20,21 @@ export default function OrderSummary() {
const { useLoyaltyPoints } = useAppSelector(selectCart); const { useLoyaltyPoints } = useAppSelector(selectCart);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const subtotal = useAppSelector(selectCartTotal); const subtotal = useAppSelector(selectCartTotal);
const tax = subtotal * 0.1; // 10% tax const subtotalWithLoyaltyDiscount = useAppSelector(
const total = subtotal + tax; selectCartTotalWithLoyaltyDiscount,
);
const loyaltyValidation = useAppSelector(selectLoyaltyValidation);
const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem);
const tax = subtotalWithLoyaltyDiscount * 0.1; // 10% tax on discounted amount
const total = subtotalWithLoyaltyDiscount + tax;
const loyaltyDiscountAmount = subtotal - subtotalWithLoyaltyDiscount;
const { data: restaurant } = useGetRestaurantDetailsQuery("595");
const isHasLoyaltyGift =
(restaurant?.loyalty_stamps ?? 0) -
(restaurant?.customer_loyalty_points ?? 0) ===
0;
return ( return (
<> <>
@@ -31,7 +48,7 @@ export default function OrderSummary() {
</div> </div>
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.discount")}</ProText> <ProText type="secondary">{t("cart.discount")}</ProText>
<ArabicPrice price={0} /> <ArabicPrice price={loyaltyDiscountAmount} />
</div> </div>
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.riderTip")}</ProText> <ProText type="secondary">{t("cart.riderTip")}</ProText>
@@ -45,16 +62,36 @@ export default function OrderSummary() {
<ArabicPrice price={total} strong /> <ArabicPrice price={total} strong />
</div> </div>
</Space> </Space>
<br />
<br /> {isHasLoyaltyGift && (
<Checkbox <>
checked={useLoyaltyPoints} <br />
onChange={(value) => { <br />
dispatch(updateUseLoyaltyPoints(value.target.checked)); <Checkbox
}} checked={useLoyaltyPoints}
> onChange={(value) => {
{t("cart.useLoyaltyPoints")} dispatch(updateUseLoyaltyPoints(value.target.checked));
</Checkbox> }}
>
{t("cart.useLoyaltyPoints")}
</Checkbox>
</>
)}
{isHasLoyaltyGift && loyaltyValidation.errorMessage && (
<div style={{ marginTop: 8, color: "red", fontSize: "12px" }}>
{t(loyaltyValidation.errorMessage)}
</div>
)}
{isHasLoyaltyGift && useLoyaltyPoints && highestLoyaltyItem && (
<div style={{ marginTop: 8, color: "green", fontSize: "12px" }}>
{t("cart.loyaltyDiscountApplied", {
itemName: highestLoyaltyItem.name,
amount: Math.round(loyaltyDiscountAmount).toFixed(2),
})}
</div>
)}
</Card> </Card>
</> </>
); );

View File

@@ -53,33 +53,35 @@ interface CartState {
paymentMethod: string; paymentMethod: string;
orderType: string; orderType: string;
useLoyaltyPoints: boolean; useLoyaltyPoints: boolean;
loyaltyValidationError: string | null;
} }
// localStorage keys // localStorage keys
export const CART_STORAGE_KEYS = { export const CART_STORAGE_KEYS = {
ITEMS: 'fascano_cart_items', ITEMS: "fascano_cart_items",
SPECIAL_REQUEST: 'fascano_special_request', SPECIAL_REQUEST: "fascano_special_request",
COUPON: 'fascano_coupon', COUPON: "fascano_coupon",
TIP: 'fascano_tip', TIP: "fascano_tip",
TABLES: 'fascano_tables', TABLES: "fascano_tables",
LOCATION: 'fascano_location', LOCATION: "fascano_location",
ROOM_DETAILS: 'fascano_room_details', ROOM_DETAILS: "fascano_room_details",
OFFICE_DETAILS: 'fascano_office_details', OFFICE_DETAILS: "fascano_office_details",
GIFT_DETAILS: 'fascano_gift_details', GIFT_DETAILS: "fascano_gift_details",
ESTIMATE_TIME: 'fascano_estimate_time', ESTIMATE_TIME: "fascano_estimate_time",
ESTIMATE_TIME_DATE: 'fascano_estimate_time_date', ESTIMATE_TIME_DATE: "fascano_estimate_time_date",
ESTIMATE_TIME_TIME: 'fascano_estimate_time_time', ESTIMATE_TIME_TIME: "fascano_estimate_time_time",
COLLECTION_METHOD: 'fascano_collection_method', COLLECTION_METHOD: "fascano_collection_method",
PHONE: 'fascano_phone', PHONE: "fascano_phone",
PAYMENT_METHOD: 'fascano_payment_method', PAYMENT_METHOD: "fascano_payment_method",
ORDER_TYPE: 'fascano_order_type', ORDER_TYPE: "fascano_order_type",
USE_LOYALTY_POINTS: 'fascano_use_loyalty_points', USE_LOYALTY_POINTS: "fascano_use_loyalty_points",
LOYALTY_VALIDATION_ERROR: "fascano_loyalty_validation_error",
} as const; } as const;
// Utility functions for localStorage // Utility functions for localStorage
const getFromLocalStorage = <T>(key: string, defaultValue: T): T => { const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
if (typeof window === 'undefined') return defaultValue; if (typeof window === "undefined") return defaultValue;
try { try {
const item = localStorage.getItem(key); const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue; return item ? JSON.parse(item) : defaultValue;
@@ -100,14 +102,37 @@ const initialState: CartState = {
coupon: getFromLocalStorage(CART_STORAGE_KEYS.COUPON, ""), coupon: getFromLocalStorage(CART_STORAGE_KEYS.COUPON, ""),
tip: getFromLocalStorage(CART_STORAGE_KEYS.TIP, ""), tip: getFromLocalStorage(CART_STORAGE_KEYS.TIP, ""),
tables: getFromLocalStorage(CART_STORAGE_KEYS.TABLES, []), tables: getFromLocalStorage(CART_STORAGE_KEYS.TABLES, []),
estimateTime: new Date(getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME, new Date().toISOString())), estimateTime: new Date(
estimateTimeDate: new Date(getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME_DATE, new Date().toISOString())), getFromLocalStorage(
estimateTimeTime: getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME_TIME, ""), CART_STORAGE_KEYS.ESTIMATE_TIME,
collectionMethod: getFromLocalStorage(CART_STORAGE_KEYS.COLLECTION_METHOD, ""), 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, ""), phone: getFromLocalStorage(CART_STORAGE_KEYS.PHONE, ""),
paymentMethod: getFromLocalStorage(CART_STORAGE_KEYS.PAYMENT_METHOD, ""), paymentMethod: getFromLocalStorage(CART_STORAGE_KEYS.PAYMENT_METHOD, ""),
orderType: getFromLocalStorage(CART_STORAGE_KEYS.ORDER_TYPE, ""), orderType: getFromLocalStorage(CART_STORAGE_KEYS.ORDER_TYPE, ""),
useLoyaltyPoints: getFromLocalStorage(CART_STORAGE_KEYS.USE_LOYALTY_POINTS, false), useLoyaltyPoints: getFromLocalStorage(
CART_STORAGE_KEYS.USE_LOYALTY_POINTS,
false,
),
loyaltyValidationError: getFromLocalStorage(
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
null,
),
}; };
const orderSlice = createSlice({ const orderSlice = createSlice({
@@ -117,40 +142,86 @@ const orderSlice = createSlice({
reset() { reset() {
return initialState; return initialState;
}, },
addItem(state, action: PayloadAction<{ item: Omit<CartItem, "quantity">; quantity: number }>) { addItem(
state,
action: PayloadAction<{
item: Omit<CartItem, "quantity">;
quantity: number;
}>,
) {
const { item, quantity } = action.payload; const { item, quantity } = action.payload;
const existingItem = state.items.find((i) => i.id === item.id); const existingItem = state.items.find((i) => i.id === item.id);
if (existingItem) { if (existingItem) {
state.items = state.items.map((i) => state.items = state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + quantity } : i i.id === item.id ? { ...i, quantity: i.quantity + quantity } : i,
); );
} else { } else {
state.items = [...state.items, { ...item, quantity }]; state.items = [...state.items, { ...item, quantity }];
} }
// 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 // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items)); 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; quantity: number }>) { updateQuantity(
state,
action: PayloadAction<{ id: number | string; quantity: number }>,
) {
const { id, quantity } = action.payload; const { id, quantity } = action.payload;
state.items = state.items.map((item) => state.items = state.items.map((item) =>
item.id === id ? { ...item, quantity } : item item.id === id ? { ...item, quantity } : item,
); );
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items)); localStorage.setItem(
CART_STORAGE_KEYS.ITEMS,
JSON.stringify(state.items),
);
} }
}, },
removeItem(state, action: PayloadAction<number | string>) { removeItem(state, action: PayloadAction<number | string>) {
state.items = state.items.filter((item) => item.id !== action.payload); state.items = state.items.filter((item) => item.id !== 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 // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items)); localStorage.setItem(
CART_STORAGE_KEYS.ITEMS,
JSON.stringify(state.items),
);
localStorage.setItem(
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
JSON.stringify(state.loyaltyValidationError),
);
} }
}, },
clearCart(state) { clearCart(state) {
@@ -169,58 +240,70 @@ const orderSlice = createSlice({
state.estimateTimeTime = ""; state.estimateTimeTime = "";
state.collectionMethod = ""; state.collectionMethod = "";
state.paymentMethod = ""; state.paymentMethod = "";
state.loyaltyValidationError = null;
// Clear all cart data from localStorage // Clear all cart data from localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
Object.values(CART_STORAGE_KEYS).filter(key => key !== CART_STORAGE_KEYS.ORDER_TYPE).forEach(key => { Object.values(CART_STORAGE_KEYS)
localStorage.removeItem(key); .filter((key) => key !== CART_STORAGE_KEYS.ORDER_TYPE)
}); .forEach((key) => {
localStorage.removeItem(key);
});
} }
}, },
updateSpecialRequest(state, action: PayloadAction<string>) { updateSpecialRequest(state, action: PayloadAction<string>) {
state.specialRequest = action.payload; state.specialRequest = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.SPECIAL_REQUEST, JSON.stringify(state.specialRequest)); localStorage.setItem(
CART_STORAGE_KEYS.SPECIAL_REQUEST,
JSON.stringify(state.specialRequest),
);
} }
}, },
clearSpecialRequest(state) { clearSpecialRequest(state) {
state.specialRequest = ""; state.specialRequest = "";
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.removeItem(CART_STORAGE_KEYS.SPECIAL_REQUEST); localStorage.removeItem(CART_STORAGE_KEYS.SPECIAL_REQUEST);
} }
}, },
updateCoupon(state, action: PayloadAction<string>) { updateCoupon(state, action: PayloadAction<string>) {
state.coupon = action.payload; state.coupon = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.COUPON, JSON.stringify(state.coupon)); localStorage.setItem(
CART_STORAGE_KEYS.COUPON,
JSON.stringify(state.coupon),
);
} }
}, },
updateTip(state, action: PayloadAction<string>) { updateTip(state, action: PayloadAction<string>) {
state.tip = action.payload; state.tip = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.TIP, JSON.stringify(state.tip)); localStorage.setItem(CART_STORAGE_KEYS.TIP, JSON.stringify(state.tip));
} }
}, },
updateTables(state, action: PayloadAction<string[]>) { updateTables(state, action: PayloadAction<string[]>) {
state.tables = action.payload; state.tables = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.TABLES, JSON.stringify(state.tables)); localStorage.setItem(
CART_STORAGE_KEYS.TABLES,
JSON.stringify(state.tables),
);
} }
}, },
removeTable(state) { removeTable(state) {
state.tables = []; state.tables = [];
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.removeItem(CART_STORAGE_KEYS.TABLES); localStorage.removeItem(CART_STORAGE_KEYS.TABLES);
} }
}, },
@@ -229,94 +312,183 @@ const orderSlice = createSlice({
}, },
updateLocation(state, action: PayloadAction<LocationData | null>) { updateLocation(state, action: PayloadAction<LocationData | null>) {
state.location = action.payload; state.location = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.LOCATION, JSON.stringify(state.location)); localStorage.setItem(
CART_STORAGE_KEYS.LOCATION,
JSON.stringify(state.location),
);
} }
}, },
clearLocation(state) { clearLocation(state) {
state.location = null; state.location = null;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.removeItem(CART_STORAGE_KEYS.LOCATION); localStorage.removeItem(CART_STORAGE_KEYS.LOCATION);
} }
}, },
updateRoomDetails(state, action: PayloadAction<RoomDetailsType | null>) { updateRoomDetails(state, action: PayloadAction<RoomDetailsType | null>) {
state.roomDetails = action.payload; state.roomDetails = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.ROOM_DETAILS, JSON.stringify(state.roomDetails)); localStorage.setItem(
CART_STORAGE_KEYS.ROOM_DETAILS,
JSON.stringify(state.roomDetails),
);
} }
}, },
updateOfficeDetails(state, action: PayloadAction<OfficeDetailsType | null>) { updateOfficeDetails(
state,
action: PayloadAction<OfficeDetailsType | null>,
) {
state.officeDetails = action.payload; state.officeDetails = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.OFFICE_DETAILS, JSON.stringify(state.officeDetails)); localStorage.setItem(
CART_STORAGE_KEYS.OFFICE_DETAILS,
JSON.stringify(state.officeDetails),
);
} }
}, },
updateGiftDetails(state, action: PayloadAction<GiftDetailsType | null>) { updateGiftDetails(state, action: PayloadAction<GiftDetailsType | null>) {
state.giftDetails = action.payload; state.giftDetails = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.GIFT_DETAILS, JSON.stringify(state.giftDetails)); localStorage.setItem(
CART_STORAGE_KEYS.GIFT_DETAILS,
JSON.stringify(state.giftDetails),
);
} }
}, },
updateEstimateTime(state, action: PayloadAction<{ date: Date; time: string }>) { updateEstimateTime(
state,
action: PayloadAction<{ date: Date; time: string }>,
) {
state.estimateTime = action.payload.date; state.estimateTime = action.payload.date;
state.estimateTimeDate = action.payload.date; state.estimateTimeDate = action.payload.date;
state.estimateTimeTime = action.payload.time; state.estimateTimeTime = action.payload.time;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME, JSON.stringify(state.estimateTime.toISOString())); localStorage.setItem(
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME_DATE, JSON.stringify(state.estimateTimeDate.toISOString())); CART_STORAGE_KEYS.ESTIMATE_TIME,
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME_TIME, JSON.stringify(state.estimateTimeTime)); 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>) { updateCollectionMethod(state, action: PayloadAction<string>) {
state.collectionMethod = action.payload; state.collectionMethod = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.COLLECTION_METHOD, JSON.stringify(state.collectionMethod)); localStorage.setItem(
CART_STORAGE_KEYS.COLLECTION_METHOD,
JSON.stringify(state.collectionMethod),
);
} }
}, },
updatePhone(state, action: PayloadAction<string>) { updatePhone(state, action: PayloadAction<string>) {
state.phone = action.payload; state.phone = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.PHONE, JSON.stringify(state.phone)); localStorage.setItem(
CART_STORAGE_KEYS.PHONE,
JSON.stringify(state.phone),
);
} }
}, },
updatePaymentMethod(state, action: PayloadAction<string>) { updatePaymentMethod(state, action: PayloadAction<string>) {
state.paymentMethod = action.payload; state.paymentMethod = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.PAYMENT_METHOD, JSON.stringify(state.paymentMethod)); localStorage.setItem(
CART_STORAGE_KEYS.PAYMENT_METHOD,
JSON.stringify(state.paymentMethod),
);
} }
}, },
updateOrderType(state, action: PayloadAction<string>) { updateOrderType(state, action: PayloadAction<string>) {
state.orderType = action.payload; state.orderType = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.ORDER_TYPE, JSON.stringify(state.orderType)); localStorage.setItem(
CART_STORAGE_KEYS.ORDER_TYPE,
JSON.stringify(state.orderType),
);
} }
}, },
updateUseLoyaltyPoints(state, action: PayloadAction<boolean>) { updateUseLoyaltyPoints(state, action: PayloadAction<boolean>) {
state.useLoyaltyPoints = action.payload; 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 // Sync to localStorage
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.USE_LOYALTY_POINTS, JSON.stringify(state.useLoyaltyPoints)); 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),
);
} }
}, },
}, },
@@ -345,17 +517,63 @@ export const {
updatePaymentMethod, updatePaymentMethod,
updateOrderType, updateOrderType,
updateUseLoyaltyPoints, updateUseLoyaltyPoints,
validateLoyaltyPoints,
clearLoyaltyValidationError,
reset, reset,
} = orderSlice.actions; } = orderSlice.actions;
// Selectors // Selectors
export const selectCart = (state: RootState) => state.order; export const selectCart = (state: RootState) => state.order;
export const selectCartItems = (state: RootState) => state.order.items; export const selectCartItems = (state: RootState) => state.order.items;
export const selectCartTotal = (state: RootState) => export const selectCartTotal = (state: RootState) =>
state.order.items.reduce((total, item) => total + item.price * item.quantity, 0); state.order.items.reduce(
export const selectCartItemsQuantity = (id: number | string) => (state: RootState) => { (total, item) => total + item.price * item.quantity,
const item = state.order.items.find((i) => i.id === id); 0,
return item ? item.quantity : 0; );
export const selectCartItemsQuantity =
(id: number | string) => (state: RootState) => {
const item = state.order.items.find((i) => i.id === id);
return item ? item.quantity : 0;
};
// Loyalty selectors
export const selectLoyaltyItems = (state: RootState) =>
state.order.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 selectCartTotalWithLoyaltyDiscount = (state: RootState) => {
const total = selectCartTotal(state);
const useLoyaltyPoints = state.order.useLoyaltyPoints;
const highestLoyaltyItem = selectHighestPricedLoyaltyItem(state);
if (useLoyaltyPoints && highestLoyaltyItem) {
return total - highestLoyaltyItem.price;
}
return total;
};
export const selectLoyaltyValidation = (state: RootState) => {
const useLoyaltyPoints = state.order.useLoyaltyPoints;
const loyaltyItems = selectLoyaltyItems(state);
return {
canUseLoyaltyPoints: loyaltyItems.length > 0,
hasLoyaltyItems: loyaltyItems.length > 0,
loyaltyItemsCount: loyaltyItems.length,
errorMessage:
useLoyaltyPoints && loyaltyItems.length === 0
? "cart.noLoyaltyItemsInCart"
: null,
};
}; };
export default orderSlice.reducer; export default orderSlice.reducer;

View File

@@ -109,6 +109,8 @@ export default function YouMightAlsoLike() {
image: item.image, image: item.image,
description: item.description, description: item.description,
variant: "None", variant: "None",
isHasLoyalty: item.isHasLoyalty,
no_of_stamps_give: item.no_of_stamps_give,
}, },
quantity: 1, quantity: 1,
}), }),

View File

@@ -1,5 +1,10 @@
import { message } from "antd"; import { message } from "antd";
import { clearCart, selectCart } from "features/order/orderSlice"; import {
clearCart,
selectCart,
selectCartTotalWithLoyaltyDiscount,
selectHighestPricedLoyaltyItem,
} from "features/order/orderSlice";
import { useCallback } from "react"; import { useCallback } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
@@ -29,6 +34,10 @@ export default function useOrder() {
orderType, orderType,
giftDetails, giftDetails,
} = useAppSelector(selectCart); } = useAppSelector(selectCart);
const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem);
const { useLoyaltyPoints } = useAppSelector(selectCart);
const orderPrice = useAppSelector(selectCartTotalWithLoyaltyDiscount);
const [createOrder] = useCreateOrderMutation(); const [createOrder] = useCreateOrderMutation();
@@ -59,10 +68,8 @@ export default function useOrder() {
pickup_comments: "", pickup_comments: "",
pickup_time: estimateTime, pickup_time: estimateTime,
delivery_pickup_interval: "", delivery_pickup_interval: "",
orderPrice: items.reduce( orderPrice: orderPrice,
(acc, item) => acc + item.price * item.quantity, use_loylaty: useLoyaltyPoints && highestLoyaltyItem ? 1 : 0,
0,
),
useWallet: 0, useWallet: 0,
tip, tip,
...(orderType === "gift" ...(orderType === "gift"
@@ -74,7 +81,7 @@ export default function useOrder() {
senderEmail: giftDetails?.senderEmail, senderEmail: giftDetails?.senderEmail,
senderPhone: giftDetails?.senderPhone, senderPhone: giftDetails?.senderPhone,
senderName: giftDetails?.senderName, senderName: giftDetails?.senderName,
dineType: orderType dineType: orderType,
} }
: {}), : {}),
}) })
@@ -120,6 +127,7 @@ export default function useOrder() {
user_uuid, user_uuid,
estimateTime, estimateTime,
tip, tip,
orderPrice,
t, t,
navigate, navigate,
dispatch, dispatch,

View File

@@ -69,6 +69,8 @@ export default function ProductFooter({
variant: variantId, variant: variantId,
extras: selectedExtras, extras: selectedExtras,
extrasgroup: selectedGroups, extrasgroup: selectedGroups,
isHasLoyalty: product?.isHasLoyalty,
no_of_stamps_give: product?.no_of_stamps_give,
}, },
quantity: quantity, quantity: quantity,
}), }),

View File

@@ -331,6 +331,8 @@ export interface CartItem {
variant?: string; variant?: string;
extras?: string[]; extras?: string[];
extrasgroup?: string[]; extrasgroup?: string[];
isHasLoyalty?: boolean;
no_of_stamps_give?: number;
} }
export interface User { export interface User {