diff --git a/public/action.svg b/public/action.svg
new file mode 100644
index 0000000..d3e025e
--- /dev/null
+++ b/public/action.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/pages/menu/components/AddToCartButton/AddToCartButton.module.css b/src/pages/menu/components/AddToCartButton/AddToCartButton.module.css
index aeea8f2..8a7fa1c 100644
--- a/src/pages/menu/components/AddToCartButton/AddToCartButton.module.css
+++ b/src/pages/menu/components/AddToCartButton/AddToCartButton.module.css
@@ -5,19 +5,21 @@
.addButton {
position: absolute;
- bottom: -10px;
z-index: 1;
font-weight: 600;
border: 0;
- color: #FFF;
- font-size: 1rem; width: 82px;
- height: 32px;
+ color: #fff;
+ font-size: 1rem;
}
-.addButtonRTL {
- right: 5%;
+.actionRect {
+ fill: var(--background) !important;
}
-.addButtonLTR {
- left: 5%;
+.addButton svg rect {
+ fill: var(--background) !important;
+}
+
+:global(.darkApp) .addButton rect {
+ fill: var(--background) !important;
}
diff --git a/src/pages/menu/components/AddToCartButton/AddToCartButton.tsx b/src/pages/menu/components/AddToCartButton/AddToCartButton.tsx
index 7aa11b8..352eb0f 100644
--- a/src/pages/menu/components/AddToCartButton/AddToCartButton.tsx
+++ b/src/pages/menu/components/AddToCartButton/AddToCartButton.tsx
@@ -1,42 +1,180 @@
-import { PlusOutlined } from "@ant-design/icons";
+import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
import { Button, message } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
-import { useAppSelector } from "redux/hooks.ts";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { colors } from "ThemeConstants.ts";
import styles from "./AddToCartButton.module.css";
+import { useAppSelector, useAppDispatch } from "redux/hooks";
+import { Product } from "utils/types/appTypes";
+import NextIcon from "components/Icons/NextIcon";
+import { addItem } from "features/order/orderSlice";
-export function AddToCartButton() {
- const { isRTL } = useAppSelector((state) => state.locale);
+export function AddToCartButton({ item }: { item: Product }) {
const { t } = useTranslation();
const { subdomain } = useParams();
const navigate = useNavigate();
+ const dispatch = useAppDispatch();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain, {
skip: !subdomain,
});
+ const { items } = useAppSelector((state) => state.order);
+
+ // Check if product is in cart
+ const isInCart =
+ items
+ .filter((i) => i.id === item.id)
+ .reduce((total, item) => total + item.quantity, 0) > 0;
+
+ // Check if item has extras, variants, or groups
+ const hasOptions =
+ (item.extras && item.extras.length > 0) ||
+ (item.variants && item.variants.length > 0) ||
+ (item.theExtrasGroups && item.theExtrasGroups.length > 0);
const handleClick = () => {
if (restaurant && !restaurant.isOpened) {
message.warning(t("menu.restaurantIsClosed"));
return;
}
- navigate(`/${subdomain}/menu`);
+
+ // If item has options, navigate to product details page
+ if (hasOptions) {
+ navigate(`/${subdomain}/product/${item.id}`);
+ return;
+ }
+
+ // If no options, add item directly to cart
+ console.log("hasOptions", hasOptions);
+ if (!hasOptions) {
+ dispatch(
+ addItem({
+ item: {
+ id: Number(item.id),
+ name: item.name,
+ price: item.price,
+ image: item.image,
+ description: item.description,
+ variant: "None",
+ isHasLoyalty: item.isHasLoyalty,
+ no_of_stamps_give: item.no_of_stamps_give,
+ },
+ quantity: 1,
+ }),
+ );
+ }
};
- return (
- }
- size="small"
- onClick={handleClick}
- className={`${styles.addButton} ${isRTL ? styles.addButtonRTL : styles.addButtonLTR}`}
- style={{ backgroundColor: colors.primary }}
- >
- {t("common.add")}
-
+ return isInCart ? (
+ <>
+
+
+ }
+ size="small"
+ onClick={handleClick}
+ className={styles.addButton}
+ style={{
+ backgroundColor: "white",
+ width: 28,
+ height: 28,
+ position: "absolute",
+ bottom: 8,
+ right: 64,
+ minWidth: 28,
+ }}
+ />
+ }
+ size="small"
+ onClick={handleClick}
+ className={styles.addButton}
+ style={{
+ backgroundColor: colors.primary,
+ width: 28,
+ height: 28,
+ position: "absolute",
+ bottom: 8,
+ right: 10,
+ minWidth: 28,
+ }}
+ />
+
+ >
+ ) : (
+ <>
+
+
+ ) : (
+
+ )
+ }
+ size="small"
+ onClick={handleClick}
+ className={styles.addButton}
+ style={{
+ backgroundColor: colors.primary,
+ width: 36,
+ height: 36,
+ position: "absolute",
+ bottom: 6,
+ right: 6,
+ }}
+ />
+
+ >
);
}
diff --git a/src/pages/menu/components/MenuList/ProductCard.tsx b/src/pages/menu/components/MenuList/ProductCard.tsx
index 67351de..7e89ef3 100644
--- a/src/pages/menu/components/MenuList/ProductCard.tsx
+++ b/src/pages/menu/components/MenuList/ProductCard.tsx
@@ -11,6 +11,7 @@ import { useAppSelector } from "redux/hooks.ts";
import { useParams, useNavigate } from "react-router-dom";
import { ProductPreviewDialog } from "pages/menu/components/ProductPreviewDialog";
import { useState } from "react";
+import { AddToCartButton } from "../AddToCartButton/AddToCartButton";
type Props = {
item: Product;
@@ -34,10 +35,12 @@ export default function ProductCard({ item }: Props) {
localStorage.setItem("productId", item.id.toString());
if (isDesktop) {
setIsDialogOpen(true);
- } else {
- navigate(`/${subdomain}/product/${item.id}`);
}
+ // else {
+ // navigate(`/${subdomain}/product/${item.id}`);
+ // }
};
+
return (
<>
-
-
@@ -184,14 +191,13 @@ export default function ProductCard({ item }: Props) {
? styles.popularMenuItemImageTablet
: styles.popularMenuItemImageDesktop
}`}
- width={92}
- height={92}
+ width={90}
+ height={90}
/>
-
- {/* */}
+
diff --git a/src/pages/redeem/page.tsx b/src/pages/redeem/page.tsx
new file mode 100644
index 0000000..673af65
--- /dev/null
+++ b/src/pages/redeem/page.tsx
@@ -0,0 +1,263 @@
+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 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 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";
+
+export default function RedeemPage() {
+ const { t } = useTranslation();
+ const { orderId } = useParams();
+ const { isRTL } = useAppSelector((state) => state.locale);
+ const { restaurant } = useAppSelector((state) => state.order);
+ const navigate = useNavigate();
+ const hasRefetchedRef = useRef(false);
+
+ const { data: orderDetails } = useGetOrderDetailsQuery(
+ {
+ orderID: orderId || "",
+ restaurantID: localStorage.getItem("restaurantID") || "",
+ },
+ {
+ skip: !orderId,
+ // return it t0 60000 after finish testing
+ pollingInterval: 10000,
+ refetchOnMountOrArgChange: true,
+ },
+ );
+
+ // Get restaurant subdomain for refetching
+ const restaurantSubdomain = restaurant?.subdomain;
+ const { refetch: refetchRestaurantDetails } = useGetRestaurantDetailsQuery(
+ restaurantSubdomain || "",
+ {
+ skip: !restaurantSubdomain,
+ },
+ );
+
+ // 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;
+ }
+ }
+ }, [orderDetails?.status, restaurantSubdomain, refetchRestaurantDetails]);
+
+ return (
+ <>
+ {t("order.title")}
+
+
+
+
+
+
+ {t("order.yourOrderFromFascanoRestaurant")}
+
+
+
+ {" "}
+ {isRTL ? orderDetails?.restaurantAR : orderDetails?.restaurant}
+
+
+
+
+
+
+
+
+ {t("order.inProgressOrder")} (1)
+
+
+
+
+ #{orderDetails?.order.id}
+
+
+
+ ordered :- Today -{" "}
+ {dayjs(orderDetails?.status[0]?.pivot?.created_at).format(
+ "h:mm A",
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("order.yourOrderFrom")}
+
+
+
+ {" "}
+ {t("order.muscat")}
+
+
+ }
+ >
+
+ {orderDetails?.orderItems.map((item, index) => (
+
+ ))}
+
+
+
+
+
+ navigate(`/${restaurant?.subdomain}`)}
+ >
+
+
+
+
+ {isRTL ? restaurant?.nameAR : restaurant?.restautantName}
+
+
+ {isRTL ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/redeem/redeem.module.css b/src/pages/redeem/redeem.module.css
new file mode 100644
index 0000000..78b105e
--- /dev/null
+++ b/src/pages/redeem/redeem.module.css
@@ -0,0 +1,247 @@
+.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 {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+}
+
+.orderCard :global(.ant-card-body) {
+ 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;
+}
+
+/* Enhanced responsive summary rows */
+@media (min-width: 769px) and (max-width: 1024px) {
+ .summaryRow {
+ padding: 12px 0;
+ font-size: 16px;
+ }
+}
+
+@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 {
+ 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;
+}
+
+[data-theme="dark"] .orderSummary {
+ background-color: #181818 !important;
+ border-color: #363636 !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 {
+ width: 100%;
+ height: 48px;
+ display: flex;
+ justify-content: flex-start;
+ padding: 12px 18px !important;
+ row-gap: 10px;
+ transition: all 0.3s ease;
+ border-radius: 50px;
+}
+
+.backToHomePage :global(.ant-card-body) {
+ padding: 0px !important;
+ text-align: start;
+ width: 100%;
+}
+
+.nextIcon {
+ width: 24px;
+ height: 24px;
+}
+
+.backIcon {
+ width: 24px;
+ height: 24px;
+}
+
+
diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx
index 15fe19b..a62d2ff 100644
--- a/src/routes/routes.tsx
+++ b/src/routes/routes.tsx
@@ -14,6 +14,7 @@ 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";
import SearchPage from "pages/search/page";
import SplitBillPage from "pages/split-bill/page";
@@ -146,10 +147,14 @@ export const router = createHashRouter([
path: "pay",
element: } />,
errorElement: ,
- }
+ },
+ {
+ path: "gift/redeem/:voucherId",
+ element: } />,
+ errorElement: ,
+ },
],
},
-
{
path: "errors",
errorElement: ,