Initial commit

This commit is contained in:
2025-10-04 18:22:24 +03:00
commit 2852c2c054
291 changed files with 38109 additions and 0 deletions

View 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>
</>
);
}

View 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>
);
}

View 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>
);
}

View File

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

View 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>
);
}

View 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>
);
}

View 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>
);
}

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

View 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>
);
}

View 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;
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View File

@@ -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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}