Compare commits

...

3 Commits

20 changed files with 291 additions and 96 deletions

View File

@@ -162,7 +162,8 @@
"openingHours": "ساعات العمل: {{openingTime}} - {{closingTime}}", "openingHours": "ساعات العمل: {{openingTime}} - {{closingTime}}",
"restaurantIsClosed": "المطعم مغلق", "restaurantIsClosed": "المطعم مغلق",
"address": "العنوان", "address": "العنوان",
"openingTimes": "ساعات العمل" "openingTimes": "ساعات العمل",
"customizable": "قابل للتخصيص"
}, },
"cart": { "cart": {
"title": "السلة", "title": "السلة",

View File

@@ -174,7 +174,8 @@
"pay": "Pay", "pay": "Pay",
"payDescription": "Pay for your order", "payDescription": "Pay for your order",
"address": "Address", "address": "Address",
"openingTimes": "Opening Times" "openingTimes": "Opening Times",
"customizable": "Customizable"
}, },
"cart": { "cart": {
"title": "Cart", "title": "Cart",

View File

@@ -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 = {

View File

@@ -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}
/> />

View File

@@ -1,8 +1,8 @@
/* Import Ant Design reset */ /* Import Ant Design reset */
@import "antd/dist/reset.css"; @import "antd/dist/reset.css";
/* Import Nunito Sans from Google Fonts */ /* Import Outfit 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 url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap");
:root { :root {
--background: #f7f7f7; --background: #f7f7f7;

View File

@@ -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} />

View File

@@ -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 {

View File

@@ -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}
/> />

View File

@@ -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,17 @@ 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);
return ( return (
<> <>
<Form <Form
@@ -31,7 +37,7 @@ export default function CheckoutPage() {
<ProHeader>{t("checkout.title")}</ProHeader> <ProHeader>{t("checkout.title")}</ProHeader>
<Layout.Content className={styles.checkoutContainer}> <Layout.Content className={styles.checkoutContainer}>
<AddressSummary /> <AddressSummary />
<PhoneCard /> {!token && <PhoneCard />}
{orderType === OrderType.ToRoom && ( {orderType === OrderType.ToRoom && (
<InputCard <InputCard
title={t("address.roomNo")} title={t("address.roomNo")}
@@ -52,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>

View File

@@ -44,7 +44,9 @@ export function AddToCartButton({ item }: { item: Product }) {
(!cartItem.extrasgroupnew || cartItem.extrasgroupnew.length === 0), (!cartItem.extrasgroupnew || cartItem.extrasgroupnew.length === 0),
); );
const handleClick = () => { const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
if (restaurant && !restaurant.isOpened) { if (restaurant && !restaurant.isOpened) {
message.warning(t("menu.restaurantIsClosed")); message.warning(t("menu.restaurantIsClosed"));
return; 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) { if (restaurant && !restaurant.isOpened) {
message.warning(t("menu.restaurantIsClosed")); message.warning(t("menu.restaurantIsClosed"));
return; 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) { if (restaurant && !restaurant.isOpened) {
message.warning(t("menu.restaurantIsClosed")); message.warning(t("menu.restaurantIsClosed"));
return; return;
@@ -163,9 +169,8 @@ export function AddToCartButton({ item }: { item: Product }) {
position: "absolute", position: "absolute",
bottom: 3, bottom: 3,
[isRTL ? "left" : "right"]: 1, [isRTL ? "left" : "right"]: 1,
backgroundColor: "var(--secondary-background)", background: "#FAFAFA",
borderRadius: 888, borderRadius: 888,
opacity: 0.8,
}} }}
> >
<Button <Button
@@ -215,7 +220,7 @@ export function AddToCartButton({ item }: { item: Product }) {
height: 28, height: 28,
position: "absolute", position: "absolute",
bottom: 1, bottom: 1,
[isRTL ? "left" : "right"]: 3, [isRTL ? "left" : "right"]: 2,
minWidth: 28, minWidth: 28,
}} }}
/> />
@@ -236,7 +241,7 @@ export function AddToCartButton({ item }: { item: Product }) {
size="small" size="small"
icon={ icon={
hasOptions ? ( hasOptions ? (
<NextIcon iconColor="#fff" iconSize={16} /> <NextIcon iconColor="#302E3E" iconSize={16} />
) : ( ) : (
<PlusOutlined title="add" /> <PlusOutlined title="add" />
) )
@@ -244,13 +249,15 @@ export function AddToCartButton({ item }: { item: Product }) {
onClick={handleClick} onClick={handleClick}
className={styles.addButton} className={styles.addButton}
style={{ style={{
backgroundColor: colors.primary, color: "#302E3E",
width: 28, width: 28,
height: 28, height: 28,
position: "absolute", position: "absolute",
bottom: 16, bottom: 16,
[isRTL ? "left" : "right"]: 7, [isRTL ? "left" : "right"]: 7,
minWidth: 28, 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> </div>

View File

@@ -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}

View File

@@ -1,4 +1,4 @@
import { Badge, Button } from "antd"; import { Badge, Button, message } from "antd";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import BackIcon from "components/Icons/BackIcon"; import BackIcon from "components/Icons/BackIcon";
import CartIcon from "components/Icons/cart/CartIcon"; import CartIcon from "components/Icons/cart/CartIcon";
@@ -47,10 +47,18 @@ export function MenuFooter() {
> >
<Link <Link
to={ to={
orderType === OrderType.Pay totalItems === 0
? "#"
: orderType === OrderType.Pay
? `/${subdomain}/pay` ? `/${subdomain}/pay`
: `/${subdomain}/cart` : `/${subdomain}/cart`
} }
onClick={(e) => {
if (totalItems === 0) {
e.preventDefault();
message.warning(t("cart.emptyCartMessage"));
}
}}
style={{ style={{
width: "100%", width: "100%",
padding: "0 16px", padding: "0 16px",
@@ -60,6 +68,9 @@ export function MenuFooter() {
backgroundColor: colors.primary, backgroundColor: colors.primary,
height: 48, height: 48,
borderRadius: "999px", borderRadius: "999px",
opacity: totalItems === 0 ? 0.5 : 1,
pointerEvents: totalItems === 0 ? "none" : "auto",
cursor: totalItems === 0 ? "not-allowed" : "pointer",
}} }}
> >
<Button <Button

View File

@@ -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>
</> </>
); );

View File

@@ -11,19 +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 { 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 { subdomain } = useParams();
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
@@ -32,6 +38,7 @@ export default function ProductCard({ item }: Props) {
if (isDesktop) { if (isDesktop) {
setIsDialogOpen(true); setIsDialogOpen(true);
} }
setIsBottomSheetOpen(true);
// else { // else {
// navigate(`/${subdomain}/product/${item.id}`); // navigate(`/${subdomain}/product/${item.id}`);
// } // }
@@ -48,7 +55,7 @@ export default function ProductCard({ item }: Props) {
key={item.id} key={item.id}
style={{ style={{
borderRadius: 8, borderRadius: 8,
height: item.description ? 148 : 120, height: 156,
overflow: "hide", overflow: "hide",
width: "100%", width: "100%",
boxShadow: "none", boxShadow: "none",
@@ -61,7 +68,7 @@ export default function ProductCard({ item }: Props) {
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "space-between", justifyContent: "space-between",
padding: item.description ? "12px 12px 8px 12px" : "12px", padding: item.description ? "16px 16px 8px 16px" : "16px",
overflow: "hide", overflow: "hide",
boxShadow: "none", boxShadow: "none",
}, },
@@ -123,8 +130,8 @@ export default function ProductCard({ item }: Props) {
<div <div
style={{ style={{
position: "absolute", position: "absolute",
bottom: 12, bottom: 16,
[isRTL ? "right" : "left"]: 12, [isRTL ? "right" : "left"]: 16,
}} }}
> >
{item.original_price !== item.price && ( {item.original_price !== item.price && (
@@ -134,7 +141,7 @@ export default function ProductCard({ item }: Props) {
style={{ style={{
fontSize: "1rem", fontSize: "1rem",
fontWeight: 700, fontWeight: 700,
color: colors.primary, color: "#333333",
textDecoration: "line-through", textDecoration: "line-through",
marginRight: isRTL ? 0 : 10, marginRight: isRTL ? 0 : 10,
marginLeft: isRTL ? 10 : 0, marginLeft: isRTL ? 10 : 0,
@@ -145,9 +152,13 @@ export default function ProductCard({ item }: Props) {
price={item.price} price={item.price}
strong strong
style={{ style={{
fontSize: "1rem", color: "#333333",
fontWeight: 700, fontFamily: "Outfit",
color: colors.primary, fontWeight: 600,
fontStyle: "SemiBold",
fontSize: "14px",
lineHeight: "140%",
letterSpacing: "0%",
}} }}
/> />
<div <div
@@ -188,13 +199,29 @@ export default function ProductCard({ item }: Props) {
? styles.popularMenuItemImageTablet ? styles.popularMenuItemImageTablet
: styles.popularMenuItemImageDesktop : styles.popularMenuItemImageDesktop
}`} }`}
width={92} width={91}
height={92} height={96}
/> />
<AddToCartButton item={item} /> <AddToCartButton item={item} />
</Badge> </Badge>
</div> </div>
</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> </Card>
</div> </div>

View File

@@ -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;
} }

View File

@@ -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 />}

View 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>
);
}

View File

@@ -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?.();
} }

View File

@@ -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>

View File

@@ -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;
} }