From 346dee1392b29ad78e5e90108680f8a3f0f9adb6 Mon Sep 17 00:00:00 2001 From: Mohammed Al-yaseen Date: Mon, 15 Dec 2025 00:00:24 +0300 Subject: [PATCH] working on new button in menu --- public/action.svg | 3 + .../AddToCartButton.module.css | 18 +- .../AddToCartButton/AddToCartButton.tsx | 174 ++++++++++-- .../menu/components/MenuList/ProductCard.tsx | 44 +-- src/pages/redeem/page.tsx | 263 ++++++++++++++++++ src/pages/redeem/redeem.module.css | 247 ++++++++++++++++ src/routes/routes.tsx | 9 +- 7 files changed, 711 insertions(+), 47 deletions(-) create mode 100644 public/action.svg create mode 100644 src/pages/redeem/page.tsx create mode 100644 src/pages/redeem/redeem.module.css 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 ( - + return isInCart ? ( + <> +
+ + + +
+ + ) : ( + <> +
+
+ ); } 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) => ( +
+
+ +
+ + {item.name} + +
+
+
+ ))} +
+ + + + + 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: ,