From 3ed5c4d5d6c1d54dc64d6a5b443b71663c24fd54 Mon Sep 17 00:00:00 2001 From: Mohammed Al-yaseen Date: Tue, 30 Dec 2025 11:09:52 +0300 Subject: [PATCH] gift: update UI and voucher & items BS --- src/assets/locals/ar.json | 6 ++ src/assets/locals/en.json | 6 ++ .../CustomBottomSheet/GiftTypeBottomSheet.tsx | 83 +++++++++++++++++++ src/components/Icons/GiftBalanceBtnIcon.tsx | 60 ++++++++++++++ .../Icons/GiftItemsAndBalanceBtnIcon.tsx | 74 +++++++++++++++++ src/components/Icons/GiftItemsBtnIcon.tsx | 67 +++++++++++++++ src/features/order/orderSlice.ts | 11 ++- src/pages/EGiftCards/EGiftCards.module.css | 0 src/pages/EGiftCards/EGiftCards.tsx | 24 ++++++ .../EGiftCards/components/ECardButton.tsx | 37 +++++++++ src/pages/EGiftCards/components/ECardList.tsx | 14 ++++ src/pages/EGiftCards/type.ts | 9 ++ .../cart/components/cartFooter/CartFooter.tsx | 5 +- src/pages/restaurant/RestaurantServices.tsx | 31 +++++-- src/pages/restaurant/restaurant.module.css | 2 +- src/redux/api/others.ts | 14 +++- src/utils/constants.ts | 1 + 17 files changed, 434 insertions(+), 10 deletions(-) create mode 100644 src/components/CustomBottomSheet/GiftTypeBottomSheet.tsx create mode 100644 src/components/Icons/GiftBalanceBtnIcon.tsx create mode 100644 src/components/Icons/GiftItemsAndBalanceBtnIcon.tsx create mode 100644 src/components/Icons/GiftItemsBtnIcon.tsx create mode 100644 src/pages/EGiftCards/EGiftCards.module.css create mode 100644 src/pages/EGiftCards/EGiftCards.tsx create mode 100644 src/pages/EGiftCards/components/ECardButton.tsx create mode 100644 src/pages/EGiftCards/components/ECardList.tsx create mode 100644 src/pages/EGiftCards/type.ts diff --git a/src/assets/locals/ar.json b/src/assets/locals/ar.json index ec5422e..3d651d5 100644 --- a/src/assets/locals/ar.json +++ b/src/assets/locals/ar.json @@ -453,5 +453,11 @@ "shareLink": "شارك الرابط", "shareLinkDescription": "يمكن للأصدقاء مسح الكود الباري أو استخدام الرابط لعرض الفاتورة ودفع نصيبهم بأمان من هاتفهم.", "shareThisToSplitTheBill": "شارك هذا لتقسيم الفاتورة" + }, + "gift": { + "items": "هدية العناصر", + "balance": "هدية الرصيد", + "itemsAndBalance": "العناصر والرصيد", + "selectGiftType": "اختر نوع الهدية" } } diff --git a/src/assets/locals/en.json b/src/assets/locals/en.json index a3f0514..3730e65 100644 --- a/src/assets/locals/en.json +++ b/src/assets/locals/en.json @@ -465,5 +465,11 @@ "shareLink": "Share Link", "shareLinkDescription": "Your friends can scan the QR code or use the link to view the bill and pay their share securely from their phone.", "shareThisToSplitTheBill": "Share this to split the bill" + }, + "gift": { + "items": "Gift Items", + "balance": "Gift Balance", + "itemsAndBalance": "Items and Balance", + "selectGiftType": "Select Gift Type" } } diff --git a/src/components/CustomBottomSheet/GiftTypeBottomSheet.tsx b/src/components/CustomBottomSheet/GiftTypeBottomSheet.tsx new file mode 100644 index 0000000..c8684c4 --- /dev/null +++ b/src/components/CustomBottomSheet/GiftTypeBottomSheet.tsx @@ -0,0 +1,83 @@ +import { useTranslation } from "react-i18next"; +import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet"; +import { updateGiftType } from "features/order/orderSlice"; +import { useAppDispatch } from "redux/hooks"; +import GiftItemsBtnIcon from "components/Icons/GiftItemsBtnIcon"; +import GiftBalanceBtnIcon from "components/Icons/GiftBalanceBtnIcon"; +import GiftItemsAndBalanceBtnIcon from "components/Icons/GiftItemsAndBalanceBtnIcon"; +import ProText from "components/ProText"; + +interface GiftTypeBottomSheetProps { + isOpen: boolean; + onClose: () => void; + onSave?: () => void; +} + +export function GiftTypeBottomSheet({ + isOpen, + onClose, + onSave, +}: GiftTypeBottomSheetProps) { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const handleSave = (value: string) => { + dispatch(updateGiftType(value)); + onClose(); + onSave?.(); + }; + + const handleCancel = () => { + onClose(); + }; + + const textStyle: React.CSSProperties = { + fontWeight: 500, + fontStyle: "Medium", + fontSize: 14, + lineHeight: "140%", + letterSpacing: "0%", + textAlign: "center", + color: "#86858E", + }; + return ( + +
+
handleSave("items")}> + +
+ {t("gift.items")} +
+
handleSave("vouchers")}> + +
+ {t("gift.balance")} +
+
handleSave("itemsAndVouchers")} + > + +
+ {t("gift.itemsAndBalance")} +
+
+
+ ); +} diff --git a/src/components/Icons/GiftBalanceBtnIcon.tsx b/src/components/Icons/GiftBalanceBtnIcon.tsx new file mode 100644 index 0000000..55a4210 --- /dev/null +++ b/src/components/Icons/GiftBalanceBtnIcon.tsx @@ -0,0 +1,60 @@ +interface GiftBalanceBtnIconType { + className?: string; + onClick?: () => void; + dimension?: string; +} + +const GiftBalanceBtnIcon = ({ + className, + onClick, + dimension, +}: GiftBalanceBtnIconType) => { + return ( + + + + + + + + + + + + + ); +}; + +export default GiftBalanceBtnIcon; diff --git a/src/components/Icons/GiftItemsAndBalanceBtnIcon.tsx b/src/components/Icons/GiftItemsAndBalanceBtnIcon.tsx new file mode 100644 index 0000000..74e951a --- /dev/null +++ b/src/components/Icons/GiftItemsAndBalanceBtnIcon.tsx @@ -0,0 +1,74 @@ +interface GiftItemsAndBalanceBtnIconType { + className?: string; + onClick?: () => void; + dimension?: string; +} + +const GiftItemsAndBalanceBtnIcon = ({ + className, + onClick, + dimension, +}: GiftItemsAndBalanceBtnIconType) => { + return ( + + + + + + + + + + + + + + + + + + ); +}; + +export default GiftItemsAndBalanceBtnIcon; diff --git a/src/components/Icons/GiftItemsBtnIcon.tsx b/src/components/Icons/GiftItemsBtnIcon.tsx new file mode 100644 index 0000000..6645802 --- /dev/null +++ b/src/components/Icons/GiftItemsBtnIcon.tsx @@ -0,0 +1,67 @@ +interface GiftItemsBtnIconType { + className?: string; + onClick?: () => void; + dimension?: string; +} + +const GiftItemsBtnIcon = ({ + className, + onClick, + dimension, +}: GiftItemsBtnIconType) => { + return ( + + + + + + + + + + + + + + ); +}; + +export default GiftItemsBtnIcon; diff --git a/src/features/order/orderSlice.ts b/src/features/order/orderSlice.ts index 79aad36..c9ab7f9 100644 --- a/src/features/order/orderSlice.ts +++ b/src/features/order/orderSlice.ts @@ -33,6 +33,7 @@ export interface GiftDetailsType { senderPhone: string; senderEmail: string; isSecret: boolean; + cardId: string; } interface DiscountData { @@ -75,7 +76,9 @@ interface CartState { hiddenServices: number; visibleServices: number; fee: number; - } + giftType: string; + +} // localStorage keys export const CART_STORAGE_KEYS = { @@ -108,6 +111,7 @@ export const CART_STORAGE_KEYS = { TOTAL_SERVICES: "fascano_total_services", HIDDEN_SERVICES: "fascano_hidden_services", VISIBLE_SERVICES: "fascano_visible_services", + GIFT_TYPE: "fascano_gift_type", } as const; // Utility functions for localStorage @@ -200,6 +204,7 @@ const initialState: CartState = { hiddenServices: 0, visibleServices: 0, fee: 0, + giftType: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_TYPE, ""), }; const orderSlice = createSlice({ @@ -681,6 +686,9 @@ const orderSlice = createSlice({ updateCustomerName(state, action: PayloadAction) { state.customerName = action.payload; }, + updateGiftType(state, action: PayloadAction) { + state.giftType = action.payload; + }, }, }); @@ -720,6 +728,7 @@ export const { updateOrder, updateSplitBillAmount, updateCustomerName, + updateGiftType, } = orderSlice.actions; // Tax calculation helper functions diff --git a/src/pages/EGiftCards/EGiftCards.module.css b/src/pages/EGiftCards/EGiftCards.module.css new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/EGiftCards/EGiftCards.tsx b/src/pages/EGiftCards/EGiftCards.tsx new file mode 100644 index 0000000..6d4ad8a --- /dev/null +++ b/src/pages/EGiftCards/EGiftCards.tsx @@ -0,0 +1,24 @@ +import { Layout, Spin } from "antd"; +import PaymentMethods from "components/PaymentMethods/PaymentMethods"; +import ProHeader from "components/ProHeader/ProHeader"; +import { useTranslation } from "react-i18next"; +import styles from "../address/address.module.css"; +import ECardButton from "./components/ECardButton"; +import { useGetEGiftCardsQuery } from "redux/api/others"; +import ECardList from "./components/ECardList"; + +export default function EGiftCardsPage() { + const { t } = useTranslation(); + const { data: eGiftCards, isLoading } = useGetEGiftCardsQuery(); + + return ( + + {t("checkout.title")} + + + {isLoading ? : } + + + + ); +} diff --git a/src/pages/EGiftCards/components/ECardButton.tsx b/src/pages/EGiftCards/components/ECardButton.tsx new file mode 100644 index 0000000..40a2ee5 --- /dev/null +++ b/src/pages/EGiftCards/components/ECardButton.tsx @@ -0,0 +1,37 @@ +import { Button, Layout, message } from "antd"; +import { useTranslation } from "react-i18next"; +import styles from "../../address/address.module.css"; +import { useCallback } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { selectCart } from "features/order/orderSlice"; +import { useAppSelector } from "redux/hooks"; + +export default function ECardButton() { + const { t } = useTranslation(); + const { subdomain } = useParams(); + const navigate = useNavigate(); + const { giftDetails } = useAppSelector(selectCart); + + const handleSelectCard = useCallback(() => { + if (giftDetails?.cardId) { + navigate(`/${subdomain}/cardDetails/${giftDetails?.cardId}`); + } else { + message.error(t("gift.pleaseSelectCard")); + } + }, [navigate, subdomain, giftDetails?.cardId]); + + return ( + <> + + + + + ); +} diff --git a/src/pages/EGiftCards/components/ECardList.tsx b/src/pages/EGiftCards/components/ECardList.tsx new file mode 100644 index 0000000..ca5b586 --- /dev/null +++ b/src/pages/EGiftCards/components/ECardList.tsx @@ -0,0 +1,14 @@ +import { Card, Image } from "antd"; +import { EGiftCard } from "../type"; + +export default function ECardList({ eGiftCards }: { eGiftCards: EGiftCard[] }) { + return ( + <> + {eGiftCards.map((card: EGiftCard) => ( + + {card.image} + + ))} + + ); +} diff --git a/src/pages/EGiftCards/type.ts b/src/pages/EGiftCards/type.ts new file mode 100644 index 0000000..a8adbad --- /dev/null +++ b/src/pages/EGiftCards/type.ts @@ -0,0 +1,9 @@ +export interface EGiftCard { + id: number; + image: string; + restorant_id: number | null; + is_available: number; + created_at: string; + updated_at: string; + imageURL: string; +} diff --git a/src/pages/cart/components/cartFooter/CartFooter.tsx b/src/pages/cart/components/cartFooter/CartFooter.tsx index 8f4fa8b..7387f67 100644 --- a/src/pages/cart/components/cartFooter/CartFooter.tsx +++ b/src/pages/cart/components/cartFooter/CartFooter.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import { Link, useNavigate, useParams } from "react-router-dom"; import { useAppSelector } from "redux/hooks.ts"; import styles from "./footer.module.css"; +import { OrderType } from "pages/checkout/hooks/types"; interface CartFooterProps { form: FormInstance; @@ -63,7 +64,9 @@ export default function CartFooter({ form }: CartFooterProps) { }} onClick={handleCheckoutClick} > - {t("cart.checkout")} + {orderType === OrderType.Gift + ? t("cart.continueToGiftDetails") + : t("cart.checkout")} ); diff --git a/src/pages/restaurant/RestaurantServices.tsx b/src/pages/restaurant/RestaurantServices.tsx index 1624b92..66c0f72 100644 --- a/src/pages/restaurant/RestaurantServices.tsx +++ b/src/pages/restaurant/RestaurantServices.tsx @@ -1,4 +1,3 @@ -import { ScheduleFilled } from "@ant-design/icons"; import { Card } from "antd"; import BackIcon from "components/Icons/BackIcon"; import DeliveryIcon from "components/Icons/DeliveryIcon"; @@ -11,21 +10,26 @@ import ToRoomIcon from "components/Icons/ToRoomIcon"; import { updateOrderType } from "features/order/orderSlice"; import { OrderType } from "pages/checkout/hooks/types.ts"; import { useTranslation } from "react-i18next"; -import { Link, useParams } from "react-router-dom"; +import { Link, useNavigate, useParams } from "react-router-dom"; import { useGetRestaurantDetailsQuery } from "redux/api/others"; import { useAppDispatch, useAppSelector } from "redux/hooks"; import styles from "./restaurant.module.css"; import ScheduleOrderIcon from "components/Icons/ScheduleOrderIcon"; import ProText from "components/ProText"; +import { GiftTypeBottomSheet } from "components/CustomBottomSheet/GiftTypeBottomSheet"; +import { useState } from "react"; export default function RestaurantServices() { const { t } = useTranslation(); const { isRTL } = useAppSelector((state) => state.locale); const { subdomain } = useParams(); + const navigate = useNavigate(); const dispatch = useAppDispatch(); const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain, { skip: !subdomain, }); + const [isGiftTypeBottomSheetOpen, setIsGiftTypeBottomSheetOpen] = + useState(false); const { dineIn, @@ -188,12 +192,19 @@ export default function RestaurantServices() { return (
{services?.map((s) => { + const isGift = s?.id === OrderType.Gift; return ( { - dispatch(updateOrderType(s?.id)); + onClick={(e) => { + if (isGift) { + e.preventDefault(); + dispatch(updateOrderType(s?.id)); + setIsGiftTypeBottomSheetOpen(true); + } else { + dispatch(updateOrderType(s?.id)); + } }} style={{ width: "100%", @@ -225,7 +236,7 @@ export default function RestaurantServices() { lineHeight: "140%", letterSpacing: "0%", verticalAlign: "middle", - color: "#434E5C" + color: "#434E5C", }} > {s?.title} @@ -242,6 +253,14 @@ export default function RestaurantServices() { ); })} + setIsGiftTypeBottomSheetOpen(false)} + onSave={() => { + setIsGiftTypeBottomSheetOpen(false); + navigate(`/${subdomain}/menu?orderType=${OrderType.Gift}`); + }} + />
); } diff --git a/src/pages/restaurant/restaurant.module.css b/src/pages/restaurant/restaurant.module.css index 07f45ab..d5f0afc 100644 --- a/src/pages/restaurant/restaurant.module.css +++ b/src/pages/restaurant/restaurant.module.css @@ -145,7 +145,7 @@ display: grid; gap: 16px; padding: 0 1rem; - margin: 10px 0; + margin: 24px 0 10px 0; } } diff --git a/src/redux/api/others.ts b/src/redux/api/others.ts index 34a6b0b..7006a3f 100644 --- a/src/redux/api/others.ts +++ b/src/redux/api/others.ts @@ -9,6 +9,7 @@ import { RESTAURANT_DETAILS_URL, TABLES_URL, USER_DETAILS_URL, + EGIFT_CARDS_URL, } from "utils/constants"; import { OrderDetails } from "pages/checkout/hooks/types"; @@ -19,6 +20,7 @@ import { UserType, } from "utils/types/appTypes"; import { baseApi } from "./apiSlice"; +import { EGiftCard } from "pages/EGiftCards/type"; export const branchApi = baseApi.injectEndpoints({ endpoints: (builder) => ({ @@ -31,7 +33,7 @@ export const branchApi = baseApi.injectEndpoints({ }, }), transformResponse: (response: any) => { - return response?.result?.restaurants?.[0] + return response?.result?.restaurants?.[0]; }, providesTags: ["Restaurant"], }), @@ -160,6 +162,15 @@ export const branchApi = baseApi.injectEndpoints({ }), invalidatesTags: ["Orders", "Restaurant"], }), + getEGiftCards: builder.query({ + query: () => ({ + url: EGIFT_CARDS_URL, + method: "GET", + }), + transformResponse: (response: any) => { + return response.result; + }, + }), }), }); export const { @@ -173,4 +184,5 @@ export const { useGetDiscountMutation, useRateOrderMutation, useGetUserDetailsQuery, + useGetEGiftCardsQuery, } = branchApi; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 44d7745..e18dd34 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -108,3 +108,4 @@ export const SEND_OTP_URL = `${API_BASE_URL}sendOtp`; export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`; export const PAYMENT_CONFIRMATION_URL = `https://menu.fascano.com/payment/confirmation`; export const DISCOUNT_URL = `${BASE_URL}getDiscount`; +export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`; \ No newline at end of file