Compare commits
3 Commits
20ef4f416c
...
ebe9928091
| Author | SHA1 | Date | |
|---|---|---|---|
| ebe9928091 | |||
| ad036d1e64 | |||
| 14c36518cc |
@@ -300,7 +300,16 @@
|
|||||||
"viewOrder": "عرض الطلب",
|
"viewOrder": "عرض الطلب",
|
||||||
"itemsSummary": "ملخص العناصر",
|
"itemsSummary": "ملخص العناصر",
|
||||||
"removeSplitBill": "إزالة التقسيم",
|
"removeSplitBill": "إزالة التقسيم",
|
||||||
"cardBalance": "رصيد البطاقة"
|
"cardBalance": "رصيد البطاقة",
|
||||||
|
"collectAtCounter": "استلام في المكتب",
|
||||||
|
"collectAtParking": "استلام في الموقف",
|
||||||
|
"scheduled": "مجدول",
|
||||||
|
"pickupNow": "استلام الآن",
|
||||||
|
"pickupEstimate": "تقدير الاستلام",
|
||||||
|
"today": "اليوم",
|
||||||
|
"change": "تغيير",
|
||||||
|
"pickup":"استلام",
|
||||||
|
"setPickupTime":"تحديد وقت الاستلام"
|
||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"title": "العنوان",
|
"title": "العنوان",
|
||||||
@@ -511,6 +520,11 @@
|
|||||||
},
|
},
|
||||||
"car":{
|
"car":{
|
||||||
"addCar":"إضافة سيارة",
|
"addCar":"إضافة سيارة",
|
||||||
"selectCar":"اختر السيارة"
|
"selectCar":"اختر السيارة",
|
||||||
|
"addCarDetails":"إضافة تفاصيل السيارة",
|
||||||
|
"brand":"العلامة التجارية",
|
||||||
|
"color":"اللون",
|
||||||
|
"category":"الفئة",
|
||||||
|
"plateNumber":"رقم السيارة"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,7 +312,17 @@
|
|||||||
"viewOrder": "View Order",
|
"viewOrder": "View Order",
|
||||||
"itemsSummary": "Items Summary",
|
"itemsSummary": "Items Summary",
|
||||||
"removeSplitBill": "Remove Split Bill",
|
"removeSplitBill": "Remove Split Bill",
|
||||||
"cardBalance": "Card Balance"
|
"cardBalance": "Card Balance",
|
||||||
|
"collectAtCounter": "Collect at counter",
|
||||||
|
"collectAtParking": "Collect at parking",
|
||||||
|
"scheduled": "Scheduled",
|
||||||
|
"pickupNow": "Pickup Now",
|
||||||
|
"pickupEstimate": "Pickup Estimate",
|
||||||
|
"now": "Now",
|
||||||
|
"today": "Today",
|
||||||
|
"change": "Change",
|
||||||
|
"pickup": "Pickup",
|
||||||
|
"setPickupTime": "Set Pickup Time"
|
||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"title": "Address",
|
"title": "Address",
|
||||||
@@ -523,6 +533,11 @@
|
|||||||
},
|
},
|
||||||
"car": {
|
"car": {
|
||||||
"addCar": "Add Car",
|
"addCar": "Add Car",
|
||||||
"selectCar": "Select Car"
|
"selectCar": "Select Car",
|
||||||
|
"addCarDetails": "Add Car Details",
|
||||||
|
"brand": "Brand",
|
||||||
|
"color": "Color",
|
||||||
|
"category": "Category",
|
||||||
|
"plateNumber": "Plate Number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
126
src/components/CustomBottomSheet/AddCarBottomSheet.tsx
Normal file
126
src/components/CustomBottomSheet/AddCarBottomSheet.tsx
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { Button, Form, Input } from "antd";
|
||||||
|
import ProPhoneInput from "components/ProPhoneInput";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||||
|
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
interface CarDetailsType {
|
||||||
|
brandId: string;
|
||||||
|
category: string;
|
||||||
|
color: string;
|
||||||
|
plateNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddCarBottomSheetProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (value: CarDetailsType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddCarBottomSheet({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
}: AddCarBottomSheetProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [carForm] = Form.useForm();
|
||||||
|
useEffect(() => {
|
||||||
|
carForm.setFieldsValue({});
|
||||||
|
}, [carForm]);
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onSave(carForm.getFieldsValue());
|
||||||
|
carForm.resetFields();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
carForm.resetFields();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProBottomSheet
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={handleCancel}
|
||||||
|
title={t("car.addCarDetails")}
|
||||||
|
showCloseButton={false}
|
||||||
|
initialSnap={1}
|
||||||
|
height={475}
|
||||||
|
snapPoints={[475]}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
style={{ marginTop: 24}}
|
||||||
|
name="carForm"
|
||||||
|
form={carForm}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
|
||||||
|
<Form.Item
|
||||||
|
name="brandId"
|
||||||
|
rules={[{ required: true, message: "" }]}
|
||||||
|
colon={false}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder={t("car.brand")}
|
||||||
|
size="large"
|
||||||
|
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
|
||||||
|
autoFocus={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="color"
|
||||||
|
rules={[{ required: true, message: "" }]}
|
||||||
|
colon={false}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder={t("car.color")}
|
||||||
|
size="large"
|
||||||
|
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
|
||||||
|
autoFocus={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="category"
|
||||||
|
rules={[{ required: true, message: "" }]}
|
||||||
|
colon={false}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder={t("car.category")}
|
||||||
|
size="large"
|
||||||
|
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
|
||||||
|
autoFocus={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="plateNumber"
|
||||||
|
rules={[{ required: true, message: "" }]}
|
||||||
|
colon={false}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder={t("car.plateNumber")}
|
||||||
|
size="large"
|
||||||
|
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
|
||||||
|
autoFocus={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ width: "100%", boxShadow: "none" }}
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
{t("car.save")}
|
||||||
|
</Button>
|
||||||
|
</ProBottomSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
interface TimeIconType {
|
interface TimeIconType {
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimeIcon = ({ className, onClick }: TimeIconType) => {
|
const TimeIcon = ({ className, onClick, color }: TimeIconType) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="17"
|
width="17"
|
||||||
@@ -17,7 +18,7 @@ const TimeIcon = ({ className, onClick }: TimeIconType) => {
|
|||||||
<g clipPath="url(#clip0_2448_7814)">
|
<g clipPath="url(#clip0_2448_7814)">
|
||||||
<path
|
<path
|
||||||
d="M8.50016 4.08171V8.08171L11.1668 9.41504M15.1668 8.08171C15.1668 11.7636 12.1821 14.7484 8.50016 14.7484C4.81826 14.7484 1.8335 11.7636 1.8335 8.08171C1.8335 4.39981 4.81826 1.41504 8.50016 1.41504C12.1821 1.41504 15.1668 4.39981 15.1668 8.08171Z"
|
d="M8.50016 4.08171V8.08171L11.1668 9.41504M15.1668 8.08171C15.1668 11.7636 12.1821 14.7484 8.50016 14.7484C4.81826 14.7484 1.8335 11.7636 1.8335 8.08171C1.8335 4.39981 4.81826 1.41504 8.50016 1.41504C12.1821 1.41504 15.1668 4.39981 15.1668 8.08171Z"
|
||||||
stroke="#333333"
|
stroke={color || "#333333"}
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Button, Card } from "antd";
|
|||||||
import CarRatioGroups from "./CarRatioGroups/CarRatioGroups";
|
import CarRatioGroups from "./CarRatioGroups/CarRatioGroups";
|
||||||
import PlusIcon from "components/Icons/PlusIcon";
|
import PlusIcon from "components/Icons/PlusIcon";
|
||||||
import styles from "../checkout.module.css";
|
import styles from "../checkout.module.css";
|
||||||
|
import { AddCarBottomSheet } from "components/CustomBottomSheet/AddCarBottomSheet";
|
||||||
|
|
||||||
interface CarBottomSheetProps {
|
interface CarBottomSheetProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -15,6 +16,7 @@ interface CarBottomSheetProps {
|
|||||||
export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
|
export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [value, setValue] = useState<string | null>(null);
|
const [value, setValue] = useState<string | null>(null);
|
||||||
|
const [isAddCarOpen, setIsAddCarOpen] = useState(false);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setValue(null);
|
setValue(null);
|
||||||
@@ -26,6 +28,34 @@ export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
|
|||||||
setValue(value);
|
setValue(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddCarClick = () => {
|
||||||
|
setIsAddCarOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCarClose = () => {
|
||||||
|
setIsAddCarOpen(false);
|
||||||
|
// Reopen CarBottomSheet when AddCarBottomSheet closes
|
||||||
|
// The parent component should handle reopening, but we'll ensure state is correct
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCarSave = (carDetails: any) => {
|
||||||
|
// Handle saving the new car details
|
||||||
|
console.log("Car details saved:", carDetails);
|
||||||
|
// After saving, close AddCarBottomSheet which will trigger reopening CarBottomSheet
|
||||||
|
setIsAddCarOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show AddCarBottomSheet when it's open, otherwise show CarBottomSheet
|
||||||
|
if (isAddCarOpen) {
|
||||||
|
return (
|
||||||
|
<AddCarBottomSheet
|
||||||
|
isOpen={isAddCarOpen}
|
||||||
|
onClose={handleAddCarClose}
|
||||||
|
onSave={handleAddCarSave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProBottomSheet
|
<ProBottomSheet
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
@@ -78,9 +108,14 @@ export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
|
|||||||
color: "#5F6C7B",
|
color: "#5F6C7B",
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
}}
|
}}
|
||||||
onClick={handleSave}
|
onClick={handleAddCarClick}
|
||||||
disabled={!value}
|
icon={
|
||||||
icon={<PlusIcon color="#5F6C7B" dimension="16" className={styles.plusIcon} />}
|
<PlusIcon
|
||||||
|
color="#5F6C7B"
|
||||||
|
dimension="16"
|
||||||
|
className={styles.plusIcon}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("car.addCar")}
|
{t("car.addCar")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
.collectWay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
background-color: #ebebec;
|
||||||
|
height: 44;
|
||||||
|
border-radius: 888px;
|
||||||
|
padding: 4px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeButton {
|
||||||
|
background-color: #030014;
|
||||||
|
color: #ffffff;
|
||||||
|
height: 36px;
|
||||||
|
border: none !important;
|
||||||
|
width: 100%;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeButton:hover,
|
||||||
|
.activeButton:focus,
|
||||||
|
.activeButton:active,
|
||||||
|
.activeButton:focus-visible {
|
||||||
|
background-color: #030014 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
outline: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeButton::before,
|
||||||
|
.activeButton::after {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactiveButton {
|
||||||
|
background-color: #ebebec;
|
||||||
|
color: #595764;
|
||||||
|
height: 36px;
|
||||||
|
border: none !important;
|
||||||
|
width: 100%;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactiveButton:hover,
|
||||||
|
.inactiveButton:focus,
|
||||||
|
.inactiveButton:active,
|
||||||
|
.inactiveButton:focus-visible {
|
||||||
|
background-color: #ebebec !important;
|
||||||
|
color: #595764 !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
outline: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactiveButton::before,
|
||||||
|
.inactiveButton::after {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
34
src/pages/checkout/components/CollectWay/CollectWay.tsx
Normal file
34
src/pages/checkout/components/CollectWay/CollectWay.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
|
import styles from "./CollectWay.module.css";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export const CollectWay = (): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [activeButton, setActiveButton] = useState<string>("counter");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.collectWay}>
|
||||||
|
<Button
|
||||||
|
className={
|
||||||
|
activeButton === "counter"
|
||||||
|
? styles.activeButton
|
||||||
|
: styles.inactiveButton
|
||||||
|
}
|
||||||
|
onClick={() => setActiveButton("counter")}
|
||||||
|
>
|
||||||
|
{t("checkout.collectAtCounter")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={
|
||||||
|
activeButton === "parking"
|
||||||
|
? styles.activeButton
|
||||||
|
: styles.inactiveButton
|
||||||
|
}
|
||||||
|
onClick={() => setActiveButton("parking")}
|
||||||
|
>
|
||||||
|
{t("checkout.collectAtParking")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
31
src/pages/checkout/components/pickupEstimate/BottomSheet.tsx
Normal file
31
src/pages/checkout/components/pickupEstimate/BottomSheet.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet.tsx";
|
||||||
|
import PickupEstimateContent from "pages/checkout/components/pickupEstimate/Content.tsx";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface PickupEstimateBottomSheetProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (date: string, time: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PickupEstimateBottomSheet({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
}: PickupEstimateBottomSheetProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProBottomSheet
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t("checkout.setPickupTime")}
|
||||||
|
showCloseButton={true}
|
||||||
|
initialSnap={1}
|
||||||
|
height={550}
|
||||||
|
snapPoints={[550]}
|
||||||
|
>
|
||||||
|
<PickupEstimateContent onSave={onSave} onClose={onClose} />
|
||||||
|
</ProBottomSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
340
src/pages/checkout/components/pickupEstimate/Content.tsx
Normal file
340
src/pages/checkout/components/pickupEstimate/Content.tsx
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Picker from "components/WheelPicker";
|
||||||
|
import { SERVER_DATE_FORMAT } from "utils/constants.ts";
|
||||||
|
|
||||||
|
interface PickupEstimateContentProps {
|
||||||
|
onSave: (date: string, time: string) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PickupEstimateContent({
|
||||||
|
onSave,
|
||||||
|
onClose,
|
||||||
|
}: PickupEstimateContentProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// Generate day options: Today, Tomorrow, then formatted dates (90 days total for scrolling)
|
||||||
|
const dayOptions = useMemo(() => {
|
||||||
|
const options = [];
|
||||||
|
const today = dayjs();
|
||||||
|
|
||||||
|
// Today
|
||||||
|
options.push({
|
||||||
|
value: "today",
|
||||||
|
label: "Today",
|
||||||
|
date: today.format(SERVER_DATE_FORMAT),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tomorrow
|
||||||
|
options.push({
|
||||||
|
value: "tomorrow",
|
||||||
|
label: "Tomorrow",
|
||||||
|
date: today.add(1, "day").format(SERVER_DATE_FORMAT),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next days with formatted dates (up to 90 days total for smooth scrolling)
|
||||||
|
for (let i = 2; i < 90; i++) {
|
||||||
|
const date = today.add(i, "day");
|
||||||
|
const formatted = date.toDate().toLocaleDateString("en-US", {
|
||||||
|
weekday: "long",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
options.push({
|
||||||
|
value: `day-${i}`,
|
||||||
|
label: formatted,
|
||||||
|
date: date.format(SERVER_DATE_FORMAT),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Generate time options: Now, then time slots (5 items total)
|
||||||
|
const timeOptions = useMemo(() => {
|
||||||
|
const options = [];
|
||||||
|
const now = dayjs();
|
||||||
|
|
||||||
|
// Now option
|
||||||
|
options.push({
|
||||||
|
value: "now",
|
||||||
|
label: "Now",
|
||||||
|
time: now.format("HH:mm"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate next 15-minute interval (always the next one, even if we're on a boundary)
|
||||||
|
const currentMinute = now.minute();
|
||||||
|
let nextMinute = Math.floor(currentMinute / 15) * 15 + 15;
|
||||||
|
let nextHour = now.hour();
|
||||||
|
|
||||||
|
if (nextMinute >= 60) {
|
||||||
|
nextMinute = 0;
|
||||||
|
nextHour += 1;
|
||||||
|
if (nextHour >= 24) {
|
||||||
|
nextHour = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate next 4 time slots (15-minute intervals)
|
||||||
|
let hour = nextHour;
|
||||||
|
let minute = nextMinute;
|
||||||
|
|
||||||
|
for (let i = 1; i < 48; i++) {
|
||||||
|
const time = dayjs().hour(hour).minute(minute).second(0);
|
||||||
|
const formatted = time.format("h:mm A");
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
value: `time-${i}`,
|
||||||
|
label: formatted,
|
||||||
|
time: time.format("HH:mm"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate next interval
|
||||||
|
minute += 15;
|
||||||
|
if (minute >= 60) {
|
||||||
|
minute = 0;
|
||||||
|
hour += 1;
|
||||||
|
if (hour >= 24) {
|
||||||
|
hour = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [selectedDay, setSelectedDay] = useState<string>("today");
|
||||||
|
const [selectedTime, setSelectedTime] = useState<string>("now");
|
||||||
|
|
||||||
|
const handlePickerChange = (value: { day?: string; time?: string }) => {
|
||||||
|
console.log(value);
|
||||||
|
if (value.day !== undefined) {
|
||||||
|
setSelectedDay(value.day);
|
||||||
|
}
|
||||||
|
if (value.time !== undefined) {
|
||||||
|
setSelectedTime(value.time);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const selectedDayOption = dayOptions.find(
|
||||||
|
(opt) => opt.value === selectedDay,
|
||||||
|
);
|
||||||
|
const selectedTimeOption = timeOptions.find(
|
||||||
|
(opt) => opt.value === selectedTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedDayOption && selectedTimeOption) {
|
||||||
|
onSave(selectedDayOption.date, selectedTimeOption.time);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchEnd = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWheel = (e: React.WheelEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 30,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
onTouchEnd={handleTouchEnd}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
>
|
||||||
|
{/* Day and Time Pickers - Same Row */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: "8px",
|
||||||
|
marginBottom: 30,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Day Picker */}
|
||||||
|
<div style={{ width: "60%" }}>
|
||||||
|
<Picker
|
||||||
|
value={{ day: selectedDay }}
|
||||||
|
onChange={(value) => handlePickerChange(value)}
|
||||||
|
width="100%"
|
||||||
|
height={250}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
display: "flex",
|
||||||
|
backgroundColor: "var(--secondary-background)",
|
||||||
|
overflow: "hidden",
|
||||||
|
width: "100%",
|
||||||
|
height: 250,
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
aria-selected={false}
|
||||||
|
>
|
||||||
|
<Picker.Column
|
||||||
|
name="day"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "var(--secondary-background)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{dayOptions.map((option) => (
|
||||||
|
<Picker.Item key={option.value} value={option.value}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontWeight: selected ? "600" : "400",
|
||||||
|
color: selected
|
||||||
|
? "var(--foreground)"
|
||||||
|
: "var(--text-color-gray)",
|
||||||
|
fontSize: "16px",
|
||||||
|
padding: "9px 0",
|
||||||
|
textAlign: "center",
|
||||||
|
opacity: selected ? 1 : 0.7,
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
lineHeight: "1.2",
|
||||||
|
height: 36,
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: selected
|
||||||
|
? "var(--background)"
|
||||||
|
: "transparent",
|
||||||
|
borderRadius: selected ? "4px" : "0",
|
||||||
|
margin: selected ? "2px 4px" : "0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Picker.Item>
|
||||||
|
))}
|
||||||
|
</Picker.Column>
|
||||||
|
</Picker>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Time Picker */}
|
||||||
|
<div style={{ width: "40%" }}>
|
||||||
|
<Picker
|
||||||
|
value={{ time: selectedTime }}
|
||||||
|
onChange={(value) => handlePickerChange(value)}
|
||||||
|
width="100%"
|
||||||
|
height={250}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
display: "flex",
|
||||||
|
backgroundColor: "var(--secondary-background)",
|
||||||
|
overflow: "hidden",
|
||||||
|
width: "100%",
|
||||||
|
height: 250,
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
aria-selected={false}
|
||||||
|
>
|
||||||
|
<Picker.Column
|
||||||
|
name="time"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "var(--secondary-background)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{timeOptions.map((option) => (
|
||||||
|
<Picker.Item key={option.value} value={option.value}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontWeight: selected ? "600" : "400",
|
||||||
|
color: selected
|
||||||
|
? "var(--foreground)"
|
||||||
|
: "var(--text-color-gray)",
|
||||||
|
fontSize: "16px",
|
||||||
|
padding: "9px 0",
|
||||||
|
textAlign: "center",
|
||||||
|
opacity: selected ? 1 : 0.7,
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
lineHeight: "1.2",
|
||||||
|
height: 36,
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: selected
|
||||||
|
? "var(--background)"
|
||||||
|
: "transparent",
|
||||||
|
borderRadius: selected ? "4px" : "0",
|
||||||
|
margin: selected ? "2px 4px" : "0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Picker.Item>
|
||||||
|
))}
|
||||||
|
</Picker.Column>
|
||||||
|
</Picker>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Save Button */}
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSave}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 48,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 600,
|
||||||
|
backgroundColor: "var(--primary)",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("checkout.scheduled")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 48,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 600,
|
||||||
|
border: "1px solid ##C0BFC4",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("checkout.pickupNow")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
src/pages/checkout/components/pickupEstimate/Dialog.tsx
Normal file
27
src/pages/checkout/components/pickupEstimate/Dialog.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Modal } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import PickupEstimateContent from "pages/checkout/components/pickupEstimate/Content.tsx";
|
||||||
|
|
||||||
|
interface PickupEstimateDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (date: string, time: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PickupEstimateDialog({ isOpen, onClose, onSave }: PickupEstimateDialogProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t("checkout.pickupEstimate")}
|
||||||
|
open={isOpen}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={[]}
|
||||||
|
width={500}
|
||||||
|
destroyOnHidden
|
||||||
|
>
|
||||||
|
<PickupEstimateContent onSave={onSave} onClose={onClose} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.timeButton {
|
||||||
|
height: 32px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
border-radius: 888px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 16 !important;
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(95, 108, 123, 0.05);
|
||||||
|
color: rgba(95, 108, 123, 1);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleButton {
|
||||||
|
height: 32px !important;
|
||||||
|
border-radius: 888px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 16 !important;
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(95, 108, 123, 0.05);
|
||||||
|
color: rgba(95, 108, 123, 1);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: rgba(255, 183, 0, 0.12) !important;
|
||||||
|
color: rgba(204, 147, 0, 1) !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import useFormInstance from "antd/es/form/hooks/useFormInstance";
|
||||||
|
import TimeIcon from "components/Icons/order/TimeIcon";
|
||||||
|
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
|
||||||
|
import ProText from "components/ProText";
|
||||||
|
import {
|
||||||
|
selectCart,
|
||||||
|
updatePickupDate,
|
||||||
|
updatePickupTime,
|
||||||
|
} from "features/order/orderSlice";
|
||||||
|
import useBreakPoint from "hooks/useBreakPoint.ts";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAppDispatch, useAppSelector } from "redux/hooks";
|
||||||
|
import { PickupEstimateDialog } from "./Dialog";
|
||||||
|
import { PickupEstimateBottomSheet } from "./BottomSheet";
|
||||||
|
|
||||||
|
export default function PickupTimeCard() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const form = useFormInstance();
|
||||||
|
const { isDesktop } = useBreakPoint();
|
||||||
|
const { pickupTime, pickupDate } = useAppSelector(selectCart);
|
||||||
|
const [isEstimateTimeOpen, setIsEstimateTimeOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleEstimateTimeSave = (date: string, time: string) => {
|
||||||
|
form.setFieldsValue({ pickupTime: time, pickupDate: date });
|
||||||
|
dispatch(updatePickupTime(time));
|
||||||
|
dispatch(updatePickupDate(date));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEstimateTimeClose = () => {
|
||||||
|
setIsEstimateTimeOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setFieldsValue({ pickupTime, pickupDate });
|
||||||
|
}, [pickupTime, pickupDate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProInputCard
|
||||||
|
title={t("checkout.pickup")}
|
||||||
|
titleRight={
|
||||||
|
<ProText
|
||||||
|
onClick={() => setIsEstimateTimeOpen(true)}
|
||||||
|
style={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
color: "#FFB700",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("checkout.change")}
|
||||||
|
</ProText>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span style={{ position: "relative", top: 3 }}>
|
||||||
|
<TimeIcon color="#777580" />
|
||||||
|
</span>
|
||||||
|
<ProText
|
||||||
|
style={{
|
||||||
|
fontWeight: 400,
|
||||||
|
fontStyle: "Regular",
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
color: "#777580",
|
||||||
|
padding: "0 2px 0 4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pickupTime}
|
||||||
|
</ProText>
|
||||||
|
<ProText
|
||||||
|
style={{
|
||||||
|
fontWeight: 400,
|
||||||
|
fontStyle: "Regular",
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
color: "#777580",
|
||||||
|
padding: "0 2px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
(Est. 10-15 minutes)
|
||||||
|
</ProText>
|
||||||
|
</ProInputCard>
|
||||||
|
|
||||||
|
{isDesktop ? (
|
||||||
|
<PickupEstimateDialog
|
||||||
|
isOpen={isEstimateTimeOpen}
|
||||||
|
onClose={handleEstimateTimeClose}
|
||||||
|
onSave={handleEstimateTimeSave}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PickupEstimateBottomSheet
|
||||||
|
isOpen={isEstimateTimeOpen}
|
||||||
|
onClose={handleEstimateTimeClose}
|
||||||
|
onSave={handleEstimateTimeSave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Form, Layout } from "antd";
|
import { Flex, Form, Layout } from "antd";
|
||||||
import InputCard from "components/InputCard";
|
import InputCard from "components/InputCard";
|
||||||
import OrderSummary from "components/OrderSummary/OrderSummary";
|
import OrderSummary from "components/OrderSummary/OrderSummary";
|
||||||
import PaymentMethods from "components/PaymentMethods/PaymentMethods";
|
import PaymentMethods from "components/PaymentMethods/PaymentMethods";
|
||||||
@@ -18,9 +18,10 @@ import CouponCard from "pages/cart/components/CouponCard";
|
|||||||
import BriefMenuCard from "./components/BriefMenuCard";
|
import BriefMenuCard from "./components/BriefMenuCard";
|
||||||
import CustomerInformationCard from "./components/CustomerInformationCard";
|
import CustomerInformationCard from "./components/CustomerInformationCard";
|
||||||
import Ads1 from "components/Ads/Ads1";
|
import Ads1 from "components/Ads/Ads1";
|
||||||
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { CarCard } from "./components/CarCard";
|
import { CarCard } from "./components/CarCard";
|
||||||
|
import { CollectWay } from "./components/CollectWay/CollectWay";
|
||||||
|
import PickupTimeCard from "./components/pickupEstimate/TimeEstimateCard";
|
||||||
|
|
||||||
export default function CheckoutPage() {
|
export default function CheckoutPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -45,8 +46,10 @@ export default function CheckoutPage() {
|
|||||||
<Layout>
|
<Layout>
|
||||||
<ProHeader>{t("checkout.title")}</ProHeader>
|
<ProHeader>{t("checkout.title")}</ProHeader>
|
||||||
<Layout.Content className={styles.checkoutContainer}>
|
<Layout.Content className={styles.checkoutContainer}>
|
||||||
|
{orderType === OrderType.Pickup && <CollectWay />}
|
||||||
{(orderType === OrderType.Pickup ||
|
{(orderType === OrderType.Pickup ||
|
||||||
orderType === OrderType.ScheduledOrder) && <TimeEstimateCard />}
|
orderType === OrderType.ScheduledOrder) && <PickupTimeCard />}
|
||||||
|
|
||||||
{orderType === OrderType.Pickup && <CarCard />}
|
{orderType === OrderType.Pickup && <CarCard />}
|
||||||
{orderType === OrderType.Gift && <GiftCard />}
|
{orderType === OrderType.Gift && <GiftCard />}
|
||||||
<PaymentMethods />
|
<PaymentMethods />
|
||||||
|
|||||||
Reference in New Issue
Block a user