Compare commits

..

4 Commits

Author SHA1 Message Date
20ef4f416c working on car card & car view BS 2026-01-05 23:30:03 +03:00
c8bf8ff621 working on tip card and BS 2026-01-05 20:00:54 +03:00
ab265bf09a CouponBottomSheet: ehancements 2026-01-05 15:37:05 +03:00
8563d90e8f update coupon code 2026-01-05 08:50:11 +03:00
20 changed files with 832 additions and 128 deletions

View File

@@ -268,10 +268,13 @@
"checkRequiredFields": "يرجى التحقق من الحقول المطلوبة"
},
"checkout": {
"addCarDetails": "إضافة تفاصيل السيارة",
"soTheRestaurantCanRecognizeYourCarWhenYouArrive": "لتتمكن المطعم من التعرف على سيارتك عند وصولك.",
"customerName": "اسم العميل",
"paymentSummary": "ملخص الدفع",
"holdayGiftCard": "هدية العيد",
"messageIncluded": "الرسالة مضمنة",
"save":"حفظ",
"to": "ل",
"giftSummary": "ملخص الهدية",
"customerInformation": "تفاصيل العميل",
@@ -505,5 +508,9 @@
"add": "أضف",
"senderNameRequired": "يجب أن يكون اسم المرسل مطلوب",
"receiverNameRequired": "يجب أن يكون اسم المستلم مطلوب"
},
"car":{
"addCar":"إضافة سيارة",
"selectCar":"اختر السيارة"
}
}

View File

@@ -279,7 +279,10 @@
"checkRequiredFields": "Please check required fields"
},
"checkout": {
"addCarDetails": "Add Car Details",
"soTheRestaurantCanRecognizeYourCarWhenYouArrive": "So the restaurant can recognize your car when you arrive.",
"customerName": "Customer Name",
"save": "Save",
"paymentSummary": "Payment Summary",
"orderSummary": "Order Summary",
"holdayGiftCard": "Holday gift card",
@@ -517,5 +520,9 @@
"add": "Add",
"senderNameRequired": "Sender name is required",
"receiverNameRequired": "Receiver name is required"
},
"car": {
"addCar": "Add Car",
"selectCar": "Select Car"
}
}

View File

