working on car card & car view BS

This commit is contained in:
2026-01-05 23:30:03 +03:00
parent c8bf8ff621
commit 20ef4f416c
14 changed files with 658 additions and 29 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

@@ -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,10 +1,11 @@
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;
@@ -42,8 +43,8 @@ export function TipBottomSheet({
onClose={handleCancel}
title={t("cart.tip")}
initialSnap={1}
height={370}
snapPoints={[370]}
height={380}
snapPoints={[380]}
>
<div
style={{
@@ -51,29 +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 }}
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>

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

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

@@ -161,19 +161,34 @@ export default function RewardWaiterCard() {
iconPlacement="start"
onClick={() => setIsTipOpen(true)}
>
<ProText
style={{
color: tip && !isDefaultTip ? "#CC9300" : "#FFB700",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
}}
>
{tip && !isDefaultTip ? tip : t("cart.other")}
</ProText>
{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>

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