Compare commits

...

3 Commits

Author SHA1 Message Date
167b26e0b9 redeem: keep intergration 2026-01-11 23:44:46 +03:00
b0288ebcf6 redeem: integration 2026-01-11 15:25:45 +03:00
6271c14eff redeem: initial commit 2026-01-08 23:26:45 +03:00
29 changed files with 1739 additions and 524 deletions

View File

@@ -274,7 +274,7 @@
"paymentSummary": "ملخص الدفع", "paymentSummary": "ملخص الدفع",
"holdayGiftCard": "هدية العيد", "holdayGiftCard": "هدية العيد",
"messageIncluded": "الرسالة مضمنة", "messageIncluded": "الرسالة مضمنة",
"save":"حفظ", "save": "حفظ",
"to": "ل", "to": "ل",
"giftSummary": "ملخص الهدية", "giftSummary": "ملخص الهدية",
"customerInformation": "تفاصيل العميل", "customerInformation": "تفاصيل العميل",
@@ -308,8 +308,8 @@
"pickupEstimate": "تقدير الاستلام", "pickupEstimate": "تقدير الاستلام",
"today": "اليوم", "today": "اليوم",
"change": "تغيير", "change": "تغيير",
"pickup":"استلام", "pickup": "استلام",
"setPickupTime":"تحديد وقت الاستلام" "setPickupTime": "تحديد وقت الاستلام"
}, },
"address": { "address": {
"title": "العنوان", "title": "العنوان",
@@ -505,7 +505,7 @@
"yourName": "اسمك", "yourName": "اسمك",
"yourPhone": "رقم هاتفك", "yourPhone": "رقم هاتفك",
"keepMyNameSecret": "الاحتفاظ باسمي مخفياً", "keepMyNameSecret": "الاحتفاظ باسمي مخفياً",
"receiverInformation": "تفاصيل المستلم", "receiverInformation": "تفاصيل المستلم",
"costumeAmount": "مبلغ البطاقة", "costumeAmount": "مبلغ البطاقة",
"enterCustomOucherAmount": "أدخل مبلغ البطاقة المخصص", "enterCustomOucherAmount": "أدخل مبلغ البطاقة المخصص",
"amount": "المبلغ", "amount": "المبلغ",
@@ -518,13 +518,53 @@
"senderNameRequired": "يجب أن يكون اسم المرسل مطلوب", "senderNameRequired": "يجب أن يكون اسم المرسل مطلوب",
"receiverNameRequired": "يجب أن يكون اسم المستلم مطلوب" "receiverNameRequired": "يجب أن يكون اسم المستلم مطلوب"
}, },
"car":{ "car": {
"addCar":"إضافة سيارة", "addCar": "إضافة سيارة",
"selectCar":"اختر السيارة", "selectCar": "اختر السيارة",
"addCarDetails":"إضافة تفاصيل السيارة", "addCarDetails": "إضافة تفاصيل السيارة",
"brand":"العلامة التجارية", "brand": "العلامة التجارية",
"color":"اللون", "color": "اللون",
"category":"الفئة", "category": "الفئة",
"plateNumber":"رقم السيارة" "plateNumber": "رقم السيارة"
},
"redeem": {
"title": "استخدم الهدية",
"redeem": "استخدم",
"redeemDescription": "استخدم بطاقة الهدية",
"redeemButton": "استخدم",
"redeemButtonDescription": "استخدم بطاقة الهدية",
"showThisCodeAtTheRestaurant": "اعرض هذا الرمز في المطعم",
"addGiftDetails": "اضف تفاصيل الهدية",
"description": "اضف تفاصيل الهدية بالرسالة، ووقت التوصيل، وتفاصيل المستلم.",
"checkout": "الدفع",
"yourName": "اسمك",
"yourPhone": "رقم هاتفك",
"keepMyNameSecret": "الاحتفاظ باسمي مخفياً",
"receiverInformation": "تفاصيل المستلم",
"costumeAmount": "مبلغ البطاقة",
"enterCustomOucherAmount": "أدخل مبلغ البطاقة المخصص",
"amount": "المبلغ",
"eCardAmount": "مبلغ البطاقة الإلكترونية",
"receiverName": "اسم المستلم",
"edit": "تعديل",
"yourInformation": "تفاصيلك",
"minimumAmountShouldBe1OMR": "يجب أن يكون المبلغ الأدنى 1 OMR",
"add": "إضافة",
"senderNameRequired": "يجب أن يكون اسم المرسل مطلوب",
"receiverNameRequired": "يجب أن يكون اسم المستلم مطلوب",
"includesFreeItemsInThisOrder": "يشمل العناصر المجانية في هذا الطلب",
"redeemGiftedItems": "استخدم الهديات",
"pending": "قيد المعالجة",
"useThisCodeIfScanningNotPossible": "استخدم هذا الرمز إذا كان من المستحيل المسح",
"voucherWillBeAppliedAtCheckout": "سيتم تطبيق القسيمة عند الدفع",
"yourGiftCardBalance": "رصيد بطاقة الهدية",
"redeemNow": "استخدم الآن",
"restaurantLocation": "موقع المطعم",
"voucherBalance": "رصيد القسيمة",
"getDirections": "الحصول على الاتجاهات",
"giftedItems": "العناصر المهدية",
"viewAll": "عرض الكل",
"voucherCodeCopied": "تم نسخ رمز القسيمة",
"copyFailed": "فشل نسخ رمز القسيمة"
} }
} }

View File

@@ -179,6 +179,11 @@
"customizable": "Customizable" "customizable": "Customizable"
}, },
"cart": { "cart": {
"remainingToPay": "Remaining to Pay",
"remainingVoucherAmount": "Remaining Voucher Amount",
"voucherApplied": "Voucher Applied",
"giftedItems": "Gifted Items",
"voucherBalance": "Voucher Balance",
"addSpecialRequestOptional": "Add Special Request (Optional)", "addSpecialRequestOptional": "Add Special Request (Optional)",
"continueToGiftDetails": "Continue to Gift Details", "continueToGiftDetails": "Continue to Gift Details",
"continueToGiftDetailsDescription": "Please fill in the details of the gift recipient and sender to continue.", "continueToGiftDetailsDescription": "Please fill in the details of the gift recipient and sender to continue.",
@@ -539,5 +544,47 @@
"color": "Color", "color": "Color",
"category": "Category", "category": "Category",
"plateNumber": "Plate Number" "plateNumber": "Plate Number"
},
"redeem": {
"title": "Redeem Gift",
"redeem": "Redeem",
"redeemDescription": "Redeem your gift card",
"redeemButton": "Redeem",
"redeemButtonDescription": "Redeem your gift card",
"showThisCodeAtTheRestaurant": "Show this code at the restaurant",
"addGiftDetails": "Add Gift Details",
"description": "Personalize your gift with a message, delivery timing, and recipient details.",
"checkout": "Checkout",
"yourName": "Your Name",
"yourPhone": "Your Phone",
"keepMyNameSecret": "Keep my name secret",
"receiverInformation": "Receiver Information",
"costumeAmount": "Costume amount",
"enterCustomOucherAmount": "Enter custom oucher amount",
"amount": "Amount",
"eCardAmount": "E-Card Amount",
"receiverName": "Receiver Name",
"edit": "Edit",
"yourInformation": "Your Information",
"minimumAmountShouldBe1OMR": "Minimum amount should be 1 OMR",
"add": "Add",
"senderNameRequired": "Sender name is required",
"receiverNameRequired": "Receiver name is required",
"includesFreeItemsInThisOrder": "Includes free items in this order",
"redeemGiftedItems": "Redeem gifted items",
"pending": "Pending",
"useThisCodeIfScanningNotPossible": "Use this code if scanning is not possible",
"voucherWillBeAppliedAtCheckout": "Voucher will be applied at checkout",
"yourGiftCardBalance": "Your gift card balance",
"redeemNow": "Redeem Now",
"restaurantLocation": "Restaurant Location",
"voucherBalance": "Voucher Balance",
"getDirections": "Get Directions",
"giftedItems": "Gifted Items",
"viewAll": "View All",
"voucherCodeCopied": "Voucher code copied!",
"copyFailed": "Failed to copy voucher code",
"hiX": "Hi {{name}}!",
"youHaveReceivedAGiftCarFromX": "You have received a gift car from {{name}}!"
} }
} }

