diff --git a/src/assets/locals/ar.json b/src/assets/locals/ar.json index 221f328..7619ca0 100644 --- a/src/assets/locals/ar.json +++ b/src/assets/locals/ar.json @@ -77,7 +77,7 @@ "office": "المكتب", "booking": "الحجز", "scheduledOrder": "الطلب المجدول", - "loyaltyAndReward": "المكافأة واللايفور" + "rewardsAndLoyalty": "المكافأة واللايفور" }, "home": { "title": "العنوان", @@ -567,5 +567,30 @@ "viewAll": "عرض الكل", "voucherCodeCopied": "تم نسخ رمز القسيمة", "copyFailed": "فشل نسخ رمز القسيمة" - } + }, + "rewardsAndLoyalty": { + "title": "المكافأة واللايفور", + "description": "المكافأة واللايفور", + "rewardsAndLoyalty": "المكافأة واللايفور", + "rewardsAndLoyaltyDescription": "المكافأة واللايفور", + "rewardsAndLoyaltyButton": "المكافأة واللايفور", + "rewardsAndLoyaltyButtonDescription": "المكافأة واللايفور", + "completedPurchases": "المشتريات المكتملة", + "totalPurchased": "المشتريات الكلية", + "saved": "المحفوظ", + "almosthere": "قريب جدا!", + "youreJustXCupsAwayFromYourNextReward": "أنت فقط {{cups}} أكواب بعيد عن المكافأة التالية!", + "youCanRedeemDuringTheCheckout": "يمكنك استخدام المكافأة أثناء الدفع", + "youCurrentlyHave": "لديك", + "freeItems": "عناصر مجانية", + "redeemNow": "استخدم الآن", + "yourAvailableRewards": "المكافأات المتاحة", + "loyaltyHistory": "سجل الولاء", + "loyaltyHistoryDescription": "سجل الولاء", + "earnedPoints": "حصل على {{points}} نقطة", + "order": "الطلب", + "yourOrderFrom": "طلبك من {{restaurantName}}", + "earned": "حصل على", + "xPoints": "{{points}} نقطة" + } } diff --git a/src/assets/locals/en.json b/src/assets/locals/en.json index b851658..d0da9c6 100644 --- a/src/assets/locals/en.json +++ b/src/assets/locals/en.json @@ -77,7 +77,7 @@ "restaurantCover": "Restaurant Cover", "restaurantLogo": "Restaurant Logo", "scheduledOrder": "Scheduled Order", - "loyaltyAndReward": "Rewards And Loyalty" + "rewardsAndLoyalty": "Rewards And Loyalty" }, "home": { "title": "title", @@ -587,5 +587,30 @@ "copyFailed": "Failed to copy voucher code", "hiX": "Hi {{name}}!", "youHaveReceivedAGiftCarFromX": "You have received a gift car from {{name}}!" + }, + "rewardsAndLoyalty": { + "title": "Rewards And Loyalty", + "description": "Rewards And Loyalty", + "rewardsAndLoyalty": "Rewards And Loyalty", + "rewardsAndLoyaltyDescription": "Rewards And Loyalty", + "rewardsAndLoyaltyButton": "Rewards And Loyalty", + "rewardsAndLoyaltyButtonDescription": "Rewards And Loyalty", + "completedPurchases": "Completed Purchases", + "totalPurchased": "Total Purchased", + "saved": "Saved", + "almosthere": "Almost here!", + "youreJustXCupsAwayFromYourNextReward": "You're just {{cups}} cups away from your next reward!", + "youCanRedeemDuringTheCheckout": "You can redeem during the checkout", + "youCurrentlyHave": "You currently have", + "freeItems": "free items", + "redeemNow": "Redeem Now", + "yourAvailableRewards": "Your available rewards", + "loyaltyHistory": "Loyalty History", + "loyaltyHistoryDescription": "Loyalty History", + "earnedPoints": "Earned {{points}} points", + "order": "Order", + "yourOrderFrom": "Your order from {{restaurantName}}", + "earned": "Earned", + "xPoints": "{{points}} points" } } diff --git a/src/components/Icons/BranchesIcon.tsx b/src/components/Icons/BranchesIcon.tsx index 5c54510..963e60e 100644 --- a/src/components/Icons/BranchesIcon.tsx +++ b/src/components/Icons/BranchesIcon.tsx @@ -17,44 +17,44 @@ const BranchesIcon = ({ className, onClick }: BranchesIconType) => { ); diff --git a/src/components/Icons/CoinsIcon.tsx b/src/components/Icons/CoinsIcon.tsx new file mode 100644 index 0000000..31bb600 --- /dev/null +++ b/src/components/Icons/CoinsIcon.tsx @@ -0,0 +1,47 @@ +interface CoinsIconType { + className?: string; + onClick?: () => void; +} + +const CoinsIcon = ({ className, onClick }: CoinsIconType) => { + return ( + + + + + + + ); +}; + +export default CoinsIcon; diff --git a/src/components/Icons/CupIcon.tsx b/src/components/Icons/CupIcon.tsx new file mode 100644 index 0000000..46eb953 --- /dev/null +++ b/src/components/Icons/CupIcon.tsx @@ -0,0 +1,25 @@ +interface CupIconType { + className?: string; + onClick?: () => void; +} + +const CupIcon = ({ className, onClick }: CupIconType) => { + return ( + + + + ); +}; + +export default CupIcon; diff --git a/src/components/Icons/PopularIcon.tsx b/src/components/Icons/PopularIcon.tsx new file mode 100644 index 0000000..82a8c1c --- /dev/null +++ b/src/components/Icons/PopularIcon.tsx @@ -0,0 +1,51 @@ +interface PopularIconType { + className?: string; + onClick?: () => void; +} + +const PopularIcon = ({ className, onClick }: PopularIconType) => { + return ( + + + + + + + + + + ); +}; + +export default PopularIcon; diff --git a/src/components/Icons/RaiseIcon.tsx b/src/components/Icons/RaiseIcon.tsx new file mode 100644 index 0000000..68394c9 --- /dev/null +++ b/src/components/Icons/RaiseIcon.tsx @@ -0,0 +1,33 @@ +interface RaiseIconType { + className?: string; + onClick?: () => void; +} + +const RaiseIcon = ({ className, onClick }: RaiseIconType) => { + return ( + + + + + ); +}; + +export default RaiseIcon; diff --git a/src/layouts/app/hooks/useHeaderMenu.tsx b/src/layouts/app/hooks/useHeaderMenu.tsx index 29e6bec..e73b1de 100644 --- a/src/layouts/app/hooks/useHeaderMenu.tsx +++ b/src/layouts/app/hooks/useHeaderMenu.tsx @@ -66,13 +66,16 @@ export default function useHeaderMenu() { }, }, { - key: "loyalty", + key: "rewardsAndLoyalty", icon: , label: ( - - {t("common.loyaltyAndReward")} + + {t("common.rewardsAndLoyalty")} ), + onClick: () => { + navigate(`/${subdomain}/rewards-and-loyalty`); + }, }, { key: "branches", diff --git a/src/pages/rewardsAndLoyalty/page.tsx b/src/pages/rewardsAndLoyalty/page.tsx new file mode 100644 index 0000000..bc99a4c --- /dev/null +++ b/src/pages/rewardsAndLoyalty/page.tsx @@ -0,0 +1,472 @@ +import { Button, Card, Flex, Layout, Progress, Timeline, Tooltip } from "antd"; + +import ProHeader from "components/ProHeader/ProHeader"; +import ProText from "components/ProText"; +import { useTranslation } from "react-i18next"; +import { useNavigate, useParams } from "react-router-dom"; +import styles from "./rewardsAndLoyalty.module.css"; + +import { OrderType } from "pages/checkout/hooks/types.ts"; +import { useAppSelector } from "redux/hooks"; +import { selectCart } from "features/order/orderSlice"; +import CoinsIcon from "components/Icons/CoinsIcon"; +import ArabicPrice from "components/ArabicPrice"; +import RaiseIcon from "components/Icons/RaiseIcon"; +import CupIcon from "components/Icons/CupIcon"; +import ProInputCard from "components/ProInputCard/ProInputCard"; +import { useGetLoyaltyHistoryQuery } from "redux/api/others"; +import dayjs from "dayjs"; +import PopularIcon from "components/Icons/PopularIcon"; + +export default function RewardsAndLoyalityPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { subdomain } = useParams(); + + const { restaurant } = useAppSelector(selectCart); + const loyaltyStamps = restaurant?.loyalty_stamps ?? 0; + const customerLoyaltyPoints = restaurant?.customer_loyalty_points ?? 0; + const { data: loyaltyHistory } = useGetLoyaltyHistoryQuery(); + + console.log(loyaltyHistory); + + // Calculate percentage: (customer_loyalty_points / loyalty_stamps) * 100 + const progressPercent = + loyaltyStamps > 0 + ? Math.min((customerLoyaltyPoints / loyaltyStamps) * 100, 100) + : 0; + + const handleCheckout = () => { + navigate(`/${subdomain}/menu?orderType=${OrderType.Redeem}`); + }; + + return ( + <> + + {t("rewardsAndLoyalty.title")} + + + +
+ ( +
+ + {Number(customerLoyaltyPoints / loyaltyStamps).toFixed( + 0, + )} + + + {t("rewardsAndLoyalty.completedPurchases")} + +
+ )} + strokeColor="#FFB700" + size={250} + type="circle" + /> +
+
+
+ + +
+
+ +
+
+ + {t("rewardsAndLoyalty.saved")} + + + + + +
+
+ +
+
+ +
+
+ + {t("rewardsAndLoyalty.totalPurchased")} + + + + {loyaltyStamps} + +
+
+
+ +
+
+ +
+
+ + {t("rewardsAndLoyalty.almosthere")} + + + + {t("rewardsAndLoyalty.youreJustXCupsAwayFromYourNextReward", { + cups: loyaltyStamps - (customerLoyaltyPoints % loyaltyStamps), + })} + +
+
+ + +
+
+ +
+ + {t("rewardsAndLoyalty.youCurrentlyHave")} + + + + {Math.floor(customerLoyaltyPoints / loyaltyStamps)} + + {t("rewardsAndLoyalty.freeItems")} + + + {t("rewardsAndLoyalty.youCanRedeemDuringTheCheckout")} + + +
+
+ + + 0 + ? loyaltyHistory.map((item: any, index: number) => ({ + content: ( +
+ {/* + {item.restaurant_name || + item.name || + item.restaurantName || + t("rewardsAndLoyalty.order")} + */} + {(item.created_at || + item.date || + item.order_date || + true) && ( + + {dayjs( + item.created_at || item.date || item.order_date, + ).format("DD MMMM YYYY")} + + )} +
+
+ +
+
+ + {t("rewardsAndLoyalty.earned")}{" "} + + {t("rewardsAndLoyalty.xPoints", { + points: + item.total_points || + item.points || + item.loyalty_stamps, + })} + + + + + {t("rewardsAndLoyalty.yourOrderFrom", { + restaurantName: + item.restaurant_name || + item.name || + item.restaurantName, + })} + +
+
+
+ ), + })) + : [] + } + /> +
+
+
+ + ); +} diff --git a/src/pages/rewardsAndLoyalty/rewardsAndLoyalty.module.css b/src/pages/rewardsAndLoyalty/rewardsAndLoyalty.module.css new file mode 100644 index 0000000..2b146ec --- /dev/null +++ b/src/pages/rewardsAndLoyalty/rewardsAndLoyalty.module.css @@ -0,0 +1,45 @@ +.redeemContainer { + display: flex; + flex-direction: column; + padding: 16px; + gap: 16px; + overflow: auto; + scrollbar-width: none; +} + +.orderNotes { + gap: 20px; + border-radius: 16px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + background-color: var(--secondary-background); + padding: 16px; + width: 100%; +} + +.nextRewards { + gap: 20px; + border-radius: 16px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + background-color: #fefbf2; + padding: 16px; + width: 100%; + border: 1px solid #ffedb0; +} + +.loyaltyHistoryItem { + gap: 20px; + border-radius: 16px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + background-color: var(--secondary-background); + padding: 16px; + width: 100%; +} diff --git a/src/pages/rewardsAndLoyalty/types.ts b/src/pages/rewardsAndLoyalty/types.ts new file mode 100644 index 0000000..039bff8 --- /dev/null +++ b/src/pages/rewardsAndLoyalty/types.ts @@ -0,0 +1,94 @@ +export interface RedeemResponse { + gift: Gift + working_hours: WorkingHours + notes: Notes + is_restaurant_closed: boolean + } + + export interface Gift { + id: number + voucher_amount: string + amount: string + restorant_id: number + voucher_code: string + used_amount: string + show_sender_info: number + remaining_amount: string + is_used: number + sender_phone: string + gift_type: string + sender_name: string + recipient_phone: string + recipient_name: string + message: any + created_at: string + card_url: string + gr_url: string + restaurant: string + global_currency: string + lat: string + lng: string + local_currency: string + restaurant_iimage: string + order_id: number + items: Item[] + itemsImagePrefix: string + itemsImagePrefixOld: string + } + + export interface Item { + id: number + is_loyalty_used: number + no_of_stamps_give: number + isHasLoyalty: boolean + is_vat_disabled: number + name: string + price: number + qty: number + variant_price: string + image: string + imageName: string + variantName: string + variantLocalName: string + extras: any[] + descriptionEN: string + descriptionAR: string + itemline: string + itemlineAR: string + itemlineAREN: string + extrasgroups: any[] + extragroupnew: any[] + itemComment: string + variant: any + itemExtras: any[] + AvaiilableVariantExtras: any[] + isPrinted: number + category_id: number + pos_order_id: string + updated_at: string + created_at: string + old_qty: number + new_qty: number + last_printed_qty: number + deleted_qty: number + discount_value: any + discount_type_id: any + original_price: number + pricing_method: string + is_already_paid: number + hash_item: string + } + + export interface WorkingHours { + opening_time: string + closing_time: string + opening_time_2: any + closing_time_2: any + is_open: boolean + } + + export interface Notes { + en: string + ar: string + } + \ No newline at end of file diff --git a/src/redux/api/others.ts b/src/redux/api/others.ts index 5665cc2..df0286f 100644 --- a/src/redux/api/others.ts +++ b/src/redux/api/others.ts @@ -11,6 +11,7 @@ import { USER_DETAILS_URL, EGIFT_CARDS_URL, REDEEM_DETAILS_URL, + LOYALTY_HISTORY_URL, } from "utils/constants"; import { OrderDetails } from "pages/checkout/hooks/types"; @@ -182,6 +183,25 @@ export const branchApi = baseApi.injectEndpoints({ return response.result; }, }), + getLoyaltyHistory: builder.query< + { + restaurant_id: number; + restaurant_name: string; + restaurant_icon: string; + total_points: string; + loyalty_stamp_image: string; + loyalty_stamps: number; + }[], + void + >({ + query: () => ({ + url: LOYALTY_HISTORY_URL, + method: "GET", + }), + transformResponse: (response: any) => { + return response.result.data.orders; + }, + }), }), }); export const { @@ -197,4 +217,5 @@ export const { useGetUserDetailsQuery, useGetEGiftCardsQuery, useGetRedeemDetailsQuery, + useGetLoyaltyHistoryQuery, } = branchApi; diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 533dbf4..eea3eb2 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -18,6 +18,7 @@ import OtpPage from "pages/otp/page"; import ProductDetailPage from "pages/product/page"; import RedeemPage from "pages/redeem/page"; import RestaurantPage from "pages/restaurant/page"; +import RewardsAndLoyalityPage from "pages/rewardsAndLoyalty/page"; import SearchPage from "pages/search/page"; import SplitBillPage from "pages/split-bill/page"; import React, { ReactNode, Suspense, useEffect } from "react"; @@ -170,6 +171,11 @@ export const router = createHashRouter([ element: } />, errorElement: , }, + { + path: "rewards-and-loyalty", + element: } />, + errorElement: , + }, ], }, { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7202743..c668b82 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -109,4 +109,5 @@ 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`; -export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`; \ No newline at end of file +export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`; +export const LOYALTY_HISTORY_URL = `${BASE_URL}loyaltyHistory`; \ No newline at end of file