Compare commits

..

9 Commits

33 changed files with 1376 additions and 196 deletions

View File

@@ -76,7 +76,8 @@
"room": "الغرفة", "room": "الغرفة",
"office": "المكتب", "office": "المكتب",
"booking": "الحجز", "booking": "الحجز",
"scheduledOrder": "الطلب المجدول" "scheduledOrder": "الطلب المجدول",
"rewardsAndLoyalty": "المكافأة واللايفور"
}, },
"home": { "home": {
"title": "العنوان", "title": "العنوان",
@@ -91,7 +92,7 @@
"scheduledOrder": "طلب مجدول" "scheduledOrder": "طلب مجدول"
}, },
"promotion": { "promotion": {
"title": "الترويجات", "title": "عرض التفاصيل",
"description": "احصل على خصم 10% على طلبك الأول" "description": "احصل على خصم 10% على طلبك الأول"
} }
}, },
@@ -129,7 +130,7 @@
"payDescription": "الدفع", "payDescription": "الدفع",
"rating": "التقييم ", "rating": "التقييم ",
"loyaltyPoints": "نقاط الولاء", "loyaltyPoints": "نقاط الولاء",
"loyaltyDescription": "اشترى {{value}} وجبات واحصل على وجبة مجانية", "loyaltyDescription": "اشترى {{value}} وجبات واحصل على وجبة مجانية!",
"youMightAlsoLike": "قد تعجبك أيضاً..", "youMightAlsoLike": "قد تعجبك أيضاً..",
"choose1": "اختر 1", "choose1": "اختر 1",
"specialRequest": "طلب خاص", "specialRequest": "طلب خاص",
@@ -164,7 +165,10 @@
"restaurantIsClosed": "المطعم مغلق", "restaurantIsClosed": "المطعم مغلق",
"address": "العنوان", "address": "العنوان",
"openingTimes": "ساعات العمل", "openingTimes": "ساعات العمل",
"customizable": "قابل للتخصيص" "customizable": "قابل للتخصيص",
"youHaveXEarnedRewardsReadyToRedeem": "🎉 لديك {{rewards}} مكافأة مستحقة للاستخدام!",
"justXMorePurchasesToUnlockYourFREEItem": "فقط {{cups}} أكثر للفتح الوجبة المجانية!",
"youreJustXCupsAwayFromYourNextReward": "🎉 أنت فقط {{cups}} أكثر للحصول على المكافأة التالية!"
}, },
"cart": { "cart": {
"addSpecialRequestOptional": "إضافة طلب خاص (اختياري)", "addSpecialRequestOptional": "إضافة طلب خاص (اختياري)",
@@ -566,5 +570,30 @@
"viewAll": "عرض الكل", "viewAll": "عرض الكل",
"voucherCodeCopied": "تم نسخ رمز القسيمة", "voucherCodeCopied": "تم نسخ رمز القسيمة",
"copyFailed": "فشل نسخ رمز القسيمة" "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}} نقطة"
} }
} }

View File

@@ -76,7 +76,8 @@
"noMenuItemsAvailable": "No menu items available", "noMenuItemsAvailable": "No menu items available",
"restaurantCover": "Restaurant Cover", "restaurantCover": "Restaurant Cover",
"restaurantLogo": "Restaurant Logo", "restaurantLogo": "Restaurant Logo",
"scheduledOrder": "Scheduled Order" "scheduledOrder": "Scheduled Order",
"rewardsAndLoyalty": "Rewards And Loyalty"
}, },
"home": { "home": {
"title": "title", "title": "title",
@@ -107,7 +108,7 @@
"scheduledOrder": "Scheduled Order" "scheduledOrder": "Scheduled Order"
}, },
"promotion": { "promotion": {
"title": "Promotions", "title": "Show details",
"description": "Get 10% off your first order" "description": "Get 10% off your first order"
} }
}, },
@@ -143,7 +144,7 @@
"close": "Close", "close": "Close",
"rating": "Rating ", "rating": "Rating ",
"loyaltyPoints": "Loyalty Points", "loyaltyPoints": "Loyalty Points",
"loyaltyDescription": "Buy {{value}} meals and get 1 FREE", "loyaltyDescription": "Buy {{value}} meals and get 1 FREE!",
"choose1": "Choose 1", "choose1": "Choose 1",
"youMightAlsoLike": "You might also like..", "youMightAlsoLike": "You might also like..",
"specialRequest": "Special Request", "specialRequest": "Special Request",
@@ -176,7 +177,10 @@
"payDescription": "Pay for your order", "payDescription": "Pay for your order",
"address": "Address", "address": "Address",
"openingTimes": "Opening Times", "openingTimes": "Opening Times",
"customizable": "Customizable" "customizable": "Customizable",
"youHaveXEarnedRewardsReadyToRedeem": "🎉 You have {{rewards}} rewards ready to redeem!",
"justXMorePurchasesToUnlockYourFREEItem": "Just {{cups}} more purchases to unlock your FREE item!",
"youreJustXCupsAwayFromYourNextReward": "🎉 You're just {{cups}} stamps away from your next reward!"
}, },
"cart": { "cart": {
"remainingToPay": "Remaining to Pay", "remainingToPay": "Remaining to Pay",
@@ -281,7 +285,9 @@
"am": "AM", "am": "AM",
"pm": "PM", "pm": "PM",
"cannotSelectPastDate": "You cannot select a past date. Please select today or a future date.", "cannotSelectPastDate": "You cannot select a past date. Please select today or a future date.",
"checkRequiredFields": "Please check required fields" "checkRequiredFields": "Please check required fields",
"applyYourAvailableRewardsToGetDiscountsOnItemsInYourCart": "Apply your available rewards to get discounts on items in your cart",
"loyalty": "Loyalty"
}, },
"checkout": { "checkout": {
"addCarDetails": "Add Car Details", "addCarDetails": "Add Car Details",
@@ -586,5 +592,30 @@
"copyFailed": "Failed to copy voucher code", "copyFailed": "Failed to copy voucher code",
"hiX": "Hi {{name}}!", "hiX": "Hi {{name}}!",
"youHaveReceivedAGiftCarFromX": "You have received a gift car from {{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"
} }
} }

View File