View File

@@ -0,0 +1,33 @@
interface CopyIconType {
className?: string;
onClick?: () => void;
}
const CopyIcon = ({ className, onClick }: CopyIconType) => {
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="M5.83203 8.05453C5.83203 7.46509 6.06619 6.89979 6.48299 6.48299C6.89979 6.06619 7.46509 5.83203 8.05453 5.83203H15.2762C15.5681 5.83203 15.8571 5.88952 16.1267 6.00121C16.3964 6.1129 16.6414 6.27661 16.8477 6.48299C17.0541 6.68936 17.2178 6.93437 17.3295 7.20402C17.4412 7.47366 17.4987 7.76267 17.4987 8.05453V15.2762C17.4987 15.5681 17.4412 15.8571 17.3295 16.1267C17.2178 16.3964 17.0541 16.6414 16.8477 16.8477C16.6414 17.0541 16.3964 17.2178 16.1267 17.3295C15.8571 17.4412 15.5681 17.4987 15.2762 17.4987H8.05453C7.76267 17.4987 7.47366 17.4412 7.20402 17.3295C6.93437 17.2178 6.68936 17.0541 6.48299 16.8477C6.27661 16.6414 6.1129 16.3964 6.00121 16.1267C5.88952 15.8571 5.83203 15.5681 5.83203 15.2762V8.05453Z"
stroke="#A4A3AA"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3.34333 13.9475C3.0875 13.8021 2.87471 13.5916 2.72658 13.3374C2.57846 13.0832 2.50028 12.7942 2.5 12.5V4.16667C2.5 3.25 3.25 2.5 4.16667 2.5H12.5C13.125 2.5 13.465 2.82083 13.75 3.33333"
stroke="#A4A3AA"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
};
export default CopyIcon;

View File

@@ -0,0 +1,25 @@
interface DirectionsIconType {
className?: string;
onClick?: () => void;
}
const DirectionsIcon = ({ className, onClick }: DirectionsIconType) => {
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="M7.5 9H10.125V9.975C10.125 10.15 10.2 10.2687 10.35 10.3312C10.5 10.3938 10.6375 10.3625 10.7625 10.2375L12.225 8.775C12.375 8.625 12.45 8.45 12.45 8.25C12.45 8.05 12.375 7.875 12.225 7.725L10.7625 6.2625C10.6375 6.1375 10.5 6.10625 10.35 6.16875C10.2 6.23125 10.125 6.35 10.125 6.525V7.5H6.75C6.5375 7.5 6.3595 7.572 6.216 7.716C6.0725 7.86 6.0005 8.038 6 8.25V10.5C6 10.7125 6.072 10.8907 6.216 11.0347C6.36 11.1788 6.538 11.2505 6.75 11.25C6.962 11.2495 7.14025 11.1775 7.28475 11.034C7.42925 10.8905 7.501 10.7125 7.5 10.5V9ZM9 16.5C8.8125 16.5 8.62825 16.4625 8.44725 16.3875C8.26625 16.3125 8.1005 16.2 7.95 16.05L1.95 10.05C1.8 9.9 1.6875 9.73425 1.6125 9.55275C1.5375 9.37125 1.5 9.187 1.5 9C1.5 8.813 1.5375 8.62875 1.6125 8.44725C1.6875 8.26575 1.8 8.1 1.95 7.95L7.95 1.95C8.1 1.8 8.26575 1.6875 8.44725 1.6125C8.62875 1.5375 8.813 1.5 9 1.5C9.187 1.5 9.3715 1.5375 9.5535 1.6125C9.7355 1.6875 9.901 1.8 10.05 1.95L16.05 7.95C16.2 8.1 16.3125 8.26575 16.3875 8.44725C16.4625 8.62875 16.5 8.813 16.5 9C16.5 9.187 16.4625 9.3715 16.3875 9.5535C16.3125 9.7355 16.2 9.901 16.05 10.05L10.05 16.05C9.9 16.2 9.73425 16.3125 9.55275 16.3875C9.37125 16.4625 9.187 16.5 9 16.5ZM6 12L9 15L15 9L9 3L3 9L6 12Z"
fill="#E8B400"
/>
</svg>
);
};
export default DirectionsIcon;

View File

