Compare commits
3 Commits
20ef4f416c
...
ebe9928091
| Author | SHA1 | Date | |
|---|---|---|---|
| ebe9928091 | |||
| ad036d1e64 | |||
| 14c36518cc |
@@ -300,7 +300,16 @@
|
||||
"viewOrder": "عرض الطلب",
|
||||
"itemsSummary": "ملخص العناصر",
|
||||
"removeSplitBill": "إزالة التقسيم",
|
||||
"cardBalance": "رصيد البطاقة"
|
||||
"cardBalance": "رصيد البطاقة",
|
||||
"collectAtCounter": "استلام في المكتب",
|
||||
"collectAtParking": "استلام في الموقف",
|
||||
"scheduled": "مجدول",
|
||||
"pickupNow": "استلام الآن",
|
||||
"pickupEstimate": "تقدير الاستلام",
|
||||
"today": "اليوم",
|
||||
"change": "تغيير",
|
||||
"pickup":"استلام",
|
||||
"setPickupTime":"تحديد وقت الاستلام"
|
||||
},
|
||||
"address": {
|
||||
"title": "العنوان",
|
||||
@@ -511,6 +520,11 @@
|
||||
},
|
||||
"car":{
|
||||
"addCar":"إضافة سيارة",
|
||||
"selectCar":"اختر السيارة"
|
||||
"selectCar":"اختر السيارة",
|
||||
"addCarDetails":"إضافة تفاصيل السيارة",
|
||||
"brand":"العلامة التجارية",
|
||||
"color":"اللون",
|
||||
"category":"الفئة",
|
||||
"plateNumber":"رقم السيارة"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,17 @@
|
||||
"viewOrder": "View Order",
|
||||
"itemsSummary": "Items Summary",
|
||||
"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": {
|
||||
"title": "Address",
|
||||
@@ -523,6 +533,11 @@
|
||||
},
|
||||
"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 {
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const TimeIcon = ({ className, onClick }: TimeIconType) => {
|
||||
const TimeIcon = ({ className, onClick, color }: TimeIconType) => {
|
||||
return (
|
||||
<svg
|
||||
width="17"
|
||||
@@ -17,7 +18,7 @@ const TimeIcon = ({ className, onClick }: TimeIconType) => {
|
||||
<g clipPath="url(#clip0_2448_7814)">
|
||||
<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"
|
||||
stroke="#333333"
|
||||
stroke={color || "#333333"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button, Card } from "antd";
|
||||
import CarRatioGroups from "./CarRatioGroups/CarRatioGroups";
|
||||
import PlusIcon from "components/Icons/PlusIcon";
|
||||
import styles from "../checkout.module.css";
|
||||
import { AddCarBottomSheet } from "components/CustomBottomSheet/AddCarBottomSheet";
|
||||
|
||||
interface CarBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
@@ -15,6 +16,7 @@ interface CarBottomSheetProps {
|
||||
export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
const [isAddCarOpen, setIsAddCarOpen] = useState(false);
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(null);
|
||||
@@ -26,6 +28,34 @@ export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
|
||||
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 (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
@@ -78,9 +108,14 @@ export function CarBottomSheet({ isOpen, onClose }: CarBottomSheetProps) {
|
||||
color: "#5F6C7B",
|
||||
marginTop: 16,
|
||||
}}
|
||||
onClick={handleSave}
|
||||
disabled={!value}
|
||||
icon={<PlusIcon color="#5F6C7B" dimension="16" className={styles.plusIcon} />}
|
||||
onClick={handleAddCarClick}
|
||||
icon={
|
||||
<PlusIcon
|
||||
color="#5F6C7B"
|
||||
dimension="16"
|
||||
className={styles.plusIcon}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t("car.addCar")}
|
||||
</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 OrderSummary from "components/OrderSummary/OrderSummary";
|
||||
import PaymentMethods from "components/PaymentMethods/PaymentMethods";
|
||||
@@ -18,9 +18,10 @@ import CouponCard from "pages/cart/components/CouponCard";
|
||||
import BriefMenuCard from "./components/BriefMenuCard";
|
||||
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";
|
||||
import { CollectWay } from "./components/CollectWay/CollectWay";
|
||||
import PickupTimeCard from "./components/pickupEstimate/TimeEstimateCard";
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -45,8 +46,10 @@ export default function CheckoutPage() {
|
||||
<Layout>
|
||||
<ProHeader>{t("checkout.title")}</ProHeader>
|
||||
<Layout.Content className={styles.checkoutContainer}>
|
||||
{orderType === OrderType.Pickup && <CollectWay />}
|
||||
{(orderType === OrderType.Pickup ||
|
||||
orderType === OrderType.ScheduledOrder) && <TimeEstimateCard />}
|
||||
orderType === OrderType.ScheduledOrder) && <PickupTimeCard />}
|
||||
|
||||
{orderType === OrderType.Pickup && <CarCard />}
|
||||
{orderType === OrderType.Gift && <GiftCard />}
|
||||
<PaymentMethods />
|
||||
|
||||
Reference in New Issue
Block a user