fix: remove guest picker store

This commit is contained in:
Christel Westerberg
2024-11-12 16:45:25 +01:00
parent 58678244fc
commit ca3819f7cc
17 changed files with 368 additions and 597 deletions

View File

@@ -114,6 +114,8 @@
/* Z-INDEX */
--header-z-index: 11;
--menu-overlay-z-index: 11;
--booking-widget-z-index: 10;
--booking-widget-open-z-index: 100;
--dialog-z-index: 9;
--sidepeek-z-index: 100;
--lightbox-z-index: 150;

View File

@@ -36,7 +36,6 @@ export default function BookingWidgetClient({
name: StickyElementNameEnum.BOOKING_WIDGET,
})
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined =
searchParams
? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
@@ -79,9 +78,7 @@ export default function BookingWidgetClient({
const methods = useForm<BookingWidgetSchema>({
defaultValues: {
search: selectedLocation?.name ?? "",
location: selectedLocation
? JSON.stringify(selectedLocation)
: undefined,
location: selectedLocation ? JSON.stringify(selectedLocation) : undefined,
date: {
// UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507
// This is specifically to handle timezones falling in different dates.
@@ -147,29 +144,34 @@ export default function BookingWidgetClient({
? JSON.parse(sessionStorageSearchData)
: undefined
!(selectedLocation?.name) && initialSelectedLocation?.name &&
!selectedLocation?.name &&
initialSelectedLocation?.name &&
methods.setValue("search", initialSelectedLocation.name)
!selectedLocation && sessionStorageSearchData &&
!selectedLocation &&
sessionStorageSearchData &&
methods.setValue("location", encodeURIComponent(sessionStorageSearchData))
}, [methods, selectedLocation])
return (
<FormProvider {...methods}>
<section ref={bookingWidgetRef} className={styles.containerDesktop}>
<Form locations={locations} type={type} />
</section>
<section className={styles.containerMobile} data-open={isOpen}>
<button
className={styles.close}
onClick={closeMobileSearch}
type="button"
>
<CloseLargeIcon />
</button>
<Form locations={locations} type={type} />
<section
ref={bookingWidgetRef}
className={styles.wrapper}
data-open={isOpen}
>
<MobileToggleButton openMobileSearch={openMobileSearch} />
<div className={styles.formContainer}>
<button
className={styles.close}
onClick={closeMobileSearch}
type="button"
>
<CloseLargeIcon />
</button>
<Form locations={locations} type={type} />
</div>
</section>
<div className={styles.backdrop} onClick={closeMobileSearch} />
<MobileToggleButton openMobileSearch={openMobileSearch} />
</FormProvider>
)
}

View File

@@ -6,8 +6,6 @@
display: grid;
gap: var(--Spacing-x-one-and-half);
padding: var(--Spacing-x2);
position: sticky;
top: 0;
z-index: 1;
background-color: var(--Base-Surface-Primary-light-Normal);
}

View File

@@ -31,12 +31,6 @@ export default function MobileToggleButton({
const location = useWatch({ name: "location" })
const rooms: BookingWidgetSchema["rooms"] = useWatch({ name: "rooms" })
const bookingWidgetMobileRef = useRef(null)
useStickyPosition({
ref: bookingWidgetMobileRef,
name: StickyElementNameEnum.BOOKING_WIDGET_MOBILE,
})
const parsedLocation: Location | null = location
? JSON.parse(decodeURIComponent(location))
: null
@@ -75,7 +69,6 @@ export default function MobileToggleButton({
className={locationAndDateIsSet ? styles.complete : styles.partial}
onClick={openMobileSearch}
role="button"
ref={bookingWidgetMobileRef}
>
{!locationAndDateIsSet && (
<>

View File

@@ -1,60 +1,62 @@
.containerDesktop,
.containerMobile,
.close {
display: none;
.wrapper {
position: sticky;
z-index: var(--booking-widget-z-index);
}
@media screen and (max-width: 767px) {
.containerMobile {
background-color: var(--UI-Input-Controls-Surface-Normal);
bottom: -100%;
display: grid;
gap: var(--Spacing-x3);
grid-template-rows: 36px 1fr;
height: calc(100dvh - 20px);
padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x7);
position: fixed;
transition: bottom 300ms ease;
width: 100%;
z-index: 10000;
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
}
.formContainer {
display: grid;
grid-template-rows: 36px 1fr;
background-color: var(--UI-Input-Controls-Surface-Normal);
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
gap: var(--Spacing-x3);
height: calc(100dvh - 20px);
width: 100%;
padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x7);
position: fixed;
bottom: -100%;
transition: bottom 300ms ease;
}
.containerMobile[data-open="true"] {
bottom: 0;
}
.wrapper[data-open="true"] {
z-index: var(--booking-widget-open-z-index);
}
.close {
background: none;
border: none;
cursor: pointer;
justify-self: flex-end;
}
.wrapper[data-open="true"] .formContainer {
bottom: 0;
}
.containerMobile[data-open="true"] + .backdrop {
background-color: rgba(0, 0, 0, 0.4);
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 1000;
}
.close {
background: none;
border: none;
cursor: pointer;
justify-self: flex-end;
}
.wrapper[data-open="true"] + .backdrop {
background-color: rgba(0, 0, 0, 0.4);
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: calc(var(--booking-widget-open-z-index) - 1);
}
@media screen and (min-width: 768px) {
.containerDesktop {
display: block;
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
position: sticky;
.wrapper {
top: 0;
z-index: 10;
background-color: var(--Base-Surface-Primary-light-Normal);
}
}
@media screen and (min-width: 1367px) {
.container {
z-index: 9;
.formContainer {
display: block;
background-color: var(--Base-Surface-Primary-light-Normal);
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
height: auto;
position: static;
padding: 0;
}
.close {
display: none;
}
}

View File

@@ -1,5 +1,4 @@
"use client"
import { useState } from "react"
import { useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
@@ -7,7 +6,6 @@ import { dt } from "@/lib/dt"
import DatePicker from "@/components/DatePicker"
import GuestsRoomsPickerForm from "@/components/GuestsRoomsPicker"
import GuestsRoomsProvider from "@/components/GuestsRoomsPicker/Provider/GuestsRoomsProvider"
import { SearchIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
@@ -26,12 +24,10 @@ export default function FormContent({
const intl = useIntl()
const selectedDate = useWatch({ name: "date" })
const rooms = intl.formatMessage({ id: "Guests & Rooms" })
const roomsLabel = intl.formatMessage({ id: "Guests & Rooms" })
const nights = dt(selectedDate.toDate).diff(dt(selectedDate.fromDate), "days")
const selectedGuests = useWatch({ name: "rooms" })
return (
<>
<div className={styles.input}>
@@ -51,12 +47,10 @@ export default function FormContent({
<div className={styles.rooms}>
<label>
<Caption color="red" type="bold" asChild>
<span>{rooms}</span>
<span>{roomsLabel}</span>
</Caption>
</label>
<GuestsRoomsProvider selectedGuests={selectedGuests}>
<GuestsRoomsPickerForm name="rooms" />
</GuestsRoomsProvider>
<GuestsRoomsPickerForm />
</div>
</div>
<div className={styles.voucherContainer}>

View File

@@ -3,8 +3,6 @@
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Counter from "../Counter"
@@ -13,39 +11,37 @@ import styles from "./adult-selector.module.css"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import {
AdultSelectorProps,
Child,
SelectorProps,
} from "@/types/components/bookingWidget/guestsRoomsPicker"
export default function AdultSelector({ roomIndex = 0 }: AdultSelectorProps) {
export default function AdultSelector({
roomIndex = 0,
currentAdults,
currentChildren,
childrenInAdultsBed,
}: SelectorProps) {
const intl = useIntl()
const adultsLabel = intl.formatMessage({ id: "Adults" })
const { setValue } = useFormContext()
const { adults, child, childrenInAdultsBed } = useGuestsRoomsStore(
(state) => state.rooms[roomIndex]
)
const increaseAdults = useGuestsRoomsStore((state) => state.increaseAdults)
const decreaseAdults = useGuestsRoomsStore((state) => state.decreaseAdults)
function increaseAdultsCount(roomIndex: number) {
if (adults < 6) {
increaseAdults(roomIndex)
setValue(`rooms.${roomIndex}.adults`, adults + 1)
if (currentAdults < 6) {
setValue(`rooms.${roomIndex}.adults`, currentAdults + 1)
}
}
function decreaseAdultsCount(roomIndex: number) {
if (adults > 1) {
decreaseAdults(roomIndex)
setValue(`rooms.${roomIndex}.adults`, adults - 1)
if (childrenInAdultsBed > adults) {
const toUpdateIndex = child.findIndex(
if (currentAdults > 1) {
setValue(`rooms.${roomIndex}.adults`, currentAdults - 1)
if (childrenInAdultsBed > currentAdults) {
const toUpdateIndex = currentChildren.findIndex(
(child: Child) => child.bed == ChildBedMapEnum.IN_ADULTS_BED
)
if (toUpdateIndex != -1) {
setValue(
`rooms.${roomIndex}.children.${toUpdateIndex}.bed`,
child[toUpdateIndex].age < 3
currentChildren[toUpdateIndex].age < 3
? ChildBedMapEnum.IN_CRIB
: ChildBedMapEnum.IN_EXTRA_BED
)
@@ -60,15 +56,15 @@ export default function AdultSelector({ roomIndex = 0 }: AdultSelectorProps) {
{adultsLabel}
</Caption>
<Counter
count={adults}
count={currentAdults}
handleOnDecrease={() => {
decreaseAdultsCount(roomIndex)
}}
handleOnIncrease={() => {
increaseAdultsCount(roomIndex)
}}
disableDecrease={adults == 1}
disableIncrease={adults == 6}
disableDecrease={currentAdults == 1}
disableIncrease={currentAdults == 6}
/>
</section>
)

View File

@@ -3,8 +3,6 @@
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
import { ErrorCircleIcon } from "@/components/Icons"
import Select from "@/components/TempDesignSystem/Select"
import Caption from "@/components/TempDesignSystem/Text/Caption"
@@ -19,6 +17,8 @@ import {
export default function ChildInfoSelector({
child = { age: -1, bed: -1 },
childrenInAdultsBed,
adults,
index = 0,
roomIndex = 0,
}: ChildInfoSelectorProps) {
@@ -26,23 +26,7 @@ export default function ChildInfoSelector({
const ageLabel = intl.formatMessage({ id: "Age" })
const ageReqdErrMsg = intl.formatMessage({ id: "Child age is required" })
const bedLabel = intl.formatMessage({ id: "Bed" })
const { setValue } = useFormContext()
const { adults, childrenInAdultsBed } = useGuestsRoomsStore(
(state) => state.rooms[roomIndex]
)
const {
isValidated,
updateChildAge,
updateChildBed,
increaseChildInAdultsBed,
decreaseChildInAdultsBed,
} = useGuestsRoomsStore((state) => ({
isValidated: state.isValidated,
updateChildAge: state.updateChildAge,
updateChildBed: state.updateChildBed,
increaseChildInAdultsBed: state.increaseChildInAdultsBed,
decreaseChildInAdultsBed: state.decreaseChildInAdultsBed,
}))
const { setValue, formState } = useFormContext()
const ageList = Array.from(Array(13).keys()).map((age) => ({
label: `${age}`,
@@ -50,7 +34,6 @@ export default function ChildInfoSelector({
}))
function updateSelectedAge(age: number) {
updateChildAge(age, roomIndex, index)
setValue(`rooms.${roomIndex}.child.${index}.age`, age, {
shouldValidate: true,
})
@@ -59,12 +42,6 @@ export default function ChildInfoSelector({
}
function updateSelectedBed(bed: number) {
if (bed == ChildBedMapEnum.IN_ADULTS_BED) {
increaseChildInAdultsBed(roomIndex)
} else if (child.bed == ChildBedMapEnum.IN_ADULTS_BED) {
decreaseChildInAdultsBed(roomIndex)
}
updateChildBed(bed, roomIndex, index)
setValue(`rooms.${roomIndex}.child.${index}.bed`, bed)
}
@@ -97,6 +74,7 @@ export default function ChildInfoSelector({
return availableBedTypes
}
console.log("ALL TJHE ERORRORS", formState.errors)
return (
<>
<div key={index} className={styles.childInfoContainer}>
@@ -131,12 +109,13 @@ export default function ChildInfoSelector({
) : null}
</div>
</div>
{isValidated && child.age < 0 ? (
{/* {isValidated && child.age < 0 ? (
<Caption color="red" className={styles.error}>
<ErrorCircleIcon color="red" />
{ageReqdErrMsg}
</Caption>
) : null}
) : null} */}
</>
)
}

View File

@@ -3,8 +3,6 @@
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Counter from "../Counter"
@@ -13,25 +11,22 @@ import ChildInfoSelector from "./ChildInfoSelector"
import styles from "./child-selector.module.css"
import { BookingWidgetSchema } from "@/types/components/bookingWidget"
import { ChildSelectorProps } from "@/types/components/bookingWidget/guestsRoomsPicker"
import { SelectorProps } from "@/types/components/bookingWidget/guestsRoomsPicker"
export default function ChildSelector({ roomIndex = 0 }: ChildSelectorProps) {
export default function ChildSelector({
roomIndex = 0,
currentAdults,
childrenInAdultsBed,
currentChildren,
}: SelectorProps) {
const intl = useIntl()
const childrenLabel = intl.formatMessage({ id: "Children" })
const { setValue, trigger } = useFormContext<BookingWidgetSchema>()
const children = useGuestsRoomsStore((state) => state.rooms[roomIndex].child)
const increaseChildren = useGuestsRoomsStore(
(state) => state.increaseChildren
)
const decreaseChildren = useGuestsRoomsStore(
(state) => state.decreaseChildren
)
const { setValue } = useFormContext<BookingWidgetSchema>()
function increaseChildrenCount(roomIndex: number) {
if (children.length < 5) {
increaseChildren(roomIndex)
if (currentChildren.length < 5) {
setValue(
`rooms.${roomIndex}.child.${children.length}`,
`rooms.${roomIndex}.child.${currentChildren.length}`,
{
age: -1,
bed: -1,
@@ -41,9 +36,9 @@ export default function ChildSelector({ roomIndex = 0 }: ChildSelectorProps) {
}
}
function decreaseChildrenCount(roomIndex: number) {
if (children.length > 0) {
const newChildrenList = decreaseChildren(roomIndex)
setValue(`rooms.${roomIndex}.child`, newChildrenList, {
if (currentChildren.length > 0) {
currentChildren.pop()
setValue(`rooms.${roomIndex}.child`, currentChildren, {
shouldValidate: true,
})
}
@@ -56,23 +51,25 @@ export default function ChildSelector({ roomIndex = 0 }: ChildSelectorProps) {
{childrenLabel}
</Caption>
<Counter
count={children.length}
count={currentChildren.length}
handleOnDecrease={() => {
decreaseChildrenCount(roomIndex)
}}
handleOnIncrease={() => {
increaseChildrenCount(roomIndex)
}}
disableDecrease={children.length == 0}
disableIncrease={children.length == 5}
disableDecrease={currentChildren.length == 0}
disableIncrease={currentChildren.length == 5}
/>
</section>
{children.map((child, index) => (
{currentChildren.map((child, index) => (
<ChildInfoSelector
roomIndex={roomIndex}
index={index}
child={child}
adults={currentAdults}
key={"child_" + index}
childrenInAdultsBed={childrenInAdultsBed}
/>
))}
</>

View File

@@ -1,130 +0,0 @@
"use client"
import { Dialog } from "react-aria-components"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
import { CloseLargeIcon, PlusCircleIcon, PlusIcon } from "../Icons"
import Button from "../TempDesignSystem/Button"
import Divider from "../TempDesignSystem/Divider"
import Subtitle from "../TempDesignSystem/Text/Subtitle"
import { Tooltip } from "../TempDesignSystem/Tooltip"
import AdultSelector from "./AdultSelector"
import ChildSelector from "./ChildSelector"
import styles from "./guests-rooms-picker.module.css"
import { BookingWidgetSchema } from "@/types/components/bookingWidget"
export default function GuestsRoomsPickerDialog() {
const intl = useIntl()
const doneLabel = intl.formatMessage({ id: "Done" })
const roomLabel = intl.formatMessage({ id: "Room" })
const disabledBookingOptionsHeader = intl.formatMessage({
id: "Disabled booking options header",
})
const disabledBookingOptionsText = intl.formatMessage({
id: "Disabled adding room",
})
const addRoomLabel = intl.formatMessage({ id: "Add Room" })
const { getFieldState } = useFormContext<BookingWidgetSchema>()
const rooms = useGuestsRoomsStore((state) => state.rooms)
return (
<Dialog className={styles.pickerContainer}>
{({ close }) => {
return (
<>
<header className={styles.header}>
<button type="button" className={styles.close}>
<CloseLargeIcon />
</button>
</header>
<div className={styles.contentContainer}>
{rooms.map((room, index) => (
<div className={styles.roomContainer} key={index}>
<section className={styles.roomDetailsContainer}>
<Subtitle type="two" className={styles.roomHeading}>
{roomLabel} {index + 1}
</Subtitle>
<AdultSelector roomIndex={index} />
<ChildSelector roomIndex={index} />
</section>
<Divider color="primaryLightSubtle" />
</div>
))}
<div className={styles.addRoomMobileContainer}>
<Tooltip
heading={disabledBookingOptionsHeader}
text={disabledBookingOptionsText}
position="top"
arrow="left"
>
{rooms.length < 4 ? (
<Button
intent="text"
variant="icon"
wrapping
disabled
theme="base"
fullWidth
>
<PlusIcon />
{addRoomLabel}
</Button>
) : null}
</Tooltip>
</div>
</div>
<footer className={styles.footer}>
<div className={styles.hideOnMobile}>
<Tooltip
heading={disabledBookingOptionsHeader}
text={disabledBookingOptionsText}
position="top"
arrow="left"
>
{rooms.length < 4 ? (
<Button
intent="text"
variant="icon"
wrapping
disabled
theme="base"
>
<PlusCircleIcon />
{addRoomLabel}
</Button>
) : null}
</Tooltip>
</div>
<Button
onPress={close}
disabled={getFieldState("rooms").invalid}
className={styles.hideOnMobile}
intent="tertiary"
theme="base"
size="small"
>
{doneLabel}
</Button>
<Button
onPress={close}
disabled={getFieldState("rooms").invalid}
className={styles.hideOnDesktop}
intent="tertiary"
theme="base"
size="large"
>
{doneLabel}
</Button>
</footer>
</>
)
}}
</Dialog>
)
}

View File

@@ -0,0 +1,146 @@
"use client"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { CloseLargeIcon, PlusCircleIcon, PlusIcon } from "../Icons"
import Button from "../TempDesignSystem/Button"
import Divider from "../TempDesignSystem/Divider"
import Subtitle from "../TempDesignSystem/Text/Subtitle"
import { Tooltip } from "../TempDesignSystem/Tooltip"
import AdultSelector from "./AdultSelector"
import ChildSelector from "./ChildSelector"
import styles from "./guests-rooms-picker.module.css"
import { BookingWidgetSchema } from "@/types/components/bookingWidget"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import { GuestsRoom } from "@/types/components/bookingWidget/guestsRoomsPicker"
export default function GuestsRoomsPickerDialog({
rooms,
onClose,
}: {
rooms: GuestsRoom[]
onClose: () => void
}) {
const intl = useIntl()
const doneLabel = intl.formatMessage({ id: "Done" })
const roomLabel = intl.formatMessage({ id: "Room" })
const disabledBookingOptionsHeader = intl.formatMessage({
id: "Disabled booking options header",
})
const disabledBookingOptionsText = intl.formatMessage({
id: "Disabled adding room",
})
const addRoomLabel = intl.formatMessage({ id: "Add Room" })
const { getFieldState } = useFormContext<BookingWidgetSchema>()
return (
<>
<header className={styles.header}>
<button type="button" className={styles.close} onClick={onClose}>
<CloseLargeIcon />
</button>
</header>
<div className={styles.contentContainer}>
{rooms.map((room, index) => {
const currentAdults = room.adults
const currentChildren = room.child
const childrenInAdultsBed = currentChildren.filter(
(child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED
).length
return (
<div className={styles.roomContainer} key={index}>
<section className={styles.roomDetailsContainer}>
<Subtitle type="two" className={styles.roomHeading}>
{roomLabel} {index + 1}
</Subtitle>
<AdultSelector
roomIndex={index}
currentAdults={currentAdults}
currentChildren={currentChildren}
childrenInAdultsBed={childrenInAdultsBed}
/>
<ChildSelector
roomIndex={index}
currentAdults={currentAdults}
currentChildren={currentChildren}
childrenInAdultsBed={childrenInAdultsBed}
/>
</section>
<Divider color="primaryLightSubtle" />
</div>
)
})}
<div className={styles.addRoomMobileContainer}>
<Tooltip
heading={disabledBookingOptionsHeader}
text={disabledBookingOptionsText}
position="top"
arrow="left"
>
{rooms.length < 4 ? (
<Button
intent="text"
variant="icon"
wrapping
disabled
theme="base"
fullWidth
>
<PlusIcon />
{addRoomLabel}
</Button>
) : null}
</Tooltip>
</div>
</div>
<footer className={styles.footer}>
<div className={styles.hideOnMobile}>
<Tooltip
heading={disabledBookingOptionsHeader}
text={disabledBookingOptionsText}
position="top"
arrow="left"
>
{rooms.length < 4 ? (
<Button
intent="text"
variant="icon"
wrapping
disabled
theme="base"
>
<PlusCircleIcon />
{addRoomLabel}
</Button>
) : null}
</Tooltip>
</div>
<Button
onPress={onClose}
disabled={getFieldState("rooms").invalid}
className={styles.hideOnMobile}
intent="tertiary"
theme="base"
size="small"
>
{doneLabel}
</Button>
<Button
onPress={onClose}
disabled={getFieldState("rooms").invalid}
className={styles.hideOnDesktop}
intent="tertiary"
theme="base"
size="large"
>
{doneLabel}
</Button>
</footer>
</>
)
}

View File

@@ -1,26 +0,0 @@
"use client"
import { PropsWithChildren, useRef } from "react"
import {
GuestsRoomsContext,
type GuestsRoomsStore,
initGuestsRoomsState,
} from "@/stores/guests-rooms"
import { GuestsRoom } from "@/types/components/bookingWidget/guestsRoomsPicker"
export default function GuestsRoomsProvider({
selectedGuests,
children,
}: PropsWithChildren<{ selectedGuests?: GuestsRoom[] }>) {
const initialStore = useRef<GuestsRoomsStore>()
if (!initialStore.current) {
initialStore.current = initGuestsRoomsState(selectedGuests)
}
return (
<GuestsRoomsContext.Provider value={initialStore.current}>
{children}
</GuestsRoomsContext.Provider>
)
}

View File

@@ -1,7 +1,23 @@
.container {
overflow: hidden;
position: relative;
.triggerDesktop {
display: none;
}
.pickerContainerMobile {
background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
transition: top 300ms ease;
z-index: 1000;
}
.pickerContainerDesktop {
display: none;
}
.roomContainer {
display: grid;
gap: var(--Spacing-x2);
@@ -23,9 +39,7 @@
width: 100%;
text-align: left;
}
.body {
opacity: 0.8;
}
.footer {
display: grid;
gap: var(--Spacing-x1);
@@ -33,25 +47,7 @@
margin-top: var(--Spacing-x2);
}
.pickerContainer {
--header-height: 72px;
--sticky-button-height: 140px;
background-color: var(--Main-Grey-White);
display: grid;
border-radius: var(--Corner-radius-Large);
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
max-width: calc(100vw - 20px);
padding: var(--Spacing-x2) var(--Spacing-x3);
width: 360px;
}
@media screen and (max-width: 1366px) {
.container[data-isopen="true"] .hideWrapper {
top: 20px;
}
.contentContainer {
grid-area: content;
overflow-y: scroll;
@@ -107,6 +103,32 @@
}
@media screen and (min-width: 1367px) {
.pickerContainerMobisse {
display: none;
}
.triggerMobile {
display: none;
}
.triggerDesktop {
display: block;
}
.pickerContainerDesktop {
--header-height: 72px;
--sticky-button-height: 140px;
background-color: var(--Main-Grey-White);
display: grid;
border-radius: var(--Corner-radius-Large);
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
max-width: calc(100vw - 20px);
padding: var(--Spacing-x2) var(--Spacing-x3);
width: 360px;
}
.header {
display: none;
}

View File

@@ -1,37 +1,64 @@
"use client"
import { useCallback, useEffect, useRef, useState } from "react"
import { Button, DialogTrigger, Popover } from "react-aria-components"
import {
Button,
Dialog,
DialogTrigger,
Modal,
Popover,
} from "react-aria-components"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
import { guestRoomsSchema } from "@/components/Forms/BookingWidget/schema"
import Body from "@/components/TempDesignSystem/Text/Body"
import Dialog from "./Dialog"
import PickerForm from "./Form"
import styles from "./guests-rooms-picker.module.css"
export default function GuestsRoomsPickerForm({
name = "rooms",
import { GuestsRoom } from "@/types/components/bookingWidget/guestsRoomsPicker"
export default function GuestsRoomsPickerForm() {
const { watch } = useFormContext()
const rooms = watch("rooms") as GuestsRoom[]
return (
<>
<DialogTrigger>
<Trigger rooms={rooms} className={styles.triggerMobile} />
<Modal className="my-modal">
<Dialog className={styles.pickerContainerMobile}>
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
</Dialog>
</Modal>
</DialogTrigger>
<DialogTrigger>
<Trigger rooms={rooms} className={styles.triggerDesktop} />
<Popover placement="bottom start" offset={22}>
<Dialog className={styles.pickerContainerDesktop}>
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
</Dialog>
</Popover>
</DialogTrigger>
</>
)
}
function Trigger({
rooms,
className,
}: {
name: string
rooms: GuestsRoom[]
className: string
}) {
const intl = useIntl()
const { rooms, adultCount, childCount } = useGuestsRoomsStore((state) => ({
rooms: state.rooms,
adultCount: state.adultCount,
childCount: state.childCount,
}))
return (
<DialogTrigger>
<Button className={styles.btn} type="button">
<Body className={styles.body} asChild>
<span>
<Button className={`${className} ${styles.btn}`} type="button">
<Body>
{rooms.map((room, i) => (
<span key={i}>
{intl.formatMessage(
{ id: "booking.rooms" },
{ totalRooms: rooms.length }
@@ -39,21 +66,18 @@ export default function GuestsRoomsPickerForm({
{", "}
{intl.formatMessage(
{ id: "booking.adults" },
{ totalAdults: adultCount }
{ totalAdults: room.adults }
)}
{childCount > 0
{room.child.length > 0
? ", " +
intl.formatMessage(
{ id: "booking.children" },
{ totalChildren: childCount }
{ totalChildren: room.child.length }
)
: null}
</span>
</Body>
</Button>
<Popover>
<Dialog />
</Popover>
</DialogTrigger>
))}
</Body>
</Button>
)
}

View File

@@ -1,227 +0,0 @@
"use client"
import { produce } from "immer"
import { createContext, useContext } from "react"
import { create, useStore } from "zustand"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import {
Child,
GuestsRoom,
} from "@/types/components/bookingWidget/guestsRoomsPicker"
const SESSION_STORAGE_KEY = "guests_rooms"
interface extendedGuestsRoom extends GuestsRoom {
childrenInAdultsBed: number
}
interface GuestsRoomsState {
rooms: extendedGuestsRoom[]
adultCount: number
childCount: number
isValidated: boolean
}
interface GuestsRoomsStoreState extends GuestsRoomsState {
increaseAdults: (roomIndex: number) => void
decreaseAdults: (roomIndex: number) => void
increaseChildren: (roomIndex: number) => void
decreaseChildren: (roomIndex: number) => Child[]
updateChildAge: (age: number, roomIndex: number, childIndex: number) => void
updateChildBed: (bed: number, roomIndex: number, childIndex: number) => void
increaseChildInAdultsBed: (roomIndex: number) => void
decreaseChildInAdultsBed: (roomIndex: number) => void
increaseRoom: () => void
decreaseRoom: (roomIndex: number) => void
setIsValidated: (isValidated: boolean) => void
}
export function validateBedTypes(data: extendedGuestsRoom[]) {
data.forEach((room) => {
room.child.forEach((child) => {
const allowedBedTypes: number[] = []
if (child.age <= 5 && room.adults >= room.childrenInAdultsBed) {
allowedBedTypes.push(ChildBedMapEnum.IN_ADULTS_BED)
} else if (child.age <= 5) {
room.childrenInAdultsBed = room.childrenInAdultsBed - 1
}
if (child.age < 3) {
allowedBedTypes.push(ChildBedMapEnum.IN_CRIB)
}
if (child.age > 2) {
allowedBedTypes.push(ChildBedMapEnum.IN_EXTRA_BED)
}
if (!allowedBedTypes.includes(child.bed)) {
child.bed = allowedBedTypes[0]
}
})
})
}
export function initGuestsRoomsState(initData?: GuestsRoom[]) {
const isBrowser = typeof window !== "undefined"
const sessionData = isBrowser
? sessionStorage.getItem(SESSION_STORAGE_KEY)
: null
const defaultGuestsData: extendedGuestsRoom = {
adults: 1,
child: [],
childrenInAdultsBed: 0,
}
const defaultData: GuestsRoomsState = {
rooms: [defaultGuestsData],
adultCount: 1,
childCount: 0,
isValidated: false,
}
let inputData: GuestsRoomsState = defaultData
if (sessionData) {
inputData = JSON.parse(sessionData)
}
if (initData) {
inputData.rooms = initData.map((room) => {
const childrenInAdultsBed = room.child
? room.child.reduce((acc, child) => {
acc = acc + (child.bed == ChildBedMapEnum.IN_ADULTS_BED ? 1 : 0)
return acc
}, 0)
: 0
return { ...defaultGuestsData, ...room, childrenInAdultsBed }
}) as extendedGuestsRoom[]
inputData.adultCount = initData.reduce((acc, room) => {
acc = acc + room.adults
return acc
}, 0)
inputData.childCount = initData.reduce((acc, room) => {
acc = acc + room.child?.length
return acc
}, 0)
validateBedTypes(inputData.rooms)
}
return create<GuestsRoomsStoreState>()((set, get) => ({
...inputData,
increaseAdults: (roomIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms[roomIndex].adults = state.rooms[roomIndex].adults + 1
state.adultCount = state.adultCount + 1
})
),
decreaseAdults: (roomIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms[roomIndex].adults = state.rooms[roomIndex].adults - 1
state.adultCount = state.adultCount - 1
if (
state.rooms[roomIndex].childrenInAdultsBed >
state.rooms[roomIndex].adults
) {
const toUpdateIndex = state.rooms[roomIndex].child.findIndex(
(child) => child.bed == ChildBedMapEnum.IN_ADULTS_BED
)
if (toUpdateIndex != -1) {
state.rooms[roomIndex].child[toUpdateIndex].bed =
state.rooms[roomIndex].child[toUpdateIndex].age < 3
? ChildBedMapEnum.IN_CRIB
: ChildBedMapEnum.IN_EXTRA_BED
state.rooms[roomIndex].childrenInAdultsBed =
state.rooms[roomIndex].adults
}
}
})
),
increaseChildren: (roomIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms[roomIndex].child.push({
age: -1,
bed: -1,
})
state.childCount = state.childCount + 1
})
),
decreaseChildren: (roomIndex) => {
set(
produce((state: GuestsRoomsState) => {
const roomChildren = state.rooms[roomIndex].child
if (
roomChildren.length &&
roomChildren[roomChildren.length - 1].bed ==
ChildBedMapEnum.IN_ADULTS_BED
) {
state.rooms[roomIndex].childrenInAdultsBed =
state.rooms[roomIndex].childrenInAdultsBed - 1
}
state.rooms[roomIndex].child.pop()
state.childCount = state.childCount - 1
})
)
return get().rooms[roomIndex].child
},
updateChildAge: (age, roomIndex, childIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms[roomIndex].child[childIndex].age = age
})
),
updateChildBed: (bed, roomIndex, childIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms[roomIndex].child[childIndex].bed = bed
})
),
increaseChildInAdultsBed: (roomIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms[roomIndex].childrenInAdultsBed =
state.rooms[roomIndex].childrenInAdultsBed + 1
})
),
decreaseChildInAdultsBed: (roomIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms[roomIndex].childrenInAdultsBed =
state.rooms[roomIndex].childrenInAdultsBed - 1
})
),
increaseRoom: () =>
set(
produce((state: GuestsRoomsState) => {
state.rooms.push({
adults: 1,
child: [],
childrenInAdultsBed: 0,
})
})
),
decreaseRoom: (roomIndex) =>
set(
produce((state: GuestsRoomsState) => {
state.rooms.splice(roomIndex, 1)
})
),
setIsValidated: (isValidated) => set(() => ({ isValidated })),
}))
}
export type GuestsRoomsStore = ReturnType<typeof initGuestsRoomsState>
export const GuestsRoomsContext = createContext<GuestsRoomsStore | null>(null)
export const useGuestsRoomsStore = <T>(
selector: (store: GuestsRoomsStoreState) => T
): T => {
const guestsRoomsContextStore = useContext(GuestsRoomsContext)
if (!guestsRoomsContextStore) {
throw new Error(
`guestsRoomsContextStore must be used within GuestsRoomsContextProvider`
)
}
return useStore(guestsRoomsContextStore, selector)
}

View File

@@ -3,7 +3,6 @@ import { create } from "zustand"
export enum StickyElementNameEnum {
SITEWIDE_ALERT = "SITEWIDE_ALERT",
BOOKING_WIDGET = "BOOKING_WIDGET",
BOOKING_WIDGET_MOBILE = "BOOKING_WIDGET_MOBILE",
HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION",
HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP",
}
@@ -32,7 +31,6 @@ interface StickyStore {
const priorityMap: Record<StickyElementNameEnum, number> = {
[StickyElementNameEnum.SITEWIDE_ALERT]: 1,
[StickyElementNameEnum.BOOKING_WIDGET]: 2,
[StickyElementNameEnum.BOOKING_WIDGET_MOBILE]: 2,
[StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3,
[StickyElementNameEnum.HOTEL_STATIC_MAP]: 3,

View File

@@ -17,18 +17,19 @@ export type GuestsRoomPickerProps = {
index: number
}
export type AdultSelectorProps = {
roomIndex: number
}
export type ChildSelectorProps = {
export type SelectorProps = {
roomIndex: number
currentAdults: number
currentChildren: Child[]
childrenInAdultsBed: number
}
export type ChildInfoSelectorProps = {
child: Child
adults: number
index: number
roomIndex: number
childrenInAdultsBed: number
}
export interface CounterProps {