@@ -0,0 +1,45 @@
interface GiftIconType {
className?: string;
onClick?: () => void;
}
const GiftIcon = ({ className, onClick }: GiftIconType) => {
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="M13 4.67188H3C2.44772 4.67188 2 5.11959 2 5.67187V13.0052C2 13.5575 2.44772 14.0052 3 14.0052H13C13.5523 14.0052 14 13.5575 14 13.0052V5.67187C14 5.11959 13.5523 4.67188 13 4.67188Z"
stroke="#7950E6"
strokeLinejoin="round"
/>
<path
d="M2 10.6719H14V13.0052C14 13.2704 13.8946 13.5248 13.7071 13.7123C13.5196 13.8999 13.2652 14.0052 13 14.0052H3C2.73478 14.0052 2.48043 13.8999 2.29289 13.7123C2.10536 13.5248 2 13.2704 2 13.0052V10.6719Z"
stroke="#7950E6"
strokeLinejoin="round"
/>
<path
d="M6.33464 4.66927C7.25511 4.66927 8.0013 3.92308 8.0013 3.0026C8.0013 2.08213 7.25511 1.33594 6.33464 1.33594C5.41416 1.33594 4.66797 2.08213 4.66797 3.0026C4.66797 3.92308 5.41416 4.66927 6.33464 4.66927Z"
stroke="#7950E6"
/>
<path
d="M9.33333 4.66667C10.0697 4.66667 10.6667 4.06971 10.6667 3.33333C10.6667 2.59695 10.0697 2 9.33333 2C8.59695 2 8 2.59695 8 3.33333C8 4.06971 8.59695 4.66667 9.33333 4.66667Z"
stroke="#7950E6"
/>
<path
d="M5.66797 6.67188L8.0013 4.67188L10.3346 6.67188"
stroke="#7950E6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default GiftIcon;

View File

@@ -25,7 +25,7 @@ export default function OrderSummary() {
const { useLoyaltyPoints, splitBillAmount } = useAppSelector(selectCart); const { useLoyaltyPoints, splitBillAmount } = useAppSelector(selectCart);
const { subdomain } = useParams(); const { subdomain } = useParams();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain); const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
const { orderType } = useAppSelector(selectCart); const { orderType, tip } = useAppSelector(selectCart);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const subtotal = useAppSelector(selectCartTotal); const subtotal = useAppSelector(selectCartTotal);
const loyaltyValidation = useAppSelector(selectLoyaltyValidation); const loyaltyValidation = useAppSelector(selectLoyaltyValidation);
@@ -82,25 +82,62 @@ export default function OrderSummary() {
/> />
</div> </div>
)} )}
<div className={styles.summaryRow}> {orderType !== OrderType.Redeem && (
<ProText type="secondary" style={titlesStyle}> <div className={styles.summaryRow}>
{t("cart.discount")} <ProText type="secondary" style={titlesStyle}>
</ProText> {t("cart.discount")}
<ArabicPrice </ProText>
price={discountAmount} <ArabicPrice
style={{ ...titlesStyle, color: "#434E5C" }} price={discountAmount}
/> style={{ ...titlesStyle, color: "#434E5C" }}
</div> />
<div className={styles.summaryRow}> </div>
<ProText type="secondary" style={titlesStyle}> )}
{t("cart.tax")} {orderType !== OrderType.Redeem && (
</ProText> <div className={styles.summaryRow}>
<ArabicPrice <ProText type="secondary" style={titlesStyle}>
price={taxAmount || 0} {t("cart.tip")}
style={{ ...titlesStyle, color: "#434E5C" }} </ProText>
/> <ArabicPrice
</div> price={tip || 0}
{splitBillAmount > 0 && ( style={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
{orderType === OrderType.Redeem && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.giftedItems")}
</ProText>
<ArabicPrice
price={0}
style={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
{orderType === OrderType.Redeem && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.voucherApplied")}
</ProText>
<ArabicPrice
price={0}
style={{ ...titlesStyle, color: "#32AD6D" }}
/>
</div>
)}
{orderType !== OrderType.Redeem && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.tax")}
</ProText>
<ArabicPrice
price={taxAmount || 0}
style={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
{orderType !== OrderType.Redeem && splitBillAmount > 0 && (
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}> <ProText type="secondary" style={titlesStyle}>
{t("splitBill.splitBillAmount")} {t("splitBill.splitBillAmount")}
@@ -124,7 +161,9 @@ export default function OrderSummary() {
color: "#333333", color: "#333333",
}} }}
> >
{t("cart.totalAmount")} {orderType === OrderType.Redeem
? t("cart.remainingToPay")
: t("cart.totalAmount")}
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={grandTotal} price={grandTotal}
@@ -140,27 +179,32 @@ export default function OrderSummary() {
</div> </div>
</Space> </Space>
{isHasLoyaltyGift && restaurant?.is_loyalty_enabled === 1 && ( {isHasLoyaltyGift &&
<> restaurant?.is_loyalty_enabled === 1 &&
<Checkbox orderType !== OrderType.Redeem && (
checked={useLoyaltyPoints} <>
onChange={(value) => { <Checkbox
dispatch(updateUseLoyaltyPoints(value.target.checked)); checked={useLoyaltyPoints}
}} onChange={(value) => {
style={{ marginTop: 8 }} dispatch(updateUseLoyaltyPoints(value.target.checked));
> }}
{t("cart.useLoyaltyPoints")} style={{ marginTop: 8 }}
</Checkbox> >
</> {t("cart.useLoyaltyPoints")}
)} </Checkbox>
</>
{isHasLoyaltyGift && loyaltyValidation.errorMessage && ( )}
<div style={{ marginTop: 8, color: "red", fontSize: "12px" }}>
{t(loyaltyValidation.errorMessage)}
</div>
)}
{isHasLoyaltyGift && {isHasLoyaltyGift &&
loyaltyValidation.errorMessage &&
orderType !== OrderType.Redeem && (
<div style={{ marginTop: 8, color: "red", fontSize: "12px" }}>
{t(loyaltyValidation.errorMessage)}
</div>
)}
{isHasLoyaltyGift &&
orderType !== OrderType.Redeem &&
useLoyaltyPoints && useLoyaltyPoints &&
highestLoyaltyItem && highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 && ( restaurant?.is_loyalty_enabled === 1 && (

View File

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

View File

@@ -37,6 +37,9 @@
0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
--tw-ring-offset-shadow: 0 0 rgba(0, 0, 0, 0); --tw-ring-offset-shadow: 0 0 rgba(0, 0, 0, 0);
--tw-ring-shadow: 0 0 rgba(0, 0, 0, 0); --tw-ring-shadow: 0 0 rgba(0, 0, 0, 0);
--greylight-hover: rgba(234, 234, 234, 1);
--variable-collection-PP: rgba(255, 183, 0, 1);
} }
/* Dark theme variables */ /* Dark theme variables */

View File

@@ -23,6 +23,7 @@ import { useTranslation } from "react-i18next";
import { Variant } from "utils/types/appTypes"; import { Variant } from "utils/types/appTypes";
import DeleteIcon from "components/Icons/DeleteIcon"; import DeleteIcon from "components/Icons/DeleteIcon";
import PlusIcon from "components/Icons/PlusIcon"; import PlusIcon from "components/Icons/PlusIcon";
import { GiftItemsCard } from "pages/redeem/components/GiftItemsCard";
interface CartMobileTabletLayoutProps { interface CartMobileTabletLayoutProps {
form: FormInstance; form: FormInstance;
@@ -66,7 +67,9 @@ export default function CartMobileTabletLayout({
> >
{/* Table Number */} {/* Table Number */}
{(orderType === OrderType.DineIn || {(orderType === OrderType.DineIn ||
orderType === OrderType.ToOffice) && <TableNumberCard />} orderType === OrderType.ToOffice) && <TableNumberCard />}
{orderType === OrderType.Redeem && <GiftItemsCard isCart={true} />}
<div className={`${styles.cartContent} ${getResponsiveClass()}`}> <div className={`${styles.cartContent} ${getResponsiveClass()}`}>
<Card className={styles.cartItem}> <Card className={styles.cartItem}>

View File

@@ -17,7 +17,7 @@ export default function RewardWaiterCard() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { tip } = useAppSelector(selectCart); const { tip } = useAppSelector(selectCart);
const { isDesktop } = useBreakPoint(); const { isDesktop } = useBreakPoint();
const [selectedTip, setSelectedTip] = useState<number | null>(null); const [selectedTip, setSelectedTip] = useState<number | null>(parseFloat(tip));
const [isTipOpen, setIsTipOpen] = useState(false); const [isTipOpen, setIsTipOpen] = useState(false);

View File

@@ -246,5 +246,6 @@ export enum OrderType {
ToRoom = "room", ToRoom = "room",
ToOffice = "office", ToOffice = "office",
Booking = "booking", Booking = "booking",
Pay = "pay" Pay = "pay",
Redeem = "redeem"
} }

View File

@@ -22,17 +22,25 @@ import { useEffect } from "react";
import { CarCard } from "./components/CarCard"; 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";
export default function CheckoutPage() { export default function CheckoutPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const { phone, order, orderType, collectionMethod, coupon, customerName } = const {
useAppSelector(selectCart); phone,
order,
orderType,
collectionMethod,
coupon,
customerName,
tip,
} = useAppSelector(selectCart);
const { token } = useAppSelector((state) => state.auth); const { token } = useAppSelector((state) => state.auth);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
useEffect(() => { useEffect(() => {
form.setFieldsValue({ coupon, collectionMethod, phone, customerName }); form.setFieldsValue({ coupon, collectionMethod, phone, customerName, tip });
}, [form, phone, coupon, collectionMethod, customerName]); }, [form, phone, coupon, collectionMethod, customerName, tip]);
return ( return (
<> <>
@@ -71,12 +79,13 @@ export default function CheckoutPage() {
value={order?.officeNumber} value={order?.officeNumber}
/> />
)} )}
{orderType === OrderType.Redeem && <VoucherSummary />}
{/* {orderType === OrderType.Gift && <GiftCard />} */} {/* {orderType === OrderType.Gift && <GiftCard />} */}
{/* <RoomDetails /> {/* <RoomDetails />
<OfficeDetails /> */} <OfficeDetails /> */}
{/* <GiftDetails /> */} {/* <GiftDetails /> */}
{/* <BriefMenu /> */} {/* <BriefMenu /> */}
<CouponCard /> {orderType !== OrderType.Redeem && <CouponCard />}
{/* Collection Method */} {/* Collection Method */}
{orderType === OrderType.Pickup && ( {orderType === OrderType.Pickup && (
@@ -114,7 +123,7 @@ export default function CheckoutPage() {
)} )}
{/* Reward Your Waiter */} {/* Reward Your Waiter */}
<RewardWaiterCard /> {orderType !== OrderType.Redeem && <RewardWaiterCard />}
<BriefMenuCard /> <BriefMenuCard />
<Ads1 /> <Ads1 />
<OrderSummary /> <OrderSummary />

View File

@@ -483,9 +483,6 @@
position: absolute; position: absolute;
z-index: 999; z-index: 999;
top: 70px; top: 70px;
opacity: 0.9;
background: #aaa8a833;
backdrop-filter: blur(40px);
} }
.backButtonContainer { .backButtonContainer {
@@ -567,7 +564,7 @@
.ratingScore { .ratingScore {
position: relative; position: relative;
top:6px; top: 6px;
font-family: Roboto; font-family: Roboto;
font-weight: 600; font-weight: 600;
font-style: SemiBold; font-style: SemiBold;
@@ -802,3 +799,48 @@
display: none !important; display: none !important;
} }
} }
.frame {
align-items: flex-start;
background-color: #aaa7a733;
border-radius: 60px;
display: inline-flex;
flex-direction: column;
gap: 10px;
padding: 8px 10px;
position: relative;
}
.div {
align-items: center;
align-self: stretch;
display: flex;
flex: 0 0 auto;
gap: 4px;
position: relative;
}
.pickup {
color: #ffffff;
font-family: "Roboto-Medium", Helvetica;
font-size: 14px;
font-weight: 500;
letter-spacing: 0;
line-height: normal;
margin-top: -1px;
position: relative;
white-space: nowrap;
width: fit-content;
}
.elementMin {
color: var(--greylight-hover);
font-family: "Roboto-Regular", Helvetica;
font-size: 12px;
font-weight: 400;
letter-spacing: 0;
line-height: normal;
position: relative;
white-space: nowrap;
width: fit-content;
}

View File

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

View File

@@ -3,11 +3,9 @@ import { useGetOrderDetailsQuery } from "redux/api/others";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import styles from "./OrderDetails.module.css"; import styles from "./OrderDetails.module.css";
import ProHeader from "components/ProHeader/ProHeader"; import ProHeader from "components/ProHeader/ProHeader";
import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ProText from "components/ProText"; import ProText from "components/ProText";
import useBreakPoint from "hooks/useBreakPoint"; import useBreakPoint from "hooks/useBreakPoint";
import ArabicPrice from "components/ArabicPrice";
import ImageWithFallback from "components/ImageWithFallback"; import ImageWithFallback from "components/ImageWithFallback";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
@@ -16,6 +14,7 @@ export default function OrderDetails() {
const { t } = useTranslation(); const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { isMobile, isTablet } = useBreakPoint(); const { isMobile, isTablet } = useBreakPoint();
const { data: orderDetails } = useGetOrderDetailsQuery( const { data: orderDetails } = useGetOrderDetailsQuery(
{ {
orderID: orderId || "", orderID: orderId || "",
@@ -25,6 +24,7 @@ export default function OrderDetails() {
skip: !orderId, skip: !orderId,
}, },
); );
const getMenuItemImageStyle = () => { const getMenuItemImageStyle = () => {
if (isMobile) { if (isMobile) {
return { return {
@@ -86,7 +86,6 @@ export default function OrderDetails() {
<br /> <br />
<ProText <ProText
type="secondary" type="secondary"
className={`${styles.itemDescription} responsive-text`}
style={{ style={{
margin: 0, margin: 0,
lineClamp: 1, lineClamp: 1,
@@ -135,7 +134,9 @@ export default function OrderDetails() {
border: "none", border: "none",
}} }}
> >
<ProText style={{color: "#1F1C2E"}}>{item.qty} </ProText> <ProText style={{ color: "#1F1C2E" }}>
{item.qty}{" "}
</ProText>
</Button> </Button>
</div> </div>
</Space> </Space>

View File

@@ -0,0 +1,77 @@
.floatingContainer {
position: relative;
display: inline-block;
height: 150px;
}
.floatingPresent {
position: relative;
z-index: 2;
animation: float 3s ease-in-out infinite;
}
.floatingShadow {
position: absolute;
bottom: 10px;
left: 50%;
width: 80px;
height: 20px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 50%;
filter: blur(6px);
z-index: 1;
transform-origin: center;
transform: translateX(-50%);
animation: shadowPulse 3s ease-in-out infinite;
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-15px);
}
}
@keyframes shadowPulse {
0%,
100% {
transform: translateX(-50%) scale(1);
opacity: 0.3;
}
50% {
transform: translateX(-50%) scale(0.6);
opacity: 0.15;
}
}
.orderNotes {
gap: 20px;
opacity: 1;
border-radius: 6px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.menuItemImage {
object-fit: cover;
border-radius: 8px;
transition: transform 0.3s ease;
width: 90px;
height: 80px;
margin-bottom: 6px;
}
.backIcon {
position: relative;
top: 1px;
}
.nextIcon {
position: relative;
top: 1px;
}

View File

@@ -0,0 +1,329 @@
import { Button, Divider, Space, Switch, Tag, Skeleton } from "antd";
import ProInputCard from "components/ProInputCard/ProInputCard";
import ProText from "components/ProText";
import { useTranslation } from "react-i18next";
import styles from "./GiftItemsCard.module.css";
import { useParams } from "react-router-dom";
import {
useGetOrderDetailsQuery,
useGetRedeemDetailsQuery,
} from "redux/api/others";
import ImageWithFallback from "components/ImageWithFallback";
import { useAppSelector } from "redux/hooks";
import useBreakPoint from "hooks/useBreakPoint";
import GiftIcon from "components/Icons/GiftIcon";
import NextIcon from "components/Icons/NextIcon";
import BackIcon from "components/Icons/BackIcon";
export function GiftItemsCard({ isCart = false }: { isCart?: boolean }) {
const { t } = useTranslation();
const { voucherId } = useParams();
const { data: redeemDetails, isLoading } = useGetRedeemDetailsQuery(
voucherId || "",
{
skip: !voucherId,
},
);
const { isRTL } = useAppSelector((state) => state.locale);
const { isMobile, isTablet } = useBreakPoint();
const getMenuItemImageStyle = () => {
if (isMobile) {
return {
width: 72,
height: 72,
};
}
return {
width: 120,
height: 120,
};
};
return (
<>
<ProInputCard
title={t("redeem.giftedItems")}
titleRight={
<>
{!isCart && (
<Tag
style={{
height: 23,
textAlign: "center",
opacity: 1,
paddingRight: 10,
paddingLeft: 10,
borderRadius: 100,
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
cursor: "pointer",
backgroundColor: "#FFF9E6",
color: "#B58D00",
}}
>
{t("redeem.pending")}
</Tag>
)}
</>
}
>
{!isCart && (
<>
<div className={styles.orderNotes}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 7,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("redeem.redeemGiftedItems")}
</ProText>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("redeem.includesFreeItemsInThisOrder")}
</ProText>
</div>
<Switch />
</div>
<Divider style={{ margin: "16px 0" }} />
</>
)}
{isLoading ? (
// Skeleton loading state
<>
{[1, 2].map((skeletonIndex) => (
<div key={skeletonIndex} style={{ position: "relative" }}>
<Space
size="middle"
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "100%",
}}
>
<Space orientation="vertical" size="small">
<div>
<Skeleton.Input
active
style={{
width: isMobile ? 150 : 200,
height: 20,
marginBottom: 8,
}}
/>
<Skeleton.Input
active
style={{
width: isMobile ? 120 : 180,
height: 16,
}}
/>
</div>
<div>
<Skeleton.Button
active
style={{
width: isMobile ? 80 : 100,
height: 21,
}}
size="small"
/>
</div>
</Space>
<div style={{ position: "relative" }}>
<Skeleton.Image
active
style={{
width: getMenuItemImageStyle().width,
height: getMenuItemImageStyle().height,
borderRadius: 8,
}}
/>
</div>
</Space>
{skeletonIndex !== 3 && (
<Divider style={{ margin: "16px 0" }} />
)}
</div>
))}
</>
) : (
redeemDetails?.gift?.items?.map((item: any, index: number) => (
<div key={index} style={{ position: "relative" }}>
<Space
size="middle"
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "100%",
}}
>
<Space orientation="vertical" size="small">
<div>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{item.name}
<span
style={{
fontWeight: 400,
}}
>
{/* {isRTL
? (item.variant as Variant)?.optionsAR?.[0]?.value
: (item.variant as Variant)?.options?.[0]?.value} */}
</span>
</ProText>
<br />
<ProText
className={`${styles.itemDescription} responsive-text`}
style={{
margin: 0,
lineClamp: 1,
padding: isMobile ? "3px 0" : isTablet ? 8 : 10,
fontSize: isMobile ? 12 : isTablet ? 18 : 20,
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 1,
overflow: "hidden",
textOverflow: "ellipsis",
wordWrap: "break-word",
overflowWrap: "break-word",
lineHeight: "140%",
maxHeight: isMobile ? "3em" : isTablet ? "5em" : "7em",
width: "55%",
fontWeight: 400,
fontStyle: "Regular",
letterSpacing: "0%",
color: "#777580",
}}
>
{item.name}
</ProText>
</div>
<div>
<Button
iconPlacement="start"
size="small"
style={{
background: "#F5F5F6",
height: 21,
border: "none",
borderRadius: 888,
}}
>
<GiftIcon />
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "right",
color: "#7950E6",
padding: "2px 8px 2px 0",
}}
>
Gift x {item.qty}
</ProText>
</Button>
</div>
</Space>
<div style={{ position: "relative" }}>
<ImageWithFallback
src={item.image}
alt={item.name}
className={`${styles.menuItemImage} responsive-image`}
{...getMenuItemImageStyle()}
fallbackSrc={
"https://fascano-space.s3.me-central-1.amazonaws.com/uploads/restorants/685a8fc884a8c_large.jpg"
}
style={{
width: 72,
height: 72,
borderRadius: 8,
}}
/>
</div>
</Space>
{index !== redeemDetails?.gift?.items?.length - 1 && (
<Divider style={{ margin: "16px 0" }} />
)}
</div>
)) || null
)}
{!isCart && (
<>
<Divider style={{ margin: "16px 0" }} />
<Button
style={{
height: 48,
borderRadius: 888,
gap: 16,
opacity: 1,
borderWidth: 1,
paddingTop: 8,
paddingRight: 32,
paddingBottom: 8,
paddingLeft: 32,
width: "100%",
color: "#4C4A58",
}}
icon={
isRTL ? (
<BackIcon className={styles.backIcon} iconColor="#FFC600" />
) : (
<NextIcon className={styles.nextIcon} iconColor="#FFC600" />
)
}
iconPlacement={isRTL ? "start" : "end"}
>
{t("redeem.viewAll")}
</Button>
</>
)}
</ProInputCard>
</>
);
}

View File

@@ -0,0 +1,58 @@
.floatingContainer {
position: relative;
display: inline-block;
height: 150px;
}
.floatingPresent {
position: relative;
z-index: 2;
animation: float 3s ease-in-out infinite;
}
.floatingShadow {
position: absolute;
bottom: 10px;
left: 50%;
width: 80px;
height: 20px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 50%;
filter: blur(6px);
z-index: 1;
transform-origin: center;
transform: translateX(-50%);
animation: shadowPulse 3s ease-in-out infinite;
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-15px);
}
}
@keyframes shadowPulse {
0%,
100% {
transform: translateX(-50%) scale(1);
opacity: 0.3;
}
50% {
transform: translateX(-50%) scale(0.6);
opacity: 0.15;
}
}
.orderNotes {
gap: 20px;
opacity: 1;
border-radius: 6px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}

View File

@@ -0,0 +1,112 @@
import { Divider, Tag } from "antd";
import ProInputCard from "components/ProInputCard/ProInputCard";
import ProText from "components/ProText";
import { useTranslation } from "react-i18next";
import styles from "./LocationCard.module.css";
import { GoogleMap } from "components/CustomBottomSheet/GoogleMap";
import DirectionsIcon from "components/Icons/DirectionsIcon";
import { useGetRedeemDetailsQuery } from "redux/api/others";
import { useParams } from "react-router-dom";
export function LocationCard() {
const { t } = useTranslation();
const { voucherId } = useParams();
const { data: redeemDetails } = useGetRedeemDetailsQuery(voucherId || "", {
skip: !voucherId,
});
const handleGetDirections = () => {
const lat = redeemDetails?.gift?.lat;
const lng = redeemDetails?.gift?.lng;
if (lat && lng) {
// Google Maps URL that works on both mobile and desktop
// On mobile devices, this will open the Google Maps app if installed
const googleMapsUrl = `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}`;
// Use window.location.href for better mobile compatibility (works in webviews)
window.location.href = googleMapsUrl;
}
};
return (
<>
<ProInputCard title={t("redeem.restaurantLocation")}>
<div className={styles.mapContainer}>
<GoogleMap
readOnly={true}
initialLocation={{
lat: parseFloat(redeemDetails?.gift?.lat || "0"),
lng: parseFloat(redeemDetails?.gift?.lng || "0"),
address: "",
}}
height="160px"
/>
</div>
<Divider style={{ margin: "16px 0" }} />
<div className={styles.orderNotes}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{redeemDetails?.gift?.restaurant}
</ProText>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{redeemDetails?.gift?.restaurant}
</ProText>
</div>
<Tag
onClick={handleGetDirections}
style={{
backgroundColor: "#FFF9E6",
color: "#E8B400",
display: "flex",
alignItems: "center",
gap: 4,
cursor: "pointer",
}}
>
<DirectionsIcon />
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#E8B400",
}}
>
{t("redeem.getDirections")}
</ProText>
</Tag>
</div>
</ProInputCard>
</>
);
}

View File

@@ -0,0 +1,58 @@
.floatingContainer {
position: relative;
display: inline-block;
height: 150px;
}
.floatingPresent {
position: relative;
z-index: 2;
animation: float 3s ease-in-out infinite;
}
.floatingShadow {
position: absolute;
bottom: 10px;
left: 50%;
width: 80px;
height: 20px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 50%;
filter: blur(6px);
z-index: 1;
transform-origin: center;
transform: translateX(-50%);
animation: shadowPulse 3s ease-in-out infinite;
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-15px);
}
}
@keyframes shadowPulse {
0%,
100% {
transform: translateX(-50%) scale(1);
opacity: 0.3;
}
50% {
transform: translateX(-50%) scale(0.6);
opacity: 0.15;
}
}
.orderNotes {
gap: 20px;
opacity: 1;
border-radius: 6px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}

View File

@@ -0,0 +1,119 @@
import { Divider, Switch, Tag } from "antd";
import ProInputCard from "components/ProInputCard/ProInputCard";
import ProText from "components/ProText";
import { useTranslation } from "react-i18next";
import styles from "./VoucherBalanceCard.module.css";
import { useNavigate, useParams } from "react-router-dom";
import CardAmountIcon from "components/Icons/CardAmountIcon";
import ArabicPrice from "components/ArabicPrice";
import { useGetRedeemDetailsQuery } from "redux/api/others";
export function VoucherBalanceCard() {
const { t } = useTranslation();
const navigate = useNavigate();
const { voucherId } = useParams();
const { data: redeemDetails } = useGetRedeemDetailsQuery(voucherId || "", {
skip: !voucherId,
});
return (
<>
<ProInputCard
title={t("redeem.voucherBalance")}
titleRight={
<Tag
style={{
height: 23,
textAlign: "center",
opacity: 1,
paddingRight: 10,
paddingLeft: 10,
borderRadius: 100,
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
cursor: "pointer",
backgroundColor: "#FFF9E6",
color: "#B58D00",
}}
>
{t("redeem.pending")}
</Tag>
}
>
<div className={styles.orderNotes}>
<div
style={{
height: 42,
width: 64,
backgroundColor: "var(--background)",
borderRadius: 8,
}}
>
<CardAmountIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("redeem.yourGiftCardBalance")}
</ProText>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
<ArabicPrice price={redeemDetails?.gift?.amount || 0} />
</ProText>
</div>
<Switch />
</div>
<Divider style={{ margin: "10px 0" }} />
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
cursor: "pointer",
}}
>
{t("redeem.voucherWillBeAppliedAtCheckout")}
</ProText>
</div>
</ProInputCard>
</>
);
}

View File

@@ -0,0 +1,101 @@
.voucherSummary :global(.ant-card-body) {
padding: 16px !important;
}
.voucherSummary {
transition: all 0.3s ease;
}
.summaryRow {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
}
.summaryDivider {
margin: 0 !important;
}
.totalRow {
font-weight: bold;
font-size: 16px;
}
.desktopOrderSummary {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.desktopSummaryRow {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
font-size: 16px;
}
.desktopTotalRow {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
margin-top: 16px;
}
/* Enhanced responsive focus states */
.voucherSummary:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Enhanced responsive animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ===== MEDIA QUERIES ===== */
/* Tablet styles (769px - 1024px) */
@media (min-width: 769px) and (max-width: 1024px) {
.summaryRow {
font-size: 16px;
}
.summaryDivider {
margin: 10px 0 !important;
}
.totalRow {
font-size: 18px;
}
}
/* Desktop styles (1025px+) */
@media (min-width: 1025px) {
.summaryRow {
font-size: 18px;
}
.summaryDivider {
margin: 10px 0 !important;
}
.totalRow {
font-size: 20px;
}
}
/* Focus states for larger screens */
@media (min-width: 768px) {
.voucherSummary:focus {
outline-offset: 4px;
}
}

View File

@@ -0,0 +1,90 @@
import { Card, Checkbox, Divider, Space } from "antd";
import ArabicPrice from "components/ArabicPrice";
import {
selectCart,
selectCartTotal,
selectGrandTotal,
} from "features/order/orderSlice";
import { OrderType } from "pages/checkout/hooks/types";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { useAppSelector } from "redux/hooks";
import ProText from "components/ProText";
import ProTitle from "components/ProTitle";
import styles from "./VoucherSummary.module.css";
import { CSSProperties } from "react";
export default function VoucherSummary() {
const { t } = useTranslation();
const { subdomain } = useParams();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
const subtotal = useAppSelector(selectCartTotal);
const grandTotal = useAppSelector(selectGrandTotal);
const titlesStyle: CSSProperties = {
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
};
return (
<>
<Card className={`${styles.voucherSummary}`}>
<ProTitle
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("cart.voucherSummary")}
</ProTitle>
<Divider style={{ margin: "15px 0 15px 0" }} />
<Space orientation="vertical" style={{ width: "100%", gap: 16 }}>
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.voucherBalance")}
</ProText>
<ArabicPrice price={subtotal} textStyle={titlesStyle} />
</div>
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.voucherApplied")}
</ProText>
<ArabicPrice
price={Number(restaurant?.delivery_fees || 0)}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
<div className={`${styles.summaryRow} ${styles.totalRow}`}>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
{t("cart.remainingVoucherAmount")}
</ProText>
<ArabicPrice
price={grandTotal}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
</Space>
</Card>
</>
);
}

View File

@@ -1,263 +1,290 @@
import { Button, Card, Divider, Image } from "antd"; import {
import Ads2 from "components/Ads/Ads2"; Button,
import { CancelOrderBottomSheet } from "components/CustomBottomSheet/CancelOrderBottomSheet"; Card,
import LocationIcon from "components/Icons/LocationIcon"; Form,
import InvoiceIcon from "components/Icons/order/InvoiceIcon"; Image,
import TimeIcon from "components/Icons/order/TimeIcon"; Layout,
import OrderDishIcon from "components/Icons/OrderDishIcon"; QRCode,
import PaymentDetails from "components/PaymentDetails/PaymentDetails"; Skeleton,
message,
} from "antd";
import ProHeader from "components/ProHeader/ProHeader"; import ProHeader from "components/ProHeader/ProHeader";
import ProInputCard from "components/ProInputCard/ProInputCard";
import ProText from "components/ProText"; import ProText from "components/ProText";
import ProTitle from "components/ProTitle";
import dayjs from "dayjs";
import { useEffect, useRef } 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";
import { import { useGetRedeemDetailsQuery } from "redux/api/others";
useGetOrderDetailsQuery,
useGetRestaurantDetailsQuery,
} from "redux/api/others";
import { useAppSelector } from "redux/hooks";
import styles from "./redeem.module.css"; import styles from "./redeem.module.css";
import BackIcon from "components/Icons/BackIcon"; import CopyIcon from "components/Icons/CopyIcon";
import NextIcon from "components/Icons/NextIcon"; import { LocationCard } from "./components/LocationCard.tsx";
import { RateBottomSheet } from "components/CustomBottomSheet/RateBottomSheet"; import { GiftItemsCard } from "./components/GiftItemsCard.tsx";
import Stepper from "pages/order/components/Stepper"; import { VoucherBalanceCard } from "./components/VoucherBalanceCard.tsx";
import { OrderType } from "pages/checkout/hooks/types.ts";
import { Loader } from "components/Loader/Loader.tsx";
import { CollectWay } from "pages/checkout/components/CollectWay/CollectWay.tsx";
import PickupTimeCard from "pages/checkout/components/pickupEstimate/TimeEstimateCard.tsx";
export default function RedeemPage() { export default function RedeemPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const { orderId } = useParams(); const { voucherId } = useParams();
const { isRTL } = useAppSelector((state) => state.locale);
const { restaurant } = useAppSelector((state) => state.order);
const navigate = useNavigate(); const navigate = useNavigate();
const hasRefetchedRef = useRef(false); const { subdomain } = useParams();
const [form] = Form.useForm();
const { data: orderDetails } = useGetOrderDetailsQuery( const { data: redeemDetails, isLoading } = useGetRedeemDetailsQuery(
voucherId || "",
{ {
orderID: orderId || "", skip: !voucherId,
restaurantID: localStorage.getItem("restaurantID") || "",
},
{
skip: !orderId,
// return it t0 60000 after finish testing
pollingInterval: 10000,
refetchOnMountOrArgChange: true,
}, },
); );
// Get restaurant subdomain for refetching const handleCheckout = () => {
const restaurantSubdomain = restaurant?.subdomain; navigate(`/${subdomain}/menu?orderType=${OrderType.Redeem}`);
const { refetch: refetchRestaurantDetails } = useGetRestaurantDetailsQuery( };
restaurantSubdomain || "",
{
skip: !restaurantSubdomain,
},
);
// Reset refetch flag when orderId changes const handleCopyVoucherCode = async () => {
useEffect(() => { const voucherCode = redeemDetails?.gift?.voucher_code;
hasRefetchedRef.current = false; if (voucherCode) {
}, [orderId]); try {
await navigator.clipboard.writeText(voucherCode);
// Refetch restaurant details when order status has alias "closed" message.success(
useEffect(() => { t("redeem.voucherCodeCopied") || "Voucher code copied!",
if (orderDetails?.status && !hasRefetchedRef.current) { );
const hasClosedStatus = orderDetails.status.some( } catch (error) {
(status) => status?.alias === "closed", message.error(t("redeem.copyFailed") || "Failed to copy voucher code");
);
if (hasClosedStatus && restaurantSubdomain) {
refetchRestaurantDetails();
hasRefetchedRef.current = true;
} }
} }
}, [orderDetails?.status, restaurantSubdomain, refetchRestaurantDetails]); };
if (isLoading) return <Loader />;
return ( return (
<> <>
<ProHeader>{t("order.title")}</ProHeader> <Layout>
<div <ProHeader>{t("redeem.title")}</ProHeader>
style={{ <Layout.Content className={styles.redeemContainer}>
display: "flex",
flexDirection: "column",
height: "92vh",
padding: 16,
gap: 16,
overflow: "auto",
scrollbarWidth: "none",
}}
>
<Card className={styles.orderCard}>
<div <div
style={{ style={{
textAlign: "center",
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "column",
gap: "1rem", alignItems: "center",
backgroundColor: "rgba(255, 183, 0, 0.08)", justifyContent: "center",
borderRadius: "12px", margin: "32px 28px 21px 28px",
padding: 16, gap: 8,
}} }}
> >
<Button <ProText
type="text" style={{ fontSize: 16, fontWeight: 600, color: "#333333" }}
shape="circle"
style={{
backgroundColor: "rgba(255, 183, 0, 0.08)",
}}
> >
<Image {t("redeem.hiX", { name: redeemDetails?.gift?.recipient_name })}
src={orderDetails?.restaurant_iimage} </ProText>
className={styles.profileImage} <ProText
width={50} style={{ fontSize: 14, fontWeight: 400, color: "#95949C" }}
height={50} >
preview={false} {t("redeem.youHaveReceivedAGiftCarFromX", {
/> name: redeemDetails?.gift?.sender_name,
</Button> })}
<div> </ProText>
<ProText style={{ fontSize: "1rem" }}>
{t("order.yourOrderFromFascanoRestaurant")}
</ProText>
<br />
<ProText type="secondary">
<LocationIcon className={styles.locationIcon} />{" "}
{isRTL ? orderDetails?.restaurantAR : orderDetails?.restaurant}
</ProText>
</div>
</div> </div>
<OrderDishIcon className={styles.orderDishIcon} /> {isLoading || !redeemDetails?.gift?.restaurant_iimage ? (
<div className={styles.carouselContainer}>
<div className={styles.cardWrapper}>
<Skeleton.Image
active
style={{
width: 205,
height: 134,
borderRadius: 8,
}}
/>
</div>
</div>
) : (
<div className={styles.carouselContainer}>
<div className={styles.cardWrapper}>
<Image
src={redeemDetails?.gift?.card_url}
width={205}
height={134}
className={styles.cardImage}
/>
</div>
</div>
)}
<div> <div
<ProTitle style={{
level={5} textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
margin: "18px 28px 26px 28px",
gap: 12,
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#95949C",
}}
>
{redeemDetails?.gift?.message || t("redeem.description")}
</ProText>
<ProText
style={{ style={{
fontWeight: 600, fontWeight: 600,
fontSize: "18px", fontStyle: "SemiBold",
marginBottom: "0.75rem", fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}} }}
> >
{t("order.inProgressOrder")} (1) {redeemDetails?.gift?.sender_name}
</ProTitle> </ProText>
<div style={{ display: "flex", flexDirection: "row", gap: 8 }}>
<InvoiceIcon className={styles.invoiceIcon} />
<ProText type="secondary" style={{ fontSize: "14px" }}>
#{orderDetails?.order.id}
</ProText>
<TimeIcon className={styles.timeIcon} />
<ProText type="secondary" style={{ fontSize: "14px" }}>
ordered :- Today -{" "}
{dayjs(orderDetails?.status[0]?.pivot?.created_at).format(
"h:mm A",
)}
</ProText>
</div>
<Divider style={{ margin: "12px 0" }} />
<Stepper statuses={orderDetails?.status} />
</div> </div>
</Card>
<Ads2 /> <div>
<Card
<ProInputCard style={{
title={ borderRadius: 0,
<div style={{ marginBottom: 7 }}> borderTopRightRadius: 16,
<ProText style={{ fontSize: "1rem" }}> borderTopLeftRadius: 16,
{t("order.yourOrderFrom")} border: "1px solid transparent",
borderImageSource:
"radial-gradient(38.92% 103.83% at 49.85% -3.83%, #FFB700 0%, rgba(255, 233, 179, 0) 100%)",
borderImageSlice: 1,
}}
styles={{
body: {
display: "flex",
flexDirection: "column",
gap: 24,
alignItems: "center",
justifyContent: "center",
padding: 32,
},
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#95949C",
alignItems: "center",
}}
>
{t("redeem.showThisCodeAtTheRestaurant")}
</ProText> </ProText>
<br /> <QRCode value={redeemDetails?.gift?.gr_url || "-"} />
<ProText type="secondary"> <div
<LocationIcon className={styles.locationIcon} />{" "} style={{ display: "flex", flexDirection: "column", gap: 12 }}
{t("order.muscat")} >
</ProText> <Button
</div>
}
>
<div
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
{orderDetails?.orderItems.map((item, index) => (
<div key={item.id}>
<div
style={{ style={{
display: "flex", height: 40,
flexDirection: "row", borderRadius: 888,
justifyContent: "flex-start", gap: 16,
gap: "1rem", opacity: 1,
borderWidth: 1,
backgroundColor: "var(--background)",
}}
icon={<CopyIcon className={styles.copyIcon} />}
iconPlacement="end"
onClick={handleCopyVoucherCode}
>
{redeemDetails?.gift?.voucher_code}
</Button>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#95949C",
alignItems: "center",
}} }}
> >
<Button {t("redeem.useThisCodeIfScanningNotPossible")}
type="text" </ProText>
shape="circle"
style={{
backgroundColor: "rgba(255, 183, 0, 0.08)",
}}
>
{index + 1}X
</Button>
<div>
<ProText
style={{ fontSize: "1rem", position: "relative", top: 8 }}
>
{item.name}
</ProText>
</div>
</div>
</div> </div>
))} </Card>
</div>
</ProInputCard>
<PaymentDetails order={orderDetails?.order} /> <div
<Card
className={styles.backToHomePage}
onClick={() => navigate(`/${restaurant?.subdomain}`)}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginTop: 1,
}}
>
<Image
src={restaurant?.restautantImage}
width={30}
height={30}
preview={false}
style={{ style={{
borderRadius: "50%", width: "100%",
objectFit: "cover", height: 44,
position: "relative", opacity: 1,
top: -4, borderBottomRightRadius: 16,
}} borderBottomLeftRadius: 16,
/> paddingTop: 12,
paddingBottom: 12,
<ProTitle gap: 10,
level={5} borderTopWidth: 1,
style={{ background: "#FFF9E6",
fontSize: 14, borderTop: "1px solid #FFEDB0",
textAlign: "center",
}} }}
> >
{isRTL ? restaurant?.nameAR : restaurant?.restautantName} <ProText
</ProTitle> style={{
fontSize: 14,
{isRTL ? ( fontWeight: 500,
<BackIcon className={styles.serviceIcon} /> fontStyle: "Medium",
) : ( lineHeight: "140%",
<NextIcon className={styles.serviceIcon} /> letterSpacing: "0%",
)} textAlign: "center",
color: "#E8B400",
}}
>
Active - Expires in 12 days
</ProText>
</div>
</div> </div>
</Card>
{/* <RateBottomSheet /> */} <Form form={form}>
<div
style={{
margin: "20px 0",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
{redeemDetails?.gift?.gift_type === "ORDER" && <GiftItemsCard />}
{redeemDetails?.gift?.gift_type === "AMOUNT" && (
<VoucherBalanceCard />
)}
<LocationCard />
<CollectWay />
<PickupTimeCard />
</div>
</Form>
</Layout.Content>
<CancelOrderBottomSheet /> <Layout.Footer className={styles.checkoutButtonContainer}>
</div> <Button
type="primary"
shape="round"
className={styles.checkoutButton}
onClick={handleCheckout}
>
{t("redeem.redeemNow")}
</Button>
</Layout.Footer>
</Layout>
</> </>
); );
} }

