Compare commits

...

10 Commits

31 changed files with 1166 additions and 582 deletions

View File

@@ -377,7 +377,10 @@
"howWasYourExperienceWithFascanoRestaurant": "كيف كانت تجربتك مع مطعم فاسكانو؟",
"rateOrder": "تقييم الطلب",
"submitRating": "إرسال التقييم",
"pleaseLoginToAllowRating": "يرجى تسجيل الدخول لتمكين التقييم"
"pleaseLoginToAllowRating": "يرجى تسجيل الدخول لتمكين التقييم",
"remainingTime": "الوقت المتبقي",
"sec": "ثانية",
"min": "دقيقة"
},
"orderTypes": {
"dine-in": "في المطعم",
@@ -425,7 +428,11 @@
"yourAmount": "مبلغك",
"selectedTotal": "المجموع المحدد",
"splitBillAmount": "مبلغ تقسيم الفاتورة",
"removeSplitWay": "إزالة طريقة التقسيم",
"amount": "المبلغ"
"removeSplit": "إزالة التقسيم",
"amount": "المبلغ",
"howMuchWouldYouLikeToPay": "كم مبلغ تريد دفعه؟",
"confirm": "تأكيد",
"totalBill": "الفاتورة الكلية",
"remainingToPay": "المبلغ المتبقي"
}
}

View File

@@ -388,7 +388,10 @@
"howWasYourExperienceWithFascanoRestaurant": "How was your experience with Fascano Restaurant?",
"rateOrder": "Rate Order",
"submitRating": "Submit Rating",
"pleaseLoginToAllowRating": "Please login to allow rating"
"pleaseLoginToAllowRating": "Please login to allow rating",
"remainingTime": "Remaining Time",
"sec": "Sec",
"min": "Min"
},
"orderTypes": {
"dine-in": "Dine In",
@@ -437,7 +440,11 @@
"yourAmount": "Your Amount",
"selectedTotal": "Selected Total",
"splitBillAmount": "Split Bill Amount",
"removeSplitWay": "Remove Split Way",
"amount": "Amount"
"removeSplit": "Remove Split",
"amount": "Amount",
"howMuchWouldYouLikeToPay": "How much would you like to pay?",
"confirm": "Confirm",
"totalBill": "Total Bill",
"remainingToPay": "Remaining to Pay"
}
}

View File

