Compare commits
10 Commits
7b47d47e78
...
ed23b10240
| Author | SHA1 | Date | |
|---|---|---|---|
| ed23b10240 | |||
| e43675d018 | |||
| 58b2258339 | |||
| 8e857c88f6 | |||
| 02e1e42496 | |||
| 9a3608d5ab | |||
| c369807f40 | |||
| 76a06cc3ee | |||
| 3c692d8e7e | |||
| 8d5351d82b |
@@ -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": "المبلغ المتبقي"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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={() =>
|
||||
|
||||
@@ -110,7 +110,7 @@ export function GiftBottomSheet({
|
||||
|
||||
<ProPhoneInput
|
||||
propName="senderPhone"
|
||||
label={t("address.receiverPhone")}
|
||||
label={t("address.senderPhone")}
|
||||
/>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
gap: 1rem;
|
||||
background-color: var(--secondary-background);
|
||||
box-shadow: none;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.splitBillButton {
|
||||
|
||||
@@ -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" }}>
|
||||
|
||||
@@ -73,6 +73,7 @@ export default function TableNumberCard() {
|
||||
width: "100%",
|
||||
height: 50,
|
||||
fontSize: 12,
|
||||
borderRadius: 888,
|
||||
}}
|
||||
onChange={(value) => {
|
||||
console.log(value);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
125
src/pages/product/components/ActionsButtons/ActionsButtons.tsx
Normal file
125
src/pages/product/components/ActionsButtons/ActionsButtons.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user