View File

@@ -1,247 +1,81 @@
.orderSummary :global(.ant-card-body) { .carouselContainer {
padding: 16px !important;
}
.profileImage {
border-radius: 50%;
width: 50px;
height: 50px;
}
/* Enhanced responsive order summary */
@media (min-width: 769px) and (max-width: 1024px) {
.orderSummary {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
.fascanoIcon {
position: relative;
top: 3px;
}
.locationIcon {
position: relative;
top: 3px;
}
.orderDishIcon {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
justify-content: center;
gap: 16px;
width: 100%; width: 100%;
} }
.orderCard :global(.ant-card-body) { .cardWrapper {
display: flex; display: flex;
flex-direction: column;
justify-content: flex-start;
}
.orderCard :global(.ant-card-body) > *:not(:last-child) {
margin-bottom: 3.5rem;
}
.orderSummary {
transition: all 0.3s ease;
}
.invoiceIcon {
position: relative;
top: 3px;
}
.timeIcon {
position: relative;
top: 3px;
}
/* Enhanced responsive order summary */
@media (min-width: 769px) and (max-width: 1024px) {
.orderSummary {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
.summaryRow {
display: flex;
justify-content: space-between;
align-items: center; align-items: center;
font-size: 16px; justify-content: center;
} }
/* Enhanced responsive summary rows */ .cardImage {
@media (min-width: 769px) and (max-width: 1024px) { width: 205px;
.summaryRow { height: 134px;
padding: 12px 0; object-fit: cover;
font-size: 16px; border-radius: 8px;
}
} }
@media (min-width: 1025px) { .arrowButton {
.summaryRow {
padding: 16px 0;
font-size: 18px;
}
}
.summaryDivider {
margin: 8px 0 !important;
}
/* Enhanced responsive summary divider */
@media (min-width: 769px) and (max-width: 1024px) {
.summaryDivider {
margin: 20px 0 !important;
}
}
@media (min-width: 1025px) {
.summaryDivider {
margin: 24px 0 !important;
}
}
.totalRow {
font-weight: bold;
font-size: 16px;
}
/* Enhanced responsive total row */
@media (min-width: 769px) and (max-width: 1024px) {
.totalRow {
font-size: 18px;
padding-top: 20px;
margin-top: 12px;
}
}
@media (min-width: 1025px) {
.totalRow {
font-size: 20px;
padding-top: 24px;
margin-top: 16px;
}
}
.desktopOrderSummary {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.desktopSummaryRow {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
padding: 12px 0; justify-content: center;
font-size: 16px; min-width: 40px;
height: 40px;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
} }
.desktopTotalRow { .arrowButton:hover {
background: rgba(0, 0, 0, 0.04);
border-radius: 50%;
}
/* CheckoutButton Styles */
.checkoutButtonContainer {
width: 100%;
padding: 16px 16px 0;
position: sticky;
bottom: 0;
left: 0;
height: 80px;
display: flex; display: flex;
justify-content: space-between; flex-direction: row;
align-items: center; justify-content: space-around;
padding: 16px 0; gap: 1rem;
margin-top: 16px; background-color: var(--secondary-background);
box-shadow: none;
z-index: 999;
} }
[data-theme="dark"] .orderSummary { /* Dark theme styles for checkout */
background-color: #181818 !important; :global(.darkApp) .checkoutButtonContainer {
border-color: #363636 !important; background-color: #000000 !important;
} }
[data-theme="dark"] .orderSummary:hover { .checkoutButton {
background-color: #363636 !important;
border-color: #424242 !important;
}
[data-theme="dark"] .summaryRow {
color: #b0b0b0;
}
[data-theme="dark"] .totalRow {
color: #ffffff;
border-top-color: #424242;
}
/* Enhanced responsive animations */
@media (prefers-reduced-motion: no-preference) {
.orderSummary {
animation: fadeInUp 0.8s ease-out;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Enhanced responsive focus states */
.orderSummary:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
@media (min-width: 768px) {
.orderSummary:focus {
outline-offset: 4px;
}
}
/* Enhanced responsive print styles */
@media print {
.orderSummary {
box-shadow: none !important;
border: 1px solid #ccc !important;
}
}
/* Enhanced responsive hover effects */
@media (hover: hover) {
.orderSummary:hover {
transform: translateY(-2px);
}
.menuItemImage:hover {
transform: scale(1.05);
}
[data-theme="dark"] .orderSummary:hover {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
}
}
.backToHomePage {
width: 100%; width: 100%;
height: 48px; height: 48px;
margin-bottom: 16px;
box-shadow: none;
}
.copyIcon {
cursor: pointer;
position: relative;
top: 3px;
}
.redeemContainer {
display: flex; display: flex;
justify-content: flex-start; flex-direction: column;
padding: 12px 18px !important; padding: 16px;
row-gap: 10px; gap: 16px;
transition: all 0.3s ease; overflow: auto;
border-radius: 50px; scrollbar-width: none;
} }
.backToHomePage :global(.ant-card-body) {
padding: 0px !important;
text-align: start;
width: 100%;
}
.nextIcon {
width: 24px;
height: 24px;
}
.backIcon {
width: 24px;
height: 24px;
}

94
src/pages/redeem/types.ts Normal file
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

@@ -10,6 +10,7 @@ import {
TABLES_URL, TABLES_URL,
USER_DETAILS_URL, USER_DETAILS_URL,
EGIFT_CARDS_URL, EGIFT_CARDS_URL,
REDEEM_DETAILS_URL,
} from "utils/constants"; } from "utils/constants";
import { OrderDetails } from "pages/checkout/hooks/types"; import { OrderDetails } from "pages/checkout/hooks/types";
@@ -21,6 +22,7 @@ import {
} from "utils/types/appTypes"; } from "utils/types/appTypes";
import { baseApi } from "./apiSlice"; import { baseApi } from "./apiSlice";
import { EGiftCard } from "pages/EGiftCards/type"; import { EGiftCard } from "pages/EGiftCards/type";
import { RedeemResponse } from "pages/redeem/types";
export const branchApi = baseApi.injectEndpoints({ export const branchApi = baseApi.injectEndpoints({
endpoints: (builder) => ({ endpoints: (builder) => ({
@@ -171,6 +173,15 @@ export const branchApi = baseApi.injectEndpoints({
return response.result; return response.result;
}, },
}), }),
getRedeemDetails: builder.query<RedeemResponse, string>({
query: (voucherId: string) => ({
url: `${REDEEM_DETAILS_URL}/${voucherId}`,
method: "GET",
}),
transformResponse: (response: any) => {
return response.result;
},
}),
}), }),
}); });
export const { export const {
@@ -185,4 +196,5 @@ export const {
useRateOrderMutation, useRateOrderMutation,
useGetUserDetailsQuery, useGetUserDetailsQuery,
useGetEGiftCardsQuery, useGetEGiftCardsQuery,
useGetRedeemDetailsQuery,
} = branchApi; } = branchApi;

View File

@@ -15,7 +15,6 @@ import OrderDetails from "pages/order/components/OrderDetails";
import OrderPage from "pages/order/page"; import OrderPage from "pages/order/page";
import OrdersPage from "pages/orders/page"; import OrdersPage from "pages/orders/page";
import OtpPage from "pages/otp/page"; import OtpPage from "pages/otp/page";
import PayPage from "pages/pay/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";

View File

@@ -108,4 +108,5 @@ export const SEND_OTP_URL = `${API_BASE_URL}sendOtp`;
export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`; 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`;