Compare commits
3 Commits
ebe9928091
...
167b26e0b9
| Author | SHA1 | Date | |
|---|---|---|---|
| 167b26e0b9 | |||
| b0288ebcf6 | |||
| 6271c14eff |
@@ -526,5 +526,45 @@
|
||||
"color": "اللون",
|
||||
"category": "الفئة",
|
||||
"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": "فشل نسخ رمز القسيمة"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,11 @@
|
||||
"customizable": "Customizable"
|
||||
},
|
||||
"cart": {
|
||||
"remainingToPay": "Remaining to Pay",
|
||||
"remainingVoucherAmount": "Remaining Voucher Amount",
|
||||
"voucherApplied": "Voucher Applied",
|
||||
"giftedItems": "Gifted Items",
|
||||
"voucherBalance": "Voucher Balance",
|
||||
"addSpecialRequestOptional": "Add Special Request (Optional)",
|
||||
"continueToGiftDetails": "Continue to Gift Details",
|
||||
"continueToGiftDetailsDescription": "Please fill in the details of the gift recipient and sender to continue.",
|
||||
@@ -539,5 +544,47 @@
|
||||
"color": "Color",
|
||||
"category": "Category",
|
||||
"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}}!"
|
||||
}
|
||||
}
|
||||
|
||||
33
src/components/Icons/CopyIcon.tsx
Normal file
33
src/components/Icons/CopyIcon.tsx
Normal 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;
|
||||
25
src/components/Icons/DirectionsIcon.tsx
Normal file
25
src/components/Icons/DirectionsIcon.tsx
Normal 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;
|
||||
45
src/components/Icons/GiftIcon.tsx
Normal file
45
src/components/Icons/GiftIcon.tsx
Normal 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;
|
||||
@@ -25,7 +25,7 @@ export default function OrderSummary() {
|
||||
const { useLoyaltyPoints, splitBillAmount } = useAppSelector(selectCart);
|
||||
const { subdomain } = useParams();
|
||||
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
|
||||
const { orderType } = useAppSelector(selectCart);
|
||||
const { orderType, tip } = useAppSelector(selectCart);
|
||||
const dispatch = useAppDispatch();
|
||||
const subtotal = useAppSelector(selectCartTotal);
|
||||
const loyaltyValidation = useAppSelector(selectLoyaltyValidation);
|
||||
@@ -82,6 +82,7 @@ export default function OrderSummary() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{orderType !== OrderType.Redeem && (
|
||||
<div className={styles.summaryRow}>
|
||||
<ProText type="secondary" style={titlesStyle}>
|
||||
{t("cart.discount")}
|
||||
@@ -91,6 +92,41 @@ export default function OrderSummary() {
|
||||
style={{ ...titlesStyle, color: "#434E5C" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{orderType !== OrderType.Redeem && (
|
||||
<div className={styles.summaryRow}>
|
||||
<ProText type="secondary" style={titlesStyle}>
|
||||
{t("cart.tip")}
|
||||
</ProText>
|
||||
<ArabicPrice
|
||||
price={tip || 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")}
|
||||
@@ -100,7 +136,8 @@ export default function OrderSummary() {
|
||||
style={{ ...titlesStyle, color: "#434E5C" }}
|
||||
/>
|
||||
</div>
|
||||
{splitBillAmount > 0 && (
|
||||
)}
|
||||
{orderType !== OrderType.Redeem && splitBillAmount > 0 && (
|
||||
<div className={styles.summaryRow}>
|
||||
<ProText type="secondary" style={titlesStyle}>
|
||||
{t("splitBill.splitBillAmount")}
|
||||
@@ -124,7 +161,9 @@ export default function OrderSummary() {
|
||||
color: "#333333",
|
||||
}}
|
||||
>
|
||||
{t("cart.totalAmount")}
|
||||
{orderType === OrderType.Redeem
|
||||
? t("cart.remainingToPay")
|
||||
: t("cart.totalAmount")}
|
||||
</ProText>
|
||||
<ArabicPrice
|
||||
price={grandTotal}
|
||||
@@ -140,7 +179,9 @@ export default function OrderSummary() {
|
||||
</div>
|
||||
</Space>
|
||||
|
||||
{isHasLoyaltyGift && restaurant?.is_loyalty_enabled === 1 && (
|
||||
{isHasLoyaltyGift &&
|
||||
restaurant?.is_loyalty_enabled === 1 &&
|
||||
orderType !== OrderType.Redeem && (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={useLoyaltyPoints}
|
||||
@@ -154,13 +195,16 @@ export default function OrderSummary() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{isHasLoyaltyGift && loyaltyValidation.errorMessage && (
|
||||
{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 && (
|
||||
|
||||
@@ -873,7 +873,8 @@ export const selectGrandTotal = (state: RootState) => {
|
||||
taxAmount -
|
||||
totalDiscount +
|
||||
deliveryFee -
|
||||
state.order.splitBillAmount
|
||||
state.order.splitBillAmount -
|
||||
Number(state.order.tip)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
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-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 */
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Variant } from "utils/types/appTypes";
|
||||
import DeleteIcon from "components/Icons/DeleteIcon";
|
||||
import PlusIcon from "components/Icons/PlusIcon";
|
||||
import { GiftItemsCard } from "pages/redeem/components/GiftItemsCard";
|
||||
|
||||
interface CartMobileTabletLayoutProps {
|
||||
form: FormInstance;
|
||||
@@ -68,6 +69,8 @@ export default function CartMobileTabletLayout({
|
||||
{(orderType === OrderType.DineIn ||
|
||||
orderType === OrderType.ToOffice) && <TableNumberCard />}
|
||||
|
||||
{orderType === OrderType.Redeem && <GiftItemsCard isCart={true} />}
|
||||
|
||||
<div className={`${styles.cartContent} ${getResponsiveClass()}`}>
|
||||
<Card className={styles.cartItem}>
|
||||
<div
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function RewardWaiterCard() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { tip } = useAppSelector(selectCart);
|
||||
const { isDesktop } = useBreakPoint();
|
||||
const [selectedTip, setSelectedTip] = useState<number | null>(null);
|
||||
const [selectedTip, setSelectedTip] = useState<number | null>(parseFloat(tip));
|
||||
|
||||
const [isTipOpen, setIsTipOpen] = useState(false);
|
||||
|
||||
|
||||
@@ -246,5 +246,6 @@ export enum OrderType {
|
||||
ToRoom = "room",
|
||||
ToOffice = "office",
|
||||
Booking = "booking",
|
||||
Pay = "pay"
|
||||
Pay = "pay",
|
||||
Redeem = "redeem"
|
||||
}
|
||||
|
||||
@@ -22,17 +22,25 @@ import { useEffect } from "react";
|
||||
import { CarCard } from "./components/CarCard";
|
||||
import { CollectWay } from "./components/CollectWay/CollectWay";
|
||||
import PickupTimeCard from "./components/pickupEstimate/TimeEstimateCard";
|
||||
import VoucherSummary from "pages/redeem/components/VoucherSummary/VoucherSummary";
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { phone, order, orderType, collectionMethod, coupon, customerName } =
|
||||
useAppSelector(selectCart);
|
||||
const {
|
||||
phone,
|
||||
order,
|
||||
orderType,
|
||||
collectionMethod,
|
||||
coupon,
|
||||
customerName,
|
||||
tip,
|
||||
} = useAppSelector(selectCart);
|
||||
const { token } = useAppSelector((state) => state.auth);
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ coupon, collectionMethod, phone, customerName });
|
||||
}, [form, phone, coupon, collectionMethod, customerName]);
|
||||
form.setFieldsValue({ coupon, collectionMethod, phone, customerName, tip });
|
||||
}, [form, phone, coupon, collectionMethod, customerName, tip]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -71,12 +79,13 @@ export default function CheckoutPage() {
|
||||
value={order?.officeNumber}
|
||||
/>
|
||||
)}
|
||||
{orderType === OrderType.Redeem && <VoucherSummary />}
|
||||
{/* {orderType === OrderType.Gift && <GiftCard />} */}
|
||||
{/* <RoomDetails />
|
||||
<OfficeDetails /> */}
|
||||
{/* <GiftDetails /> */}
|
||||
{/* <BriefMenu /> */}
|
||||
<CouponCard />
|
||||
{orderType !== OrderType.Redeem && <CouponCard />}
|
||||
|
||||
{/* Collection Method */}
|
||||
{orderType === OrderType.Pickup && (
|
||||
@@ -114,7 +123,7 @@ export default function CheckoutPage() {
|
||||
)}
|
||||
|
||||
{/* Reward Your Waiter */}
|
||||
<RewardWaiterCard />
|
||||
{orderType !== OrderType.Redeem && <RewardWaiterCard />}
|
||||
<BriefMenuCard />
|
||||
<Ads1 />
|
||||
<OrderSummary />
|
||||
|
||||
@@ -483,9 +483,6 @@
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 70px;
|
||||
opacity: 0.9;
|
||||
background: #aaa8a833;
|
||||
backdrop-filter: blur(40px);
|
||||
}
|
||||
|
||||
.backButtonContainer {
|
||||
@@ -802,3 +799,48 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ function MenuPage() {
|
||||
<div
|
||||
className={`${styles.headerFloatingBtn} ${styles.orderTypeSelectContainer} order-type-select-container`}
|
||||
>
|
||||
{orderType !== OrderType.Redeem && (
|
||||
<Select
|
||||
value={orderType}
|
||||
options={orderTypeOptions}
|
||||
@@ -111,6 +112,15 @@ function MenuPage() {
|
||||
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>
|
||||
<SearchButton />
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,9 @@ import { useGetOrderDetailsQuery } from "redux/api/others";
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
import styles from "./OrderDetails.module.css";
|
||||
import ProHeader from "components/ProHeader/ProHeader";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ProText from "components/ProText";
|
||||
import useBreakPoint from "hooks/useBreakPoint";
|
||||
import ArabicPrice from "components/ArabicPrice";
|
||||
import ImageWithFallback from "components/ImageWithFallback";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
@@ -16,6 +14,7 @@ export default function OrderDetails() {
|
||||
const { t } = useTranslation();
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { isMobile, isTablet } = useBreakPoint();
|
||||
|
||||
const { data: orderDetails } = useGetOrderDetailsQuery(
|
||||
{
|
||||
orderID: orderId || "",
|
||||
@@ -25,6 +24,7 @@ export default function OrderDetails() {
|
||||
skip: !orderId,
|
||||
},
|
||||
);
|
||||
|
||||
const getMenuItemImageStyle = () => {
|
||||
if (isMobile) {
|
||||
return {
|
||||
@@ -86,7 +86,6 @@ export default function OrderDetails() {
|
||||
<br />
|
||||
<ProText
|
||||
type="secondary"
|
||||
className={`${styles.itemDescription} responsive-text`}
|
||||
style={{
|
||||
margin: 0,
|
||||
lineClamp: 1,
|
||||
@@ -135,7 +134,9 @@ export default function OrderDetails() {
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
<ProText style={{color: "#1F1C2E"}}>{item.qty} </ProText>
|
||||
<ProText style={{ color: "#1F1C2E" }}>
|
||||
{item.qty}{" "}
|
||||
</ProText>
|
||||
</Button>
|
||||
</div>
|
||||
</Space>
|
||||
|
||||
77
src/pages/redeem/components/GiftItemsCard.module.css
Normal file
77
src/pages/redeem/components/GiftItemsCard.module.css
Normal 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;
|
||||
}
|
||||
329
src/pages/redeem/components/GiftItemsCard.tsx
Normal file
329
src/pages/redeem/components/GiftItemsCard.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
58
src/pages/redeem/components/LocationCard.module.css
Normal file
58
src/pages/redeem/components/LocationCard.module.css
Normal 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;
|
||||
}
|
||||
112
src/pages/redeem/components/LocationCard.tsx
Normal file
112
src/pages/redeem/components/LocationCard.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
58
src/pages/redeem/components/VoucherBalanceCard.module.css
Normal file
58
src/pages/redeem/components/VoucherBalanceCard.module.css
Normal 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;
|
||||
}
|
||||
119
src/pages/redeem/components/VoucherBalanceCard.tsx
Normal file
119
src/pages/redeem/components/VoucherBalanceCard.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,263 +1,290 @@
|
||||
import { Button, Card, Divider, Image } from "antd";
|
||||
import Ads2 from "components/Ads/Ads2";
|
||||
import { CancelOrderBottomSheet } from "components/CustomBottomSheet/CancelOrderBottomSheet";
|
||||
import LocationIcon from "components/Icons/LocationIcon";
|
||||
import InvoiceIcon from "components/Icons/order/InvoiceIcon";
|
||||
import TimeIcon from "components/Icons/order/TimeIcon";
|
||||
import OrderDishIcon from "components/Icons/OrderDishIcon";
|
||||
import PaymentDetails from "components/PaymentDetails/PaymentDetails";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
Image,
|
||||
Layout,
|
||||
QRCode,
|
||||
Skeleton,
|
||||
message,
|
||||
} from "antd";
|
||||
|
||||
import ProHeader from "components/ProHeader/ProHeader";
|
||||
import ProInputCard from "components/ProInputCard/ProInputCard";
|
||||
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 { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
useGetOrderDetailsQuery,
|
||||
useGetRestaurantDetailsQuery,
|
||||
} from "redux/api/others";
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
import { useGetRedeemDetailsQuery } from "redux/api/others";
|
||||
import styles from "./redeem.module.css";
|
||||
import BackIcon from "components/Icons/BackIcon";
|
||||
import NextIcon from "components/Icons/NextIcon";
|
||||
import { RateBottomSheet } from "components/CustomBottomSheet/RateBottomSheet";
|
||||
import Stepper from "pages/order/components/Stepper";
|
||||
import CopyIcon from "components/Icons/CopyIcon";
|
||||
import { LocationCard } from "./components/LocationCard.tsx";
|
||||
import { GiftItemsCard } from "./components/GiftItemsCard.tsx";
|
||||
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() {
|
||||
const { t } = useTranslation();
|
||||
const { orderId } = useParams();
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { restaurant } = useAppSelector((state) => state.order);
|
||||
const { voucherId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const hasRefetchedRef = useRef(false);
|
||||
|
||||
const { data: orderDetails } = useGetOrderDetailsQuery(
|
||||
const { subdomain } = useParams();
|
||||
const [form] = Form.useForm();
|
||||
const { data: redeemDetails, isLoading } = useGetRedeemDetailsQuery(
|
||||
voucherId || "",
|
||||
{
|
||||
orderID: orderId || "",
|
||||
restaurantID: localStorage.getItem("restaurantID") || "",
|
||||
},
|
||||
{
|
||||
skip: !orderId,
|
||||
// return it t0 60000 after finish testing
|
||||
pollingInterval: 10000,
|
||||
refetchOnMountOrArgChange: true,
|
||||
skip: !voucherId,
|
||||
},
|
||||
);
|
||||
|
||||
// Get restaurant subdomain for refetching
|
||||
const restaurantSubdomain = restaurant?.subdomain;
|
||||
const { refetch: refetchRestaurantDetails } = useGetRestaurantDetailsQuery(
|
||||
restaurantSubdomain || "",
|
||||
{
|
||||
skip: !restaurantSubdomain,
|
||||
},
|
||||
const handleCheckout = () => {
|
||||
navigate(`/${subdomain}/menu?orderType=${OrderType.Redeem}`);
|
||||
};
|
||||
|
||||
const handleCopyVoucherCode = async () => {
|
||||
const voucherCode = redeemDetails?.gift?.voucher_code;
|
||||
if (voucherCode) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(voucherCode);
|
||||
message.success(
|
||||
t("redeem.voucherCodeCopied") || "Voucher code copied!",
|
||||
);
|
||||
|
||||
// Reset refetch flag when orderId changes
|
||||
useEffect(() => {
|
||||
hasRefetchedRef.current = false;
|
||||
}, [orderId]);
|
||||
|
||||
// Refetch restaurant details when order status has alias "closed"
|
||||
useEffect(() => {
|
||||
if (orderDetails?.status && !hasRefetchedRef.current) {
|
||||
const hasClosedStatus = orderDetails.status.some(
|
||||
(status) => status?.alias === "closed",
|
||||
);
|
||||
|
||||
if (hasClosedStatus && restaurantSubdomain) {
|
||||
refetchRestaurantDetails();
|
||||
hasRefetchedRef.current = true;
|
||||
} catch (error) {
|
||||
message.error(t("redeem.copyFailed") || "Failed to copy voucher code");
|
||||
}
|
||||
}
|
||||
}, [orderDetails?.status, restaurantSubdomain, refetchRestaurantDetails]);
|
||||
};
|
||||
|
||||
if (isLoading) return <Loader />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProHeader>{t("order.title")}</ProHeader>
|
||||
<Layout>
|
||||
<ProHeader>{t("redeem.title")}</ProHeader>
|
||||
<Layout.Content className={styles.redeemContainer}>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "92vh",
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
overflow: "auto",
|
||||
scrollbarWidth: "none",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
margin: "32px 28px 21px 28px",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Card className={styles.orderCard}>
|
||||
<ProText
|
||||
style={{ fontSize: 16, fontWeight: 600, color: "#333333" }}
|
||||
>
|
||||
{t("redeem.hiX", { name: redeemDetails?.gift?.recipient_name })}
|
||||
</ProText>
|
||||
<ProText
|
||||
style={{ fontSize: 14, fontWeight: 400, color: "#95949C" }}
|
||||
>
|
||||
{t("redeem.youHaveReceivedAGiftCarFromX", {
|
||||
name: redeemDetails?.gift?.sender_name,
|
||||
})}
|
||||
</ProText>
|
||||
</div>
|
||||
|
||||
{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
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: "1rem",
|
||||
backgroundColor: "rgba(255, 183, 0, 0.08)",
|
||||
borderRadius: "12px",
|
||||
padding: 16,
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
margin: "18px 28px 26px 28px",
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
shape="circle"
|
||||
<ProText
|
||||
style={{
|
||||
backgroundColor: "rgba(255, 183, 0, 0.08)",
|
||||
fontWeight: 400,
|
||||
fontStyle: "Regular",
|
||||
fontSize: 14,
|
||||
lineHeight: "140%",
|
||||
letterSpacing: "0%",
|
||||
textAlign: "center",
|
||||
color: "#95949C",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={orderDetails?.restaurant_iimage}
|
||||
className={styles.profileImage}
|
||||
width={50}
|
||||
height={50}
|
||||
preview={false}
|
||||
/>
|
||||
</Button>
|
||||
<div>
|
||||
<ProText style={{ fontSize: "1rem" }}>
|
||||
{t("order.yourOrderFromFascanoRestaurant")}
|
||||
{redeemDetails?.gift?.message || t("redeem.description")}
|
||||
</ProText>
|
||||
<br />
|
||||
<ProText type="secondary">
|
||||
<LocationIcon className={styles.locationIcon} />{" "}
|
||||
{isRTL ? orderDetails?.restaurantAR : orderDetails?.restaurant}
|
||||
</ProText>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OrderDishIcon className={styles.orderDishIcon} />
|
||||
|
||||
<div>
|
||||
<ProTitle
|
||||
level={5}
|
||||
<ProText
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "18px",
|
||||
marginBottom: "0.75rem",
|
||||
fontStyle: "SemiBold",
|
||||
fontSize: 14,
|
||||
lineHeight: "140%",
|
||||
letterSpacing: "0%",
|
||||
textAlign: "center",
|
||||
color: "#333333",
|
||||
}}
|
||||
>
|
||||
{t("order.inProgressOrder")} (1)
|
||||
</ProTitle>
|
||||
<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",
|
||||
)}
|
||||
{redeemDetails?.gift?.sender_name}
|
||||
</ProText>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: "12px 0" }} />
|
||||
|
||||
<Stepper statuses={orderDetails?.status} />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Ads2 />
|
||||
|
||||
<ProInputCard
|
||||
title={
|
||||
<div style={{ marginBottom: 7 }}>
|
||||
<ProText style={{ fontSize: "1rem" }}>
|
||||
{t("order.yourOrderFrom")}
|
||||
</ProText>
|
||||
<br />
|
||||
<ProText type="secondary">
|
||||
<LocationIcon className={styles.locationIcon} />{" "}
|
||||
{t("order.muscat")}
|
||||
</ProText>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
|
||||
>
|
||||
{orderDetails?.orderItems.map((item, index) => (
|
||||
<div key={item.id}>
|
||||
<div
|
||||
<div>
|
||||
<Card
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
gap: "1rem",
|
||||
borderRadius: 0,
|
||||
borderTopRightRadius: 16,
|
||||
borderTopLeftRadius: 16,
|
||||
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>
|
||||
<QRCode value={redeemDetails?.gift?.gr_url || "-"} />
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", gap: 12 }}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
shape="circle"
|
||||
style={{
|
||||
backgroundColor: "rgba(255, 183, 0, 0.08)",
|
||||
height: 40,
|
||||
borderRadius: 888,
|
||||
gap: 16,
|
||||
opacity: 1,
|
||||
borderWidth: 1,
|
||||
backgroundColor: "var(--background)",
|
||||
}}
|
||||
icon={<CopyIcon className={styles.copyIcon} />}
|
||||
iconPlacement="end"
|
||||
onClick={handleCopyVoucherCode}
|
||||
>
|
||||
{index + 1}X
|
||||
{redeemDetails?.gift?.voucher_code}
|
||||
</Button>
|
||||
<div>
|
||||
<ProText
|
||||
style={{ fontSize: "1rem", position: "relative", top: 8 }}
|
||||
>
|
||||
{item.name}
|
||||
</ProText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ProInputCard>
|
||||
|
||||
<PaymentDetails order={orderDetails?.order} />
|
||||
|
||||
<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={{
|
||||
borderRadius: "50%",
|
||||
objectFit: "cover",
|
||||
position: "relative",
|
||||
top: -4,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ProTitle
|
||||
level={5}
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontStyle: "Regular",
|
||||
fontSize: 14,
|
||||
lineHeight: "140%",
|
||||
letterSpacing: "0%",
|
||||
color: "#95949C",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{isRTL ? restaurant?.nameAR : restaurant?.restautantName}
|
||||
</ProTitle>
|
||||
|
||||
{isRTL ? (
|
||||
<BackIcon className={styles.serviceIcon} />
|
||||
) : (
|
||||
<NextIcon className={styles.serviceIcon} />
|
||||
)}
|
||||
{t("redeem.useThisCodeIfScanningNotPossible")}
|
||||
</ProText>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* <RateBottomSheet /> */}
|
||||
|
||||
<CancelOrderBottomSheet />
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 44,
|
||||
opacity: 1,
|
||||
borderBottomRightRadius: 16,
|
||||
borderBottomLeftRadius: 16,
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
gap: 10,
|
||||
borderTopWidth: 1,
|
||||
background: "#FFF9E6",
|
||||
borderTop: "1px solid #FFEDB0",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<ProText
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
fontStyle: "Medium",
|
||||
lineHeight: "140%",
|
||||
letterSpacing: "0%",
|
||||
textAlign: "center",
|
||||
color: "#E8B400",
|
||||
}}
|
||||
>
|
||||
Active - Expires in 12 days
|
||||
</ProText>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<Layout.Footer className={styles.checkoutButtonContainer}>
|
||||
<Button
|
||||
type="primary"
|
||||
shape="round"
|
||||
className={styles.checkoutButton}
|
||||
onClick={handleCheckout}
|
||||
>
|
||||
{t("redeem.redeemNow")}
|
||||
</Button>
|
||||
</Layout.Footer>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,247 +1,81 @@
|
||||
.orderSummary :global(.ant-card-body) {
|
||||
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 {
|
||||
.carouselContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.orderCard :global(.ant-card-body) {
|
||||
.cardWrapper {
|
||||
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;
|
||||
font-size: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Enhanced responsive summary rows */
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.summaryRow {
|
||||
padding: 12px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.cardImage {
|
||||
width: 205px;
|
||||
height: 134px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.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 {
|
||||
.arrowButton {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
font-size: 16px;
|
||||
justify-content: center;
|
||||
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;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
margin-top: 16px;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
gap: 1rem;
|
||||
background-color: var(--secondary-background);
|
||||
box-shadow: none;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .orderSummary {
|
||||
background-color: #181818 !important;
|
||||
border-color: #363636 !important;
|
||||
/* Dark theme styles for checkout */
|
||||
:global(.darkApp) .checkoutButtonContainer {
|
||||
background-color: #000000 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .orderSummary:hover {
|
||||
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 {
|
||||
.checkoutButton {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.copyIcon {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.redeemContainer {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 12px 18px !important;
|
||||
row-gap: 10px;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 50px;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
overflow: auto;
|
||||
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
94
src/pages/redeem/types.ts
Normal 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
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TABLES_URL,
|
||||
USER_DETAILS_URL,
|
||||
EGIFT_CARDS_URL,
|
||||
REDEEM_DETAILS_URL,
|
||||
} from "utils/constants";
|
||||
|
||||
import { OrderDetails } from "pages/checkout/hooks/types";
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
} from "utils/types/appTypes";
|
||||
import { baseApi } from "./apiSlice";
|
||||
import { EGiftCard } from "pages/EGiftCards/type";
|
||||
import { RedeemResponse } from "pages/redeem/types";
|
||||
|
||||
export const branchApi = baseApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
@@ -171,6 +173,15 @@ export const branchApi = baseApi.injectEndpoints({
|
||||
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 {
|
||||
@@ -185,4 +196,5 @@ export const {
|
||||
useRateOrderMutation,
|
||||
useGetUserDetailsQuery,
|
||||
useGetEGiftCardsQuery,
|
||||
useGetRedeemDetailsQuery,
|
||||
} = branchApi;
|
||||
|
||||
@@ -15,7 +15,6 @@ import OrderDetails from "pages/order/components/OrderDetails";
|
||||
import OrderPage from "pages/order/page";
|
||||
import OrdersPage from "pages/orders/page";
|
||||
import OtpPage from "pages/otp/page";
|
||||
import PayPage from "pages/pay/page";
|
||||
import ProductDetailPage from "pages/product/page";
|
||||
import RedeemPage from "pages/redeem/page";
|
||||
import RestaurantPage from "pages/restaurant/page";
|
||||
|
||||
@@ -109,3 +109,4 @@ export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`;
|
||||
export const PAYMENT_CONFIRMATION_URL = `https://menu.fascano.com/payment/confirmation`;
|
||||
export const DISCOUNT_URL = `${BASE_URL}getDiscount`;
|
||||
export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`;
|
||||
export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`;
|
||||
Reference in New Issue
Block a user