Compare commits
3 Commits
b149fabfc9
...
5c27303695
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c27303695 | |||
| fc1a75bc4b | |||
| f580653ef2 |
@@ -162,7 +162,8 @@
|
||||
"openingHours": "ساعات العمل: {{openingTime}} - {{closingTime}}",
|
||||
"restaurantIsClosed": "المطعم مغلق",
|
||||
"address": "العنوان",
|
||||
"openingTimes": "ساعات العمل"
|
||||
"openingTimes": "ساعات العمل",
|
||||
"customizable": "قابل للتخصيص"
|
||||
},
|
||||
"cart": {
|
||||
"title": "السلة",
|
||||
|
||||
@@ -174,7 +174,8 @@
|
||||
"pay": "Pay",
|
||||
"payDescription": "Pay for your order",
|
||||
"address": "Address",
|
||||
"openingTimes": "Opening Times"
|
||||
"openingTimes": "Opening Times",
|
||||
"customizable": "Customizable"
|
||||
},
|
||||
"cart": {
|
||||
"title": "Cart",
|
||||
|
||||
@@ -253,6 +253,8 @@ export function ProBottomSheet({
|
||||
border: `1px solid ${themeName === "dark" ? "#424242" : "transparent"}`,
|
||||
transition: "all 0.3s ease",
|
||||
cursor: "pointer",
|
||||
width: 30,
|
||||
height: 30,
|
||||
};
|
||||
|
||||
const titleStyle: React.CSSProperties = {
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useAppSelector } from "redux/hooks";
|
||||
import { ProBlack2 } from "ThemeConstants";
|
||||
import ProTitle from "../ProTitle";
|
||||
import useBreakPoint from "hooks/useBreakPoint";
|
||||
import NextIcon from "components/Icons/NextIcon";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ProHeaderProps {
|
||||
@@ -41,7 +43,6 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
|
||||
top: 0,
|
||||
display: "flex",
|
||||
zIndex: 1000,
|
||||
flexDirection: isRTL ? "row-reverse" : "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center", // This centers vertically
|
||||
backgroundColor: themeName === "light" ? "white" : ProBlack2,
|
||||
@@ -52,10 +53,9 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<BackIcon />}
|
||||
icon={isRTL ? <NextIcon /> : <BackIcon />}
|
||||
style={{
|
||||
flex: 0, // Don't allow this to grow
|
||||
marginRight: "auto", // Push it to the start
|
||||
}}
|
||||
onClick={handleBack}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* Import Ant Design reset */
|
||||
@import "antd/dist/reset.css";
|
||||
|
||||
/* Import Nunito Sans from Google Fonts */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap");
|
||||
/* Import Outfit from Google Fonts */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap");
|
||||
|
||||
:root {
|
||||
--background: #f7f7f7;
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
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 CartActionsButtons from "components/CartActionsButtons/CartActionsButtons.tsx";
|
||||
import ImageWithFallback from "components/ImageWithFallback";
|
||||
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 ProTitle from "components/ProTitle.tsx";
|
||||
import {
|
||||
selectCart,
|
||||
updateCollectionMethod,
|
||||
} from "features/order/orderSlice.ts";
|
||||
import { selectCart } from "features/order/orderSlice.ts";
|
||||
import styles from "pages/cart/cart.module.css";
|
||||
import YouMightAlsoLike from "pages/cart/components/youMayLike/YouMightAlsoLike.tsx";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { colors } from "ThemeConstants.ts";
|
||||
|
||||
import OrderSummary from "components/OrderSummary/OrderSummary.tsx";
|
||||
import { useAppSelector } from "redux/hooks.ts";
|
||||
|
||||
import { FormInstance } from "antd";
|
||||
@@ -25,7 +19,6 @@ import useBreakPoint from "hooks/useBreakPoint.ts";
|
||||
import CarPlateCard from "pages/cart/components/CarPlateCard.tsx";
|
||||
import CartFooter from "pages/cart/components/cartFooter/CartFooter.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 TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
|
||||
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx";
|
||||
@@ -41,7 +34,7 @@ export default function CartMobileTabletLayout({
|
||||
form,
|
||||
}: CartMobileTabletLayoutProps) {
|
||||
const { t } = useTranslation();
|
||||
const { items, collectionMethod, orderType } = useAppSelector(selectCart);
|
||||
const { items, orderType } = useAppSelector(selectCart);
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { subdomain } = useParams();
|
||||
const { pickup_type } = useAppSelector((state) => state.order.restaurant);
|
||||
@@ -52,8 +45,8 @@ export default function CartMobileTabletLayout({
|
||||
const getMenuItemImageStyle = () => {
|
||||
if (isMobile) {
|
||||
return {
|
||||
width: 90,
|
||||
height: 80,
|
||||
width: 115,
|
||||
height: 96,
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -73,6 +66,10 @@ export default function CartMobileTabletLayout({
|
||||
size={isMobile ? "middle" : isTablet ? "large" : "large"}
|
||||
style={{ width: "100%", gap: 16 }}
|
||||
>
|
||||
{/* Table Number */}
|
||||
{(orderType === OrderType.DineIn ||
|
||||
orderType === OrderType.ToOffice) && <TableNumberCard />}
|
||||
|
||||
<div className={`${styles.cartContent} ${getResponsiveClass()}`}>
|
||||
<div className={styles.cartItems}>
|
||||
<Card hoverable className={styles.cartItem}>
|
||||
@@ -170,7 +167,7 @@ export default function CartMobileTabletLayout({
|
||||
style={{
|
||||
margin: 0,
|
||||
lineClamp: 1,
|
||||
padding: isMobile ? 3 : isTablet ? 8 : 10,
|
||||
padding: isMobile ? "3px 0" : isTablet ? 8 : 10,
|
||||
fontSize: isMobile ? 14 : isTablet ? 18 : 20,
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
@@ -187,7 +184,7 @@ export default function CartMobileTabletLayout({
|
||||
: "6.8em",
|
||||
fontWeight: 500,
|
||||
letterSpacing: "0.01em",
|
||||
width: "65%",
|
||||
width: "55%",
|
||||
}}
|
||||
>
|
||||
{item.description}
|
||||
@@ -196,7 +193,7 @@ export default function CartMobileTabletLayout({
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: index !== items.length - 1 ? 16 : 6,
|
||||
bottom: index !== items.length - 1 ? 16 : 3,
|
||||
[isRTL ? "right" : "left"]: 0,
|
||||
}}
|
||||
>
|
||||
@@ -218,7 +215,9 @@ export default function CartMobileTabletLayout({
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
position: "absolute",
|
||||
right: 3,
|
||||
bottom: 3,
|
||||
}}
|
||||
>
|
||||
<CartActionsButtons item={item} />
|
||||
@@ -239,8 +238,6 @@ export default function CartMobileTabletLayout({
|
||||
|
||||
<SpecialRequestCard />
|
||||
|
||||
<CouponCard />
|
||||
|
||||
{/* Car Plate*/}
|
||||
{((orderType === OrderType.Pickup && pickup_type === "car") ||
|
||||
orderType === OrderType.ScheduledOrder) && <CarPlateCard />}
|
||||
@@ -250,7 +247,7 @@ export default function CartMobileTabletLayout({
|
||||
orderType === OrderType.ScheduledOrder) && <TimeEstimateCard />}
|
||||
|
||||
{/* Collection Method */}
|
||||
{orderType === OrderType.Pickup && (
|
||||
{/* {orderType === OrderType.Pickup && (
|
||||
<ProInputCard title={t("cart.collectionMethod")}>
|
||||
<Form.Item
|
||||
name="collectionMethod"
|
||||
@@ -282,17 +279,13 @@ export default function CartMobileTabletLayout({
|
||||
/>
|
||||
</Form.Item>
|
||||
</ProInputCard>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{/* Reward Your Waiter */}
|
||||
<RewardWaiterCard />
|
||||
|
||||
{/* Table Number */}
|
||||
{(orderType === OrderType.DineIn ||
|
||||
orderType === OrderType.ToOffice) && <TableNumberCard />}
|
||||
{/* <RewardWaiterCard /> */}
|
||||
|
||||
{/* Invoice Summary */}
|
||||
<OrderSummary />
|
||||
{/* <OrderSummary /> */}
|
||||
</Space>
|
||||
</Layout.Content>
|
||||
<CartFooter form={form} />
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
overflow-x: hidden;
|
||||
overflow-x: auto;
|
||||
padding: 0;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
:global(.darkApp) .youMightAlsoLikeContainer path {
|
||||
@@ -56,7 +57,7 @@
|
||||
outline-offset: 4px;
|
||||
}
|
||||
.youMightAlsoLikeContainer {
|
||||
height: 130px !important;
|
||||
height: 150px !important;
|
||||
}
|
||||
|
||||
.itemDescriptionIconsContainer {
|
||||
|
||||
@@ -191,19 +191,19 @@ export default function YouMightAlsoLike() {
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: isMobile ? "95px" : isTablet ? "120px" : "140px",
|
||||
width: isMobile ? "120px" : isTablet ? "120px" : "140px",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: isMobile ? 18 : 24,
|
||||
height: isMobile ? 18 : 24,
|
||||
width: isMobile ? 28 : 24,
|
||||
height: isMobile ? 28 : 24,
|
||||
borderRadius: "50%",
|
||||
top: isMobile ? 50 : isTablet ? 60 : 80,
|
||||
top: isMobile ? 65 : isTablet ? 60 : 80,
|
||||
position: "absolute",
|
||||
[isRTL ? "left" : "right"]: isMobile ? 15 : 20,
|
||||
[isRTL ? "left" : "right"]: isMobile ? 10 : 20,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
@@ -234,8 +234,8 @@ export default function YouMightAlsoLike() {
|
||||
? styles.popularMenuItemImageTablet
|
||||
: styles.popularMenuItemImageDesktop
|
||||
}`}
|
||||
width={isMobile ? 73 : isTablet ? 90 : 110}
|
||||
height={isMobile ? 73 : isTablet ? 90 : 110}
|
||||
width={isMobile ? 106 : isTablet ? 90 : 110}
|
||||
height={isMobile ? 96 : isTablet ? 90 : 110}
|
||||
fallbackSrc={default_image}
|
||||
/>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import InputCard from "components/InputCard";
|
||||
import OrderSummary from "components/OrderSummary/OrderSummary";
|
||||
import PaymentMethods from "components/PaymentMethods/PaymentMethods";
|
||||
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 { useAppSelector } from "redux/hooks";
|
||||
import styles from "../address/address.module.css";
|
||||
@@ -13,11 +13,17 @@ import CheckoutButton from "./components/CheckoutButton";
|
||||
import { GiftCard } from "./components/GiftCard";
|
||||
import PhoneCard from "./components/phoneCard";
|
||||
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() {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { phone, order, orderType } = useAppSelector(selectCart);
|
||||
const { phone, order, orderType, collectionMethod } =
|
||||
useAppSelector(selectCart);
|
||||
const { token } = useAppSelector((state) => state.auth);
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
@@ -31,7 +37,7 @@ export default function CheckoutPage() {
|
||||
<ProHeader>{t("checkout.title")}</ProHeader>
|
||||
<Layout.Content className={styles.checkoutContainer}>
|
||||
<AddressSummary />
|
||||
<PhoneCard />
|
||||
{!token && <PhoneCard />}
|
||||
{orderType === OrderType.ToRoom && (
|
||||
<InputCard
|
||||
title={t("address.roomNo")}
|
||||
@@ -52,8 +58,47 @@ export default function CheckoutPage() {
|
||||
{/* <RoomDetails />
|
||||
<OfficeDetails /> */}
|
||||
{/* <GiftDetails /> */}
|
||||
<BriefMenu />
|
||||
{/* <BriefMenu /> */}
|
||||
<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 />
|
||||
</Layout.Content>
|
||||
|
||||
|
||||
@@ -44,7 +44,9 @@ export function AddToCartButton({ item }: { item: Product }) {
|
||||
(!cartItem.extrasgroupnew || cartItem.extrasgroupnew.length === 0),
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (restaurant && !restaurant.isOpened) {
|
||||
message.warning(t("menu.restaurantIsClosed"));
|
||||
return;
|
||||
@@ -76,7 +78,9 @@ export function AddToCartButton({ item }: { item: Product }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMinusClick = () => {
|
||||
const handleMinusClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (restaurant && !restaurant.isOpened) {
|
||||
message.warning(t("menu.restaurantIsClosed"));
|
||||
return;
|
||||
@@ -115,7 +119,9 @@ export function AddToCartButton({ item }: { item: Product }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePlusClick = () => {
|
||||
const handlePlusClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (restaurant && !restaurant.isOpened) {
|
||||
message.warning(t("menu.restaurantIsClosed"));
|
||||
return;
|
||||
@@ -163,9 +169,8 @@ export function AddToCartButton({ item }: { item: Product }) {
|
||||
position: "absolute",
|
||||
bottom: 3,
|
||||
[isRTL ? "left" : "right"]: 1,
|
||||
backgroundColor: "var(--secondary-background)",
|
||||
background: "#FAFAFA",
|
||||
borderRadius: 888,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -215,7 +220,7 @@ export function AddToCartButton({ item }: { item: Product }) {
|
||||
height: 28,
|
||||
position: "absolute",
|
||||
bottom: 1,
|
||||
[isRTL ? "left" : "right"]: 3,
|
||||
[isRTL ? "left" : "right"]: 2,
|
||||
minWidth: 28,
|
||||
}}
|
||||
/>
|
||||
@@ -236,7 +241,7 @@ export function AddToCartButton({ item }: { item: Product }) {
|
||||
size="small"
|
||||
icon={
|
||||
hasOptions ? (
|
||||
<NextIcon iconColor="#fff" iconSize={16} />
|
||||
<NextIcon iconColor="#302E3E" iconSize={16} />
|
||||
) : (
|
||||
<PlusOutlined title="add" />
|
||||
)
|
||||
@@ -244,13 +249,15 @@ export function AddToCartButton({ item }: { item: Product }) {
|
||||
onClick={handleClick}
|
||||
className={styles.addButton}
|
||||
style={{
|
||||
backgroundColor: colors.primary,
|
||||
color: "#302E3E",
|
||||
width: 28,
|
||||
height: 28,
|
||||
position: "absolute",
|
||||
bottom: 16,
|
||||
[isRTL ? "left" : "right"]: 7,
|
||||
minWidth: 28,
|
||||
boxShadow:
|
||||
"0px 1px 2px 0px #8585851A, 0px 3px 3px 0px #85858517, -1px 7px 4px 0px #8585850D, -1px 13px 5px 0px #85858503, -2px 20px 6px 0px #85858500",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from "antd";
|
||||
import BackIcon from "components/Icons/BackIcon";
|
||||
|
||||
import NextIcon from "components/Icons/NextIcon";
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
|
||||
interface BackButtonProps {
|
||||
navigateBack?: boolean; // true = use router.back(), false = just clear state
|
||||
@@ -10,6 +11,7 @@ export default function BackButton({ navigateBack = true }: BackButtonProps) {
|
||||
const handleBack = () => {
|
||||
if (navigateBack) window.history.back();
|
||||
};
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -23,8 +25,14 @@ export default function BackButton({ navigateBack = true }: BackButtonProps) {
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
icon={
|
||||
<div style={{ position: "relative", top: 2.5, right: 1 }}>
|
||||
<BackIcon />
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
top: 2.5,
|
||||
[isRTL ? "left" : "right"]: 1,
|
||||
}}
|
||||
>
|
||||
{isRTL ? <NextIcon /> : <BackIcon />}
|
||||
</div>
|
||||
}
|
||||
onClick={handleBack}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Badge, Button } from "antd";
|
||||
import { Badge, Button, message } from "antd";
|
||||
import ArabicPrice from "components/ArabicPrice";
|
||||
import BackIcon from "components/Icons/BackIcon";
|
||||
import CartIcon from "components/Icons/cart/CartIcon";
|
||||
@@ -47,10 +47,18 @@ export function MenuFooter() {
|
||||
>
|
||||
<Link
|
||||
to={
|
||||
orderType === OrderType.Pay
|
||||
totalItems === 0
|
||||
? "#"
|
||||
: orderType === OrderType.Pay
|
||||
? `/${subdomain}/pay`
|
||||
: `/${subdomain}/cart`
|
||||
}
|
||||
onClick={(e) => {
|
||||
if (totalItems === 0) {
|
||||
e.preventDefault();
|
||||
message.warning(t("cart.emptyCartMessage"));
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "0 16px",
|
||||
@@ -60,6 +68,9 @@ export function MenuFooter() {
|
||||
backgroundColor: colors.primary,
|
||||
height: 48,
|
||||
borderRadius: "999px",
|
||||
opacity: totalItems === 0 ? 0.5 : 1,
|
||||
pointerEvents: totalItems === 0 ? "none" : "auto",
|
||||
cursor: totalItems === 0 ? "not-allowed" : "pointer",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useAppSelector } from "redux/hooks";
|
||||
import { Product } from "utils/types/appTypes";
|
||||
import styles from "./MenuList.module.css";
|
||||
import ProductCard from "pages/menu/components/MenuList/ProductCard.tsx";
|
||||
import { ProductBottomSheet } from "pages/product/components/ProductBottomSheet";
|
||||
import { useState } from "react";
|
||||
|
||||
interface MenuListProps {
|
||||
data:
|
||||
@@ -20,6 +22,7 @@ interface MenuListProps {
|
||||
export function MenuList({ data, categoryRefs }: MenuListProps) {
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const products = data?.products;
|
||||
const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { themeName } = useAppSelector((state) => state.theme);
|
||||
@@ -80,12 +83,20 @@ export function MenuList({ data, categoryRefs }: MenuListProps) {
|
||||
</ProTitle>
|
||||
<div className={styles.menuItemsGrid}>
|
||||
{categoryProducts.map((item: Product) => (
|
||||
<ProductCard item={item} key={item.id} />
|
||||
<ProductCard
|
||||
item={item}
|
||||
key={item.id}
|
||||
setIsBottomSheetOpen={setIsBottomSheetOpen}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<ProductBottomSheet
|
||||
isOpen={isBottomSheetOpen}
|
||||
onClose={() => setIsBottomSheetOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -11,19 +11,25 @@ import { useAppSelector } from "redux/hooks.ts";
|
||||
import { ProductPreviewDialog } from "pages/menu/components/ProductPreviewDialog";
|
||||
import { useState } from "react";
|
||||
import { AddToCartButton } from "../AddToCartButton/AddToCartButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
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 { isMobile, isTablet, isDesktop } = useBreakPoint();
|
||||
const { items } = useAppSelector((state) => state.order);
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
// const navigate = useNavigate();
|
||||
// const { subdomain } = useParams();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Handle dialog close
|
||||
const handleDialogClose = () => {
|
||||
setIsDialogOpen(false);
|
||||
setIsBottomSheetOpen(false);
|
||||
};
|
||||
|
||||
// Handle product click - open dialog on desktop, navigate on mobile/tablet
|
||||
@@ -32,6 +38,7 @@ export default function ProductCard({ item }: Props) {
|
||||
if (isDesktop) {
|
||||
setIsDialogOpen(true);
|
||||
}
|
||||
setIsBottomSheetOpen(true);
|
||||
// else {
|
||||
// navigate(`/${subdomain}/product/${item.id}`);
|
||||
// }
|
||||
@@ -48,7 +55,7 @@ export default function ProductCard({ item }: Props) {
|
||||
key={item.id}
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
height: item.description ? 148 : 120,
|
||||
height: 156,
|
||||
overflow: "hide",
|
||||
width: "100%",
|
||||
boxShadow: "none",
|
||||
@@ -61,7 +68,7 @@ export default function ProductCard({ item }: Props) {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
padding: item.description ? "12px 12px 8px 12px" : "12px",
|
||||
padding: item.description ? "16px 16px 8px 16px" : "16px",
|
||||
overflow: "hide",
|
||||
boxShadow: "none",
|
||||
},
|
||||
@@ -123,8 +130,8 @@ export default function ProductCard({ item }: Props) {
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 12,
|
||||
[isRTL ? "right" : "left"]: 12,
|
||||
bottom: 16,
|
||||
[isRTL ? "right" : "left"]: 16,
|
||||
}}
|
||||
>
|
||||
{item.original_price !== item.price && (
|
||||
@@ -134,7 +141,7 @@ export default function ProductCard({ item }: Props) {
|
||||
style={{
|
||||
fontSize: "1rem",
|
||||
fontWeight: 700,
|
||||
color: colors.primary,
|
||||
color: "#333333",
|
||||
textDecoration: "line-through",
|
||||
marginRight: isRTL ? 0 : 10,
|
||||
marginLeft: isRTL ? 10 : 0,
|
||||
@@ -145,9 +152,13 @@ export default function ProductCard({ item }: Props) {
|
||||
price={item.price}
|
||||
strong
|
||||
style={{
|
||||
fontSize: "1rem",
|
||||
fontWeight: 700,
|
||||
color: colors.primary,
|
||||
color: "#333333",
|
||||
fontFamily: "Outfit",
|
||||
fontWeight: 600,
|
||||
fontStyle: "SemiBold",
|
||||
fontSize: "14px",
|
||||
lineHeight: "140%",
|
||||
letterSpacing: "0%",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
@@ -188,13 +199,29 @@ export default function ProductCard({ item }: Props) {
|
||||
? styles.popularMenuItemImageTablet
|
||||
: styles.popularMenuItemImageDesktop
|
||||
}`}
|
||||
width={92}
|
||||
height={92}
|
||||
width={91}
|
||||
height={96}
|
||||
/>
|
||||
<AddToCartButton item={item} />
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<ProText
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontStyle: "Regular",
|
||||
fontSize: "12px",
|
||||
lineHeight: "140%",
|
||||
letterSpacing: "0%",
|
||||
textAlign: "center",
|
||||
position: "absolute",
|
||||
bottom: 19,
|
||||
[isRTL ? "left" : "right"]: 26,
|
||||
color: "#A4A3AA",
|
||||
}}
|
||||
>
|
||||
{t("menu.customizable")}
|
||||
</ProText>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -45,10 +45,9 @@
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
|
||||
.closeButton {
|
||||
background: #DD41434d;
|
||||
color: #DD4143;
|
||||
background: #dd41434d;
|
||||
color: #dd4143;
|
||||
width: 62px !important;
|
||||
height: 20px !important;
|
||||
border: none;
|
||||
@@ -503,11 +502,6 @@
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.searchButtonContainer {
|
||||
right: 20px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
@@ -575,19 +569,24 @@
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.searchButtonContainer {
|
||||
right: 20px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* RTL support for back button */
|
||||
.backButtonContainer[dir="rtl"] {
|
||||
:global(.ant-app-rtl) .backButtonContainer {
|
||||
left: auto;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
/* RTL support for search button */
|
||||
.searchButtonContainer[dir="rtl"] {
|
||||
right: auto;
|
||||
:global(.ant-app-rtl) .searchButtonContainer {
|
||||
left: 20px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
:global(.rtl) .dineInIcon {
|
||||
:global(.ant-app-rtl) .dineInIcon {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ function MenuPage() {
|
||||
</div>
|
||||
|
||||
<div className={`${styles.pageContainer}`}>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<Space orientation="vertical" style={{ width: "100%" }}>
|
||||
<div>
|
||||
{restaurant?.loyalty_stamps &&
|
||||
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?.theExtrasGroups?.length,
|
||||
]);
|
||||
const isBottomSheetView = useMemo(() => {
|
||||
return window.location.href.includes("menu");
|
||||
}, []);
|
||||
|
||||
const handleAddToCart = () => {
|
||||
if (restaurant && !restaurant.isOpened) {
|
||||
@@ -114,7 +117,7 @@ export default function ProductFooter({
|
||||
}),
|
||||
);
|
||||
// Navigate back to menu - scroll position will be restored automatically
|
||||
if (!isDesktop) window.history.back();
|
||||
if (!isDesktop && !isBottomSheetView ) window.history.back();
|
||||
else {
|
||||
onClose?.();
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ export default function ProductDetailPage({
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { productId } = useParams();
|
||||
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { restaurant } = useAppSelector((state) => state.order);
|
||||
const { isDesktop, isTablet } = useBreakPoint();
|
||||
@@ -33,6 +32,14 @@ export default function ProductDetailPage({
|
||||
const [viewportHeight, setViewportHeight] = useState<number>(
|
||||
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.)
|
||||
useEffect(() => {
|
||||
@@ -55,7 +62,10 @@ export default function ProductDetailPage({
|
||||
window.removeEventListener("resize", updateViewportHeight);
|
||||
window.removeEventListener("orientationchange", updateViewportHeight);
|
||||
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
|
||||
// and the product id is not passed in the url
|
||||
const productIdLocalStorage = localStorage.getItem("productId");
|
||||
if (!menuData?.products || (!productId && !productIdLocalStorage))
|
||||
if (!menuData?.products || (!currenctProductId && !productIdLocalStorage))
|
||||
return null;
|
||||
|
||||
// Find the product with matching IDs
|
||||
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
|
||||
const [selectedVariants, setSelectedVariants] = useState<
|
||||
@@ -205,14 +216,15 @@ export default function ProductDetailPage({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: viewportHeight > 0
|
||||
height:
|
||||
viewportHeight > 0
|
||||
? `${viewportHeight - 195}px`
|
||||
: "calc(100dvh - 195px)",
|
||||
overflow: "auto",
|
||||
scrollbarWidth: "none",
|
||||
}}
|
||||
>
|
||||
{!isDesktop && (
|
||||
{!isDesktop && !isBottomSheetView && (
|
||||
<div className={styles.backButtonContainer}>
|
||||
<BackButton />
|
||||
</div>
|
||||
@@ -371,6 +383,7 @@ export default function ProductDetailPage({
|
||||
selectedGroups={selectedExtrasByGroup}
|
||||
quantity={quantity}
|
||||
setQuantity={(quantity: number) => setQuantity(quantity)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -315,6 +315,11 @@
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:global(.ant-app-rtl) .backButtonContainer {
|
||||
left: auto;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
:global(.darkApp) .itemDescriptionIcons path {
|
||||
fill: #ffffff !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user