Initial commit
This commit is contained in:
138
src/components/CustomBottomSheet/CancelOrderBottomSheet.tsx
Normal file
138
src/components/CustomBottomSheet/CancelOrderBottomSheet.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
// import { useGlobals } from "../../hooks/useGlobals";
|
||||
import { Button, Card } from "antd";
|
||||
import BackIcon from "components/Icons/BackIcon";
|
||||
import CancelIcon from "components/Icons/CancelIcon";
|
||||
import CancelPopupIcon from "components/Icons/CancelPopupIcon";
|
||||
import NextIcon from "components/Icons/NextIcon";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import ProText from "../ProText";
|
||||
import ProTitle from "../ProTitle";
|
||||
import styles from "./CustomBottomSheet.module.css";
|
||||
|
||||
interface CancelOrderBottomSheetProps {
|
||||
initialValue?: string;
|
||||
onSave?: (value: string) => void;
|
||||
}
|
||||
|
||||
export function CancelOrderBottomSheet({}: CancelOrderBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const isRTL = false; // Default to LTR
|
||||
|
||||
const handleSave = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className={styles.homeServiceCard} onClick={() => setIsOpen(true)}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 1,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "row", gap: 10 }}>
|
||||
<CancelIcon />
|
||||
|
||||
<ProTitle
|
||||
level={5}
|
||||
style={{
|
||||
marginTop: 1,
|
||||
fontSize: 14,
|
||||
color: "#ea1f22",
|
||||
}}
|
||||
>
|
||||
{t("order.cancelOrder")}
|
||||
</ProTitle>
|
||||
</div>
|
||||
|
||||
{isRTL ? (
|
||||
<BackIcon className={styles.serviceIcon} />
|
||||
) : (
|
||||
<NextIcon className={styles.serviceIcon} />
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("order.cancelOrder")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"45vh"}
|
||||
snapPoints={["40vh"]}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<CancelPopupIcon />
|
||||
|
||||
<ProText
|
||||
style={{
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
{t("order.areYouSureYouWantToCancelThisOrder?")}
|
||||
</ProText>
|
||||
|
||||
<ProText
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
{t("order.thisActionCannotBeUndone")}
|
||||
</ProText>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ width: "100%", height: 50, color: "#FFF" }}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t("order.keepOrder")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 50,
|
||||
color: "#ea1f22",
|
||||
borderColor: "#ea1f22",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t("order.cancelOrder")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
</>
|
||||
);
|
||||
}
|
||||
70
src/components/CustomBottomSheet/CouponBottomSheet.tsx
Normal file
70
src/components/CustomBottomSheet/CouponBottomSheet.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import ProRatioGroups from "../ProRatioGroups/ProRatioGroups";
|
||||
|
||||
interface CouponBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
export function CouponBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: CouponBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(value);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("cart.coupon")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"40vh"}
|
||||
snapPoints={["30vh"]}
|
||||
>
|
||||
<div>
|
||||
<ProRatioGroups
|
||||
options={[
|
||||
{
|
||||
label: "50% off, Min order : SDG 10,000",
|
||||
value: "50",
|
||||
price: "0"
|
||||
},
|
||||
{
|
||||
label: "Buy one get one free, Min order : SDG 5,000",
|
||||
value: "buy",
|
||||
price: "0"
|
||||
},
|
||||
{
|
||||
label: "30% off on select items, Min order : SDG 15,000",
|
||||
value: "30",
|
||||
price: "0"
|
||||
},
|
||||
]}
|
||||
onRatioClick={handleSave}
|
||||
/>
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
73
src/components/CustomBottomSheet/CouponDialog.tsx
Normal file
73
src/components/CustomBottomSheet/CouponDialog.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Button, Modal } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ProRatioGroups from "../ProRatioGroups/ProRatioGroups";
|
||||
|
||||
interface CouponDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
export function CouponDialog({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: CouponDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
console.log(value);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const handleSave = (selectedValue: string) => {
|
||||
onSave(selectedValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("cart.coupon")}
|
||||
open={isOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleCancel}>
|
||||
{t("cart.cancel")}
|
||||
</Button>,
|
||||
]}
|
||||
width={600}
|
||||
destroyOnHidden
|
||||
>
|
||||
<div>
|
||||
<ProRatioGroups
|
||||
options={[
|
||||
{
|
||||
label: "50% off, Min order : SDG 10,000",
|
||||
value: "50",
|
||||
},
|
||||
{
|
||||
label: "Buy one get one free, Min order : SDG 5,000",
|
||||
value: "buy",
|
||||
},
|
||||
{
|
||||
label: "30% off on select items, Min order : SDG 15,000",
|
||||
value: "30",
|
||||
},
|
||||
]}
|
||||
onRatioClick={handleSave}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
.homeServiceCard {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 12px 18px !important;
|
||||
row-gap: 10px;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 50px;
|
||||
background-color: rgba(234, 31, 34, 0.04);
|
||||
color: #ea1f22;
|
||||
}
|
||||
|
||||
.homeServiceCard :global(.ant-card-body) {
|
||||
padding: 0px !important;
|
||||
text-align: start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.serviceIcon path {
|
||||
stroke: #ea1f22;
|
||||
}
|
||||
|
||||
.nextIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.backIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
263
src/components/CustomBottomSheet/DatePickerBottomSheet.tsx
Normal file
263
src/components/CustomBottomSheet/DatePickerBottomSheet.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
import { Button } from "antd";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import Picker from "../WheelPicker";
|
||||
|
||||
interface DatePickerBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onDateSelect?: (date: { month: string; day: string; year: string }) => void;
|
||||
initialDate?: Date;
|
||||
}
|
||||
|
||||
export default function DatePickerBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
onDateSelect,
|
||||
initialDate = new Date(),
|
||||
}: DatePickerBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selectedDate, setSelectedDate] = useState({
|
||||
month: (initialDate.getMonth() + 1).toString().padStart(2, "0"),
|
||||
day: initialDate.getDate().toString().padStart(2, "0"),
|
||||
year: initialDate.getFullYear().toString(),
|
||||
});
|
||||
|
||||
// Generate picker options
|
||||
const pickerOptions = useMemo(() => {
|
||||
const months = [
|
||||
{ value: "01", label: t("common.January") },
|
||||
{ value: "02", label: t("common.February") },
|
||||
{ value: "03", label: t("common.March") },
|
||||
{ value: "04", label: t("common.April") },
|
||||
{ value: "05", label: t("common.May") },
|
||||
{ value: "06", label: t("common.June") },
|
||||
{ value: "07", label: t("common.July") },
|
||||
{ value: "08", label: t("common.August") },
|
||||
{ value: "09", label: t("common.September") },
|
||||
{ value: "10", label: t("common.October") },
|
||||
{ value: "11", label: t("common.November") },
|
||||
{ value: "12", label: t("common.December") },
|
||||
];
|
||||
|
||||
const days = Array.from({ length: 31 }, (_, i) => ({
|
||||
value: (i + 1).toString().padStart(2, "0"),
|
||||
label: (i + 1).toString(),
|
||||
}));
|
||||
|
||||
const years = Array.from({ length: 21 }, (_, i) => {
|
||||
const year = new Date().getFullYear() - 10 + i;
|
||||
return {
|
||||
value: year.toString(),
|
||||
label: year.toString(),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
month: months,
|
||||
day: days,
|
||||
year: years,
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const handleDateChange = (month: string, day: string, year: string) => {
|
||||
setSelectedDate((prev) => ({
|
||||
...prev,
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
onDateSelect?.(selectedDate);
|
||||
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 (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={t("common.pickYourBirthday")}
|
||||
snapPoints={["55vh"]}
|
||||
showCloseButton={true}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
{/* Date Picker */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Picker
|
||||
value={selectedDate}
|
||||
onChange={(value) => {
|
||||
handleDateChange(value.month, value.day, value.year);
|
||||
}}
|
||||
width="100%"
|
||||
height={250}
|
||||
style={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
backgroundColor: "#ffffff",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
height: 250,
|
||||
}}
|
||||
aria-selected={false}
|
||||
>
|
||||
<Picker.Column
|
||||
name="month"
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRight: "1px solid #e5e7eb",
|
||||
backgroundColor: "#ffffff",
|
||||
}}
|
||||
>
|
||||
{pickerOptions.month.map((option) => (
|
||||
<Picker.Item key={option.value} value={option.value}>
|
||||
{({ selected }) => (
|
||||
<div
|
||||
style={{
|
||||
fontWeight: selected ? "600" : "400",
|
||||
color: selected ? "#171717" : "#9ca3af",
|
||||
fontSize: "16px",
|
||||
padding: "9px 0",
|
||||
textAlign: "center",
|
||||
opacity: selected ? 1 : 0.6,
|
||||
transition: "all 0.2s ease",
|
||||
lineHeight: "1.2",
|
||||
height: 36,
|
||||
width: "100%",
|
||||
backgroundColor: selected ? "#f3f4f6" : "transparent",
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
)}
|
||||
</Picker.Item>
|
||||
))}
|
||||
</Picker.Column>
|
||||
<Picker.Column
|
||||
name="day"
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRight: "1px solid #e5e7eb",
|
||||
backgroundColor: "#ffffff",
|
||||
}}
|
||||
>
|
||||
{pickerOptions.day.map((option) => (
|
||||
<Picker.Item key={option.value} value={option.value}>
|
||||
{({ selected }) => (
|
||||
<div
|
||||
style={{
|
||||
fontWeight: selected ? "600" : "400",
|
||||
color: selected ? "#171717" : "#9ca3af",
|
||||
fontSize: "16px",
|
||||
padding: "9px 0",
|
||||
textAlign: "center",
|
||||
opacity: selected ? 1 : 0.6,
|
||||
transition: "all 0.2s ease",
|
||||
lineHeight: "1.2",
|
||||
height: 36,
|
||||
width: "100%",
|
||||
backgroundColor: selected ? "#f3f4f6" : "transparent",
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
)}
|
||||
</Picker.Item>
|
||||
))}
|
||||
</Picker.Column>
|
||||
<Picker.Column
|
||||
name="year"
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: "#ffffff",
|
||||
}}
|
||||
>
|
||||
{pickerOptions.year.map((option) => (
|
||||
<Picker.Item key={option.value} value={option.value}>
|
||||
{({ selected }) => (
|
||||
<div
|
||||
style={{
|
||||
fontWeight: selected ? "600" : "400",
|
||||
color: selected ? "#171717" : "#9ca3af",
|
||||
fontSize: "16px",
|
||||
padding: "9px 0",
|
||||
textAlign: "center",
|
||||
opacity: selected ? 1 : 0.6,
|
||||
transition: "all 0.2s ease",
|
||||
lineHeight: "1.2",
|
||||
height: 36,
|
||||
width: "100%",
|
||||
backgroundColor: selected ? "#f3f4f6" : "transparent",
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
)}
|
||||
</Picker.Item>
|
||||
))}
|
||||
</Picker.Column>
|
||||
</Picker>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ width: "100%", height: 50, marginTop: 10 }}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
{t("common.confirm")}
|
||||
</Button>
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
265
src/components/CustomBottomSheet/EstimateTimeBottomSheet.tsx
Normal file
265
src/components/CustomBottomSheet/EstimateTimeBottomSheet.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
// import { useGlobals } from "../../hooks/useGlobals";
|
||||
import { Button, Divider } from "antd";
|
||||
import BackIcon from "components/Icons/BackIcon";
|
||||
import NextIcon from "components/Icons/NextIcon";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import styles from "./CustomBottomSheet.module.css";
|
||||
|
||||
interface EstimateTimeBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (date: Date, time: string) => void;
|
||||
initialDate?: Date;
|
||||
}
|
||||
|
||||
export function EstimateTimeBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
initialDate = new Date(),
|
||||
}: EstimateTimeBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const isRTL = false; // Default to LTR
|
||||
const [selectedDate, setSelectedDate] = useState(initialDate);
|
||||
const [selectedTime, setSelectedTime] = useState("12:00");
|
||||
const [isAM, setIsAM] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedDate(initialDate);
|
||||
}, [initialDate]);
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const goToPreviousDay = () => {
|
||||
const newDate = new Date(selectedDate);
|
||||
newDate.setDate(newDate.getDate() - 1);
|
||||
setSelectedDate(newDate);
|
||||
};
|
||||
|
||||
const goToNextDay = () => {
|
||||
const newDate = new Date(selectedDate);
|
||||
newDate.setDate(newDate.getDate() + 1);
|
||||
setSelectedDate(newDate);
|
||||
};
|
||||
|
||||
const handleTimeSelect = (time: string) => {
|
||||
setSelectedTime(time);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const [hours, minutes] = selectedTime.split(":");
|
||||
const date = new Date(selectedDate);
|
||||
date.setHours(
|
||||
isAM ? parseInt(hours) : parseInt(hours) + 12,
|
||||
parseInt(minutes),
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
onSave(date, `${selectedTime} ${isAM ? "AM" : "PM"}`);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const timeSlots = [
|
||||
"12:00",
|
||||
"1:00",
|
||||
"2:00",
|
||||
"3:00",
|
||||
"4:00",
|
||||
"5:00",
|
||||
"6:00",
|
||||
"7:00",
|
||||
"8:00",
|
||||
"9:00",
|
||||
"10:00",
|
||||
"11:00",
|
||||
];
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title="Select Estimate Time"
|
||||
showCloseButton={true}
|
||||
initialSnap={1}
|
||||
height={"50vh"}
|
||||
snapPoints={["50vh", "60vh"]}
|
||||
>
|
||||
<div style={{ padding: "0 20px" }}>
|
||||
{/* Day Selection */}
|
||||
<div style={{ marginBottom: 30 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={
|
||||
isRTL ? (
|
||||
<NextIcon className={styles.nextIcon} />
|
||||
) : (
|
||||
<BackIcon className={styles.backIcon} />
|
||||
)
|
||||
}
|
||||
onClick={goToPreviousDay}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: "center",
|
||||
padding: "0 20px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontWeight: 600,
|
||||
color: "rgba(95, 108, 123, 1)",
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
{formatDate(selectedDate)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="text"
|
||||
icon={
|
||||
isRTL ? (
|
||||
<BackIcon className={styles.backIcon} />
|
||||
) : (
|
||||
<NextIcon className={styles.nextIcon} />
|
||||
)
|
||||
}
|
||||
onClick={goToNextDay}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Time Selection */}
|
||||
<div>
|
||||
{/* AM/PM Toggle */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "end",
|
||||
marginBottom: 20,
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type={isAM ? "primary" : "default"}
|
||||
onClick={() => setIsAM(true)}
|
||||
style={{
|
||||
height: 32,
|
||||
borderRadius: 888,
|
||||
fontWeight: 600,
|
||||
backgroundColor: isAM
|
||||
? "rgba(255, 183, 0, 0.12)"
|
||||
: "transparent",
|
||||
color: isAM ? "rgba(204, 147, 0, 1)" : "rgba(95, 108, 123, 1)",
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
AM
|
||||
</Button>
|
||||
<Button
|
||||
type={!isAM ? "primary" : "default"}
|
||||
onClick={() => setIsAM(false)}
|
||||
style={{
|
||||
height: 32,
|
||||
borderRadius: 888,
|
||||
fontWeight: 600,
|
||||
backgroundColor: !isAM
|
||||
? "rgba(255, 183, 0, 0.12)"
|
||||
: "rgba(95, 108, 123, 0.05)",
|
||||
color: !isAM ? "rgba(204, 147, 0, 1)" : "rgba(95, 108, 123, 1)",
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
PM
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Time Slots Grid */}
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(4, 1fr)",
|
||||
gap: 12,
|
||||
marginBottom: 30,
|
||||
}}
|
||||
>
|
||||
{timeSlots.map((time) => (
|
||||
<Button
|
||||
key={time}
|
||||
type={selectedTime === time ? "primary" : "default"}
|
||||
onClick={() => handleTimeSelect(time)}
|
||||
style={{
|
||||
height: 32,
|
||||
width: 70,
|
||||
borderRadius: 888,
|
||||
fontWeight: 600,
|
||||
fontSize: 16,
|
||||
border: "none",
|
||||
backgroundColor:
|
||||
selectedTime === time
|
||||
? "rgba(255, 183, 0, 0.12)"
|
||||
: "rgba(95, 108, 123, 0.05)",
|
||||
color:
|
||||
selectedTime === time
|
||||
? "rgba(204, 147, 0, 1)"
|
||||
: "rgba(95, 108, 123, 1)",
|
||||
}}
|
||||
>
|
||||
{time}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Save Button */}
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 50,
|
||||
borderRadius: 12,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
backgroundColor: "var(--primary)",
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
{t("cart.confirmTime")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
156
src/components/CustomBottomSheet/GiftBottomSheet.tsx
Normal file
156
src/components/CustomBottomSheet/GiftBottomSheet.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import { Button, Checkbox, Form, Input } from "antd";
|
||||
import { GiftDetailsType } from "features/order/orderSlice";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import ProText from "../ProText";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface GiftBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: GiftDetailsType | null;
|
||||
onSave: (value: GiftDetailsType) => void;
|
||||
}
|
||||
|
||||
export function GiftBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: GiftBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [giftForm] = Form.useForm();
|
||||
useEffect(() => {
|
||||
giftForm.setFieldsValue(initialValue);
|
||||
}, [initialValue, giftForm]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(giftForm.getFieldsValue());
|
||||
giftForm.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
giftForm.setFieldsValue(initialValue);
|
||||
giftForm.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("address.giftDetails")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"90vh"}
|
||||
snapPoints={["90vh"]}
|
||||
>
|
||||
<Form
|
||||
layout="vertical"
|
||||
style={{ marginTop: 12 }}
|
||||
name="giftForm"
|
||||
form={giftForm}
|
||||
initialValues={initialValue as GiftDetailsType}
|
||||
>
|
||||
<Form.Item
|
||||
name="receiverName"
|
||||
label={t("address.receiverName")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.receiverName")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="receiverPhone"
|
||||
label={t("address.receiverPhone")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.receiverPhone")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="message" label={t("address.message")}>
|
||||
<TextArea
|
||||
placeholder={t("address.message")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
rows={2}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="senderName"
|
||||
label={t("address.senderName")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.senderName")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="senderPhone"
|
||||
label={t("address.senderPhone")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.senderPhone")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="isSecret"
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Checkbox
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
>
|
||||
<ProText>{t("address.keepMyNameSecret")}</ProText>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<br />
|
||||
|
||||
<Button type="primary" style={{ width: "100%" }} onClick={handleSave}>
|
||||
{t("address.save")}
|
||||
</Button>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
268
src/components/CustomBottomSheet/GoogleMap.tsx
Normal file
268
src/components/CustomBottomSheet/GoogleMap.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
|
||||
import { Status, Wrapper } from "@googlemaps/react-wrapper";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface GoogleMapProps {
|
||||
onLocationSelect?: (lat: number, lng: number, address: string) => void;
|
||||
height?: string;
|
||||
readOnly?: boolean;
|
||||
initialLocation?: { lat: number; lng: number; address?: string };
|
||||
}
|
||||
|
||||
function MapComponent({
|
||||
onLocationSelect,
|
||||
height = "100%",
|
||||
readOnly = false,
|
||||
initialLocation,
|
||||
}: GoogleMapProps) {
|
||||
const mapRef = useRef<HTMLDivElement>(null);
|
||||
const markerRef = useRef<google.maps.Marker | null>(null);
|
||||
const [map, setMap] = useState<google.maps.Map | null>(null);
|
||||
|
||||
// Prevent touch events from bubbling up to parent components
|
||||
const handleTouchStart = (e: React.TouchEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e: React.TouchEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
// Prevent mouse events from bubbling up
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleMouseUp = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
// Prevent wheel events from bubbling up
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (mapRef.current && !map) {
|
||||
const center = initialLocation
|
||||
? { lat: initialLocation.lat, lng: initialLocation.lng }
|
||||
: { lat: 24.7136, lng: 46.6753 }; // Default to Riyadh, Saudi Arabia
|
||||
|
||||
const newMap = new google.maps.Map(mapRef.current, {
|
||||
center,
|
||||
zoom: initialLocation ? 15 : 13,
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP,
|
||||
mapTypeControl: false,
|
||||
streetViewControl: false,
|
||||
fullscreenControl: false,
|
||||
zoomControl: true,
|
||||
styles: [
|
||||
{
|
||||
featureType: "poi",
|
||||
elementType: "labels",
|
||||
stylers: [{ visibility: "off" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
setMap(newMap);
|
||||
console.log('Map initialized successfully');
|
||||
|
||||
// If we have an initial location, add a marker
|
||||
if (initialLocation) {
|
||||
const initialMarker = new google.maps.Marker({
|
||||
position: { lat: initialLocation.lat, lng: initialLocation.lng },
|
||||
map: newMap,
|
||||
draggable: true,
|
||||
});
|
||||
markerRef.current = initialMarker;
|
||||
|
||||
// Add drag end listener to the initial marker
|
||||
initialMarker.addListener("dragend", () => {
|
||||
const position = initialMarker.getPosition();
|
||||
if (position) {
|
||||
const lat = position.lat();
|
||||
const lng = position.lng();
|
||||
|
||||
// Get address from coordinates
|
||||
const geocoder = new google.maps.Geocoder();
|
||||
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
|
||||
if (status === "OK" && results && results[0]) {
|
||||
const address = results[0].formatted_address;
|
||||
console.log('Initial marker drag - calling onLocationSelect:', { lat, lng, address });
|
||||
onLocationSelect?.(lat, lng, address);
|
||||
} else {
|
||||
console.log('Geocoding failed on initial marker drag:', status);
|
||||
// Fallback: use coordinates as address if geocoding fails
|
||||
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
|
||||
console.log('Using fallback address for initial marker drag:', fallbackAddress);
|
||||
onLocationSelect?.(lat, lng, fallbackAddress);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Only add click listener if not in readOnly mode
|
||||
if (!readOnly) {
|
||||
newMap.addListener("click", (e: google.maps.MapMouseEvent) => {
|
||||
if (e.latLng) {
|
||||
const lat = e.latLng.lat();
|
||||
const lng = e.latLng.lng();
|
||||
|
||||
// Remove existing marker
|
||||
if (markerRef.current) {
|
||||
markerRef.current.setMap(null);
|
||||
}
|
||||
|
||||
// Add new marker
|
||||
const newMarker = new google.maps.Marker({
|
||||
position: { lat, lng },
|
||||
map: newMap,
|
||||
draggable: true,
|
||||
});
|
||||
|
||||
markerRef.current = newMarker;
|
||||
|
||||
// Add drag end listener to the marker
|
||||
newMarker.addListener("dragend", () => {
|
||||
const position = newMarker.getPosition();
|
||||
if (position) {
|
||||
const lat = position.lat();
|
||||
const lng = position.lng();
|
||||
|
||||
// Get address from coordinates
|
||||
const geocoder = new google.maps.Geocoder();
|
||||
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
|
||||
if (status === "OK" && results && results[0]) {
|
||||
const address = results[0].formatted_address;
|
||||
console.log('Marker drag - calling onLocationSelect:', { lat, lng, address });
|
||||
onLocationSelect?.(lat, lng, address);
|
||||
} else {
|
||||
console.log('Geocoding failed on drag:', status);
|
||||
// Fallback: use coordinates as address if geocoding fails
|
||||
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
|
||||
console.log('Using fallback address for drag:', fallbackAddress);
|
||||
onLocationSelect?.(lat, lng, fallbackAddress);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get address from coordinates
|
||||
const geocoder = new google.maps.Geocoder();
|
||||
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
|
||||
if (status === "OK" && results && results[0]) {
|
||||
const address = results[0].formatted_address;
|
||||
console.log('Map click - calling onLocationSelect:', { lat, lng, address });
|
||||
onLocationSelect?.(lat, lng, address);
|
||||
} else {
|
||||
console.log('Geocoding failed on click:', status);
|
||||
// Fallback: use coordinates as address if geocoding fails
|
||||
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
|
||||
console.log('Using fallback address:', fallbackAddress);
|
||||
onLocationSelect?.(lat, lng, fallbackAddress);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [map, onLocationSelect, readOnly, initialLocation]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={mapRef}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: height,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onWheel={handleWheel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function GoogleMap({
|
||||
onLocationSelect,
|
||||
height,
|
||||
readOnly,
|
||||
initialLocation,
|
||||
}: GoogleMapProps) {
|
||||
const render = (status: Status) => {
|
||||
switch (status) {
|
||||
case Status.LOADING:
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: height || "100%",
|
||||
backgroundColor: "#f5f5f5",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<div>Loading map...</div>
|
||||
</div>
|
||||
);
|
||||
case Status.FAILURE:
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: height || "100%",
|
||||
backgroundColor: "#ffebee",
|
||||
color: "#c62828",
|
||||
borderRadius: "8px",
|
||||
padding: "20px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontWeight: "500", marginBottom: "8px" }}>
|
||||
Failed to load Google Maps
|
||||
</div>
|
||||
<div style={{ fontSize: "12px", marginBottom: "8px" }}>
|
||||
API not activated or key invalid
|
||||
</div>
|
||||
<div style={{ fontSize: "11px", color: "#999" }}>
|
||||
Please enable Maps JavaScript API in Google Cloud Console
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<MapComponent
|
||||
onLocationSelect={onLocationSelect}
|
||||
height={height}
|
||||
readOnly={readOnly}
|
||||
initialLocation={initialLocation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const apiKey =
|
||||
// process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ||
|
||||
"AIzaSyAPIJLJlpGyFkSImiOqI0KpymhR0A2qwzg";
|
||||
|
||||
return <Wrapper apiKey={apiKey} render={render} />;
|
||||
}
|
||||
58
src/components/CustomBottomSheet/InfoButtonSheet.tsx
Normal file
58
src/components/CustomBottomSheet/InfoButtonSheet.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Button } from "antd";
|
||||
import InfoMessageIcon from "components/Icons/InfoMessageIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import ProText from "../ProText";
|
||||
import ProTitle from "../ProTitle";
|
||||
|
||||
interface InfoButtonSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function InfoButtonSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
description,
|
||||
}: InfoButtonSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const handleSave = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={title}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"50vh"}
|
||||
snapPoints={["50vh"]}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<InfoMessageIcon />
|
||||
<ProTitle level={5}>{title}</ProTitle>
|
||||
<ProText type="secondary" style={{textAlign:"center"}}>{description}</ProText>
|
||||
</div>
|
||||
<br />
|
||||
<Button type="primary" style={{ width: "100%" }} onClick={handleSave}>
|
||||
{t("address.gotIt")}
|
||||
</Button>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
66
src/components/CustomBottomSheet/MapBottomSheet.module.css
Normal file
66
src/components/CustomBottomSheet/MapBottomSheet.module.css
Normal file
@@ -0,0 +1,66 @@
|
||||
.mapContainer {
|
||||
flex: 1;
|
||||
margin-bottom: 16px;
|
||||
touch-action: manipulation;
|
||||
overscroll-behavior: contain;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
/* Prevent any scroll interference */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
/* Ensure map takes full height */
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
/* Debug: add border to see container */
|
||||
}
|
||||
|
||||
.mapWrapper {
|
||||
/* Create a new stacking context for the map */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
/* Prevent touch events from bubbling */
|
||||
isolation: isolate;
|
||||
/* Ensure wrapper takes full height of container */
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/* Debug: add background to see wrapper */
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.fallbackButton {
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.locationInfo {
|
||||
background-color: #f0f8ff;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #d6e4ff;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.locationTitle {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.locationAddress {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.locationCoordinates {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.actionButtons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
flex: 1;
|
||||
height: 50px;
|
||||
}
|
||||
135
src/components/CustomBottomSheet/MapBottomSheet.tsx
Normal file
135
src/components/CustomBottomSheet/MapBottomSheet.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Button } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import { GoogleMap } from "./GoogleMap";
|
||||
import styles from "./MapBottomSheet.module.css";
|
||||
import { MapFallback } from "./MapFallback";
|
||||
|
||||
interface MapBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
interface LocationData {
|
||||
lat: number;
|
||||
lng: number;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export function MapBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: MapBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const [selectedLocation, setSelectedLocation] = useState<LocationData | null>(
|
||||
null
|
||||
);
|
||||
const [useMapFallback, setUseMapFallback] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
|
||||
// Try to parse initialValue as JSON to set selectedLocation
|
||||
if (initialValue) {
|
||||
try {
|
||||
const parsed = JSON.parse(initialValue);
|
||||
if (parsed.lat && parsed.lng && parsed.address) {
|
||||
setSelectedLocation(parsed);
|
||||
}
|
||||
} catch {
|
||||
// If parsing fails, it's just a string value, not a location object
|
||||
setSelectedLocation(null);
|
||||
}
|
||||
}
|
||||
}, [initialValue]);
|
||||
|
||||
const handleLocationSelect = (lat: number, lng: number, address: string) => {
|
||||
console.log('Location selected:', { lat, lng, address });
|
||||
setSelectedLocation({ lat, lng, address });
|
||||
setValue(address);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
console.log('Save clicked, selectedLocation:', selectedLocation);
|
||||
if (selectedLocation) {
|
||||
onSave(JSON.stringify(selectedLocation));
|
||||
} else {
|
||||
onSave(value);
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
setSelectedLocation(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("cart.selectLocation")}
|
||||
showCloseButton={true}
|
||||
initialSnap={1}
|
||||
height={"100vh"}
|
||||
snapPoints={["100vh"]}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
padding: 0, // Override ProBottomSheet padding
|
||||
margin: 0, // Remove any margins
|
||||
}}
|
||||
>
|
||||
<div className={styles.mapContainer}>
|
||||
<div className={styles.mapWrapper}>
|
||||
{useMapFallback ? (
|
||||
<MapFallback
|
||||
onLocationSelect={handleLocationSelect}
|
||||
height="100%"
|
||||
initialAddress={selectedLocation?.address || ""}
|
||||
/>
|
||||
) : (
|
||||
<GoogleMap onLocationSelect={handleLocationSelect} height="100%" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!useMapFallback && (
|
||||
<div className={styles.fallbackButton}>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => setUseMapFallback(true)}
|
||||
>
|
||||
{t("cart.mapFallbackText")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.actionButtons}>
|
||||
<Button className={styles.actionButton} onClick={handleCancel}>
|
||||
{t("cart.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
className={styles.actionButton}
|
||||
onClick={handleSave}
|
||||
disabled={!selectedLocation}
|
||||
>
|
||||
{t("cart.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
114
src/components/CustomBottomSheet/MapFallback.tsx
Normal file
114
src/components/CustomBottomSheet/MapFallback.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
|
||||
import { Button, Input, Space, Typography } from "antd";
|
||||
import { useState } from "react";
|
||||
|
||||
const { Text } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface MapFallbackProps {
|
||||
onLocationSelect?: (lat: number, lng: number, address: string) => void;
|
||||
height?: string;
|
||||
initialAddress?: string;
|
||||
}
|
||||
|
||||
export function MapFallback({ onLocationSelect, height = "100%", initialAddress = "" }: MapFallbackProps) {
|
||||
const [address, setAddress] = useState(initialAddress);
|
||||
|
||||
// Prevent events from bubbling up to parent components
|
||||
const handleTouchStart = (e: React.TouchEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e: React.TouchEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleMouseUp = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (address.trim()) {
|
||||
// For fallback, we'll use dummy coordinates
|
||||
// In a real implementation, you might want to use a geocoding service
|
||||
const dummyLat = 24.7136 + (Math.random() - 0.5) * 0.01;
|
||||
const dummyLng = 46.6753 + (Math.random() - 0.5) * 0.01;
|
||||
onLocationSelect?.(dummyLat, dummyLng, address);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: height,
|
||||
padding: "20px",
|
||||
backgroundColor: "#f8f9fa",
|
||||
borderRadius: "8px",
|
||||
border: "2px dashed #dee2e6",
|
||||
}}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: "48px", marginBottom: "12px" }}>📍</div>
|
||||
<Text strong style={{ fontSize: "16px" }}>
|
||||
Map Not Available
|
||||
</Text>
|
||||
<br />
|
||||
<Text type="secondary" style={{ fontSize: "12px" }}>
|
||||
Please enter your address manually
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Space direction="vertical" size="middle" style={{ flex: 1 }}>
|
||||
<div>
|
||||
<Text strong>Enter Address:</Text>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="Enter your full address including street, city, and postal code..."
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
style={{ marginTop: "8px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSubmit}
|
||||
disabled={!address.trim()}
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
>
|
||||
Use This Address
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
142
src/components/CustomBottomSheet/OfficeBottomSheet.tsx
Normal file
142
src/components/CustomBottomSheet/OfficeBottomSheet.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Button, Form, Input } from "antd";
|
||||
import { OfficeDetailsType } from "features/order/orderSlice";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface OfficeBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: OfficeDetailsType | null;
|
||||
onSave: (value: OfficeDetailsType) => void;
|
||||
}
|
||||
|
||||
export function OfficeBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: OfficeBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [officeForm] = Form.useForm();
|
||||
useEffect(() => {
|
||||
officeForm.setFieldsValue(initialValue);
|
||||
}, [initialValue, officeForm]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(officeForm.getFieldsValue());
|
||||
officeForm.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
officeForm.setFieldsValue(initialValue);
|
||||
officeForm.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("address.officeDetails")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"90vh"}
|
||||
snapPoints={["90vh"]}
|
||||
>
|
||||
<Form
|
||||
layout="vertical"
|
||||
style={{ marginTop: 12 }}
|
||||
name="officeForm"
|
||||
form={officeForm}
|
||||
initialValues={initialValue as OfficeDetailsType}
|
||||
>
|
||||
<Form.Item
|
||||
name="company"
|
||||
label={t("address.company")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.company")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="officeNo"
|
||||
label={t("address.officeNo")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.officeNo")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>{" "}
|
||||
<Form.Item
|
||||
name="floorNo"
|
||||
label={t("address.floorNo")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.floorNo")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="contact" label={t("address.contactPerson")}>
|
||||
<Input
|
||||
placeholder={t("address.contactPerson")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="phone" label={t("address.phone")}>
|
||||
<Input
|
||||
placeholder={t("address.phone")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="note" label={t("address.note")}>
|
||||
<TextArea
|
||||
placeholder={t("address.addressLabel")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
rows={2}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<br />
|
||||
|
||||
<Button type="primary" style={{ width: "100%" }} onClick={handleSave}>
|
||||
{t("address.save")}
|
||||
</Button>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
133
src/components/CustomBottomSheet/RoomBottomSheet.tsx
Normal file
133
src/components/CustomBottomSheet/RoomBottomSheet.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import { Button, Divider, Form, Input } from "antd";
|
||||
import { RoomDetailsType } from "features/order/orderSlice";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import ProRatioGroups from "../ProRatioGroups/ProRatioGroups";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface RoomBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: RoomDetailsType | null;
|
||||
onSave: (value: RoomDetailsType) => void;
|
||||
}
|
||||
|
||||
export function RoomBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: RoomBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [roomForm] = Form.useForm();
|
||||
useEffect(() => {
|
||||
roomForm.setFieldsValue(initialValue);
|
||||
}, [initialValue, roomForm]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(roomForm.getFieldsValue());
|
||||
roomForm.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
roomForm.setFieldsValue(initialValue);
|
||||
roomForm.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("address.roomDetails")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"85vh"}
|
||||
snapPoints={["85vh"]}
|
||||
>
|
||||
<Form
|
||||
layout="vertical"
|
||||
style={{ marginTop: 12 }}
|
||||
name="roomForm"
|
||||
form={roomForm}
|
||||
initialValues={initialValue as RoomDetailsType}
|
||||
>
|
||||
<Form.Item
|
||||
name="roomNo"
|
||||
label={t("address.roomNo")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
colon={false}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.roomNo")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>{" "}
|
||||
<Form.Item
|
||||
name="floorNo"
|
||||
label={t("address.floorNo")}
|
||||
rules={[{ required: true, message: "" }]}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("address.floorNo")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="guestName" label={t("address.guestName")}>
|
||||
<Input
|
||||
placeholder={t("address.guestName")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
}}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="note" label={t("address.note")}>
|
||||
<TextArea
|
||||
placeholder={t("address.addressLabel")}
|
||||
size="large"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
rows={2}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<Form.Item name="deliverWay">
|
||||
<ProRatioGroups
|
||||
options={[
|
||||
{
|
||||
label: t("address.pleaseKnockSoftly"),
|
||||
value: "pleaseKnockSoftly",
|
||||
},
|
||||
{
|
||||
label: t("address.deliverToRoom"),
|
||||
value: "deliverToRoom",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<br />
|
||||
|
||||
<Button type="primary" style={{ width: "100%" }} onClick={handleSave}>
|
||||
{t("address.save")}
|
||||
</Button>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Button, Input } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface SpecialRequestBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
export function SpecialRequestBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: SpecialRequestBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(value);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("cart.specialRequest")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"40vh"}
|
||||
snapPoints={["30vh"]}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<TextArea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder={t("cart.specialRequest")}
|
||||
rows={6}
|
||||
size="middle"
|
||||
autoFocus={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<Button type="primary" style={{ width: "100%" }} onClick={handleSave}>
|
||||
{t("cart.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
66
src/components/CustomBottomSheet/SpecialRequestDialog.tsx
Normal file
66
src/components/CustomBottomSheet/SpecialRequestDialog.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Button, Input, Modal } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface SpecialRequestDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
export function SpecialRequestDialog({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: SpecialRequestDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(value);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("cart.specialRequest")}
|
||||
open={isOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleCancel}>
|
||||
{t("cart.cancel")}
|
||||
</Button>,
|
||||
<Button key="save" type="primary" onClick={handleSave}>
|
||||
{t("cart.save")}
|
||||
</Button>,
|
||||
]}
|
||||
width={500}
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<TextArea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder={t("cart.specialRequest")}
|
||||
rows={6}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
212
src/components/CustomBottomSheet/TablesBottomSheet.tsx
Normal file
212
src/components/CustomBottomSheet/TablesBottomSheet.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import Circle4PeopleIcon from "components/Icons/tables/Circle4PeopleIcon";
|
||||
import Rectangle6PeopleIcon from "components/Icons/tables/Rectangle6PeopleIcon";
|
||||
import Square4PeopleIcon from "components/Icons/tables/Square4PeopleIcon";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
import styles from "./CustomBottomSheet.module.css";
|
||||
|
||||
interface TablesBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
export function TablesBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: TablesBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(value);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const tableList = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Table 1",
|
||||
value: "table1",
|
||||
shape: "rectangle",
|
||||
number: 6,
|
||||
is_booked: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Table 2",
|
||||
value: "table2",
|
||||
shape: "square",
|
||||
number: 4,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Table 3",
|
||||
value: "table3",
|
||||
shape: "circle",
|
||||
number: 4,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Table 4",
|
||||
value: "table4",
|
||||
shape: "rectangle",
|
||||
number: 6,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Table 5",
|
||||
value: "table5",
|
||||
shape: "rectangle",
|
||||
number: 6,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Table 6",
|
||||
value: "table6",
|
||||
shape: "rectangle",
|
||||
number: 6,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Table 7",
|
||||
value: "table7",
|
||||
shape: "square",
|
||||
number: 4,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Table 8",
|
||||
value: "table8",
|
||||
shape: "square",
|
||||
number: 4,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Table 9",
|
||||
value: "table9",
|
||||
shape: "circle",
|
||||
number: 4,
|
||||
is_booked: false,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Table 10",
|
||||
value: "table10",
|
||||
shape: "circle",
|
||||
number: 4,
|
||||
is_booked: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("cart.tables")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"70vh"}
|
||||
snapPoints={["70vh"]}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(4, 1fr)",
|
||||
gap: 20,
|
||||
columnGap: 20,
|
||||
}}
|
||||
>
|
||||
{tableList.map((table) => {
|
||||
if (table.shape === "rectangle") {
|
||||
return (
|
||||
<div
|
||||
key={table.id}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onClick={handleSave}
|
||||
>
|
||||
<Rectangle6PeopleIcon
|
||||
title={table.name}
|
||||
timer={
|
||||
table.is_booked ? table.number.toFixed(2).toString() : ""
|
||||
}
|
||||
className={styles.bookedStyles}
|
||||
fill={table.is_booked ? "#FFE299" : "#C8E6A9"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (table.shape === "square") {
|
||||
return (
|
||||
<div
|
||||
key={table.id}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onClick={handleSave}
|
||||
>
|
||||
<Square4PeopleIcon
|
||||
title={table.name}
|
||||
timer={
|
||||
table.is_booked ? table.number.toFixed(2).toString() : ""
|
||||
}
|
||||
className={styles.bookedStyles}
|
||||
fill={table.is_booked ? "#FFE299" : "#5F6C7B"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (table.shape === "circle") {
|
||||
return (
|
||||
<div
|
||||
key={table.id}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onClick={handleSave}
|
||||
>
|
||||
<Circle4PeopleIcon
|
||||
title={table.name}
|
||||
timer={
|
||||
table.is_booked ? table.number.toFixed(2).toString() : ""
|
||||
}
|
||||
className={styles.bookedStyles}
|
||||
fill={table.is_booked ? "#FFE299" : "#5F6C7B"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
79
src/components/CustomBottomSheet/TipBottomSheet.tsx
Normal file
79
src/components/CustomBottomSheet/TipBottomSheet.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Button, Input } from "antd";
|
||||
import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||
|
||||
interface TipBottomSheetProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
export function TipBottomSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: TipBottomSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(value);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProBottomSheet
|
||||
isOpen={isOpen}
|
||||
onClose={handleCancel}
|
||||
title={t("cart.tip")}
|
||||
showCloseButton={false}
|
||||
initialSnap={1}
|
||||
height={"45vh"}
|
||||
snapPoints={["30vh"]}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<WaiterRewardingIcon />
|
||||
|
||||
<br />
|
||||
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder={t("cart.amount")}
|
||||
autoFocus={false}
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ width: "100%", height: 50 }}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t("cart.addTip")}
|
||||
</Button>
|
||||
</div>
|
||||
</ProBottomSheet>
|
||||
);
|
||||
}
|
||||
74
src/components/CustomBottomSheet/TipDialog.tsx
Normal file
74
src/components/CustomBottomSheet/TipDialog.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Button, Input, Modal } from "antd";
|
||||
import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface TipDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
}
|
||||
|
||||
export function TipDialog({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: TipDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(value);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setValue(initialValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("cart.tip")}
|
||||
open={isOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleCancel}>
|
||||
{t("cart.cancel")}
|
||||
</Button>,
|
||||
<Button key="save" type="primary" onClick={handleSave}>
|
||||
{t("cart.addTip")}
|
||||
</Button>,
|
||||
]}
|
||||
width={500}
|
||||
destroyOnHidden
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<WaiterRewardingIcon />
|
||||
|
||||
<br />
|
||||
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder={t("cart.amount")}
|
||||
autoFocus={false}
|
||||
size="large"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user