menu: on click on prduct open details as BS, also add redirect button to detials page if user click on redirect button
This commit is contained in:
@@ -253,6 +253,8 @@ export function ProBottomSheet({
|
|||||||
border: `1px solid ${themeName === "dark" ? "#424242" : "transparent"}`,
|
border: `1px solid ${themeName === "dark" ? "#424242" : "transparent"}`,
|
||||||
transition: "all 0.3s ease",
|
transition: "all 0.3s ease",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
const titleStyle: React.CSSProperties = {
|
const titleStyle: React.CSSProperties = {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { useAppSelector } from "redux/hooks";
|
|||||||
import { ProBlack2 } from "ThemeConstants";
|
import { ProBlack2 } from "ThemeConstants";
|
||||||
import ProTitle from "../ProTitle";
|
import ProTitle from "../ProTitle";
|
||||||
import useBreakPoint from "hooks/useBreakPoint";
|
import useBreakPoint from "hooks/useBreakPoint";
|
||||||
|
import NextIcon from "components/Icons/NextIcon";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
interface ProHeaderProps {
|
interface ProHeaderProps {
|
||||||
@@ -41,7 +43,6 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
|
|||||||
top: 0,
|
top: 0,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
flexDirection: isRTL ? "row-reverse" : "row",
|
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center", // This centers vertically
|
alignItems: "center", // This centers vertically
|
||||||
backgroundColor: themeName === "light" ? "white" : ProBlack2,
|
backgroundColor: themeName === "light" ? "white" : ProBlack2,
|
||||||
@@ -52,10 +53,9 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<BackIcon />}
|
icon={isRTL ? <NextIcon /> : <BackIcon />}
|
||||||
style={{
|
style={{
|
||||||
flex: 0, // Don't allow this to grow
|
flex: 0, // Don't allow this to grow
|
||||||
marginRight: "auto", // Push it to the start
|
|
||||||
}}
|
}}
|
||||||
onClick={handleBack}
|
onClick={handleBack}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
import { PlusOutlined } from "@ant-design/icons";
|
import { PlusOutlined } from "@ant-design/icons";
|
||||||
import { Card, Divider, Form, Space, Layout } from "antd";
|
import { Card, Divider, Space, Layout } from "antd";
|
||||||
import ArabicPrice from "components/ArabicPrice";
|
import ArabicPrice from "components/ArabicPrice";
|
||||||
import CartActionsButtons from "components/CartActionsButtons/CartActionsButtons.tsx";
|
import CartActionsButtons from "components/CartActionsButtons/CartActionsButtons.tsx";
|
||||||
import ImageWithFallback from "components/ImageWithFallback";
|
import ImageWithFallback from "components/ImageWithFallback";
|
||||||
import ProHeader from "components/ProHeader/ProHeader.tsx";
|
import ProHeader from "components/ProHeader/ProHeader.tsx";
|
||||||
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
|
|
||||||
import ProRatioGroups from "components/ProRatioGroups/ProRatioGroups.tsx";
|
|
||||||
import ProText from "components/ProText.tsx";
|
import ProText from "components/ProText.tsx";
|
||||||
import ProTitle from "components/ProTitle.tsx";
|
import ProTitle from "components/ProTitle.tsx";
|
||||||
import {
|
import { selectCart } from "features/order/orderSlice.ts";
|
||||||
selectCart,
|
|
||||||
updateCollectionMethod,
|
|
||||||
} from "features/order/orderSlice.ts";
|
|
||||||
import styles from "pages/cart/cart.module.css";
|
import styles from "pages/cart/cart.module.css";
|
||||||
import YouMightAlsoLike from "pages/cart/components/youMayLike/YouMightAlsoLike.tsx";
|
import YouMightAlsoLike from "pages/cart/components/youMayLike/YouMightAlsoLike.tsx";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { colors } from "ThemeConstants.ts";
|
import { colors } from "ThemeConstants.ts";
|
||||||
|
|
||||||
import OrderSummary from "components/OrderSummary/OrderSummary.tsx";
|
|
||||||
import { useAppSelector } from "redux/hooks.ts";
|
import { useAppSelector } from "redux/hooks.ts";
|
||||||
|
|
||||||
import { FormInstance } from "antd";
|
import { FormInstance } from "antd";
|
||||||
@@ -25,7 +19,6 @@ import useBreakPoint from "hooks/useBreakPoint.ts";
|
|||||||
import CarPlateCard from "pages/cart/components/CarPlateCard.tsx";
|
import CarPlateCard from "pages/cart/components/CarPlateCard.tsx";
|
||||||
import CartFooter from "pages/cart/components/cartFooter/CartFooter.tsx";
|
import CartFooter from "pages/cart/components/cartFooter/CartFooter.tsx";
|
||||||
import CouponCard from "pages/cart/components/CouponCard.tsx";
|
import CouponCard from "pages/cart/components/CouponCard.tsx";
|
||||||
import RewardWaiterCard from "pages/cart/components/RewardWaiterCard.tsx";
|
|
||||||
import SpecialRequestCard from "pages/cart/components/specialRequest/SpecialRequestCard.tsx";
|
import SpecialRequestCard from "pages/cart/components/specialRequest/SpecialRequestCard.tsx";
|
||||||
import TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
|
import TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
|
||||||
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx";
|
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx";
|
||||||
@@ -41,7 +34,7 @@ export default function CartMobileTabletLayout({
|
|||||||
form,
|
form,
|
||||||
}: CartMobileTabletLayoutProps) {
|
}: CartMobileTabletLayoutProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { items, collectionMethod, orderType } = useAppSelector(selectCart);
|
const { items, orderType } = useAppSelector(selectCart);
|
||||||
const { isRTL } = useAppSelector((state) => state.locale);
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
const { subdomain } = useParams();
|
const { subdomain } = useParams();
|
||||||
const { pickup_type } = useAppSelector((state) => state.order.restaurant);
|
const { pickup_type } = useAppSelector((state) => state.order.restaurant);
|
||||||
@@ -52,8 +45,8 @@ export default function CartMobileTabletLayout({
|
|||||||
const getMenuItemImageStyle = () => {
|
const getMenuItemImageStyle = () => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return {
|
return {
|
||||||
width: 90,
|
width: 115,
|
||||||
height: 80,
|
height: 96,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -73,6 +66,10 @@ export default function CartMobileTabletLayout({
|
|||||||
size={isMobile ? "middle" : isTablet ? "large" : "large"}
|
size={isMobile ? "middle" : isTablet ? "large" : "large"}
|
||||||
style={{ width: "100%", gap: 16 }}
|
style={{ width: "100%", gap: 16 }}
|
||||||
>
|
>
|
||||||
|
{/* Table Number */}
|
||||||
|
{(orderType === OrderType.DineIn ||
|
||||||
|
orderType === OrderType.ToOffice) && <TableNumberCard />}
|
||||||
|
|
||||||
<div className={`${styles.cartContent} ${getResponsiveClass()}`}>
|
<div className={`${styles.cartContent} ${getResponsiveClass()}`}>
|
||||||
<div className={styles.cartItems}>
|
<div className={styles.cartItems}>
|
||||||
<Card hoverable className={styles.cartItem}>
|
<Card hoverable className={styles.cartItem}>
|
||||||
@@ -170,7 +167,7 @@ export default function CartMobileTabletLayout({
|
|||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
lineClamp: 1,
|
lineClamp: 1,
|
||||||
padding: isMobile ? 3 : isTablet ? 8 : 10,
|
padding: isMobile ? "3px 0" : isTablet ? 8 : 10,
|
||||||
fontSize: isMobile ? 14 : isTablet ? 18 : 20,
|
fontSize: isMobile ? 14 : isTablet ? 18 : 20,
|
||||||
display: "-webkit-box",
|
display: "-webkit-box",
|
||||||
WebkitBoxOrient: "vertical",
|
WebkitBoxOrient: "vertical",
|
||||||
@@ -187,7 +184,7 @@ export default function CartMobileTabletLayout({
|
|||||||
: "6.8em",
|
: "6.8em",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
letterSpacing: "0.01em",
|
letterSpacing: "0.01em",
|
||||||
width: "65%",
|
width: "55%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.description}
|
{item.description}
|
||||||
@@ -196,7 +193,7 @@ export default function CartMobileTabletLayout({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: index !== items.length - 1 ? 16 : 6,
|
bottom: index !== items.length - 1 ? 16 : 3,
|
||||||
[isRTL ? "right" : "left"]: 0,
|
[isRTL ? "right" : "left"]: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -218,7 +215,9 @@ export default function CartMobileTabletLayout({
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
position: "absolute",
|
||||||
|
right: 3,
|
||||||
|
bottom: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CartActionsButtons item={item} />
|
<CartActionsButtons item={item} />
|
||||||
@@ -239,8 +238,6 @@ export default function CartMobileTabletLayout({
|
|||||||
|
|
||||||
<SpecialRequestCard />
|
<SpecialRequestCard />
|
||||||
|
|
||||||
<CouponCard />
|
|
||||||
|
|
||||||
{/* Car Plate*/}
|
{/* Car Plate*/}
|
||||||
{((orderType === OrderType.Pickup && pickup_type === "car") ||
|
{((orderType === OrderType.Pickup && pickup_type === "car") ||
|
||||||
orderType === OrderType.ScheduledOrder) && <CarPlateCard />}
|
orderType === OrderType.ScheduledOrder) && <CarPlateCard />}
|
||||||
@@ -250,7 +247,7 @@ export default function CartMobileTabletLayout({
|
|||||||
orderType === OrderType.ScheduledOrder) && <TimeEstimateCard />}
|
orderType === OrderType.ScheduledOrder) && <TimeEstimateCard />}
|
||||||
|
|
||||||
{/* Collection Method */}
|
{/* Collection Method */}
|
||||||
{orderType === OrderType.Pickup && (
|
{/* {orderType === OrderType.Pickup && (
|
||||||
<ProInputCard title={t("cart.collectionMethod")}>
|
<ProInputCard title={t("cart.collectionMethod")}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="collectionMethod"
|
name="collectionMethod"
|
||||||
@@ -282,17 +279,13 @@ export default function CartMobileTabletLayout({
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</ProInputCard>
|
</ProInputCard>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{/* Reward Your Waiter */}
|
{/* Reward Your Waiter */}
|
||||||
<RewardWaiterCard />
|
{/* <RewardWaiterCard /> */}
|
||||||
|
|
||||||
{/* Table Number */}
|
|
||||||
{(orderType === OrderType.DineIn ||
|
|
||||||
orderType === OrderType.ToOffice) && <TableNumberCard />}
|
|
||||||
|
|
||||||
{/* Invoice Summary */}
|
{/* Invoice Summary */}
|
||||||
<OrderSummary />
|
{/* <OrderSummary /> */}
|
||||||
</Space>
|
</Space>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
<CartFooter form={form} />
|
<CartFooter form={form} />
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
overflow-x: hidden;
|
overflow-x: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.darkApp) .youMightAlsoLikeContainer path {
|
:global(.darkApp) .youMightAlsoLikeContainer path {
|
||||||
@@ -56,7 +57,7 @@
|
|||||||
outline-offset: 4px;
|
outline-offset: 4px;
|
||||||
}
|
}
|
||||||
.youMightAlsoLikeContainer {
|
.youMightAlsoLikeContainer {
|
||||||
height: 130px !important;
|
height: 150px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemDescriptionIconsContainer {
|
.itemDescriptionIconsContainer {
|
||||||
|
|||||||
@@ -191,19 +191,19 @@ export default function YouMightAlsoLike() {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
width: isMobile ? "95px" : isTablet ? "120px" : "140px",
|
width: isMobile ? "120px" : isTablet ? "120px" : "140px",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: isMobile ? 18 : 24,
|
width: isMobile ? 28 : 24,
|
||||||
height: isMobile ? 18 : 24,
|
height: isMobile ? 28 : 24,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
top: isMobile ? 50 : isTablet ? 60 : 80,
|
top: isMobile ? 65 : isTablet ? 60 : 80,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
[isRTL ? "left" : "right"]: isMobile ? 15 : 20,
|
[isRTL ? "left" : "right"]: isMobile ? 10 : 20,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
@@ -234,8 +234,8 @@ export default function YouMightAlsoLike() {
|
|||||||
? styles.popularMenuItemImageTablet
|
? styles.popularMenuItemImageTablet
|
||||||
: styles.popularMenuItemImageDesktop
|
: styles.popularMenuItemImageDesktop
|
||||||
}`}
|
}`}
|
||||||
width={isMobile ? 73 : isTablet ? 90 : 110}
|
width={isMobile ? 106 : isTablet ? 90 : 110}
|
||||||
height={isMobile ? 73 : isTablet ? 90 : 110}
|
height={isMobile ? 96 : isTablet ? 90 : 110}
|
||||||
fallbackSrc={default_image}
|
fallbackSrc={default_image}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import InputCard from "components/InputCard";
|
|||||||
import OrderSummary from "components/OrderSummary/OrderSummary";
|
import OrderSummary from "components/OrderSummary/OrderSummary";
|
||||||
import PaymentMethods from "components/PaymentMethods/PaymentMethods";
|
import PaymentMethods from "components/PaymentMethods/PaymentMethods";
|
||||||
import ProHeader from "components/ProHeader/ProHeader";
|
import ProHeader from "components/ProHeader/ProHeader";
|
||||||
import { selectCart } from "features/order/orderSlice";
|
import { selectCart, updateCollectionMethod } from "features/order/orderSlice";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "redux/hooks";
|
import { useAppSelector } from "redux/hooks";
|
||||||
import styles from "../address/address.module.css";
|
import styles from "../address/address.module.css";
|
||||||
@@ -13,11 +13,16 @@ import CheckoutButton from "./components/CheckoutButton";
|
|||||||
import { GiftCard } from "./components/GiftCard";
|
import { GiftCard } from "./components/GiftCard";
|
||||||
import PhoneCard from "./components/phoneCard";
|
import PhoneCard from "./components/phoneCard";
|
||||||
import { OrderType } from "./hooks/types";
|
import { OrderType } from "./hooks/types";
|
||||||
|
import RewardWaiterCard from "pages/cart/components/RewardWaiterCard";
|
||||||
|
import ProInputCard from "components/ProInputCard/ProInputCard";
|
||||||
|
import ProRatioGroups from "components/ProRatioGroups/ProRatioGroups";
|
||||||
|
import CouponCard from "pages/cart/components/CouponCard";
|
||||||
|
|
||||||
export default function CheckoutPage() {
|
export default function CheckoutPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { phone, order, orderType } = useAppSelector(selectCart);
|
const { phone, order, orderType, collectionMethod } =
|
||||||
|
useAppSelector(selectCart);
|
||||||
const { token } = useAppSelector((state) => state.auth);
|
const { token } = useAppSelector((state) => state.auth);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -53,8 +58,47 @@ export default function CheckoutPage() {
|
|||||||
{/* <RoomDetails />
|
{/* <RoomDetails />
|
||||||
<OfficeDetails /> */}
|
<OfficeDetails /> */}
|
||||||
{/* <GiftDetails /> */}
|
{/* <GiftDetails /> */}
|
||||||
<BriefMenu />
|
{/* <BriefMenu /> */}
|
||||||
<PaymentMethods />
|
<PaymentMethods />
|
||||||
|
<CouponCard />
|
||||||
|
|
||||||
|
{/* Collection Method */}
|
||||||
|
{orderType === OrderType.Pickup && (
|
||||||
|
<ProInputCard title={t("cart.collectionMethod")}>
|
||||||
|
<Form.Item
|
||||||
|
name="collectionMethod"
|
||||||
|
required
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("cart.pleaseSelectCollectionMethod"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<ProRatioGroups
|
||||||
|
options={[
|
||||||
|
{ label: t("cart.Cash"), value: "cod", price: "" },
|
||||||
|
{
|
||||||
|
label: t("cart.e-payment"),
|
||||||
|
value: "paymentgateway",
|
||||||
|
price: "",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={collectionMethod}
|
||||||
|
onRatioClick={(value) => {
|
||||||
|
if (value === "cod") {
|
||||||
|
updateCollectionMethod(value);
|
||||||
|
} else {
|
||||||
|
updateCollectionMethod(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</ProInputCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Reward Your Waiter */}
|
||||||
|
<RewardWaiterCard />
|
||||||
<OrderSummary />
|
<OrderSummary />
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
|
|
||||||
|
|||||||
@@ -85,10 +85,6 @@ export function AddToCartButton({ item }: { item: Product }) {
|
|||||||
message.warning(t("menu.restaurantIsClosed"));
|
message.warning(t("menu.restaurantIsClosed"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (restaurant && !restaurant.isOpened) {
|
|
||||||
message.warning(t("menu.restaurantIsClosed"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (basicCartItem && basicCartItem.uniqueId) {
|
if (basicCartItem && basicCartItem.uniqueId) {
|
||||||
if (basicCartItem.quantity > 1) {
|
if (basicCartItem.quantity > 1) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import BackIcon from "components/Icons/BackIcon";
|
import BackIcon from "components/Icons/BackIcon";
|
||||||
|
import NextIcon from "components/Icons/NextIcon";
|
||||||
|
import { useAppSelector } from "redux/hooks";
|
||||||
|
|
||||||
interface BackButtonProps {
|
interface BackButtonProps {
|
||||||
navigateBack?: boolean; // true = use router.back(), false = just clear state
|
navigateBack?: boolean; // true = use router.back(), false = just clear state
|
||||||
@@ -10,6 +11,7 @@ export default function BackButton({ navigateBack = true }: BackButtonProps) {
|
|||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
if (navigateBack) window.history.back();
|
if (navigateBack) window.history.back();
|
||||||
};
|
};
|
||||||
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -23,8 +25,14 @@ export default function BackButton({ navigateBack = true }: BackButtonProps) {
|
|||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
}}
|
}}
|
||||||
icon={
|
icon={
|
||||||
<div style={{ position: "relative", top: 2.5, right: 1 }}>
|
<div
|
||||||
<BackIcon />
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
top: 2.5,
|
||||||
|
[isRTL ? "left" : "right"]: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isRTL ? <NextIcon /> : <BackIcon />}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
onClick={handleBack}
|
onClick={handleBack}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { useAppSelector } from "redux/hooks";
|
|||||||
import { Product } from "utils/types/appTypes";
|
import { Product } from "utils/types/appTypes";
|
||||||
import styles from "./MenuList.module.css";
|
import styles from "./MenuList.module.css";
|
||||||
import ProductCard from "pages/menu/components/MenuList/ProductCard.tsx";
|
import ProductCard from "pages/menu/components/MenuList/ProductCard.tsx";
|
||||||
|
import { ProductBottomSheet } from "pages/product/components/ProductBottomSheet";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface MenuListProps {
|
interface MenuListProps {
|
||||||
data:
|
data:
|
||||||
@@ -20,6 +22,7 @@ interface MenuListProps {
|
|||||||
export function MenuList({ data, categoryRefs }: MenuListProps) {
|
export function MenuList({ data, categoryRefs }: MenuListProps) {
|
||||||
const { isRTL } = useAppSelector((state) => state.locale);
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
const products = data?.products;
|
const products = data?.products;
|
||||||
|
const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { themeName } = useAppSelector((state) => state.theme);
|
const { themeName } = useAppSelector((state) => state.theme);
|
||||||
@@ -80,12 +83,20 @@ export function MenuList({ data, categoryRefs }: MenuListProps) {
|
|||||||
</ProTitle>
|
</ProTitle>
|
||||||
<div className={styles.menuItemsGrid}>
|
<div className={styles.menuItemsGrid}>
|
||||||
{categoryProducts.map((item: Product) => (
|
{categoryProducts.map((item: Product) => (
|
||||||
<ProductCard item={item} key={item.id} />
|
<ProductCard
|
||||||
|
item={item}
|
||||||
|
key={item.id}
|
||||||
|
setIsBottomSheetOpen={setIsBottomSheetOpen}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<ProductBottomSheet
|
||||||
|
isOpen={isBottomSheetOpen}
|
||||||
|
onClose={() => setIsBottomSheetOpen(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,24 +11,25 @@ import { useAppSelector } from "redux/hooks.ts";
|
|||||||
import { ProductPreviewDialog } from "pages/menu/components/ProductPreviewDialog";
|
import { ProductPreviewDialog } from "pages/menu/components/ProductPreviewDialog";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { AddToCartButton } from "../AddToCartButton/AddToCartButton";
|
import { AddToCartButton } from "../AddToCartButton/AddToCartButton";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: Product;
|
item: Product;
|
||||||
|
setIsBottomSheetOpen: (isOpen: boolean) => void;
|
||||||
};
|
};
|
||||||
export default function ProductCard({ item }: Props) {
|
export default function ProductCard({ item, setIsBottomSheetOpen }: Props) {
|
||||||
const { isRTL } = useAppSelector((state) => state.locale);
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
const { isMobile, isTablet, isDesktop } = useBreakPoint();
|
const { isMobile, isTablet, isDesktop } = useBreakPoint();
|
||||||
const { items } = useAppSelector((state) => state.order);
|
const { items } = useAppSelector((state) => state.order);
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const navigate = useNavigate();
|
// const navigate = useNavigate();
|
||||||
const { subdomain } = useParams();
|
// const { subdomain } = useParams();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Handle dialog close
|
// Handle dialog close
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
|
setIsBottomSheetOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle product click - open dialog on desktop, navigate on mobile/tablet
|
// Handle product click - open dialog on desktop, navigate on mobile/tablet
|
||||||
@@ -36,9 +37,11 @@ export default function ProductCard({ item }: Props) {
|
|||||||
localStorage.setItem("productId", item.id.toString());
|
localStorage.setItem("productId", item.id.toString());
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
} else {
|
|
||||||
navigate(`/${subdomain}/product/${item.id}`);
|
|
||||||
}
|
}
|
||||||
|
setIsBottomSheetOpen(true);
|
||||||
|
// else {
|
||||||
|
// navigate(`/${subdomain}/product/${item.id}`);
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -45,10 +45,9 @@
|
|||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.closeButton {
|
.closeButton {
|
||||||
background: #DD41434d;
|
background: #dd41434d;
|
||||||
color: #DD4143;
|
color: #dd4143;
|
||||||
width: 62px !important;
|
width: 62px !important;
|
||||||
height: 20px !important;
|
height: 20px !important;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -503,11 +502,6 @@
|
|||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchButtonContainer {
|
|
||||||
right: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchButton {
|
.searchButton {
|
||||||
width: 32px !important;
|
width: 32px !important;
|
||||||
height: 32px !important;
|
height: 32px !important;
|
||||||
@@ -575,19 +569,24 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searchButtonContainer {
|
||||||
|
right: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
/* RTL support for back button */
|
/* RTL support for back button */
|
||||||
.backButtonContainer[dir="rtl"] {
|
:global(.ant-app-rtl) .backButtonContainer {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RTL support for search button */
|
/* RTL support for search button */
|
||||||
.searchButtonContainer[dir="rtl"] {
|
:global(.ant-app-rtl) .searchButtonContainer {
|
||||||
right: auto;
|
|
||||||
left: 20px;
|
left: 20px;
|
||||||
|
right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.rtl) .dineInIcon {
|
:global(.ant-app-rtl) .dineInIcon {
|
||||||
margin-bottom: 1px;
|
margin-bottom: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ function MenuPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${styles.pageContainer}`}>
|
<div className={`${styles.pageContainer}`}>
|
||||||
<Space direction="vertical" style={{ width: "100%" }}>
|
<Space orientation="vertical" style={{ width: "100%" }}>
|
||||||
<div>
|
<div>
|
||||||
{restaurant?.loyalty_stamps &&
|
{restaurant?.loyalty_stamps &&
|
||||||
restaurant?.is_loyalty_enabled && <LoyaltyCard />}
|
restaurant?.is_loyalty_enabled && <LoyaltyCard />}
|
||||||
|
|||||||
68
src/pages/product/components/ProductBottomSheet.tsx
Normal file
68
src/pages/product/components/ProductBottomSheet.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet.tsx";
|
||||||
|
import ProductDetailPage from "../page";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
|
import { useAppSelector } from "redux/hooks";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
interface ProductBottomSheetProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProductBottomSheet({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
}: ProductBottomSheetProps) {
|
||||||
|
const { themeName } = useAppSelector((state) => state.theme);
|
||||||
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
|
const { subdomain } = useParams();
|
||||||
|
const closeButtonStyle: React.CSSProperties = {
|
||||||
|
color: themeName === "dark" ? "#ffffff" : "#4D5154",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: themeName === "dark" ? "rgba(54, 54, 54, 0.8)" : "#EDEEEE",
|
||||||
|
border: `1px solid ${themeName === "dark" ? "#424242" : "transparent"}`,
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
cursor: "pointer",
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
position: "absolute",
|
||||||
|
top: 40,
|
||||||
|
[isRTL ? "right" : "left"]: 16,
|
||||||
|
zIndex: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const productId = localStorage.getItem("productId");
|
||||||
|
|
||||||
|
const redirectToProductPage = () => {
|
||||||
|
if (productId) {
|
||||||
|
navigate(`/${subdomain}/product/${productId}`);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProBottomSheet
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
showCloseButton={true}
|
||||||
|
initialSnap={1}
|
||||||
|
height={"90vh"}
|
||||||
|
snapPoints={["90vh"]}
|
||||||
|
contentStyle={{ padding: 0 }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
onClick={redirectToProductPage}
|
||||||
|
style={closeButtonStyle}
|
||||||
|
/>
|
||||||
|
<ProductDetailPage onClose={onClose} />
|
||||||
|
</ProBottomSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -55,6 +55,9 @@ export default function ProductFooter({
|
|||||||
product.extras.length,
|
product.extras.length,
|
||||||
product?.theExtrasGroups?.length,
|
product?.theExtrasGroups?.length,
|
||||||
]);
|
]);
|
||||||
|
const isBottomSheetView = useMemo(() => {
|
||||||
|
return window.location.href.includes("menu");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleAddToCart = () => {
|
const handleAddToCart = () => {
|
||||||
if (restaurant && !restaurant.isOpened) {
|
if (restaurant && !restaurant.isOpened) {
|
||||||
@@ -114,7 +117,7 @@ export default function ProductFooter({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// Navigate back to menu - scroll position will be restored automatically
|
// Navigate back to menu - scroll position will be restored automatically
|
||||||
if (!isDesktop) window.history.back();
|
if (!isDesktop && !isBottomSheetView ) window.history.back();
|
||||||
else {
|
else {
|
||||||
onClose?.();
|
onClose?.();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export default function ProductDetailPage({
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { productId } = useParams();
|
const { productId } = useParams();
|
||||||
|
|
||||||
const { isRTL } = useAppSelector((state) => state.locale);
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
const { restaurant } = useAppSelector((state) => state.order);
|
const { restaurant } = useAppSelector((state) => state.order);
|
||||||
const { isDesktop, isTablet } = useBreakPoint();
|
const { isDesktop, isTablet } = useBreakPoint();
|
||||||
@@ -33,6 +32,14 @@ export default function ProductDetailPage({
|
|||||||
const [viewportHeight, setViewportHeight] = useState<number>(
|
const [viewportHeight, setViewportHeight] = useState<number>(
|
||||||
typeof window !== "undefined" ? window.innerHeight : 0,
|
typeof window !== "undefined" ? window.innerHeight : 0,
|
||||||
);
|
);
|
||||||
|
const isBottomSheetView = useMemo(() => {
|
||||||
|
return window.location.href.includes("menu");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const currenctProductId = useMemo(() => {
|
||||||
|
// in case the prduct viewing from the bottom sheet, the product id is not passed in the url
|
||||||
|
return productId || localStorage.getItem("productId");
|
||||||
|
}, [localStorage.getItem("productId")]);
|
||||||
|
|
||||||
// Update viewport height when browser UI changes (mobile address bar, etc.)
|
// Update viewport height when browser UI changes (mobile address bar, etc.)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -55,7 +62,10 @@ export default function ProductDetailPage({
|
|||||||
window.removeEventListener("resize", updateViewportHeight);
|
window.removeEventListener("resize", updateViewportHeight);
|
||||||
window.removeEventListener("orientationchange", updateViewportHeight);
|
window.removeEventListener("orientationchange", updateViewportHeight);
|
||||||
if (window.visualViewport) {
|
if (window.visualViewport) {
|
||||||
window.visualViewport.removeEventListener("resize", updateViewportHeight);
|
window.visualViewport.removeEventListener(
|
||||||
|
"resize",
|
||||||
|
updateViewportHeight,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@@ -71,14 +81,15 @@ export default function ProductDetailPage({
|
|||||||
// because in desktop we open the product dialog from the menu list
|
// because in desktop we open the product dialog from the menu list
|
||||||
// and the product id is not passed in the url
|
// and the product id is not passed in the url
|
||||||
const productIdLocalStorage = localStorage.getItem("productId");
|
const productIdLocalStorage = localStorage.getItem("productId");
|
||||||
if (!menuData?.products || (!productId && !productIdLocalStorage))
|
if (!menuData?.products || (!currenctProductId && !productIdLocalStorage))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Find the product with matching IDs
|
// Find the product with matching IDs
|
||||||
return menuData.products.find(
|
return menuData.products.find(
|
||||||
(p: Product) => p.id.toString() === (productId || productIdLocalStorage),
|
(p: Product) =>
|
||||||
|
p.id.toString() === (currenctProductId || productIdLocalStorage),
|
||||||
);
|
);
|
||||||
}, [menuData, productId]);
|
}, [menuData, currenctProductId]);
|
||||||
|
|
||||||
// State for variant selections
|
// State for variant selections
|
||||||
const [selectedVariants, setSelectedVariants] = useState<
|
const [selectedVariants, setSelectedVariants] = useState<
|
||||||
@@ -205,14 +216,15 @@ export default function ProductDetailPage({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: viewportHeight > 0
|
height:
|
||||||
|
viewportHeight > 0
|
||||||
? `${viewportHeight - 195}px`
|
? `${viewportHeight - 195}px`
|
||||||
: "calc(100dvh - 195px)",
|
: "calc(100dvh - 195px)",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
scrollbarWidth: "none",
|
scrollbarWidth: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isDesktop && (
|
{!isDesktop && !isBottomSheetView && (
|
||||||
<div className={styles.backButtonContainer}>
|
<div className={styles.backButtonContainer}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</div>
|
</div>
|
||||||
@@ -371,6 +383,7 @@ export default function ProductDetailPage({
|
|||||||
selectedGroups={selectedExtrasByGroup}
|
selectedGroups={selectedExtrasByGroup}
|
||||||
quantity={quantity}
|
quantity={quantity}
|
||||||
setQuantity={(quantity: number) => setQuantity(quantity)}
|
setQuantity={(quantity: number) => setQuantity(quantity)}
|
||||||
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -315,6 +315,11 @@
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.ant-app-rtl) .backButtonContainer {
|
||||||
|
left: auto;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.darkApp) .itemDescriptionIcons path {
|
:global(.darkApp) .itemDescriptionIcons path {
|
||||||
fill: #ffffff !important;
|
fill: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user