@@ -2,6 +2,10 @@ import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import ProRatioGroups from "../ProRatioGroups/ProRatioGroups";
import { colors } from "ThemeConstants";
import { updateCoupon } from "features/order/orderSlice";
import { useAppDispatch } from "redux/hooks";
import { Button } from "antd";
interface CouponBottomSheetProps {
isOpen: boolean;
@@ -18,13 +22,14 @@ export function CouponBottomSheet({
}: CouponBottomSheetProps) {
const { t } = useTranslation();
const [value, setValue] = useState(initialValue);
const dispatch = useAppDispatch();
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const handleSave = () => {
onSave(value);
dispatch(updateCoupon(value));
onClose();
};
@@ -38,32 +43,65 @@ export function CouponBottomSheet({
isOpen={isOpen}
onClose={handleCancel}
title={t("cart.coupon")}
showCloseButton={false}
initialSnap={1}
height={350}
snapPoints={["30vh"]}
height={385}
snapPoints={[385]}
>
<div>
<div style={{ padding: "16px 0" }}>
<ProRatioGroups
options={[
{
label: "50% off, Min order : SDG 10,000",
value: "50",
price: "0"
value: "7CAB1",
price: "7CAB1",
},
{
label: "Buy one get one free, Min order : SDG 5,000",
value: "buy",
price: "0"
value: "7CAB2",
price: "7CAB2",
},
{
label: "30% off on select items, Min order : SDG 15,000",
value: "30",
price: "0"
value: "7CABO",
price: "7CABO",
},
]}
onRatioClick={handleSave}
value={value}
onRatioClick={(value) => setValue(value)}
showDivider={true}
optionsStyle={{
fontSize: 12,
fontWeight: 500,
color: "#5F6C7B",
}}
valueStyle={{
fontSize: 12,
fontWeight: 500,
color: colors.primary,
}}
/>
<div
style={{
display: "flex",
gap: 12,
marginTop: 20,
}}
>
<Button
type="primary"
style={{
flex: 1,
boxShadow: "none",
height: 48,
width: "100%",
}}
onClick={handleSave}
disabled={!value}
>
{t("cart.save")}
</Button>
</div>
</div>
</ProBottomSheet>
);

View File

@@ -17,8 +17,6 @@
width: 100%;
}
.serviceIcon path {
stroke: #ea1f22;
}
@@ -32,3 +30,96 @@
width: 24px;
height: 24px;
}
/* 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%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-checkbox-inner::after) {
left: auto;
right: 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;
}
:global(.ant-app-rtl)
.circleCheckbox
:global(.ant-checkbox-checked .ant-checkbox-inner::after) {
margin-left: auto;
margin-right: -9px;
}
/* Apply same circular style to Radio buttons */
.radioCheckbox :global(.ant-radio-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.radioCheckbox :global(.ant-radio-checked .ant-radio-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
.radioCheckbox :global(.ant-radio-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .radioCheckbox :global(.ant-radio-inner::after) {
left: auto;
right: 50%;
}
.radioCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl)
.radioCheckbox
:global(.ant-radio-checked .ant-radio-inner::after) {
margin-left: auto;
margin-right: -9px;
}

View File

@@ -1,23 +1,25 @@
import { Button, Input } from "antd";
import { Button, Input, InputNumber } from "antd";
import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import { updateTip } from "features/order/orderSlice";
import { useAppDispatch } from "redux/hooks";
import ProText from "components/ProText";
interface TipBottomSheetProps {
isOpen: boolean;
onClose: () => void;
initialValue: string;
onSave: (value: string) => void;
}
export function TipBottomSheet({
isOpen,
onClose,
initialValue,
onSave,
}: TipBottomSheetProps) {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const [value, setValue] = useState(initialValue);
useEffect(() => {
@@ -25,7 +27,8 @@ export function TipBottomSheet({
}, [initialValue]);
const handleSave = () => {
onSave(value);
const numAmount = parseFloat(value) || 0;
dispatch(updateTip(numAmount.toString()));
onClose();
};
@@ -39,10 +42,9 @@ export function TipBottomSheet({
isOpen={isOpen}
onClose={handleCancel}
title={t("cart.tip")}
showCloseButton={false}
initialSnap={1}
height={370}
snapPoints={[370]}
height={380}
snapPoints={[380]}
>
<div
style={{
@@ -50,28 +52,43 @@ export function TipBottomSheet({
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 10,
}}
>
<div style={{ marginTop: 20 }}>
<WaiterRewardingIcon />
</div>
<br />
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
textAlign: "left",
width: "100%",
}}
>
{t("cart.amount")}
</ProText>
<Input
<InputNumber
value={value}
onChange={(e) => setValue(e.target.value)}
onChange={(value) => setValue(value?.toString() || "")}
placeholder={t("cart.amount")}
autoFocus={false}
size="large"
style={{ height: 48, width: "100%", marginBottom: 8 }}
min={"0"}
/>
<br />
<Button
type="primary"
style={{ width: "100%", height: 48 }}
onClick={handleSave}
disabled={value === "" || parseFloat(value) <= 0}
>
{t("cart.addTip")}
</Button>

View File

@@ -2,29 +2,27 @@ import { Button, Input, Modal } from "antd";
import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAppDispatch } from "redux/hooks";
import { updateTip } from "features/order/orderSlice";
interface TipDialogProps {
isOpen: boolean;
onClose: () => void;
initialValue: string;
onSave: (value: string) => void;
}
export function TipDialog({
isOpen,
onClose,
initialValue,
onSave,
}: TipDialogProps) {
export function TipDialog({ isOpen, onClose, initialValue }: TipDialogProps) {
const { t } = useTranslation();
const [value, setValue] = useState(initialValue);
const dispatch = useAppDispatch();
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const handleSave = () => {
onSave(value);
const numAmount = parseFloat(value) || 0;
dispatch(updateTip(numAmount.toString()));
onClose();
};

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,15 @@
interface PlusIconType {
className?: string;
onClick?: () => void;
dimesion?: string
dimension?: string
color?: string
}
const PlusIcon = ({ className, onClick, dimesion, color }: PlusIconType) => {
const PlusIcon = ({ className, onClick, dimension, color }: PlusIconType) => {
return (
<svg
width={dimesion || "16"}
height={dimesion || "16"}
width={dimension || "16"}
height={dimension || "16"}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,4 +1,4 @@
import { Radio, RadioChangeEvent, Space } from "antd";
import { Divider, Radio, RadioChangeEvent, Space } from "antd";
import ProText from "components/ProText";
import styles from "./ProRatioGroups.module.css";
@@ -7,6 +7,9 @@ interface ProRatioGroupsProps {
onRatioClick?: (value: string) => void;
onChange?: (e: RadioChangeEvent) => void;
value?: string;
optionsStyle?: React.CSSProperties;
valueStyle?: React.CSSProperties;
showDivider?: boolean;
}
const ProRatioGroups = ({
@@ -14,9 +17,13 @@ const ProRatioGroups = ({
onRatioClick,
onChange,
value,
optionsStyle,
valueStyle,
showDivider = false,
...props
}: ProRatioGroupsProps) => {
const handleChange = (e: RadioChangeEvent) => {
console.log(e.target.value);
// If onChange is provided (from Form.Item), use it
if (onChange) {
onChange(e);
@@ -39,30 +46,39 @@ const ProRatioGroups = ({
>
<Space orientation="vertical" style={{ width: "100%" }}>
{options.map((option) => (
<Radio
key={option.value}
value={option.value}
className={styles.circleCheckbox}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
padding: "8px 0",
}}
<>
<Radio
key={option.value}
value={option.value}
className={styles.circleCheckbox}
>
<ProText
<div
style={{
fontSize: "1rem",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
padding: "8px 0",
placeItems: "center",
}}
>
{option.label}
</ProText>
<ProText style={{ fontSize: "1rem" }}>{option?.price}</ProText>
</div>
</Radio>
<ProText
style={{
fontSize: "1rem",
...optionsStyle,
}}
>
{option.label}
</ProText>
<ProText style={{ fontSize: "1rem", ...valueStyle }}>
{option?.price}
</ProText>
</div>
</Radio>
{showDivider && options.length !== options.length - 1 && (
<Divider style={{ margin: "0 0 16px 0 " }} />
)}
</>
))}
</Space>
</Radio.Group>

View File

@@ -1,14 +1,9 @@
import { Button, Form } from "antd";
import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet.tsx";
import { useState, useEffect } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import {
selectCart,
selectGrandTotal,
updateGiftDetails,
updateSplitBillAmount,
} from "features/order/orderSlice";
import { selectCart, updateGiftDetails } from "features/order/orderSlice";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import ProText from "components/ProText";
import { ProInputNumber } from "components/Inputs/ProInputNumber";

View File

@@ -602,12 +602,16 @@
top: 1px;
}
.editIcon {
.editIconOnSide {
position: absolute;
top: -7px;
right: 5px;
}
.editIconMiddle {
position: relative;
}
:global(.ant-app-rtl) .editIcon {
right: auto;
left: 5px;

View File

@@ -242,7 +242,7 @@ export default function CartMobileTabletLayout({
);
}}
>
<PlusIcon dimesion="18" /> {t("cart.addMore")}
<PlusIcon dimension="18" /> {t("cart.addMore")}
</Button>
</Card>
</div>

View File

@@ -1,5 +1,8 @@
import { Button, Form, Input, message } from "antd";
import { CouponBottomSheet } from "components/CustomBottomSheet/CouponBottomSheet";
import { CouponDialog } from "components/CustomBottomSheet/CouponDialog";
import CouponHeartIcon from "components/Icons/cart/CouponHeart.tsx";
import DonateIcon from "components/Icons/cart/DonateIcon";
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import ProText from "components/ProText";
import {
@@ -7,20 +10,22 @@ import {
updateCoupon,
updateDiscount,
} from "features/order/orderSlice.ts";
import useBreakPoint from "hooks/useBreakPoint";
import styles from "pages/cart/cart.module.css";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useGetDiscountMutation } from "redux/api/others";
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
import { colors } from "ThemeConstants";
export default function CouponCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { restaurant } = useAppSelector((state) => state.order);
const { coupon } = useAppSelector(selectCart);
// const { isDesktop } = useBreakPoint();
const { isDesktop } = useBreakPoint();
const [getDiscount] = useGetDiscountMutation();
// const [isCouponOpen, setIsCouponOpen] = useState(false);
const [isCouponOpen, setIsCouponOpen] = useState(false);
const handleCouponSave = (value: string) => {
getDiscount({
@@ -43,36 +48,36 @@ export default function CouponCard() {
});
};
// const handleCouponClose = () => {
// setIsCouponOpen(false);
// };
const handleCouponClose = () => {
setIsCouponOpen(false);
};
return (
<>
<ProInputCard
title={t("cart.couponCode")}
// titleRight={
// <div
// style={{
// display: "flex",
// flexDirection: "row",
// alignItems: "center",
// gap: 10,
// }}
// onClick={() => setIsCouponOpen(true)}
// >
// <ProText
// style={{
// color: colors.primary,
// fontSize: 14,
// cursor: "pointer",
// }}
// >
// {t("cart.viewOffers")}
// </ProText>
// <DonateIcon />
// </div>
// }
titleRight={
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 10,
}}
onClick={() => setIsCouponOpen(true)}
>
<ProText
style={{
color: colors.primary,
fontSize: 14,
cursor: "pointer",
}}
>
{t("cart.viewOffers")}
</ProText>
<DonateIcon />
</div>
}
>
<Form.Item name="coupon">
<Input
@@ -111,7 +116,7 @@ export default function CouponCard() {
/>
</Form.Item>
</ProInputCard>
{/* {isDesktop ? (
{isDesktop ? (
<CouponDialog
isOpen={isCouponOpen}
onClose={handleCouponClose}
@@ -125,7 +130,7 @@ export default function CouponCard() {
initialValue={coupon}
onSave={handleCouponSave}
/>
)} */}
)}
</>
);
}

View File

@@ -5,11 +5,10 @@ import { TipDialog } from "components/CustomBottomSheet/TipDialog.tsx";
import DonateHandIcon from "components/Icons/cart/DonateHandIcon.tsx";
import EditIcon from "components/Icons/EditIcon.tsx";
import ProText from "components/ProText.tsx";
import ProTitle from "components/ProTitle.tsx";
import { selectCart, updateTip } from "features/order/orderSlice.ts";
import useBreakPoint from "hooks/useBreakPoint.ts";
import styles from "pages/cart/cart.module.css";
import { useState } from "react";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
@@ -18,12 +17,18 @@ export default function RewardWaiterCard() {
const dispatch = useAppDispatch();
const { tip } = useAppSelector(selectCart);
const { isDesktop } = useBreakPoint();
const [selectedTip, setSelectedTip] = useState<number | null>(null);
const [isTipOpen, setIsTipOpen] = useState(false);
const handleTipSave = (value: string) => {
dispatch(updateTip(value));
message.success(t("cart.tip") + " " + t("updatedSuccessfully"));
const isDefaultTip = useMemo(() => {
const amount = parseFloat(tip);
return amount === 0.5 || amount === 1.0 || amount === 3.0;
}, [tip]);
const handleTipClick = (value: number) => {
setSelectedTip(value);
dispatch(updateTip(value.toString()));
};
const handleTipClose = () => {
@@ -86,9 +91,9 @@ export default function RewardWaiterCard() {
<Divider style={{ margin: "15px 0 15px 0" }} />
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-around",
display: "grid",
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
gap: 16,
}}
>
<Button
@@ -96,8 +101,11 @@ export default function RewardWaiterCard() {
borderRadius: 100,
height: 32,
border: "none",
backgroundColor: "#5F6C7B0D",
backgroundColor:
selectedTip === 0.5 && isDefaultTip ? "#FFB7001F" : "#5F6C7B0D",
}}
iconPlacement="end"
onClick={() => handleTipClick(0.5)}
>
<ArabicPrice
price={0.5}
@@ -108,7 +116,8 @@ export default function RewardWaiterCard() {
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#5F6C7B",
color:
selectedTip === 0.5 && isDefaultTip ? "#CC9300" : "#5F6C7B",
}}
/>
</Button>
@@ -117,8 +126,10 @@ export default function RewardWaiterCard() {
borderRadius: 100,
height: 32,
border: "none",
backgroundColor: "#5F6C7B0D",
backgroundColor:
selectedTip === 1 && isDefaultTip ? "#FFB7001F" : "#5F6C7B0D",
}}
onClick={() => handleTipClick(1)}
>
<ArabicPrice
price={1.0}
@@ -129,7 +140,8 @@ export default function RewardWaiterCard() {
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#5F6C7B",
color:
selectedTip === 1 && isDefaultTip ? "#CC9300" : "#5F6C7B",
}}
/>
</Button>
@@ -137,24 +149,46 @@ export default function RewardWaiterCard() {
style={{
borderRadius: 100,
height: 32,
border: "none",
backgroundColor: "#FFB7001F",
backgroundColor: tip && !isDefaultTip ? "#FFB7001F" : "inherit",
border: tip && !isDefaultTip ? "none" : "1px solid #C0BFC4",
}}
icon={<EditIcon className={styles.editIcon} />}
iconPlacement="end"
icon={
<EditIcon
className={styles.editIcon}
color={tip && !isDefaultTip ? "#CC9300" : "#3D3B4A"}
/>
}
iconPlacement="start"
onClick={() => setIsTipOpen(true)}
>
<ArabicPrice
price={3.0}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#CC9300",
}}
/>
{tip && !isDefaultTip ? (
<ArabicPrice
price={parseFloat(tip)}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#CC9300",
}}
/>
) : (
<ProText
style={{
color: "#CC9300",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
}}
>
{t("cart.other")}
</ProText>
)}
</Button>
</div>
</Card>
@@ -163,14 +197,12 @@ export default function RewardWaiterCard() {
isOpen={isTipOpen}
onClose={handleTipClose}
initialValue={tip}
onSave={handleTipSave}
/>
) : (
<TipBottomSheet
isOpen={isTipOpen}
onClose={handleTipClose}
initialValue={tip}
onSave={handleTipSave}
/>
)}
</>

View File

@@ -0,0 +1,13 @@
.carCard {
gap: 20px;
opacity: 1;
border-radius: 6px;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.plusIcon {
position: relative;
top: 2px;
}

View File

@@ -0,0 +1,112 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet";
import { colors } from "ThemeConstants";
import { Button, Card } from "antd";
import CarRatioGroups from "./CarRatioGroups/CarRatioGroups";
import PlusIcon from "components/Icons/PlusIcon";
import styles from "../checkout.module.css";
interface CarBottomSheetProps {
isOpen: boolean;
onClose: () => void;
}
export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
const { t } = useTranslation();
const [value, setValue] = useState<string | null>(null);
const handleCancel = () => {
setValue(null);
onClose();
};
const handleSave = () => {
onClose();
setValue(value);
};
return (
<ProBottomSheet
isOpen={isOpen}
onClose={handleCancel}
title={t("checkout.addCarDetails")}
initialSnap={1}
height={575}
snapPoints={[575]}
>
<Card title={t("car.selectCar")} style={{ marginTop: 24 }}>
<CarRatioGroups
options={[
{
label: "",
value: "7CAB1",
price: "7CAB1",
},
{
label: "Buy one get one free, Min order : SDG 5,000",
value: "7CAB2",
price: "7CAB2",
},
{
label: "30% off on select items, Min order : SDG 15,000",
value: "7CABO",
price: "7CABO",
},
]}
value={value || undefined}
onRatioClick={(value) => setValue(value)}
optionsStyle={{
fontSize: 12,
fontWeight: 500,
color: "#5F6C7B",
}}
valueStyle={{
fontSize: 12,
fontWeight: 500,
color: colors.primary,
}}
/>
<Button
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 14,
lineHeight: "140%",
letterSpacing: 0,
width: "100%",
color: "#5F6C7B",
marginTop: 16,
}}
onClick={handleSave}
disabled={!value}
icon={<PlusIcon color="#5F6C7B" dimension="16" className={styles.plusIcon} />}
>
{t("car.addCar")}
</Button>
</Card>
<div
style={{
display: "flex",
gap: 12,
marginTop: 20,
}}
>
<Button
type="primary"
style={{
flex: 1,
boxShadow: "none",
height: 48,
width: "100%",
}}
onClick={handleSave}
disabled={!value}
>
{t("checkout.save")}
</Button>
</div>
</ProBottomSheet>
);
}

View File

@@ -0,0 +1,78 @@
import ProText from "components/ProText";
import { useTranslation } from "react-i18next";
import { useAppSelector } from "redux/hooks";
import BackIcon from "components/Icons/BackIcon";
import NextIcon from "components/Icons/NextIcon";
import CarIcon from "components/Icons/CarIcon";
import { Button, Card } from "antd";
import styles from "../checkout.module.css";
import { useState } from "react";
import { CarBottomSheet } from "./CarBottomSheet";
export function CarCard() {
const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale);
const [isCarBottomSheetOpen, setIsCarBottomSheetOpen] = useState(false);
return (
<>
<Card onClick={() => {
setIsCarBottomSheetOpen(true);
}}>
<div className={styles.carCard}>
<Button
shape="circle"
icon={<CarIcon />}
style={{
backgroundColor: "var(--background)",
border: "none",
width: 32,
height: 32,
minWidth: 32,
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.1)",
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 18,
lineHeight: "140%",
color: "#333333",
}}
>
{t("checkout.addCarDetails")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
color: "#777580",
}}
>
{t("checkout.soTheRestaurantCanRecognizeYourCarWhenYouArrive")}
</ProText>
</div>
{isRTL ? <BackIcon iconSize={24} /> : <NextIcon iconSize={24} />}
</div>
</Card>
<CarBottomSheet
isOpen={isCarBottomSheetOpen}
onClose={() => setIsCarBottomSheetOpen(false)}
/>
</>
);
}

View File

@@ -0,0 +1,122 @@
.proRatioGroups :global(.ant-radio-wrapper:last-child) {
width: 100% !important;
}
.proRatioGroups :global(.ant-radio-label) {
width: 100% !important;
}
/* radio.module.css */
.proRatioGroups :global(.ant-radio-inner) {
width: 24px !important;
height: 24px !important;
border-radius: 30px !important;
}
.proRatioGroups :global(.ant-radio) {
width: 24px !important;
height: 24px !important;
}
.circleCheckbox {
border: 1px solid #5f6c7b1f;
height: 62px;
justify-content: space-between;
opacity: 1;
border-radius: 888px;
padding: 8px 12px;
border-width: 1px;
}
/* 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%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-checkbox-inner::after) {
left: auto;
right: 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;
}
:global(.ant-app-rtl)
.circleCheckbox
:global(.ant-checkbox-checked .ant-checkbox-inner::after) {
margin-left: auto;
margin-right: -9px;
}
/* Apply same circular style to Radio buttons */
.circleCheckbox :global(.ant-radio-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.circleCheckbox :global(.ant-radio-checked .ant-radio-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
.circleCheckbox :global(.ant-radio-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-radio-inner::after) {
left: auto;
right: 50%;
}
.circleCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl)
.circleCheckbox
:global(.ant-radio-checked .ant-radio-inner::after) {
margin-left: auto;
margin-right: -9px;
}

View File

@@ -0,0 +1,132 @@
import { Button, Card, Divider, Radio, RadioChangeEvent, Space } from "antd";
import ProText from "components/ProText";
import styles from "./CarRatioGroups.module.css";
import { useTranslation } from "react-i18next";
import CarIcon from "components/Icons/CarIcon";
interface CarRatioGroupsProps {
options: { label: string; value: string; price?: string }[];
onRatioClick?: (value: string) => void;
onChange?: (e: RadioChangeEvent) => void;
value?: string;
optionsStyle?: React.CSSProperties;
valueStyle?: React.CSSProperties;
showDivider?: boolean;
}
const CarRatioGroups = ({
options,
onRatioClick,
onChange,
value,
optionsStyle,
valueStyle,
showDivider = false,
...props
}: CarRatioGroupsProps) => {
const { t } = useTranslation();
const handleChange = (e: RadioChangeEvent) => {
console.log(e.target.value);
// If onChange is provided (from Form.Item), use it
if (onChange) {
onChange(e);
}
// Also call onRatioClick if provided (for backward compatibility)
if (onRatioClick) {
onRatioClick(e.target.value);
}
};
return (
<div className={styles.proRatioGroups}>
<Radio.Group
style={{
width: "100%",
}}
value={value}
onChange={handleChange}
{...props}
>
<Space orientation="vertical" style={{ width: "100%", gap: 16 }}>
{options.map((option) => (
<>
<Radio
key={option.value}
value={option.value}
className={styles.circleCheckbox}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<div
style={{ display: "flex", flexDirection: "row", gap: 12 }}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
justifyContent: "center",
textAlign: "left",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
color: "#333333",
}}
>
Optima
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
color: "#777580",
}}
>
42322, blue
</ProText>
</div>
</div>
<Button
shape="circle"
icon={<CarIcon />}
style={{
backgroundColor: "var(--background)",
border: "none",
width: 32,
height: 32,
minWidth: 32,
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.1)",
}}
/>
</div>
</Radio>
{showDivider && options.length !== options.length - 1 && (
<Divider style={{ margin: "0 0 16px 0 " }} />
)}
</>
))}
</Space>
</Radio.Group>
</div>
);
};
export default CarRatioGroups;

View File

@@ -20,6 +20,7 @@ import CustomerInformationCard from "./components/CustomerInformationCard";
import Ads1 from "components/Ads/Ads1";
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard";
import { useEffect } from "react";
import { CarCard } from "./components/CarCard";
export default function CheckoutPage() {
const { t } = useTranslation();
@@ -46,6 +47,7 @@ export default function CheckoutPage() {
<Layout.Content className={styles.checkoutContainer}>
{(orderType === OrderType.Pickup ||
orderType === OrderType.ScheduledOrder) && <TimeEstimateCard />}
{orderType === OrderType.Pickup && <CarCard />}
{orderType === OrderType.Gift && <GiftCard />}
<PaymentMethods />
{!token && <CustomerInformationCard />}