@@ -6,6 +6,7 @@ import GiftItemsBtnIcon from "components/Icons/GiftItemsBtnIcon";
import GiftBalanceBtnIcon from "components/Icons/GiftBalanceBtnIcon"; import GiftBalanceBtnIcon from "components/Icons/GiftBalanceBtnIcon";
import GiftItemsAndBalanceBtnIcon from "components/Icons/GiftItemsAndBalanceBtnIcon"; import GiftItemsAndBalanceBtnIcon from "components/Icons/GiftItemsAndBalanceBtnIcon";
import ProText from "components/ProText"; import ProText from "components/ProText";
import { useParams, useNavigate } from "react-router-dom";
interface GiftTypeBottomSheetProps { interface GiftTypeBottomSheetProps {
isOpen: boolean; isOpen: boolean;
@@ -13,6 +14,12 @@ interface GiftTypeBottomSheetProps {
onSave?: () => void; onSave?: () => void;
} }
export enum GiftType {
Items = "items",
Vouchers = "vouchers",
ItemsAndVouchers = "itemsAndVouchers",
}
export function GiftTypeBottomSheet({ export function GiftTypeBottomSheet({
isOpen, isOpen,
onClose, onClose,
@@ -20,11 +27,15 @@ export function GiftTypeBottomSheet({
}: GiftTypeBottomSheetProps) { }: GiftTypeBottomSheetProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { subdomain } = useParams();
const handleSave = (giftType: string) => { const navigate = useNavigate();
dispatch(updateGiftDetails({ giftType: giftType })); const handleSave = (giftType: GiftType) => {
dispatch(updateGiftDetails({ giftType }));
onClose(); onClose();
onSave?.(); onSave?.();
if (giftType !== GiftType.Vouchers)
navigate(`/${subdomain}/menu?orderType=gift`);
else navigate(`/${subdomain}/e-gift-cards`);
}; };
const handleCancel = () => { const handleCancel = () => {
@@ -40,6 +51,7 @@ export function GiftTypeBottomSheet({
textAlign: "center", textAlign: "center",
color: "#86858E", color: "#86858E",
}; };
return ( return (
<ProBottomSheet <ProBottomSheet
isOpen={isOpen} isOpen={isOpen}
@@ -59,19 +71,22 @@ export function GiftTypeBottomSheet({
padding: 24, padding: 24,
}} }}
> >
<div style={{ width: 60 }} onClick={() => handleSave("items")}> <div style={{ width: 60 }} onClick={() => handleSave(GiftType.Items)}>
<GiftItemsBtnIcon /> <GiftItemsBtnIcon />
<br /> <br />
<ProText style={textStyle}>{t("gift.items")}</ProText> <ProText style={textStyle}>{t("gift.items")}</ProText>
</div> </div>
<div style={{ width: 60 }} onClick={() => handleSave("vouchers")}> <div
style={{ width: 60 }}
onClick={() => handleSave(GiftType.Vouchers)}
>
<GiftBalanceBtnIcon /> <GiftBalanceBtnIcon />
<br /> <br />
<ProText style={textStyle}>{t("gift.balance")}</ProText> <ProText style={textStyle}>{t("gift.balance")}</ProText>
</div> </div>
<div <div
style={{ width: 60 }} style={{ width: 60 }}
onClick={() => handleSave("itemsAndVouchers")} onClick={() => handleSave(GiftType.ItemsAndVouchers)}
> >
<GiftItemsAndBalanceBtnIcon /> <GiftItemsAndBalanceBtnIcon />
<br /> <br />

View File

@@ -0,0 +1,63 @@
interface BranchesIconType {
className?: string;
onClick?: () => void;
}
const BranchesIcon = ({ className, onClick }: BranchesIconType) => {
return (
<svg
width="15"
height="16"
viewBox="0 0 15 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M5.40234 3.18555C5.54014 2.593 6.04569 1.00063 7.49875 1.00063C8.95667 1.00063 9.4607 2.60361 9.59656 3.19141"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.7224 15H2.27935C1.70007 15 1.23047 14.5305 1.23047 13.9512V4.23958C1.23047 3.66035 1.70007 3.19075 2.27935 3.19075H12.7224C13.3017 3.19075 13.7713 3.66035 13.7713 4.23958V13.9512C13.7713 14.5305 13.3017 15 12.7224 15Z"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.4466 8.39983C10.7649 8.92011 10.9482 9.53128 10.9482 10.1851V10.6016H4.05078V10.1851C4.05078 8.2877 5.59476 6.74955 7.4995 6.74955C8.09085 6.74955 8.64745 6.8978 9.13388 7.15906"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.90234 10.6016H12.0989"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.5 6.75V5.94196"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.6209 12.0625H4.37929C4.18146 12.0625 4.0038 11.9419 3.93143 11.7585L3.47656 10.605H11.5236L11.0687 11.7585C10.9964 11.9419 10.8187 12.0625 10.6209 12.0625Z"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default BranchesIcon;

View File

@@ -0,0 +1,47 @@
interface CoinsIconType {
className?: string;
onClick?: () => void;
}
const CoinsIcon = ({ className, onClick }: CoinsIconType) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M15.8555 9.85547V12.4269C15.8555 13.5412 13.1692 14.9983 9.85547 14.9983C6.54175 14.9983 3.85547 13.5412 3.85547 12.4269V10.284"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M4.10938 10.5083C4.8508 11.4923 7.14366 12.4154 9.85737 12.4154C13.1711 12.4154 15.8574 11.0388 15.8574 9.85598C15.8574 9.1917 15.0114 8.46398 13.6837 7.95312"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M13.2852 5.57031V8.14174C13.2852 9.25603 10.5989 10.7132 7.28516 10.7132C3.97144 10.7132 1.28516 9.25603 1.28516 8.14174V5.57031"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.28516 8.12914C10.5989 8.12914 13.2852 6.75257 13.2852 5.56971C13.2852 4.38686 10.5989 3 7.28516 3C3.97144 3 1.28516 4.386 1.28516 5.56971C1.28516 6.75257 3.97144 8.12914 7.28516 8.12914Z"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
};
export default CoinsIcon;

View File

@@ -0,0 +1,25 @@
interface CupIconType {
className?: string;
onClick?: () => void;
}
const CupIcon = ({ className, onClick }: CupIconType) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M4.16797 2.5H16.668C17.11 2.5 17.5339 2.67559 17.8465 2.98816C18.159 3.30072 18.3346 3.72464 18.3346 4.16667V6.66667C18.3346 7.10869 18.159 7.53262 17.8465 7.84518C17.5339 8.15774 17.11 8.33333 16.668 8.33333H15.0013V10.8333C15.0013 11.7174 14.6501 12.5652 14.025 13.1904C13.3999 13.8155 12.552 14.1667 11.668 14.1667H6.66797C5.78391 14.1667 4.93607 13.8155 4.31095 13.1904C3.68582 12.5652 3.33464 11.7174 3.33464 10.8333V3.33333C3.33464 3.11232 3.42243 2.90036 3.57871 2.74408C3.73499 2.5878 3.94695 2.5 4.16797 2.5ZM15.0013 4.16667V6.66667H16.668V4.16667H15.0013ZM1.66797 15.8333H16.668V17.5H1.66797V15.8333Z"
fill="#FFB700"
/>
</svg>
);
};
export default CupIcon;

View File

@@ -0,0 +1,27 @@
interface LoginIconType {
className?: string;
onClick?: () => void;
}
const LoginIcon = ({ className, onClick }: LoginIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M10 2H10.8C11.9201 2 12.4802 2 12.908 2.21799C13.2843 2.40973 13.5903 2.71569 13.782 3.09202C14 3.51984 14 4.0799 14 5.2V10.8C14 11.9201 14 12.4802 13.782 12.908C13.5903 13.2843 13.2843 13.5903 12.908 13.782C12.4802 14 11.9201 14 10.8 14H10M6.66667 4.66667L10 8M10 8L6.66667 11.3333M10 8L2 8"
stroke="#333333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default LoginIcon;

View File

@@ -0,0 +1,37 @@
interface LoyaltyAndRewardIconType {
className?: string;
onClick?: () => void;
color?: string
}
const LoyaltyAndRewardIcon = ({ className, onClick, color }: LoyaltyAndRewardIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M1.33203 7.9987C1.33203 5.32736 1.33203 3.99203 2.11336 3.16203C2.8947 2.33203 4.15136 2.33203 6.66536 2.33203H9.33203C11.846 2.33203 13.1034 2.33203 13.884 3.16203C14.6647 3.99203 14.6654 5.32736 14.6654 7.9987C14.6654 10.67 14.6654 12.0054 13.884 12.8354C13.1027 13.6654 11.846 13.6654 9.33203 13.6654H6.66536C4.15136 13.6654 2.89403 13.6654 2.11336 12.8354C1.3327 12.0054 1.33203 10.67 1.33203 7.9987Z"
stroke={color || "#333333"}
/>
<path
d="M4.59189 7.22074C5.21789 6.85541 5.76389 7.00274 6.09189 7.23741C6.22655 7.33341 6.29389 7.38141 6.33322 7.38141C6.37322 7.38141 6.43989 7.33341 6.57455 7.23741C6.90255 7.00274 7.44855 6.85541 8.07455 7.22074C8.89589 7.70074 9.08122 9.28341 7.18789 10.6187C6.82655 10.8727 6.64589 11.0001 6.33322 11.0001C6.02055 11.0001 5.83989 10.8734 5.47922 10.6187C3.58522 9.28341 3.76989 7.70074 4.59189 7.22074Z"
stroke={color || "#333333"}
strokeLinecap="round"
/>
<path
d="M12 11H10"
stroke={color || "#333333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default LoyaltyAndRewardIcon;

View File

@@ -0,0 +1,27 @@
interface MyOrderIconType {
className?: string;
onClick?: () => void;
}
const MyOrderIcon = ({ className, onClick }: MyOrderIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M10.6656 5.9987V3.9987C10.6656 2.52594 9.4717 1.33203 7.99894 1.33203C6.52618 1.33203 5.33228 2.52594 5.33228 3.9987V5.9987M2.39361 6.90001L1.99361 11.1667C1.87988 12.3798 1.82302 12.9864 2.02431 13.4549C2.20114 13.8665 2.51102 14.2067 2.90429 14.4212C3.35196 14.6654 3.96119 14.6654 5.17964 14.6654H10.8182C12.0367 14.6654 12.6459 14.6654 13.0936 14.4212C13.4869 14.2067 13.7967 13.8665 13.9736 13.4549C14.1749 12.9864 14.118 12.3798 14.0043 11.1667L13.6043 6.90001C13.5082 5.8756 13.4602 5.36339 13.2298 4.97614C13.0269 4.63509 12.7272 4.36211 12.3687 4.19193C11.9616 3.9987 11.4472 3.9987 10.4182 3.9987L5.57964 3.9987C4.55074 3.9987 4.03628 3.9987 3.62922 4.19193C3.27072 4.3621 2.97095 4.63509 2.76805 4.97614C2.53767 5.36339 2.48965 5.87559 2.39361 6.90001Z"
stroke="#333333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default MyOrderIcon;

View File

@@ -0,0 +1,51 @@
interface PopularIconType {
className?: string;
onClick?: () => void;
}
const PopularIcon = ({ className, onClick }: PopularIconType) => {
return (
<svg
fill="#FFC600"
height="30"
width="30"
version="1.1"
id="Icons"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 32 32"
xmlSpace="preserve"
className={className}
onClick={onClick}
>
<g>
<path
d="M12,17c0.8-4.2,1.9-5.3,6.1-6.1c0.5-0.1,0.8-0.5,0.8-1s-0.3-0.9-0.8-1C13.9,8.1,12.8,7,12,2.8C11.9,2.3,11.5,2,11,2
c-0.5,0-0.9,0.3-1,0.8C9.2,7,8.1,8.1,3.9,8.9C3.5,9,3.1,9.4,3.1,9.9s0.3,0.9,0.8,1c4.2,0.8,5.3,1.9,6.1,6.1c0.1,0.5,0.5,0.8,1,0.8
S11.9,17.4,12,17z"
/>
<path
d="M22,24c-2.8-0.6-3.4-1.2-4-4c-0.1-0.5-0.5-0.8-1-0.8s-0.9,0.3-1,0.8c-0.6,2.8-1.2,3.4-4,4c-0.5,0.1-0.8,0.5-0.8,1
s0.3,0.9,0.8,1c2.8,0.6,3.4,1.2,4,4c0.1,0.5,0.5,0.8,1,0.8s0.9-0.3,1-0.8c0.6-2.8,1.2-3.4,4-4c0.5-0.1,0.8-0.5,0.8-1
S22.4,24.1,22,24z"
/>
<path
d="M29.2,14c-2.2-0.4-2.7-0.9-3.1-3.1c-0.1-0.5-0.5-0.8-1-0.8c-0.5,0-0.9,0.3-1,0.8c-0.4,2.2-0.9,2.7-3.1,3.1
c-0.5,0.1-0.8,0.5-0.8,1s0.3,0.9,0.8,1c2.2,0.4,2.7,0.9,3.1,3.1c0.1,0.5,0.5,0.8,1,0.8c0.5,0,0.9-0.3,1-0.8
c0.4-2.2,0.9-2.7,3.1-3.1c0.5-0.1,0.8-0.5,0.8-1S29.7,14.1,29.2,14z"
/>
<path
d="M5.7,22.3C5.4,22,5,21.9,4.6,22.1c-0.1,0-0.2,0.1-0.3,0.2c-0.1,0.1-0.2,0.2-0.2,0.3C4,22.7,4,22.9,4,23s0,0.3,0.1,0.4
c0.1,0.1,0.1,0.2,0.2,0.3c0.1,0.1,0.2,0.2,0.3,0.2C4.7,24,4.9,24,5,24c0.1,0,0.3,0,0.4-0.1s0.2-0.1,0.3-0.2
c0.1-0.1,0.2-0.2,0.2-0.3C6,23.3,6,23.1,6,23s0-0.3-0.1-0.4C5.9,22.5,5.8,22.4,5.7,22.3z"
/>
<path
d="M28,7c0.3,0,0.5-0.1,0.7-0.3C28.9,6.5,29,6.3,29,6s-0.1-0.5-0.3-0.7c-0.1-0.1-0.2-0.2-0.3-0.2c-0.2-0.1-0.5-0.1-0.8,0
c-0.1,0-0.2,0.1-0.3,0.2C27.1,5.5,27,5.7,27,6c0,0.3,0.1,0.5,0.3,0.7C27.5,6.9,27.7,7,28,7z"
/>
</g>
</svg>
);
};
export default PopularIcon;

View File

@@ -0,0 +1,33 @@
interface RaiseIconType {
className?: string;
onClick?: () => void;
}
const RaiseIcon = ({ className, onClick }: RaiseIconType) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M2.25 12.75L6.75 8.25L9.75 11.25L15.75 5.25"
stroke="#FFC600"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.75 5.25H15.75V8.25"
stroke="#FFC600"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default RaiseIcon;

View File

@@ -1,4 +1,4 @@
import { GlobalOutlined, ShakeOutlined } from "@ant-design/icons"; import { GlobalOutlined } from "@ant-design/icons";
import { setLocale, setLocalesThunk } from "features/locale/localeSlice"; import { setLocale, setLocalesThunk } from "features/locale/localeSlice";
import i18n from "i18n/i18n"; import i18n from "i18n/i18n";
import { useTransition } from "react"; import { useTransition } from "react";

View File

@@ -15,10 +15,10 @@ const LoyaltyCard = () => {
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain); const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const token = localStorage.getItem(ACCESS_TOKEN); const token = localStorage.getItem(ACCESS_TOKEN);
const isHasLoyaltyGift = const loyaltyStamps = restaurant?.loyalty_stamps ?? 0;
(restaurant?.loyalty_stamps || 0) - const customerLoyaltyPoints = restaurant?.customer_loyalty_points ?? 0;
(restaurant?.customer_loyalty_points || 0) <= const remainingToNextReward =
0; loyaltyStamps - (customerLoyaltyPoints % loyaltyStamps);
return ( return (
<div className={styles.loyaltyContainer}> <div className={styles.loyaltyContainer}>
@@ -62,8 +62,14 @@ const LoyaltyCard = () => {
}} }}
> >
{token && {token &&
customerLoyaltyPoints < loyaltyStamps &&
t("menu.loyaltyDescription", { t("menu.loyaltyDescription", {
value: restaurant?.loyalty_stamps ?? 0, value: loyaltyStamps,
})}
{token &&
customerLoyaltyPoints >= loyaltyStamps &&
t("menu.youHaveXEarnedRewardsReadyToRedeem", {
rewards: Math.floor(customerLoyaltyPoints / loyaltyStamps),
})} })}
{!token && ( {!token && (
<div style={{ paddingTop: 4 }}> <div style={{ paddingTop: 4 }}>
@@ -95,21 +101,22 @@ const LoyaltyCard = () => {
)} )}
</ProText> </ProText>
</Col> </Col>
<Col> {isHasLoyaltyGift && <PresentIcon />}</Col> {/* <Col> {isHasLoyaltyGift && <PresentIcon />}</Col> */}
</Row> </Row>
{token && ( {token && (
<div <>
style={{ <div
display: "flex", style={{
flexDirection: "row", display: "flex",
gap: 12, flexDirection: "row",
overflow: "hidden", gap: 12,
scrollbarWidth: "none", overflow: "auto",
}} scrollbarWidth: "none",
> paddingBottom: 12,
{Array.from({ length: restaurant?.loyalty_stamps || 0 }).map( }}
(_, index) => { >
const currentPoints = restaurant?.customer_loyalty_points || 0; {Array.from({ length: loyaltyStamps }).map((_, index) => {
const currentPoints = customerLoyaltyPoints % loyaltyStamps;
const isCollected = index < currentPoints; const isCollected = index < currentPoints;
return ( return (
<Col key={index}> <Col key={index}>
@@ -126,9 +133,30 @@ const LoyaltyCard = () => {
/> />
</Col> </Col>
); );
}, })}
)} </div>
</div> <ProText
strong
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
color:
customerLoyaltyPoints < loyaltyStamps ? "#777580" : "#32AD6D",
}}
>
{customerLoyaltyPoints < loyaltyStamps &&
t("menu.justXMorePurchasesToUnlockYourFREEItem", {
cups: remainingToNextReward,
})}
{customerLoyaltyPoints >= loyaltyStamps &&
t("menu.youreJustXCupsAwayFromYourNextReward", {
cups: remainingToNextReward,
})}
</ProText>
</>
)} )}
</Card> </Card>
</div> </div>

View File

@@ -11,6 +11,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
font-size: 16px; font-size: 16px;
min-height: 24px;
} }
.summaryDivider { .summaryDivider {

View File

@@ -1,4 +1,4 @@
import { Card, Checkbox, Divider, Space } from "antd"; import { Card, Checkbox, Divider, Space, Tag } from "antd";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import { import {
selectCart, selectCart,
@@ -69,7 +69,7 @@ export default function OrderSummary() {
<ProText type="secondary" style={titlesStyle}> <ProText type="secondary" style={titlesStyle}>
{t("cart.basketTotal")} {t("cart.basketTotal")}
</ProText> </ProText>
<ArabicPrice price={subtotal} style={titlesStyle} /> <ArabicPrice price={subtotal} textStyle={titlesStyle} />
</div> </div>
{orderType === OrderType.Delivery && ( {orderType === OrderType.Delivery && (
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
@@ -78,7 +78,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={Number(restaurant?.delivery_fees || 0)} price={Number(restaurant?.delivery_fees || 0)}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -87,10 +87,42 @@ export default function OrderSummary() {
<ProText type="secondary" style={titlesStyle}> <ProText type="secondary" style={titlesStyle}>
{t("cart.discount")} {t("cart.discount")}
</ProText> </ProText>
<ArabicPrice <div
price={discountAmount} style={{
style={{ ...titlesStyle, color: "#434E5C" }} display: "flex",
/> alignItems: "center",
minHeight: "24px",
}}
>
{isHasLoyaltyGift &&
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 ? (
<Tag
color="green"
style={{
backgroundColor: "#EDFEF5",
borderRadius: "4px",
padding: "3px 10px",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
placeItems: "center",
margin: "0 10px",
position: "relative",
top: -1,
}}
>
{t("cart.loyalty")}
</Tag>
) : null}
<ArabicPrice
price={discountAmount}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
</div> </div>
)} )}
{orderType !== OrderType.Redeem && ( {orderType !== OrderType.Redeem && (
@@ -100,7 +132,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={tip || 0} price={tip || 0}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -111,7 +143,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={0} price={0}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -122,7 +154,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={0} price={0}
style={{ ...titlesStyle, color: "#32AD6D" }} textStyle={{ ...titlesStyle, color: "#32AD6D" }}
/> />
</div> </div>
)} )}
@@ -133,7 +165,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={taxAmount || 0} price={taxAmount || 0}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -144,7 +176,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={splitBillAmount} price={splitBillAmount}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -167,7 +199,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={grandTotal} price={grandTotal}
style={{ textStyle={{
fontWeight: 600, fontWeight: 600,
fontStyle: "SemiBold", fontStyle: "SemiBold",
fontSize: 18, fontSize: 18,
@@ -178,43 +210,6 @@ export default function OrderSummary() {
/> />
</div> </div>
</Space> </Space>
{isHasLoyaltyGift &&
restaurant?.is_loyalty_enabled === 1 &&
orderType !== OrderType.Redeem && (
<>
<Checkbox
checked={useLoyaltyPoints}
onChange={(value) => {
dispatch(updateUseLoyaltyPoints(value.target.checked));
}}
style={{ marginTop: 8 }}
>
{t("cart.useLoyaltyPoints")}
</Checkbox>
</>
)}
{isHasLoyaltyGift &&
loyaltyValidation.errorMessage &&
orderType !== OrderType.Redeem && (
<div style={{ marginTop: 8, color: "red", fontSize: "12px" }}>
{t(loyaltyValidation.errorMessage)}
</div>
)}
{isHasLoyaltyGift &&
orderType !== OrderType.Redeem &&
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 && (
<div style={{ marginTop: 8, color: "green", fontSize: "12px" }}>
{t("cart.loyaltyDiscountApplied", {
itemName: highestLoyaltyItem.name,
amount: Math.round(highestLoyaltyItem.price || 0).toFixed(2),
})}
</div>
)}
</Card> </Card>
</> </>
); );

View File

@@ -873,7 +873,7 @@ export const selectGrandTotal = (state: RootState) => {
taxAmount - taxAmount -
totalDiscount + totalDiscount +
deliveryFee - deliveryFee -
state.order.splitBillAmount - state.order.splitBillAmount +
Number(state.order.tip) Number(state.order.tip)
); );
}; };

View File

@@ -71,7 +71,7 @@ export default function HeaderMenuDrawer() {
open={mobileMenuOpen} open={mobileMenuOpen}
styles={{ styles={{
body: { body: {
padding: 0, padding: 0,
}, },
header: { header: {
padding: "5px 24px", padding: "5px 24px",
@@ -122,7 +122,18 @@ export default function HeaderMenuDrawer() {
style={mobileMenuLinkStyle(locale === item.key)} style={mobileMenuLinkStyle(locale === item.key)}
> >
{item.icon} {item.icon}
<ProText>{item.label.props.children}</ProText> <ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: 0,
color: "#333333",
}}
>
{item.label.props.children}
</ProText>
</div> </div>
</div> </div>
))} ))}

View File

@@ -1,11 +1,12 @@
import { import {
BgColorsOutlined, BgColorsOutlined,
HomeOutlined, GlobalOutlined,
LoginOutlined, LogoutOutlined,
LogoutOutlined,
MenuOutlined,
TranslationOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import BranchesIcon from "components/Icons/BranchesIcon";
import LoginIcon from "components/Icons/LoginIcon";
import LoyaltyAndRewardIcon from "components/Icons/LoyaltyAndRewardIcon";
import MyOrderIcon from "components/Icons/MyOrderIcon";
import { logoutThunk } from "features/auth/authSlice"; import { logoutThunk } from "features/auth/authSlice";
import { setLocale, setLocalesThunk } from "features/locale/localeSlice"; import { setLocale, setLocalesThunk } from "features/locale/localeSlice";
import { toggleTheme } from "features/theme/themeSlice"; import { toggleTheme } from "features/theme/themeSlice";
@@ -28,7 +29,7 @@ export default function useHeaderMenu() {
{ {
key: "language", key: "language",
icon: ( icon: (
<TranslationOutlined <GlobalOutlined
style={{ color: themeName === "dark" ? "white" : "#1f2937" }} style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
/> />
), ),
@@ -58,49 +59,57 @@ export default function useHeaderMenu() {
}, },
{ {
key: "orders", key: "orders",
icon: ( icon: <MyOrderIcon />,
<HomeOutlined
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
/>
),
label: <Link to={`/${subdomain}/orders`}>{t("common.myOrder")}</Link>, label: <Link to={`/${subdomain}/orders`}>{t("common.myOrder")}</Link>,
onClick: () => { onClick: () => {
navigate(`/${subdomain}/orders`); navigate(`/${subdomain}/orders`);
}, },
}, },
{ {
key: "branches", key: "rewardsAndLoyalty",
icon: ( icon: <LoyaltyAndRewardIcon />,
<MenuOutlined label: (
style={{ color: themeName === "dark" ? "white" : "#1f2937" }} <Link to={`/${subdomain}/rewards-and-loyalty`}>
/> {t("common.rewardsAndLoyalty")}
</Link>
), ),
onClick: () => {
navigate(`/${subdomain}/rewards-and-loyalty`);
},
},
{
key: "branches",
icon: <BranchesIcon />,
label: <Link to={`/${subdomain}/menu`}>{t("common.branches")}</Link>, label: <Link to={`/${subdomain}/menu`}>{t("common.branches")}</Link>,
}, },
...(!token ? [{ ...(!token
key: "login", ? [
icon: ( {
<LoginOutlined key: "login",
style={{ color: themeName === "dark" ? "white" : "#1f2937" }} icon: <LoginIcon />,
/> label: <Link to={`/${subdomain}/login`}>{t("common.login")}</Link>,
), onClick: () => {
label: <Link to={`/${subdomain}/login`}>{t("common.login")}</Link>, navigate(`/${subdomain}/login`);
onClick: () => { },
navigate(`/${subdomain}/login`); },
}, ]
}] : []), : []),
...(token ? [{ ...(token
key: "logout", ? [
icon: ( {
<LogoutOutlined key: "logout",
style={{ color: themeName === "dark" ? "white" : "#1f2937" }} icon: (
/> <LogoutOutlined
), style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
label: <div>{t("common.logout")}</div>, />
onClick: () => { ),
dispatch(logoutThunk()); label: <div>{t("common.logout")}</div>,
}, onClick: () => {
}] : []), dispatch(logoutThunk());
},
},
]
: []),
]; ];
return { menuItems }; return { menuItems };
} }

View File

@@ -16,6 +16,7 @@ import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCar
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import GiftAmountCard from "./components/GiftAmountCard/GiftAmountCard"; import GiftAmountCard from "./components/GiftAmountCard/GiftAmountCard";
import { GiftAmountBottomSheet } from "./components/GiftAmountBottomSheet"; import { GiftAmountBottomSheet } from "./components/GiftAmountBottomSheet";
import PickupTimeCard from "pages/checkout/components/pickupEstimate/TimeEstimateCard";
export default function CardDetailsPage() { export default function CardDetailsPage() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -168,7 +169,7 @@ export default function CardDetailsPage() {
)} )}
<ReceivernformationCard /> <ReceivernformationCard />
<SenderformationCard /> <SenderformationCard />
<TimeEstimateCard /> <PickupTimeCard />
</Form> </Form>
</Layout.Content> </Layout.Content>
<Layout.Footer className={styles.checkoutButtonContainer}> <Layout.Footer className={styles.checkoutButtonContainer}>

View File

@@ -0,0 +1,10 @@
.useLoyaltyPointsContainer {
gap: 20px;
border-radius: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: var(--secondary-background);
width: 100%;
}

View File

@@ -0,0 +1,67 @@
import { Button, Form, Switch } from "antd";
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
import { selectCart, updateUseLoyaltyPoints } from "features/order/orderSlice";
import ProText from "components/ProText";
import styles from "./EarnLoyaltyPointsCard.module.css";
export default function EarnLoyaltyPointsCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { useLoyaltyPoints } = useAppSelector(selectCart);
return (
<>
<ProInputCard title={t("cart.loyalty")}>
<Form.Item name="useLoyaltyPoints">
<div className={styles.useLoyaltyPointsContainer}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("cart.useLoyaltyPoints")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t(
"cart.applyYourAvailableRewardsToGetDiscountsOnItemsInYourCart",
)}
</ProText>
</div>
<Switch
autoFocus={false}
checked={useLoyaltyPoints}
onClick={() => {
dispatch(updateUseLoyaltyPoints(!useLoyaltyPoints));
}}
/>
</div>
</Form.Item>
</ProInputCard>
</>
);
}

View File

@@ -15,6 +15,7 @@ import { useMemo, useState } from "react";
import CardAmountIcon from "components/Icons/CardAmountIcon"; import CardAmountIcon from "components/Icons/CardAmountIcon";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import { GiftAmountBottomSheet } from "pages/CardDetails/components/GiftAmountBottomSheet"; import { GiftAmountBottomSheet } from "pages/CardDetails/components/GiftAmountBottomSheet";
import { GiftType } from "components/CustomBottomSheet/GiftTypeBottomSheet";
export function GiftCard() { export function GiftCard() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -32,6 +33,8 @@ export function GiftCard() {
const [isGiftAmountBottomSheetOpen, setIsGiftAmountBottomSheetOpen] = const [isGiftAmountBottomSheetOpen, setIsGiftAmountBottomSheetOpen] =
useState(false); useState(false);
console.log(giftDetails?.giftType);
return ( return (
<> <>
<ProInputCard title={t("address.giftDetails")}> <ProInputCard title={t("address.giftDetails")}>
@@ -108,10 +111,12 @@ export function GiftCard() {
</div> </div>
{isRTL ? <BackIcon iconSize={24} /> : <NextIcon iconSize={24} />} {isRTL ? <BackIcon iconSize={24} /> : <NextIcon iconSize={24} />}
</div> </div>
<Divider style={{ margin: "10px 0" }} /> <Divider style={{ margin: "10px 0" }} />
{giftDetails?.giftType === "vouchers" || {(giftDetails?.giftType === GiftType.Vouchers ||
(giftDetails?.giftType === "itemsAndVouchers" && ( giftDetails?.giftType === GiftType.ItemsAndVouchers) && (
<>
<div <div
className={styles.orderNotes} className={styles.orderNotes}
onClick={() => { onClick={() => {
@@ -164,46 +169,49 @@ export function GiftCard() {
</div> </div>
{isRTL ? <BackIcon iconSize={24} /> : <NextIcon iconSize={24} />} {isRTL ? <BackIcon iconSize={24} /> : <NextIcon iconSize={24} />}
</div> </div>
))} {giftDetails?.giftType === GiftType.ItemsAndVouchers && (
<Divider style={{ margin: "10px 0" }} />
)}
</>
)}
<Divider style={{ margin: "10px 0" }} /> {(giftDetails?.giftType === GiftType.Items ||
{giftDetails?.giftType === "items" || giftDetails?.giftType === GiftType.ItemsAndVouchers) && (
(giftDetails?.giftType === "itemsAndVouchers" && ( <div
<div style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
onClick={() => {
navigate(`/${subdomain}/cart`);
}}
>
<ProText
style={{ style={{
display: "flex", fontWeight: 400,
flexDirection: "row", fontStyle: "Regular",
justifyContent: "space-between", fontSize: 12,
}} lineHeight: "140%",
onClick={() => { letterSpacing: "0%",
navigate(`/${subdomain}/cart`); color: "#777580",
cursor: "pointer",
}} }}
> >
<ProText <span
style={{ style={{
fontWeight: 400, [isRTL ? "marginLeft" : "marginRight"]: 5,
fontStyle: "Regular", position: "relative",
fontSize: 12, top: 3.5,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
cursor: "pointer",
}} }}
> >
<span <InvoiceIcon />
style={{ </span>
[isRTL ? "marginLeft" : "marginRight"]: 5, {t("checkout.viewOrder")} ( {totalItems} {t("cart.items")} )
position: "relative", </ProText>
top: 3.5, {isRTL ? <BackIcon /> : <NextIcon />}
}} </div>
> )}
<InvoiceIcon />
</span>
{t("checkout.viewOrder")} ( {totalItems} {t("cart.items")} )
</ProText>
{isRTL ? <BackIcon /> : <NextIcon />}
</div>
))}
</ProInputCard> </ProInputCard>
<GiftAmountBottomSheet <GiftAmountBottomSheet
isOpen={isGiftAmountBottomSheetOpen} isOpen={isGiftAmountBottomSheetOpen}

View File

@@ -23,6 +23,7 @@ import { CarCard } from "./components/CarCard";
import { CollectWay } from "./components/CollectWay/CollectWay"; import { CollectWay } from "./components/CollectWay/CollectWay";
import PickupTimeCard from "./components/pickupEstimate/TimeEstimateCard"; import PickupTimeCard from "./components/pickupEstimate/TimeEstimateCard";
import VoucherSummary from "pages/redeem/components/VoucherSummary/VoucherSummary"; import VoucherSummary from "pages/redeem/components/VoucherSummary/VoucherSummary";
import EarnLoyaltyPointsCard from "pages/cart/components/earnLoyaltyPointsCard/EarnLoyaltyPointsCard";
export default function CheckoutPage() { export default function CheckoutPage() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -85,7 +86,9 @@ export default function CheckoutPage() {
<OfficeDetails /> */} <OfficeDetails /> */}
{/* <GiftDetails /> */} {/* <GiftDetails /> */}
{/* <BriefMenu /> */} {/* <BriefMenu /> */}
{orderType !== OrderType.Redeem && <CouponCard />} {orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
<CouponCard />
)}
{/* Collection Method */} {/* Collection Method */}
{orderType === OrderType.Pickup && ( {orderType === OrderType.Pickup && (
@@ -123,7 +126,12 @@ export default function CheckoutPage() {
)} )}
{/* Reward Your Waiter */} {/* Reward Your Waiter */}
{orderType !== OrderType.Redeem && <RewardWaiterCard />} {orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
<RewardWaiterCard />
)}
{orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
<EarnLoyaltyPointsCard />
)}
<BriefMenuCard /> <BriefMenuCard />
<Ads1 /> <Ads1 />
<OrderSummary /> <OrderSummary />

View File

@@ -44,13 +44,7 @@ export function MenuFooter() {
}} }}
> >
<Link <Link
to={ to={totalItems === 0 ? "#" : `/${subdomain}/cart`}
totalItems === 0
? "#"
: orderType === OrderType.Pay
? `/${subdomain}/pay`
: `/${subdomain}/cart`
}
onClick={(e) => { onClick={(e) => {
if (totalItems === 0) { if (totalItems === 0) {
e.preventDefault(); e.preventDefault();
@@ -106,9 +100,7 @@ export function MenuFooter() {
margin: "0 10px", margin: "0 10px",
}} }}
> >
{orderType === OrderType.Pay {t("menu.viewCart")}
? t("menu.pay")
: t("menu.viewCart")}
</ProText> </ProText>
</Button> </Button>
<div <div

View File

@@ -844,3 +844,21 @@
white-space: nowrap; white-space: nowrap;
width: fit-content; width: fit-content;
} }
.frameSelect {
align-items: flex-start;
background-color: #aaa7a733;
border-radius: 60px;
flex-direction: column;
gap: 10px;
position: relative;
}
.divSelect {
align-items: center;
align-self: stretch;
display: flex;
flex: 0 0 auto;
gap: 4px;
position: relative;
}

View File

@@ -97,21 +97,27 @@ function MenuPage() {
className={`${styles.headerFloatingBtn} ${styles.orderTypeSelectContainer} order-type-select-container`} className={`${styles.headerFloatingBtn} ${styles.orderTypeSelectContainer} order-type-select-container`}
> >
{orderType !== OrderType.Redeem && ( {orderType !== OrderType.Redeem && (
<Select <div className={styles.frameSelect}>
value={orderType} <div className={styles.divSelect}>
options={orderTypeOptions} <Select
open={false} value={orderType}
onOpenChange={() => false} options={orderTypeOptions}
onClick={(e) => { open={false}
e.stopPropagation(); onOpenChange={() => false}
setIsOrderTypesOpen(true); onClick={(e) => {
}} e.stopPropagation();
variant="borderless" setIsOrderTypesOpen(true);
size="small" }}
className={styles.orderTypeSelect} variant="borderless"
classNames={{ popup: { root: "order-type-select-dropdown" } }} size="small"
listHeight={150} className={styles.orderTypeSelect}
/> classNames={{
popup: { root: "order-type-select-dropdown" },
}}
listHeight={150}
/>
</div>
</div>
)} )}
{orderType === OrderType.Redeem && ( {orderType === OrderType.Redeem && (
<div className={styles.frame}> <div className={styles.frame}>

View File

@@ -260,7 +260,6 @@ export default function RestaurantServices() {
onClose={() => setIsGiftTypeBottomSheetOpen(false)} onClose={() => setIsGiftTypeBottomSheetOpen(false)}
onSave={() => { onSave={() => {
setIsGiftTypeBottomSheetOpen(false); setIsGiftTypeBottomSheetOpen(false);
navigate(`/${subdomain}/menu?orderType=${OrderType.Gift}`);
}} }}
/> />
</div> </div>

View File

@@ -0,0 +1,469 @@
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 { useAppDispatch, useAppSelector } from "redux/hooks";
import { selectCart, updateUseLoyaltyPoints } 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 dispatch = useAppDispatch();
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();
const loyaltyHistories = loyaltyHistory?.filter(
(LH) => Number(LH.restaurant_id) === Number(restaurant.restautantId),
);
const handleRedeem = () => {
navigate(`/${subdomain}/menu?orderType=${OrderType.DineIn}`);
dispatch(updateUseLoyaltyPoints(true));
};
const currentStampRound = customerLoyaltyPoints % loyaltyStamps;
return (
<>
<Layout>
<ProHeader>{t("rewardsAndLoyalty.title")}</ProHeader>
<Layout.Content className={styles.redeemContainer}>
<Flex gap="small" wrap justify="center">
<Tooltip title="3 done / 3 in progress / 4 to do">
<div style={{ margin: "47px 47px 20px 47px" }}>
<Progress
percent={(currentStampRound / loyaltyStamps) * 100}
format={() => (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 4,
}}
>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 48,
lineHeight: "100%",
letterSpacing: "0%",
textAlign: "center",
}}
>
{currentStampRound} / {loyaltyStamps}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#E8B400",
marginTop: 4,
}}
>
{t("rewardsAndLoyalty.completedPurchases")}
</ProText>
</div>
)}
strokeColor="#FFB700"
size={250}
type="circle"
/>
</div>
</Tooltip>
</Flex>
<Flex
gap="small"
wrap={false}
justify="center"
style={{ width: "100%" }}
>
<div className={styles.orderNotes}>
<div
style={{
height: 32,
width: 32,
minHeight: 32,
minWidth: 32,
backgroundColor: "var(--background)",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CoinsIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.saved")}
</ProText>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
<ArabicPrice price={customerLoyaltyPoints || 0} />
</ProText>
</div>
</div>
<div className={styles.orderNotes}>
<div
style={{
height: 32,
width: 32,
minHeight: 32,
minWidth: 32,
backgroundColor: "var(--background)",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<RaiseIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.totalPurchased")}
</ProText>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{loyaltyStamps}
</ProText>
</div>
</div>
</Flex>
<div className={styles.nextRewards}>
<div
style={{
height: 42,
width: 42,
minHeight: 42,
minWidth: 42,
backgroundColor: "#FFF5D4",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CupIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("rewardsAndLoyalty.almosthere")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.youreJustXCupsAwayFromYourNextReward", {
cups: loyaltyStamps - (customerLoyaltyPoints % loyaltyStamps),
})}
</ProText>
</div>
</div>
<ProInputCard title={t("rewardsAndLoyalty.yourAvailableRewards")}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 10,
alignItems: "center",
placeItems: "center",
}}
>
<div
style={{
height: 42,
width: 42,
minHeight: 42,
minWidth: 42,
backgroundColor: "#FFF5D4",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CupIcon />
</div>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
}}
>
{t("rewardsAndLoyalty.youCurrentlyHave")}
</ProText>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 20,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#E8B400",
}}
>
<span style={{ padding: "0 4px" }}>
{Math.floor(customerLoyaltyPoints / loyaltyStamps)}
</span>
{t("rewardsAndLoyalty.freeItems")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.youCanRedeemDuringTheCheckout")}
</ProText>
<Button
type="primary"
shape="round"
className={styles.checkoutButton}
onClick={handleRedeem}
style={{
width: "100%",
height: 40,
borderRadius: 888,
backgroundColor: "#FFB700",
color: "white",
}}
>
{t("rewardsAndLoyalty.redeemNow")}
</Button>
</div>
</ProInputCard>
<ProInputCard title={t("rewardsAndLoyalty.loyaltyHistory")}>
<Timeline
items={
loyaltyHistories && loyaltyHistories.length > 0
? loyaltyHistories.map((item: any, index: number) => ({
content: (
<div key={index}>
{/* <ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{item.restaurant_name ||
item.name ||
item.restaurantName ||
t("rewardsAndLoyalty.order")}
</ProText> */}
{(item.created_at ||
item.date ||
item.order_date ||
true) && (
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{dayjs(
item.created_at || item.date || item.order_date,
).format("DD MMMM YYYY")}
</ProText>
)}
<div className={styles.loyaltyHistoryItem}>
<div
style={{
height: 30,
width: 20,
minHeight: 30,
minWidth: 20,
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<PopularIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("rewardsAndLoyalty.earned")}{" "}
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#E8B400",
}}
>
{t("rewardsAndLoyalty.xPoints", {
points:
item.total_points ||
item.points ||
item.loyalty_stamps,
})}
</ProText>
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.yourOrderFrom", {
restaurantName:
item.restaurant_name ||
item.name ||
item.restaurantName,
})}
</ProText>
</div>
</div>
</div>
),
}))
: []
}
/>
</ProInputCard>
</Layout.Content>
</Layout>
</>
);
}

View File

@@ -0,0 +1,51 @@
.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(--background);
width: 100%;
width: 275;
opacity: 1;
padding-top: 14px;
padding-right: 12px;
padding-bottom: 14px;
padding-left: 12px;
margin-top: 12px;
}

View File

@@ -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
}

View File

@@ -11,6 +11,7 @@ import {
USER_DETAILS_URL, USER_DETAILS_URL,
EGIFT_CARDS_URL, EGIFT_CARDS_URL,
REDEEM_DETAILS_URL, REDEEM_DETAILS_URL,
LOYALTY_HISTORY_URL,
} from "utils/constants"; } from "utils/constants";
import { OrderDetails } from "pages/checkout/hooks/types"; import { OrderDetails } from "pages/checkout/hooks/types";
@@ -182,6 +183,25 @@ export const branchApi = baseApi.injectEndpoints({
return response.result; 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 { export const {
@@ -197,4 +217,5 @@ export const {
useGetUserDetailsQuery, useGetUserDetailsQuery,
useGetEGiftCardsQuery, useGetEGiftCardsQuery,
useGetRedeemDetailsQuery, useGetRedeemDetailsQuery,
useGetLoyaltyHistoryQuery,
} = branchApi; } = branchApi;

View File

@@ -18,6 +18,7 @@ import OtpPage from "pages/otp/page";
import ProductDetailPage from "pages/product/page"; import ProductDetailPage from "pages/product/page";
import RedeemPage from "pages/redeem/page"; import RedeemPage from "pages/redeem/page";
import RestaurantPage from "pages/restaurant/page"; import RestaurantPage from "pages/restaurant/page";
import RewardsAndLoyalityPage from "pages/rewardsAndLoyalty/page";
import SearchPage from "pages/search/page"; import SearchPage from "pages/search/page";
import SplitBillPage from "pages/split-bill/page"; import SplitBillPage from "pages/split-bill/page";
import React, { ReactNode, Suspense, useEffect } from "react"; import React, { ReactNode, Suspense, useEffect } from "react";
@@ -170,6 +171,11 @@ export const router = createHashRouter([
element: <PageWrapper children={<RedeemPage />} />, element: <PageWrapper children={<RedeemPage />} />,
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
}, },
{
path: "rewards-and-loyalty",
element: <PageWrapper children={<RewardsAndLoyalityPage />} />,
errorElement: <ErrorPage />,
},
], ],
}, },
{ {

View File

@@ -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 PAYMENT_CONFIRMATION_URL = `https://menu.fascano.com/payment/confirmation`;
export const DISCOUNT_URL = `${BASE_URL}getDiscount`; export const DISCOUNT_URL = `${BASE_URL}getDiscount`;
export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`; export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`;
export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`; export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`;
export const LOYALTY_HISTORY_URL = `${BASE_URL}loyaltyHistory`;