@@ -1,7 +1,7 @@
.quantityControls {
display: flex;
align-items: center;
background-color: #ffb70014;
background-color: var(--background);
border-radius: 888px;
width: fit-content;
}
@@ -15,7 +15,7 @@
.quantityInputContainer {
display: flex;
align-items: center;
padding: 8px;
padding: 0 1px;
border-radius: 888px;
width: 100px;
height: 32px;
@@ -77,7 +77,12 @@
}
.plusIcon {
margin-bottom: 1px;
margin-bottom: 1px;
color: var(--secondary-background);
}
.minusIcon {
color: var(--secondary-foreground);
}
.deleteIcon {

View File

@@ -1,7 +1,6 @@
import { MinusOutlined } from "@ant-design/icons";
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
import { Button, Grid, InputNumber, Popconfirm } from "antd";
import DeleteIcon from "components/Icons/DeleteIcon";
import PlusIcon from "components/Icons/PlusIcon";
import { useTranslation } from "react-i18next";
import { useAppSelector } from "redux/hooks";
import { colors } from "../../ThemeConstants";
@@ -51,14 +50,19 @@ export default function ActionsButtons({
<div className={styles.quantityInputContainer}>
{quantity > 0 ? (
<Button
type="text"
shape="circle"
iconPlacement="start"
icon={<MinusOutlined title="add" className={styles.minusIcon} />}
size="small"
onClick={() => setQuantity(Math.max(1, quantity - 1))}
className={styles.quantityButton}
{...(min && { disabled: quantity === min })}
>
<MinusOutlined style={{ fontSize: 16, color: colors.primary }} />
</Button>
style={{
width: 28,
height: 28,
border: "none",
}}
/>
) : (
<Popconfirm
title={t("cart.deleteConfirmation.title")}
@@ -70,17 +74,20 @@ export default function ActionsButtons({
placement={isRTL ? "left" : "right"}
styles={{
root: getPopconfirmOverlayStyle(),
body: {
padding: xs ? "12px" : "16px",
},
}}
>
<Button
type="text"
shape="circle"
iconPlacement="start"
icon={<DeleteIcon />}
size="small"
danger
icon={<DeleteIcon className={styles.deleteIcon} />}
className={styles.removeButton}
className={styles.addButton}
style={{
background: "#FEF2F2",
width: 28,
height: 28,
border: "none",
}}
/>
</Popconfirm>
)}
@@ -88,21 +95,27 @@ export default function ActionsButtons({
min={min || 1}
max={max || 100}
value={quantity || 1}
onChange={(value) => setQuantity(value || 1)}
onChange={(value: number | null) => setQuantity(value || 1)}
size="small"
controls={false}
className={styles.quantityInput}
name="id"
/>
<Button
type="text"
shape="circle"
iconPlacement="start"
icon={<PlusOutlined title="add" className={styles.plusIcon} />}
size="small"
onClick={() => setQuantity(Math.min(100, quantity + 1))}
className={styles.quantityButton}
{...(max && { disabled: quantity >= max })}
>
<PlusIcon className={styles.plusIcon} />
</Button>
style={{
backgroundColor: colors.primary,
width: 28,
height: 28,
border: "none",
}}
/>
</div>
</div>
</div>

View File

@@ -40,6 +40,7 @@
box-shadow: none;
font-size: 16px;
font-weight: 600;
padding: 0;
}
.removeButton {
@@ -55,7 +56,6 @@
position: absolute;
top: 12px;
right: 12px;
background-color: #ffb70014;
border-radius: 50%;
padding: 8px;
cursor: pointer;

View File

@@ -46,7 +46,7 @@ export default function CartActionsButtons({ item }: { item: CartItem }) {
{item.quantity > 1 ? (
<Button
shape="circle"
iconPosition="start"
iconPlacement="start"
icon={<MinusOutlined title="add" className={styles.minusIcon} />}
size="small"
onClick={() =>
@@ -76,14 +76,11 @@ export default function CartActionsButtons({ item }: { item: CartItem }) {
placement={isRTL ? "left" : "right"}
styles={{
root: getPopconfirmOverlayStyle(),
body: {
padding: isMobile ? "12px" : "16px",
},
}}
>
<Button
shape="circle"
iconPosition="start"
iconPlacement="start"
icon={<DeleteIcon />}
size="small"
onClick={() =>
@@ -109,7 +106,7 @@ export default function CartActionsButtons({ item }: { item: CartItem }) {
min={1}
max={100}
value={item.quantity || 1}
onChange={(value) =>
onChange={(value: number | null) =>
dispatch(
updateQuantity({
id: item.id,
@@ -125,7 +122,7 @@ export default function CartActionsButtons({ item }: { item: CartItem }) {
/>
<Button
shape="circle"
iconPosition="start"
iconPlacement="start"
icon={<PlusOutlined title="add" className={styles.plusIcon} />}
size="small"
onClick={() =>

View File

@@ -110,7 +110,7 @@ export function GiftBottomSheet({
<ProPhoneInput
propName="senderPhone"
label={t("address.receiverPhone")}
label={t("address.senderPhone")}
/>
<Form.Item

View File

@@ -6,20 +6,17 @@ interface DeleteIconType {
const DeleteIcon = ({ className, onClick }: DeleteIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M6 2H10M2 4H14M12.6667 4L12.1991 11.0129C12.129 12.065 12.0939 12.5911 11.8667 12.99C11.6666 13.3412 11.3648 13.6235 11.0011 13.7998C10.588 14 10.0607 14 9.00623 14H6.99377C5.93927 14 5.41202 14 4.99889 13.7998C4.63517 13.6235 4.33339 13.3412 4.13332 12.99C3.90607 12.5911 3.871 12.065 3.80086 11.0129L3.33333 4M6.66667 7V10.3333M9.33333 7V10.3333"
stroke="#FF6F59"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
d="M3 2.4V0.6C3 0.44087 3.06321 0.288258 3.17574 0.175736C3.28826 0.0632141 3.44087 0 3.6 0H8.4C8.55913 0 8.71174 0.0632141 8.82426 0.175736C8.93679 0.288258 9 0.44087 9 0.6V2.4H12V3.6H10.8V11.4C10.8 11.5591 10.7368 11.7117 10.6243 11.8243C10.5117 11.9368 10.3591 12 10.2 12H1.8C1.64087 12 1.48826 11.9368 1.37574 11.8243C1.26321 11.7117 1.2 11.5591 1.2 11.4V3.6H0V2.4H3ZM6.8484 7.2L7.9092 6.1392L7.0608 5.2908L6 6.3516L4.9392 5.2908L4.0908 6.1392L5.1516 7.2L4.0908 8.2608L4.9392 9.1092L6 8.0484L7.0608 9.1092L7.9092 8.2608L6.8484 7.2ZM4.2 1.2V2.4H7.8V1.2H4.2Z"
fill="#DC2626"
/>
</svg>
);

View File

@@ -43,7 +43,7 @@ export default function OrderSummary() {
<Card className={`${styles.orderSummary}`}>
<ProTitle style={{ fontSize: 18 }}>{t("cart.orderSummary")}</ProTitle>
<Divider style={{ margin: "15px 0 15px 0" }} />
<Space direction="vertical" style={{ width: "100%" }}>
<Space orientation="vertical" style={{ width: "100%" }}>
<div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.basketTotal")}</ProText>
<ArabicPrice price={subtotal} />

View File

@@ -88,7 +88,7 @@ const PaymentMethods = () => {
}}
size="large"
>
<Space direction="vertical" style={{ width: "100%" }}>
<Space orientation="vertical" style={{ width: "100%" }}>
{options.map((option) => (
<div key={option.value}>
<Radio

View File

@@ -16,6 +16,7 @@ interface ProBottomSheetProps {
initialSnap?: number;
className?: string;
themeName?: "light" | "dark";
contentStyle?: React.CSSProperties;
}
export function ProBottomSheet({
@@ -30,6 +31,7 @@ export function ProBottomSheet({
snapPoints = [500],
initialSnap = 0,
className = "",
contentStyle = {},
}: ProBottomSheetProps) {
const [currentSnap, setCurrentSnap] = useState(initialSnap);
const [isDragging, setIsDragging] = useState(false);
@@ -231,12 +233,13 @@ export function ProBottomSheet({
backdropFilter: themeName === "dark" ? "blur(8px)" : "none",
};
const contentStyle: React.CSSProperties = {
const contentWrapperStyle: React.CSSProperties = {
flex: 1,
overflow: "auto",
padding: "0 20px",
backgroundColor: themeName === "dark" ? "#0a0a0a" : "#ffffff",
color: themeName === "dark" ? "#ffffff" : "#000000",
...contentStyle,
};
const closeButtonStyle: React.CSSProperties = {
@@ -315,7 +318,7 @@ export function ProBottomSheet({
</div>
)}
<div style={contentStyle}>{children}</div>
<div style={contentWrapperStyle}>{children}</div>
</div>
</>
);

View File

@@ -69,10 +69,9 @@ export default function HeaderMenuDrawer() {
maskClosable={false}
onClose={closeMobileMenu}
open={mobileMenuOpen}
width={isMobile ? "50%" : "25%"}
styles={{
body: {
padding: 0,
padding: 0,
},
header: {
padding: "5px 24px",
@@ -80,6 +79,9 @@ export default function HeaderMenuDrawer() {
themeName === "dark" ? "#374151" : "#e5e7eb"
}`,
},
wrapper: {
width: isMobile ? "50%" : "25%",
},
}}
closeIcon={null}
>

View File

@@ -169,6 +169,7 @@
gap: 1rem;
background-color: var(--secondary-background);
box-shadow: none;
z-index: 999;
}
.splitBillButton {

View File

@@ -69,7 +69,7 @@ export default function CartMobileTabletLayout({
className={`${styles.cartContainer} '${styles.cartLayout}' ${getResponsiveClass()}`}
>
<Space
direction="vertical"
orientation="vertical"
size={isMobile ? "middle" : isTablet ? "large" : "large"}
style={{ width: "100%", gap: 16 }}
>
@@ -124,7 +124,7 @@ export default function CartMobileTabletLayout({
<Divider style={{ margin: "0px 0px 10px 0px" }} />
)}
{items.map((item, index) => (
<div key={index}>
<div key={index} style={{ position: "relative" }}>
<Space
size="middle"
style={{
@@ -134,12 +134,14 @@ export default function CartMobileTabletLayout({
height: "100%",
}}
>
<Space
direction="vertical"
size="small"
style={{ flex: 1, gap: isMobile ? 32 : isTablet ? 24 : 32 }}
>
<div>
<Space orientation="vertical" size="small">
<div
style={{
position: "absolute",
top: 0,
[isRTL ? "right" : "left"]: 0,
}}
>
<ProText
style={{
margin: 0,
@@ -155,8 +157,10 @@ export default function CartMobileTabletLayout({
}}
>
{isRTL
? (item.variant as Variant)?.optionsAR?.[0]?.value
: (item.variant as Variant)?.options?.[0]?.value}
? (item.variant as Variant)?.optionsAR?.[0]
?.value
: (item.variant as Variant)?.options?.[0]
?.value}
</span>
</ProText>
<br />
@@ -176,9 +180,14 @@ export default function CartMobileTabletLayout({
wordWrap: "break-word",
overflowWrap: "break-word",
lineHeight: "1.4",
maxHeight: isMobile ? "2.8em" : isTablet ? "4.8em" : "6.8em",
maxHeight: isMobile
? "2.8em"
: isTablet
? "4.8em"
: "6.8em",
fontWeight: 500,
letterSpacing: "0.01em",
width: "65%",
}}
>
{item.description}
@@ -186,20 +195,15 @@ export default function CartMobileTabletLayout({
</div>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "start",
gap: isMobile ? 20 : 24,
position: "absolute",
bottom: index !== items.length - 1 ? 16 : 6,
[isRTL ? "right" : "left"]: 0,
}}
>
<ProText
strong
style={{
fontSize: isMobile ? 14 : isTablet ? 16 : 18,
}}
>
<ArabicPrice price={item.price} />
</ProText>
<ArabicPrice
price={item.price}
style={{ fontStyle: "bold" }}
/>
</div>
</Space>
<div style={{ position: "relative" }}>

View File

@@ -73,6 +73,7 @@ export default function TableNumberCard() {
width: "100%",
height: 50,
fontSize: 12,
borderRadius: 888,
}}
onChange={(value) => {
console.log(value);

View File

@@ -56,7 +56,7 @@
outline-offset: 4px;
}
.youMightAlsoLikeContainer {
height: 160px !important;
height: 130px !important;
}
.itemDescriptionIconsContainer {
@@ -70,7 +70,7 @@
@media (min-width: 768px) and (max-width: 1024px) {
.youMightAlsoLikeContainer {
height: 210px !important;
height: 190px !important;
}
.itemDescriptionIconsContainer {

View File

@@ -239,14 +239,6 @@ export default function YouMightAlsoLike() {
fallbackSrc={default_image}
/>
<div
className={styles.itemDescriptionIconsContainer}
>
<ItemDescriptionIcons
className={styles.itemDescriptionIcons}
/>
</div>
<Space
direction="vertical"
size="small"
@@ -265,7 +257,11 @@ export default function YouMightAlsoLike() {
fontSize: isMobile ? 12 : isTablet ? 14 : 16,
width: isMobile ? 80 : isTablet ? 100 : 120,
display: "inline-block",
fontWeight: 600,
fontWeight: 500,
fontStyle: "Medium",
lineHeight: "140%",
letterSpacing: "0%",
marginTop: 8,
}}
>
{item.name}
@@ -282,7 +278,11 @@ export default function YouMightAlsoLike() {
textOverflow: "ellipsis",
padding: isMobile ? 3 : isTablet ? "0 8px" : "0 10px",
fontSize: isMobile ? 12 : isTablet ? 14 : 16,
fontWeight: 500,
fontWeight: 600,
fontStyle: "SemiBold",
lineHeight: "140%",
letterSpacing: "0%",
marginTop: 4,
}}
/>
</Space>

View File

@@ -14,101 +14,99 @@ export function GiftCard() {
const dispatch = useAppDispatch();
return (
<>
<ProInputCard title={t("address.giftDetails")}>
<ProPhoneInput
propName="receiverPhone"
label={t("address.receiverPhone")}
value={order?.receiverPhone}
onChange={(e) => {
dispatch(updateOrder({ ...order, receiverPhone: e }));
}}
/>
<Form.Item name="message" label={t("address.message")}>
<TextArea
placeholder={t("address.message")}
size="large"
style={{
fontSize: 14,
borderRadius: 10,
}}
rows={2}
autoFocus={false}
value={order?.senderName}
<ProInputCard title={t("address.giftDetails")}>
<ProPhoneInput
propName="receiverPhone"
label={t("address.receiverPhone")}
value={order?.receiverPhone}
onChange={(e) => {
dispatch(updateOrder({ ...order, senderName: e.target.value }));
dispatch(updateOrder({ ...order, receiverPhone: e }));
}}
/>
</Form.Item>
<Form.Item
name="senderName"
label={t("address.senderName")}
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("address.senderName")}
size="large"
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
<Form.Item name="message" label={t("address.message")}>
<TextArea
placeholder={t("address.message")}
size="large"
style={{
fontSize: 14,
borderRadius: 10,
}}
rows={2}
autoFocus={false}
value={order?.message}
onChange={(e) => {
dispatch(updateOrder({ ...order, message: e.target.value }));
}}
/>
</Form.Item>
<Form.Item
name="senderName"
label={t("address.senderName")}
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("address.senderName")}
size="large"
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
value={order?.senderName}
onChange={(e) => {
dispatch(updateOrder({ ...order, senderName: e.target.value }));
}}
/>
</Form.Item>
<ProPhoneInput
propName="senderPhone"
label={t("address.senderPhone")}
value={order?.senderPhone}
onChange={(e) => {
dispatch(updateOrder({ ...order, senderPhone: e.target.value }));
dispatch(updateOrder({ ...order, senderPhone: e }));
}}
/>
</Form.Item>
<ProPhoneInput
propName="senderPhone"
label={t("address.receiverPhone")}
/>
<Form.Item
name="senderEmail"
label={t("address.senderEmail")}
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("address.senderEmail")}
size="large"
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
value={order?.senderEmail}
onChange={(e) => {
dispatch(updateOrder({ ...order, senderEmail: e.target.value }));
}}
/>
</Form.Item>
<Form.Item
name="isSecret"
rules={[{ required: true, message: "" }]}
colon={false}
>
<Checkbox
style={{
fontSize: 14,
}}
autoFocus={false}
checked={order?.isSecret}
onChange={(e) => {
dispatch(updateOrder({ ...order, isSecret: e.target.checked }));
}}
<Form.Item
name="senderEmail"
label={t("address.senderEmail")}
rules={[{ required: true, message: "" }]}
colon={false}
>
<ProText>{t("address.keepMyNameSecret")}</ProText>
</Checkbox>
</Form.Item>
</ProInputCard>
<Input
placeholder={t("address.senderEmail")}
size="large"
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
value={order?.senderEmail}
onChange={(e) => {
dispatch(updateOrder({ ...order, senderEmail: e.target.value }));
}}
/>
</Form.Item>
<Form.Item name="isSecret" colon={false}>
<Checkbox
style={{
fontSize: 14,
}}
autoFocus={false}
checked={order?.isSecret}
onChange={(e) => {
dispatch(updateOrder({ ...order, isSecret: e.target.checked }));
}}
>
<ProText>{t("address.keepMyNameSecret")}</ProText>
</Checkbox>
</Form.Item>
</ProInputCard>
</>
);
}

View File

@@ -8,7 +8,7 @@ import styles from "./AddToCartButton.module.css";
import { useAppSelector, useAppDispatch } from "redux/hooks";
import { Product } from "utils/types/appTypes";
import NextIcon from "components/Icons/NextIcon";
import { addItem, updateQuantity, removeItem } from "features/order/orderSlice";
import { addItem, removeItem, updateQuantity } from "features/order/orderSlice";
import ProText from "components/ProText";
export function AddToCartButton({ item }: { item: Product }) {
@@ -26,7 +26,7 @@ export function AddToCartButton({ item }: { item: Product }) {
const cartItemsForProduct = items.filter((i) => i.id === item.id);
const totalQuantity = cartItemsForProduct.reduce(
(total, item) => total + item.quantity,
0
0,
);
const isInCart = totalQuantity > 0;
@@ -41,7 +41,7 @@ export function AddToCartButton({ item }: { item: Product }) {
(cartItem) =>
(!cartItem.variant || cartItem.variant === "None") &&
(!cartItem.extras || cartItem.extras.length === 0) &&
(!cartItem.extrasgroupnew || cartItem.extrasgroupnew.length === 0)
(!cartItem.extrasgroupnew || cartItem.extrasgroupnew.length === 0),
);
const handleClick = () => {
@@ -90,7 +90,7 @@ export function AddToCartButton({ item }: { item: Product }) {
id: basicCartItem.id,
uniqueId: basicCartItem.uniqueId,
quantity: basicCartItem.quantity - 1,
})
}),
);
} else {
// Remove item if quantity is 1
@@ -106,7 +106,7 @@ export function AddToCartButton({ item }: { item: Product }) {
id: firstItem.id,
uniqueId: firstItem.uniqueId,
quantity: firstItem.quantity - 1,
})
}),
);
} else {
dispatch(removeItem(firstItem.uniqueId));
@@ -128,7 +128,7 @@ export function AddToCartButton({ item }: { item: Product }) {
id: basicCartItem.id,
uniqueId: basicCartItem.uniqueId,
quantity: basicCartItem.quantity + 1,
})
}),
);
} else if (!hasOptions) {
// Add new basic item if no options
@@ -153,42 +153,24 @@ export function AddToCartButton({ item }: { item: Product }) {
}
};
return isInCart ? (
return isInCart && !hasOptions ? (
<>
<div
className={styles.addButton}
style={{
width: 107,
height: 45,
width: 90,
height: 30,
position: "absolute",
bottom: -5,
[isRTL ? "left" : "right"]: -4,
bottom: 3,
[isRTL ? "left" : "right"]: 1,
backgroundColor: "var(--secondary-background)",
borderRadius: 888,
opacity: 0.8,
}}
>
<svg
width="107"
height="45"
viewBox="0 0 107 45"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
}}
>
<rect
width="107"
height="45"
rx="22.5"
className={styles.actionRect}
/>
</svg>
<Button
shape="circle"
iconPosition="start"
iconPlacement="start"
icon={<MinusOutlined title="minus" style={{ color: "black" }} />}
size="small"
onClick={handleMinusClick}
@@ -198,16 +180,17 @@ export function AddToCartButton({ item }: { item: Product }) {
width: 28,
height: 28,
position: "absolute",
bottom: 9,
[isRTL ? "left" : "right"]: 70,
bottom: 1,
[isRTL ? "left" : "right"]: 60,
minWidth: 28,
border: "none",
}}
/>
<ProText
style={{
position: "absolute",
bottom: 17,
[isRTL ? "left" : "right"]: 50,
bottom: 7,
[isRTL ? "left" : "right"]: 45,
fontSize: 14,
fontWeight: 700,
fontStyle: "Bold",
@@ -221,7 +204,7 @@ export function AddToCartButton({ item }: { item: Product }) {
</ProText>
<Button
shape="circle"
iconPosition="start"
iconPlacement="start"
icon={<PlusOutlined title="plus" />}
size="small"
onClick={handlePlusClick}
@@ -231,53 +214,45 @@ export function AddToCartButton({ item }: { item: Product }) {
width: 28,
height: 28,
position: "absolute",
bottom: 9,
[isRTL ? "left" : "right"]: 10,
bottom: 1,
[isRTL ? "left" : "right"]: 3,
minWidth: 28,
}}
/>
</div>
</>
) : (
<>
<div
<div
style={{
position: "absolute",
bottom: -11,
[isRTL ? "left" : "right"]: -2,
borderRadius: "50%",
}}
>
<Button
shape="circle"
iconPlacement="start"
size="small"
icon={
hasOptions ? (
<NextIcon iconColor="#fff" iconSize={16} />
) : (
<PlusOutlined title="add" />
)
}
onClick={handleClick}
className={styles.addButton}
style={{
width: 48,
height: 48,
backgroundColor: colors.primary,
width: 28,
height: 28,
position: "absolute",
bottom: -11,
[isRTL ? "left" : "right"]: -2,
backgroundColor: "var(--background)",
borderRadius: "50%",
bottom: 16,
[isRTL ? "left" : "right"]: 7,
minWidth: 28,
}}
>
<Button
shape="circle"
iconPosition="start"
icon={
hasOptions ? (
<NextIcon
className={styles.plusIcon}
iconColor="#fff"
iconSize={16}
/>
) : (
<PlusOutlined title="add" className={styles.plusIcon} />
)
}
size="small"
onClick={handleClick}
className={styles.addButton}
style={{
backgroundColor: colors.primary,
width: 36,
height: 36,
position: "absolute",
bottom: 6,
[isRTL ? "left" : "right"]: 6,
}}
/>
</div>
</>
/>
</div>
);
}

View File

@@ -48,7 +48,7 @@ export default function ProductCard({ item }: Props) {
key={item.id}
style={{
borderRadius: 8,
height: 148,
height: item.description ? 148 : 120,
overflow: "hide",
width: "100%",
boxShadow: "none",
@@ -188,13 +188,13 @@ export default function ProductCard({ item }: Props) {
? styles.popularMenuItemImageTablet
: styles.popularMenuItemImageDesktop
}`}
width={90}
height={90}
width={92}
height={92}
/>
<AddToCartButton item={item} />
</Badge>
</div>
</div>
<AddToCartButton item={item} />
</Card>
</div>

View File

@@ -1,4 +1,4 @@
import { Button, Card, Divider, Image } from "antd";
import { Button, Card, Divider, Flex, Image, Progress, Tooltip } from "antd";
import Ads2 from "components/Ads/Ads2";
import { CancelOrderBottomSheet } from "components/CustomBottomSheet/CancelOrderBottomSheet";
import LocationIcon from "components/Icons/LocationIcon";
@@ -11,7 +11,7 @@ import ProInputCard from "components/ProInputCard/ProInputCard";
import ProText from "components/ProText";
import ProTitle from "components/ProTitle";
import dayjs from "dayjs";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import {
@@ -74,6 +74,147 @@ export default function OrderPage() {
}
}, [orderDetails?.status, restaurantSubdomain, refetchRestaurantDetails]);
// Check if order is in progress (check last status alias)
const lastStatus = orderDetails?.status?.[orderDetails.status.length - 1];
const isInProgress = lastStatus?.alias === "accepted_by_restaurant";
// Calculate timer and progress
const [remainingSeconds, setRemainingSeconds] = useState<number>(0);
const [progressPercent, setProgressPercent] = useState<number>(0);
useEffect(() => {
if (
!isInProgress ||
!orderDetails?.order?.time_to_prepare ||
!orderDetails?.order?.created_at
) {
return;
}
const updateTimer = () => {
const orderCreatedAt = dayjs(orderDetails.order.created_at);
const now = dayjs();
const elapsedSeconds = now.diff(orderCreatedAt, "second");
// time_to_prepare is in minutes, convert to seconds
const totalSeconds =
(Number(orderDetails.order.time_to_prepare) || 0) * 60;
const remaining = Math.max(0, totalSeconds - elapsedSeconds);
setRemainingSeconds(remaining);
// Calculate progress as remaining time percentage (starts at 100%, decreases to 0%)
const percent =
totalSeconds > 0
? Math.min(100, Math.max(0, (remaining / totalSeconds) * 100))
: 100;
setProgressPercent(percent);
};
updateTimer();
const interval = setInterval(updateTimer, 1000);
return () => clearInterval(interval);
}, [
isInProgress,
orderDetails?.order?.time_to_prepare,
orderDetails?.status,
]);
// Format remaining time with Min and Sec labels
const formatTimer = (totalSeconds: number) => {
const mins = Math.floor(totalSeconds / 60);
const secs = Math.floor(totalSeconds % 60);
return (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
}}
>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 28,
lineHeight: "100%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{mins.toString().padStart(2, "0")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{t("order.min")}
</ProText>
</div>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 28,
lineHeight: "100%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
:
</ProText>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
}}
>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 28,
lineHeight: "100%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{secs.toString().padStart(2, "0")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{t("order.sec")}
</ProText>
</div>
</div>
);
};
return (
<>
<ProHeader>{t("order.title")}</ProHeader>
@@ -126,7 +267,51 @@ export default function OrderPage() {
</div>
</div>
<OrderDishIcon className={styles.orderDishIcon} />
{isInProgress ? (
<Flex gap="small" wrap justify="center">
<Tooltip title="3 done / 3 in progress / 4 to do">
<div
style={{
transform: "scaleX(-1)",
}}
>
<Progress
percent={progressPercent}
format={() => (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 4,
transform: "scaleX(-1)",
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("order.remainingTime")}
</ProText>
{formatTimer(remainingSeconds)}
</div>
)}
strokeColor="#FFB700"
size={200}
type="dashboard"
/>
</div>
</Tooltip>
</Flex>
) : (
<OrderDishIcon className={styles.orderDishIcon} />
)}
<div>
<ProTitle

View File

@@ -9,7 +9,9 @@ import {
updateSplitBillAmount,
} from "features/order/orderSlice";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import ProInputCard from "components/ProInputCard/ProInputCard";
import ProText from "components/ProText";
import ArabicPrice from "components/ArabicPrice";
import styles from "./SplitBill.module.css";
interface SplitBillChoiceBottomSheetProps {
isOpen: boolean;
@@ -50,6 +52,14 @@ export function CustomAmountChoiceBottomSheet({
onClose();
};
// Calculate preview values using local amount state for real-time updates
const currentAmount = parseFloat(amount) || 0;
// Original total bill (before split) = grandTotal + splitBillAmount (from Redux)
const originalTotalBill = grandTotal + splitBillAmount;
// Preview: total bill stays the same, remaining = original - current amount being typed
const previewTotalBill = originalTotalBill;
const previewRemaining = originalTotalBill - currentAmount;
return (
<ProBottomSheet
isOpen={isOpen}
@@ -57,56 +67,129 @@ export function CustomAmountChoiceBottomSheet({
title={t("splitBill.payAsCustomAmount")}
showCloseButton={true}
initialSnap={1}
height={400}
snapPoints={[400]}
height={430}
snapPoints={[430]}
contentStyle={{
padding: 0,
}}
>
<div
style={{
padding: "20px 0",
padding: "20px",
display: "flex",
flexDirection: "column",
gap: 20,
}}
>
<div>
<ProInputCard title={t("splitBill.payAsCustomAmount")}>
<Form.Item
label={t("splitBill.amount")}
name="amount"
style={{ position: "relative", top: -5 }}
>
<InputNumber
value={amount}
onChange={(value) => setAmount(value?.toString() || "")}
placeholder={t("splitBill.amount")}
max={grandTotal.toString()}
min={"0"}
style={{ width: "100%", fontSize:"1rem" }}
/>
</Form.Item>
</ProInputCard>
</div>
<div
style={{
display: "flex",
gap: 12,
marginTop: 20,
}}
>
<Button style={{ flex: 1 }} onClick={handleRemoveSplitWay}>
{t("splitBill.removeSplitWay")}
</Button>
<Button
type="primary"
style={{ flex: 1, boxShadow: "none" }}
onClick={handleSave}
disabled={!amount || parseFloat(amount) <= 0}
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("cart.save")}
</Button>
{t("splitBill.howMuchWouldYouLikeToPay")}
</ProText>
<Form.Item
label={t("splitBill.amount")}
name="amount"
style={{ position: "relative", top: -5 }}
>
<InputNumber
value={amount}
onChange={(value) => {
setAmount(value?.toString() || "");
dispatch(updateSplitBillAmount(Number(value) || 0));
}}
placeholder={t("splitBill.amount")}
max={(grandTotal + splitBillAmount).toString()}
min={"0"}
style={{ width: "100%", fontSize: "1rem" }}
/>
</Form.Item>
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
backgroundColor: "var(--background)",
padding: "20px",
opacity: 1,
gap: 8,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
paddingTop: 12,
paddingRight: 24,
paddingBottom: 24,
paddingLeft: 24,
}}
>
<div className={styles.summaryRow}>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("splitBill.totalBill")}
</ProText>
<ArabicPrice price={previewTotalBill} />
</div>
<div className={styles.summaryRow}>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("splitBill.remainingToPay")}
</ProText>
<ArabicPrice price={Math.max(0, previewRemaining)} />
</div>
</div>
<div
style={{
display: "flex",
gap: 12,
margin: 20,
}}
>
<Button
style={{
flex: 1,
backgroundColor: "#FEEDED",
color: "#DD4143",
boxShadow: "none",
border: "none",
}}
onClick={handleRemoveSplitWay}
>
{t("splitBill.removeSplit")}
</Button>
<Button
type="primary"
style={{
flex: 1,
boxShadow: "none",
}}
onClick={handleSave}
disabled={!amount || parseFloat(amount) <= 0}
>
{t("splitBill.confirm")}
</Button>
</div>
</ProBottomSheet>
);
}

View File

@@ -3,8 +3,8 @@ import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet.tsx";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import PeopleIcon from "components/Icons/PeopleIcon";
import ProText from "components/ProText";
import ArabicPrice from "components/ArabicPrice";
import {
selectCart,
selectGrandTotal,
@@ -14,6 +14,7 @@ import { useAppDispatch, useAppSelector } from "redux/hooks";
import { ProGray1 } from "ThemeConstants";
import PayForActions from "../../../split-bill/components/PayForActions";
import TotalPeopleActions from "../../../split-bill/components/TotalPeopleActions";
import styles from "./SplitBill.module.css";
interface SplitBillChoiceBottomSheetProps {
isOpen: boolean;
@@ -34,20 +35,23 @@ export function EqualltyChoiceBottomSheet({
}: SplitBillChoiceBottomSheetProps) {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { tmp } = useAppSelector(selectCart);
const { tmp, splitBillAmount } = useAppSelector(selectCart);
const grandTotal = useAppSelector(selectGrandTotal);
const splitBillTmp = tmp as SplitBillTmp;
const totalPeople = splitBillTmp?.totalPeople || 2;
const payFor = splitBillTmp?.payFor || 1;
// Calculate split amount
// Calculate original total bill (grandTotal already subtracts splitBillAmount)
const originalTotalBill = grandTotal + splitBillAmount;
// Calculate split amount based on original total bill
const splitAmount = useMemo(() => {
if (totalPeople > 0) {
return (grandTotal / totalPeople) * payFor;
return (originalTotalBill / totalPeople) * payFor;
}
return 0;
}, [grandTotal, totalPeople, payFor]);
}, [originalTotalBill, totalPeople, payFor]);
const handleSave = () => {
dispatch(updateSplitBillAmount(splitAmount));
@@ -67,8 +71,11 @@ export function EqualltyChoiceBottomSheet({
title={t("splitBill.divideTheBillEqually")}
showCloseButton={true}
initialSnap={1}
height={610}
snapPoints={[610]}
height={630}
snapPoints={[630]}
contentStyle={{
padding: 0,
}}
>
<div
style={{
@@ -77,96 +84,6 @@ export function EqualltyChoiceBottomSheet({
marginTop: 20,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
gap: "1rem",
padding: 8,
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "1rem",
}}
>
<Button
type="text"
shape="circle"
style={{
backgroundColor: "rgba(95, 108, 123, 0.05)",
position: "relative",
top: -10,
}}
>
<PeopleIcon />
</Button>
<ProText
style={{
fontSize: "1rem",
marginTop: 3,
color: ProGray1,
}}
>
{t("checkout.totalPeople")}
</ProText>
</div>
<TotalPeopleActions />
</div>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
gap: "1rem",
padding: 8,
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "1rem",
}}
>
<Button
type="text"
shape="circle"
style={{
backgroundColor: "rgba(95, 108, 123, 0.05)",
position: "relative",
top: -10,
}}
>
<PeopleIcon />
</Button>
<ProText
style={{
fontSize: "1rem",
marginTop: 2,
color: ProGray1,
}}
>
{t("checkout.payFor")}
</ProText>
</div>
<PayForActions />
</div>
</div>
{/* Spinner Visualization - Blank Spin Wheel */}
{totalPeople > 0 && (
<div
@@ -203,7 +120,7 @@ export function EqualltyChoiceBottomSheet({
const startAngleRad = (startAngle * Math.PI) / 180;
const endAngleRad = (endAngle * Math.PI) / 180;
// Calculate path for pie slice
// Calculate path for pie slice (fit 200x200 viewBox)
const radius = 90;
const centerX = 100;
const centerY = 100;
@@ -230,57 +147,189 @@ export function EqualltyChoiceBottomSheet({
isSelected ? "var(--primary)" : "rgba(0, 0, 0, 0.1)"
}
stroke="#fff"
strokeWidth="2"
strokeWidth="5"
/>
);
})}
</svg>
{/* Center circle to make it look like a blank wheel */}
{/* Center circle with total bill amount */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 60,
height: 60,
// Keep the SVG at 200x200, but make the center content smaller
// so the wheel remains visible around it.
width: 160,
height: 160,
borderRadius: "50%",
backgroundColor: "var(--background)",
border: "3px solid var(--primary)",
backgroundColor: "var(--secondary-background)",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
fontSize: 18,
fontWeight: 600,
color: "var(--primary)",
zIndex: 10,
padding: 10,
}}
>
{payFor}
<div
style={{
fontSize: 20,
fontWeight: 600,
color: "var(--primary)",
lineHeight: 1.1,
textAlign: "center",
}}
>
<ArabicPrice
price={originalTotalBill}
style={{
fontSize: 20,
fontWeight: 600,
}}
/>
</div>
<ProText
style={{
fontSize: 11,
color: ProGray1,
marginTop: 6,
textAlign: "center",
lineHeight: 1.2,
fontWeight: 400,
}}
>
{t("splitBill.totalBill")}
</ProText>
</div>
</div>
<ProText
style={{
textAlign: "center",
marginTop: 12,
fontSize: 16,
fontWeight: 600,
}}
>
{t("splitBill.yourAmount")}: {splitAmount.toFixed(2)}
</ProText>
</div>
)}
<div
style={{
display: "flex",
gap: 12,
marginTop: 20,
flexDirection: "column",
gap: "1rem",
padding: 20,
}}
>
<Button style={{ flex: 1 }} onClick={handleRemoveSplitWay}>
{t("splitBill.removeSplitWay")}
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
gap: "1rem",
padding: 8,
}}
>
<ProText
style={{
fontSize: "1rem",
marginTop: 3,
color: ProGray1,
}}
>
{t("checkout.totalPeople")}
</ProText>
<TotalPeopleActions />
<ProText
style={{
fontSize: "1rem",
marginTop: 3,
color: ProGray1,
}}
>
{t("checkout.totalPeople")}
</ProText>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
gap: "1rem",
padding: 8,
}}
>
<ProText
style={{
fontSize: "1rem",
marginTop: 2,
color: ProGray1,
}}
>
{t("checkout.payFor")}
</ProText>
<PayForActions />
<ProText
style={{
fontSize: "1rem",
marginTop: 2,
color: ProGray1,
}}
>
{t("checkout.payFor")}
</ProText>
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
backgroundColor: "var(--background)",
padding: 20,
opacity: 1,
gap: 8,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
paddingTop: 12,
paddingRight: 24,
paddingBottom: 24,
paddingLeft: 24,
}}
>
<div className={styles.summaryRow}>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("splitBill.yourShare")}
</ProText>
<ArabicPrice price={splitAmount} />
</div>
</div>
<div
style={{
display: "flex",
gap: 12,
margin: 20,
}}
>
<Button
style={{
flex: 1,
backgroundColor: "#FEEDED",
color: "#DD4143",
boxShadow: "none",
border: "none",
}}
onClick={handleRemoveSplitWay}
>
{t("splitBill.removeSplit")}
</Button>
<Button
type="primary"

View File

@@ -1,4 +1,4 @@
import { Button, Card, Image } from "antd";
import { Button, Card, Checkbox, Divider, Image } from "antd";
import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet.tsx";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -7,6 +7,7 @@ import ArabicPrice from "components/ArabicPrice";
import ProText from "components/ProText";
import { selectCart, updateSplitBillAmount } from "features/order/orderSlice";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import styles from "./SplitBill.module.css";
interface SplitBillChoiceBottomSheetProps {
isOpen: boolean;
@@ -72,13 +73,13 @@ export function PayForYourItemsChoiceBottomSheet({
title={t("splitBill.payForYourItems")}
showCloseButton={true}
initialSnap={1}
height={745}
snapPoints={[745]}
height={720}
snapPoints={[720]}
contentStyle={{ padding: 0 }}
>
<div
style={{
padding: "0 20px",
margin: "20px 0",
padding: 20,
display: "flex",
flexDirection: "column",
gap: 12,
@@ -101,65 +102,71 @@ export function PayForYourItemsChoiceBottomSheet({
const itemTotal = item.price * item.quantity;
return (
<Card
key={itemId}
style={{
border: "none",
cursor: "pointer",
}}
onClick={() => handleItemToggle(itemId)}
>
<div
<>
<Card
key={itemId}
style={{
display: "flex",
flexDirection: "row",
gap: 12,
alignItems: "center",
border: "none",
cursor: "pointer",
padding: 0,
}}
onClick={() => handleItemToggle(itemId)}
>
<Image
src={item.image}
alt={item.name}
width={60}
height={60}
preview={false}
style={{
borderRadius: 8,
objectFit: "cover",
}}
/>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
<ProText style={{ fontSize: 14, fontWeight: 500 }}>
{item.name}
</ProText>
<ProText type="secondary" style={{ fontSize: 12 }}>
{t("cart.quantity")}: {item.quantity}
</ProText>
<ArabicPrice price={itemTotal} />
</div>
<Button
type={isSelected ? "primary" : "default"}
shape="circle"
style={{
width: 32,
height: 32,
minWidth: 32,
display: "flex",
flexDirection: "row",
gap: 12,
alignItems: "center",
justifyContent: "center",
}}
>
{isSelected ? "✓" : "+"}
</Button>
</div>
</Card>
<Image
src={item.image}
alt={item.name}
width={60}
height={60}
preview={false}
style={{
borderRadius: 8,
objectFit: "cover",
}}
/>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
<ProText style={{ fontSize: 14, fontWeight: 500 }}>
{item.name}
</ProText>
<ArabicPrice price={itemTotal} />
</div>
<div
style={{
width: 32,
height: 32,
minWidth: 32,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={(e) => e.stopPropagation()}
>
<Checkbox
className={styles.circleCheckbox}
checked={isSelected}
onChange={() => handleItemToggle(itemId)}
/>
</div>
</div>
</Card>
{item !== items[items.length - 1] && (
<Divider style={{ margin: "0" }} />
)}
</>
);
})
)}
@@ -189,11 +196,20 @@ export function PayForYourItemsChoiceBottomSheet({
style={{
display: "flex",
gap: 12,
marginTop: 20,
margin: 20,
}}
>
<Button style={{ flex: 1 }} onClick={handleRemoveSplitWay}>
{t("splitBill.removeSplitWay")}
<Button
style={{
flex: 1,
backgroundColor: "#FEEDED",
color: "#DD4143",
boxShadow: "none",
border: "none",
}}
onClick={handleRemoveSplitWay}
>
{t("splitBill.removeSplit")}
</Button>
<Button
type="primary"

View File

@@ -19,7 +19,6 @@
height: 48px !important;
}
.backToHomePage {
width: 100%;
height: 48px;
@@ -46,3 +45,45 @@
width: 24px;
height: 24px;
}
.summaryRow {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
}
/* Make AntD checkbox look like a circular check indicator (scoped via CSS modules) */
.circleCheckbox :global(.ant-checkbox-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #D5D8DA;
background: transparent;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
/* Replace AntD checkmark with a filled inner circle when checked (match SVG) */
.circleCheckbox :global(.ant-checkbox-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}

View File

@@ -0,0 +1,90 @@
.quantityControls {
display: flex;
align-items: center;
border-radius: 888px;
width: fit-content;
}
.quantityLabel {
font-size: 14px;
color: var(--secondary-color);
font-weight: 500;
}
.quantityInputContainer {
display: flex;
align-items: center;
padding: 0 1px;
border-radius: 888px;
width: 140px;
height: 48px;
}
.quantityButton {
padding: 0;
width: 25px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
color: var(--secondary-color);
transition: all 0.2s ease;
}
.quantityInput {
text-align: center;
border: none;
box-shadow: none;
font-size: 16px;
font-weight: 600;
}
.removeButton {
padding: 4px 0;
height: 48px;
display: flex;
align-items: center;
gap: 4px;
width: 30px;
}
.deleteButtonContainer {
position: absolute;
top: 12px;
right: 12px;
background-color: var(--primary);
border-radius: 50%;
padding: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.deleteIcon {
font-size: 18px;
color: var(--secondary-color);
}
.cartItemActions :global(.ant-input-number-outlined) {
border: none;
width: 40px;
background-color: inherit;
text-align: center;
}
.cartItemActions :global(.ant-input-number-input) {
text-align: center !important;
}
.plusIcon {
margin-bottom: 1px;
color: #1F1C2E;
}
.minusIcon {
color: var(--secondary-foreground);
}
.deleteIcon {
position: relative;
right: 1px;
}

View File

@@ -0,0 +1,125 @@
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
import { Button, Grid, InputNumber, Popconfirm } from "antd";
import DeleteIcon from "components/Icons/DeleteIcon";
import { useTranslation } from "react-i18next";
import { useAppSelector } from "redux/hooks";
import styles from "./ActionsButtons.module.css";
import { colors } from "ThemeConstants";
const { useBreakpoint } = Grid;
export default function ActionsButtons({
quantity,
setQuantity,
max,
min,
}: {
quantity: number;
setQuantity: (quantity: number) => void;
max?: number;
min?: number;
}) {
const { t } = useTranslation();
const { xs } = useBreakpoint(); // Default to desktop
const { isRTL } = useAppSelector((state) => state.locale); // Default to LTR
const getPopconfirmOverlayStyle = () => ({
width: xs ? "280px" : "auto",
maxWidth: "320px",
".antPopconfirmMessageTitle": {
fontSize: xs ? "14px" : "16px",
paddingRight: xs ? "24px" : "0",
},
".antPopconfirmMessageContent": {
fontSize: xs ? "13px" : "14px",
marginTop: "4px",
},
".antPopconfirmButtons": {
marginTop: "12px",
".ant-btn": {
fontSize: xs ? "13px" : "14px",
height: xs ? "28px" : "32px",
padding: xs ? "0 12px" : "4px 15px",
},
},
});
return (
<div className={styles.cartItemActions}>
<div className={styles.quantityControls}>
<div className={styles.quantityInputContainer}>
{quantity > 0 ? (
<Button
shape="circle"
iconPlacement="start"
icon={<MinusOutlined title="add" className={styles.minusIcon} />}
size="small"
onClick={() => setQuantity(Math.max(1, quantity - 1))}
className={styles.quantityButton}
{...(min && { disabled: quantity === min })}
style={{
width: 48,
height: 48,
minWidth: 48,
borderColor: "#DEDEE0",
}}
/>
) : (
<Popconfirm
title={t("cart.deleteConfirmation.title")}
description={t("cart.deleteConfirmation.content")}
onConfirm={() => setQuantity(0)}
okText={t("cart.deleteConfirmation.confirm")}
cancelText={t("cart.deleteConfirmation.cancel")}
okButtonProps={{ danger: true }}
placement={isRTL ? "left" : "right"}
styles={{
root: getPopconfirmOverlayStyle(),
}}
>
<Button
shape="circle"
iconPlacement="start"
icon={<DeleteIcon />}
size="small"
className={styles.addButton}
style={{
background: "#FEF2F2",
width: 48,
height: 48,
border: "none",
minWidth: 48,
}}
/>
</Popconfirm>
)}
<InputNumber
min={min || 1}
max={max || 100}
value={quantity || 1}
onChange={(value: number | null) => setQuantity(value || 1)}
size="small"
controls={false}
className={styles.quantityInput}
name="id"
/>
<Button
shape="circle"
iconPlacement="start"
icon={<PlusOutlined title="add" className={styles.plusIcon} />}
size="small"
onClick={() => setQuantity(Math.min(100, quantity + 1))}
className={styles.quantityButton}
{...(max && { disabled: quantity >= max })}
style={{
width: 48,
height: 48,
borderColor: "#DEDEE0",
minWidth: 48,
}}
/>
</div>
</div>
</div>
);
}

View File

@@ -1,12 +1,7 @@
import {
LeftOutlined,
RightOutlined,
ShoppingCartOutlined,
} from "@ant-design/icons";
import { Button, Form, Input, message, Row } from "antd";
import { ShoppingCartOutlined } from "@ant-design/icons";
import { Button, Form, message, Row } from "antd";
import { addItem } from "features/order/orderSlice";
import useBreakPoint from "hooks/useBreakPoint";
import { BottomSheet } from "pages/cart/components/specialRequest/BottomSheet.tsx";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks";
@@ -15,6 +10,9 @@ import { Extra, Product, Variant } from "utils/types/appTypes";
import styles from "../product.module.css";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { useParams } from "react-router-dom";
import TextArea from "antd/es/input/TextArea";
import ProText from "components/ProText";
import ActionsButtons from "./ActionsButtons/ActionsButtons";
export default function ProductFooter({
product,
@@ -24,6 +22,7 @@ export default function ProductFooter({
selectedGroups,
quantity,
onClose,
setQuantity,
}: {
product: Product;
isValid?: boolean;
@@ -32,11 +31,11 @@ export default function ProductFooter({
selectedGroups: Record<number, string[]>;
quantity: number;
onClose?: () => void;
setQuantity: (quantity: number) => void;
}) {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { themeName } = useAppSelector((state) => state.theme);
const [isSpecialRequestOpen, setIsSpecialRequestOpen] = useState(false);
const { isMobile, isDesktop } = useBreakPoint();
const { isRTL } = useAppSelector((state) => state.locale);
const [specialRequest, setSpecialRequest] = useState("");
@@ -122,14 +121,6 @@ export default function ProductFooter({
}
};
const handleSpecialRequestSave = (value: string) => {
setSpecialRequest(value);
};
const handleSpecialRequestClose = () => {
setIsSpecialRequestOpen(false);
};
return (
<>
<Row
@@ -149,7 +140,7 @@ export default function ProductFooter({
[isRTL ? "right" : "left"]: 0,
width: hasCustomizationOptions ? "50%" : "100%",
}),
height: "135px",
height: "195px",
}}
>
<div
@@ -160,84 +151,79 @@ export default function ProductFooter({
gap: "12px",
}}
>
<ProText>{t("cart.specialRequest")}</ProText>
<Form.Item style={{ position: "relative", top: -5, width: "100%" }}>
<TextArea
value={specialRequest}
rows={2}
placeholder={t("cart.specialRequest")}
size="large"
autoFocus={false}
className={styles.inputField}
onChange={(e) => setSpecialRequest(e.target.value)}
/>
</Form.Item>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
gap: "12px",
width: "100%",
}}
>
<Form.Item style={{ position: "relative", top: -5, width: "100%" }}>
<Input
value={specialRequest}
placeholder={t("cart.specialRequest")}
size="large"
autoFocus={false}
className={styles.inputField}
onChange={(e) => setSpecialRequest(e.target.value)}
suffix={
<div
className={styles.editButton}
onClick={() => setIsSpecialRequestOpen(true)}
>
<u>{t("cart.editNote")}</u>{" "}
{isRTL ? <LeftOutlined /> : <RightOutlined />}
</div>
}
<ActionsButtons
quantity={quantity}
setQuantity={setQuantity}
max={100}
min={1}
/>
</Form.Item>
</div>
<div
style={{
display: "flex",
gap: "12px",
width: "100%",
}}
>
<Button
type="primary"
icon={<ShoppingCartOutlined />}
onClick={handleAddToCart}
disabled={!isValid}
style={{
flex: 1,
height: "48px",
fontSize: isMobile ? "1rem" : "16px",
transition: "all 0.3s ease",
width: "100%",
borderRadius: 888,
boxShadow: "none",
backgroundColor: isValid
? colors.primary
: "rgba(233, 233, 233, 1)",
color: isValid ? "#FFF" : "#999",
cursor: isValid ? "pointer" : "not-allowed",
}}
onMouseEnter={(e) => {
if (!isMobile && isValid) {
e.currentTarget.style.transform = "translateY(-2px)";
}
}}
onMouseLeave={(e) => {
if (!isMobile) {
e.currentTarget.style.transform = "translateY(0)";
}
}}
>
{isValid ? t("menu.addToCart") : t("menu.selectRequiredOptions")}
</Button>
<Button
type="primary"
icon={<ShoppingCartOutlined />}
onClick={handleAddToCart}
disabled={!isValid}
style={{
flex: 1,
height: 48,
fontSize: isMobile ? "1rem" : "16px",
transition: "all 0.3s ease",
width: "100%",
borderRadius: 888,
boxShadow: "none",
backgroundColor: isValid
? colors.primary
: "rgba(233, 233, 233, 1)",
color: isValid ? "#FFF" : "#999",
cursor: isValid ? "pointer" : "not-allowed",
}}
onMouseEnter={(e) => {
if (!isMobile && isValid) {
e.currentTarget.style.transform = "translateY(-2px)";
}
}}
onMouseLeave={(e) => {
if (!isMobile) {
e.currentTarget.style.transform = "translateY(0)";
}
}}
>
{isValid
? t("menu.addToCart")
: t("menu.selectRequiredOptions")}
</Button>
</div>
</div>
</Row>
{!isDesktop && isSpecialRequestOpen && (
{/* {!isDesktop && isSpecialRequestOpen && (
<BottomSheet
isOpen={isSpecialRequestOpen}
onClose={handleSpecialRequestClose}
initialValue={specialRequest}
onSave={handleSpecialRequestSave}
/>
)}
)} */}
</>
);
}

View File

@@ -110,7 +110,7 @@ export default function Variants({
<>
{variantsList?.length > 0 && variantLevels.length > 0 && (
<>
{!isDesktop && <Divider style={{ margin: "0" }} />}
{!isDesktop && <Divider style={{ margin: "0 0 10px 0" }} />}
<div>
<div
style={{

View File

@@ -1,4 +1,3 @@
import ActionsButtons from "components/ActionsButtons/ActionsButtons";
import ImageWithFallback from "components/ImageWithFallback";
import { ItemDescriptionIcons } from "components/ItemDescriptionIcons/ItemDescriptionIcons";
import ProText from "components/ProText";
@@ -177,7 +176,7 @@ export default function ProductDetailPage({
return (
<div
style={{
height: "80vh",
height: "75vh",
overflow: "auto",
scrollbarWidth: "none",
}}
@@ -221,12 +220,16 @@ export default function ProductDetailPage({
<div className={styles.productHeader}>
<div className={styles.productDetails}>
<ProText
strong
style={{ fontSize: isDesktop ? "1.5rem" : "1.25rem" }}
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: "14px",
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{isRTL ? product?.nameOther : product?.name}
</ProText>
<br />
{product?.description && (
<ProText
type="secondary"
@@ -239,24 +242,19 @@ export default function ProductDetailPage({
textOverflow: "ellipsis",
wordWrap: "break-word",
overflowWrap: "break-word",
lineHeight: "1.4",
maxHeight: "2.8em",
fontWeight: "500",
letterSpacing: "0.01em",
fontSize: "1rem",
fontWeight: 400,
fontStyle: "Regular",
fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
marginTop: 10,
}}
>
{isRTL ? product?.descriptionAR : product?.description}
</ProText>
)}
<ArabicPrice
price={product?.price}
style={{
fontSize: isDesktop ? "1.2rem" : "1rem",
color: colors.primary,
marginTop: "12px",
}}
/>
<div
style={{
display: "flex",
@@ -271,14 +269,13 @@ export default function ProductDetailPage({
className={styles.itemDescriptionIcons}
/>
</div>
</div>
<div className={styles.quantitySection}>
<ActionsButtons
quantity={quantity}
setQuantity={(quantity) => setQuantity(quantity)}
max={100}
min={1}
<ArabicPrice
price={product?.price}
style={{
fontSize: isDesktop ? "1.2rem" : "1rem",
color: colors.primary,
marginTop: "12px",
}}
/>
</div>
</div>
@@ -293,6 +290,7 @@ export default function ProductDetailPage({
selectedGroups={selectedExtrasByGroup}
quantity={quantity}
onClose={onClose}
setQuantity={(quantity: number) => setQuantity(quantity)}
/>
)}
</div>
@@ -301,7 +299,7 @@ export default function ProductDetailPage({
{hasCustomizationOptions && (
<div className={isDesktop ? styles.rightColumn : styles.fullWidth}>
<Space
direction="vertical"
orientation="vertical"
size="middle"
style={{ width: "100%", padding: "0 1rem" }}
>
@@ -341,6 +339,7 @@ export default function ProductDetailPage({
selectedExtras={selectedExtras}
selectedGroups={selectedExtrasByGroup}
quantity={quantity}
setQuantity={(quantity: number) => setQuantity(quantity)}
/>
)}
</div>

View File

@@ -232,7 +232,7 @@
align-items: center;
justify-content: flex-end;
position: relative;
top: 5px
top: 5px;
}
/* Dark mode desktop styles */
@@ -320,8 +320,9 @@
}
.inputField {
height: 50px;
height: 100px;
width: 100% !important;
border-radius: 6px;
}
.editButton {
@@ -329,4 +330,3 @@
font-size: 14px;
cursor: pointer;
}