From 58678244fc6e047df558140f2b14fe3d12471690 Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Tue, 12 Nov 2024 13:34:42 +0100 Subject: [PATCH 01/16] fix: refactor GuestRoomsPicker to avoid performance bugs --- components/GuestsRoomsPicker/Dialog.tsx | 130 +++++++++++++++++ .../GuestsRoomsPicker/GuestsRoomsPicker.tsx | 136 ------------------ .../guests-rooms-picker.module.css | 55 ++----- components/GuestsRoomsPicker/index.tsx | 58 ++------ .../bookingWidget/guestsRoomsPicker.ts | 4 - 5 files changed, 158 insertions(+), 225 deletions(-) create mode 100644 components/GuestsRoomsPicker/Dialog.tsx delete mode 100644 components/GuestsRoomsPicker/GuestsRoomsPicker.tsx diff --git a/components/GuestsRoomsPicker/Dialog.tsx b/components/GuestsRoomsPicker/Dialog.tsx new file mode 100644 index 000000000..1c5d8e53d --- /dev/null +++ b/components/GuestsRoomsPicker/Dialog.tsx @@ -0,0 +1,130 @@ +"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() + + const rooms = useGuestsRoomsStore((state) => state.rooms) + + return ( + + {({ close }) => { + return ( + <> +
+ +
+
+ {rooms.map((room, index) => ( +
+
+ + {roomLabel} {index + 1} + + + +
+ +
+ ))} +
+ + {rooms.length < 4 ? ( + + ) : null} + +
+
+
+
+ + {rooms.length < 4 ? ( + + ) : null} + +
+ + +
+ + ) + }} +
+ ) +} diff --git a/components/GuestsRoomsPicker/GuestsRoomsPicker.tsx b/components/GuestsRoomsPicker/GuestsRoomsPicker.tsx deleted file mode 100644 index 8950a1585..000000000 --- a/components/GuestsRoomsPicker/GuestsRoomsPicker.tsx +++ /dev/null @@ -1,136 +0,0 @@ -"use client" -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" -import { GuestsRoomsPickerProps } from "@/types/components/bookingWidget/guestsRoomsPicker" - -export default function GuestsRoomsPicker({ - closePicker, -}: GuestsRoomsPickerProps) { - 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() - - const rooms = useGuestsRoomsStore((state) => state.rooms) - - // Not in MVP - // const increaseRoom = useGuestsRoomsStore.use.increaseRoom() - // const decreaseRoom = useGuestsRoomsStore.use.decreaseRoom() - - return ( -
-
- -
-
- {rooms.map((room, index) => ( -
-
- - {roomLabel} {index + 1} - - - -
- {/* Not in MVP - {index > 0 ? ( - - ) : null} */} - -
- ))} -
- - {rooms.length < 4 ? ( - - ) : null} - -
-
-
-
- - {rooms.length < 4 ? ( - - ) : null} - -
- - -
-
- ) -} diff --git a/components/GuestsRoomsPicker/guests-rooms-picker.module.css b/components/GuestsRoomsPicker/guests-rooms-picker.module.css index 23a84fd62..01dea2e78 100644 --- a/components/GuestsRoomsPicker/guests-rooms-picker.module.css +++ b/components/GuestsRoomsPicker/guests-rooms-picker.module.css @@ -1,9 +1,6 @@ .container { overflow: hidden; position: relative; - &[data-isopen="true"] { - overflow: visible; - } } .roomContainer { display: grid; @@ -14,9 +11,6 @@ gap: var(--Spacing-x2); padding-bottom: var(--Spacing-x1); } -.hideWrapper { - background-color: var(--Main-Grey-White); -} .roomHeading { margin-bottom: var(--Spacing-x1); } @@ -39,33 +33,25 @@ margin-top: var(--Spacing-x2); } -@media screen and (max-width: 1366px) { - .hideWrapper { - border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; - bottom: 0; - left: 0; - position: fixed; - right: 0; - top: 100%; - transition: top 300ms ease; - z-index: 10002; - overflow: hidden; - } +.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; } - .pickerContainer { - --header-height: 72px; - --sticky-button-height: 140px; - display: grid; - grid-template-areas: - "header" - "content"; - grid-template-rows: var(--header-height) calc(100dvh - var(--header-height)); - position: relative; - } .contentContainer { grid-area: content; overflow-y: scroll; @@ -121,19 +107,6 @@ } @media screen and (min-width: 1367px) { - .hideWrapper { - border-radius: var(--Corner-radius-Large); - box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); - left: calc((var(--Spacing-x1) + var(--Spacing-x2)) * -1); - max-width: calc(100vw - 20px); - padding: var(--Spacing-x2) var(--Spacing-x3); - position: absolute; - top: calc(100% + var(--Spacing-x2) + 1px + var(--Spacing-x4)); - width: 360px; - max-height: calc(100dvh - 77px - var(--Spacing-x6)); - overflow-y: auto; - } - .header { display: none; } diff --git a/components/GuestsRoomsPicker/index.tsx b/components/GuestsRoomsPicker/index.tsx index 090fc3803..84ef6f930 100644 --- a/components/GuestsRoomsPicker/index.tsx +++ b/components/GuestsRoomsPicker/index.tsx @@ -1,6 +1,7 @@ "use client" import { useCallback, useEffect, useRef, useState } from "react" +import { Button, DialogTrigger, Popover } from "react-aria-components" import { useFormContext } from "react-hook-form" import { useIntl } from "react-intl" @@ -9,7 +10,7 @@ import { useGuestsRoomsStore } from "@/stores/guests-rooms" import { guestRoomsSchema } from "@/components/Forms/BookingWidget/schema" import Body from "@/components/TempDesignSystem/Text/Body" -import GuestsRoomsPicker from "./GuestsRoomsPicker" +import Dialog from "./Dialog" import styles from "./guests-rooms-picker.module.css" @@ -19,47 +20,16 @@ export default function GuestsRoomsPickerForm({ name: string }) { const intl = useIntl() - const [isOpen, setIsOpen] = useState(false) - const { setValue } = useFormContext() - const { rooms, adultCount, childCount, setIsValidated } = useGuestsRoomsStore( - (state) => ({ - rooms: state.rooms, - adultCount: state.adultCount, - childCount: state.childCount, - setIsValidated: state.setIsValidated, - }) - ) - const ref = useRef(null) - function handleOnClick() { - setIsOpen((prevIsOpen) => !prevIsOpen) - } - const closePicker = useCallback(() => { - const guestRoomsValidData = guestRoomsSchema.safeParse(rooms) - if (guestRoomsValidData.success) { - setIsOpen(false) - setIsValidated(false) - setValue(name, guestRoomsValidData.data, { shouldValidate: true }) - } else { - setIsValidated(true) - } - }, [rooms, name, setValue, setIsValidated, setIsOpen]) - useEffect(() => { - function handleClickOutside(evt: Event) { - const target = evt.target as HTMLElement - if (ref.current && target && !ref.current.contains(target)) { - closePicker() - } - } - document.addEventListener("click", handleClickOutside) - return () => { - document.removeEventListener("click", handleClickOutside) - } - }, [closePicker]) + const { rooms, adultCount, childCount } = useGuestsRoomsStore((state) => ({ + rooms: state.rooms, + adultCount: state.adultCount, + childCount: state.childCount, + })) return ( -
- -
- -
-
+ + + + + ) } diff --git a/types/components/bookingWidget/guestsRoomsPicker.ts b/types/components/bookingWidget/guestsRoomsPicker.ts index 61e8f7d7a..b59744afc 100644 --- a/types/components/bookingWidget/guestsRoomsPicker.ts +++ b/types/components/bookingWidget/guestsRoomsPicker.ts @@ -13,10 +13,6 @@ export type GuestsRoom = { child: Child[] } -export interface GuestsRoomsPickerProps { - closePicker: () => void -} - export type GuestsRoomPickerProps = { index: number } From ca3819f7cc52131a268b61e811a229bd2f779af8 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Tue, 12 Nov 2024 16:45:25 +0100 Subject: [PATCH 02/16] fix: remove guest picker store --- app/globals.css | 2 + components/BookingWidget/Client.tsx | 40 +-- .../MobileToggleButton/button.module.css | 2 - .../MobileToggleButton/index.tsx | 7 - .../BookingWidget/bookingWidget.module.css | 96 ++++---- .../Forms/BookingWidget/FormContent/index.tsx | 12 +- .../GuestsRoomsPicker/AdultSelector/index.tsx | 38 ++- .../ChildSelector/ChildInfoSelector.tsx | 35 +-- .../GuestsRoomsPicker/ChildSelector/index.tsx | 41 ++-- components/GuestsRoomsPicker/Dialog.tsx | 130 ---------- components/GuestsRoomsPicker/Form.tsx | 146 +++++++++++ .../Provider/GuestsRoomsProvider.tsx | 26 -- .../guests-rooms-picker.module.css | 70 ++++-- components/GuestsRoomsPicker/index.tsx | 80 +++--- stores/guests-rooms.ts | 227 ------------------ stores/sticky-position.ts | 2 - .../bookingWidget/guestsRoomsPicker.ts | 11 +- 17 files changed, 368 insertions(+), 597 deletions(-) delete mode 100644 components/GuestsRoomsPicker/Dialog.tsx create mode 100644 components/GuestsRoomsPicker/Form.tsx delete mode 100644 components/GuestsRoomsPicker/Provider/GuestsRoomsProvider.tsx delete mode 100644 stores/guests-rooms.ts diff --git a/app/globals.css b/app/globals.css index 7dd0c44fb..a266e9f31 100644 --- a/app/globals.css +++ b/app/globals.css @@ -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; diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index 70daa6a20..3819eb912 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -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({ 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 ( -
-
-
-
- - +
+ +
+ + +
- ) } diff --git a/components/BookingWidget/MobileToggleButton/button.module.css b/components/BookingWidget/MobileToggleButton/button.module.css index 9a0912b07..1742ee38a 100644 --- a/components/BookingWidget/MobileToggleButton/button.module.css +++ b/components/BookingWidget/MobileToggleButton/button.module.css @@ -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); } diff --git a/components/BookingWidget/MobileToggleButton/index.tsx b/components/BookingWidget/MobileToggleButton/index.tsx index 80026ff93..8ecdfd606 100644 --- a/components/BookingWidget/MobileToggleButton/index.tsx +++ b/components/BookingWidget/MobileToggleButton/index.tsx @@ -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 && ( <> diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index f0dec979d..ccf77032c 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -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; } } diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index d5dba4d47..8001747e6 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -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 ( <>
@@ -51,12 +47,10 @@ export default function FormContent({
- - - +
diff --git a/components/GuestsRoomsPicker/AdultSelector/index.tsx b/components/GuestsRoomsPicker/AdultSelector/index.tsx index b24d5f681..d9069b6d0 100644 --- a/components/GuestsRoomsPicker/AdultSelector/index.tsx +++ b/components/GuestsRoomsPicker/AdultSelector/index.tsx @@ -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} { decreaseAdultsCount(roomIndex) }} handleOnIncrease={() => { increaseAdultsCount(roomIndex) }} - disableDecrease={adults == 1} - disableIncrease={adults == 6} + disableDecrease={currentAdults == 1} + disableIncrease={currentAdults == 6} />
) diff --git a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx index d8e541eb3..83e3893d1 100644 --- a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx @@ -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 ( <>
@@ -131,12 +109,13 @@ export default function ChildInfoSelector({ ) : null}
- {isValidated && child.age < 0 ? ( + + {/* {isValidated && child.age < 0 ? ( {ageReqdErrMsg} - ) : null} + ) : null} */} ) } diff --git a/components/GuestsRoomsPicker/ChildSelector/index.tsx b/components/GuestsRoomsPicker/ChildSelector/index.tsx index 22d594397..2d8db11f0 100644 --- a/components/GuestsRoomsPicker/ChildSelector/index.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/index.tsx @@ -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() - const children = useGuestsRoomsStore((state) => state.rooms[roomIndex].child) - const increaseChildren = useGuestsRoomsStore( - (state) => state.increaseChildren - ) - const decreaseChildren = useGuestsRoomsStore( - (state) => state.decreaseChildren - ) + const { setValue } = useFormContext() 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} { decreaseChildrenCount(roomIndex) }} handleOnIncrease={() => { increaseChildrenCount(roomIndex) }} - disableDecrease={children.length == 0} - disableIncrease={children.length == 5} + disableDecrease={currentChildren.length == 0} + disableIncrease={currentChildren.length == 5} /> - {children.map((child, index) => ( + {currentChildren.map((child, index) => ( ))} diff --git a/components/GuestsRoomsPicker/Dialog.tsx b/components/GuestsRoomsPicker/Dialog.tsx deleted file mode 100644 index 1c5d8e53d..000000000 --- a/components/GuestsRoomsPicker/Dialog.tsx +++ /dev/null @@ -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() - - const rooms = useGuestsRoomsStore((state) => state.rooms) - - return ( - - {({ close }) => { - return ( - <> -
- -
-
- {rooms.map((room, index) => ( -
-
- - {roomLabel} {index + 1} - - - -
- -
- ))} -
- - {rooms.length < 4 ? ( - - ) : null} - -
-
-
-
- - {rooms.length < 4 ? ( - - ) : null} - -
- - -
- - ) - }} -
- ) -} diff --git a/components/GuestsRoomsPicker/Form.tsx b/components/GuestsRoomsPicker/Form.tsx new file mode 100644 index 000000000..74f1dab6b --- /dev/null +++ b/components/GuestsRoomsPicker/Form.tsx @@ -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() + + return ( + <> +
+ +
+
+ {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 ( +
+
+ + {roomLabel} {index + 1} + + + +
+ +
+ ) + })} +
+ + {rooms.length < 4 ? ( + + ) : null} + +
+
+
+
+ + {rooms.length < 4 ? ( + + ) : null} + +
+ + +
+ + ) +} diff --git a/components/GuestsRoomsPicker/Provider/GuestsRoomsProvider.tsx b/components/GuestsRoomsPicker/Provider/GuestsRoomsProvider.tsx deleted file mode 100644 index 5a85102e7..000000000 --- a/components/GuestsRoomsPicker/Provider/GuestsRoomsProvider.tsx +++ /dev/null @@ -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() - if (!initialStore.current) { - initialStore.current = initGuestsRoomsState(selectedGuests) - } - - return ( - - {children} - - ) -} diff --git a/components/GuestsRoomsPicker/guests-rooms-picker.module.css b/components/GuestsRoomsPicker/guests-rooms-picker.module.css index 01dea2e78..0a6c09a6f 100644 --- a/components/GuestsRoomsPicker/guests-rooms-picker.module.css +++ b/components/GuestsRoomsPicker/guests-rooms-picker.module.css @@ -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; } diff --git a/components/GuestsRoomsPicker/index.tsx b/components/GuestsRoomsPicker/index.tsx index 84ef6f930..84595815c 100644 --- a/components/GuestsRoomsPicker/index.tsx +++ b/components/GuestsRoomsPicker/index.tsx @@ -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 ( + <> + + + + + {({ close }) => } + + + + + + + + {({ close }) => } + + + + + ) +} + +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 ( - - - - - - + ))} + + ) } diff --git a/stores/guests-rooms.ts b/stores/guests-rooms.ts deleted file mode 100644 index 04cfbc3ec..000000000 --- a/stores/guests-rooms.ts +++ /dev/null @@ -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()((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 - -export const GuestsRoomsContext = createContext(null) - -export const useGuestsRoomsStore = ( - selector: (store: GuestsRoomsStoreState) => T -): T => { - const guestsRoomsContextStore = useContext(GuestsRoomsContext) - - if (!guestsRoomsContextStore) { - throw new Error( - `guestsRoomsContextStore must be used within GuestsRoomsContextProvider` - ) - } - - return useStore(guestsRoomsContextStore, selector) -} diff --git a/stores/sticky-position.ts b/stores/sticky-position.ts index 99272ab62..93328b9e9 100644 --- a/stores/sticky-position.ts +++ b/stores/sticky-position.ts @@ -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.SITEWIDE_ALERT]: 1, [StickyElementNameEnum.BOOKING_WIDGET]: 2, - [StickyElementNameEnum.BOOKING_WIDGET_MOBILE]: 2, [StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3, [StickyElementNameEnum.HOTEL_STATIC_MAP]: 3, diff --git a/types/components/bookingWidget/guestsRoomsPicker.ts b/types/components/bookingWidget/guestsRoomsPicker.ts index b59744afc..a075c9f8d 100644 --- a/types/components/bookingWidget/guestsRoomsPicker.ts +++ b/types/components/bookingWidget/guestsRoomsPicker.ts @@ -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 { From 7a3194f978e17e5ecf46c1cec4798bc0e0066826 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Wed, 13 Nov 2024 14:43:06 +0100 Subject: [PATCH 03/16] fix: handle stickyness when scrolling is locked --- components/BookingWidget/Client.tsx | 21 ++-- .../BookingWidget/bookingWidget.module.css | 3 +- .../DatePicker/Screen/mobile.module.css | 8 +- components/DatePicker/date-picker.module.css | 2 +- components/Forms/BookingWidget/schema.ts | 14 ++- .../ChildSelector/ChildInfoSelector.tsx | 17 +-- components/GuestsRoomsPicker/Form.tsx | 117 +++++++++--------- .../guests-rooms-picker.module.css | 37 ++++-- components/GuestsRoomsPicker/index.tsx | 22 +++- 9 files changed, 141 insertions(+), 100 deletions(-) diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index 3819eb912..930cad4c0 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -38,11 +38,11 @@ export default function BookingWidgetClient({ const bookingWidgetSearchData: BookingWidgetSearchParams | undefined = searchParams - ? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), { + ? getFormattedUrlQueryParams(new URLSearchParams(searchParams), { adults: "number", age: "number", bed: "number", - }) as BookingWidgetSearchParams) + }) : undefined const getLocationObj = (destination: string): Location | undefined => { @@ -75,6 +75,16 @@ export default function BookingWidgetClient({ ) : undefined + const defaultRoomsData = bookingWidgetSearchData?.room?.map((room) => ({ + adults: room.adults, + child: room.child ?? [], + })) ?? [ + { + adults: 1, + child: [], + }, + ] + const methods = useForm({ defaultValues: { search: selectedLocation?.name ?? "", @@ -92,12 +102,7 @@ export default function BookingWidgetClient({ bookingCode: "", redemption: false, voucher: false, - rooms: bookingWidgetSearchData?.room ?? [ - { - adults: 1, - child: [], - }, - ], + rooms: defaultRoomsData, }, shouldFocusError: false, mode: "all", diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index ccf77032c..07f4880a4 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -5,7 +5,7 @@ .formContainer { display: grid; - grid-template-rows: 36px 1fr; + grid-template-rows: auto 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); @@ -30,6 +30,7 @@ border: none; cursor: pointer; justify-self: flex-end; + padding: 0; } .wrapper[data-open="true"] + .backdrop { diff --git a/components/DatePicker/Screen/mobile.module.css b/components/DatePicker/Screen/mobile.module.css index ef3f97ca0..5ee03bde5 100644 --- a/components/DatePicker/Screen/mobile.module.css +++ b/components/DatePicker/Screen/mobile.module.css @@ -1,5 +1,5 @@ .container { - --header-height: 68px; + --header-height: 72px; --sticky-button-height: 120px; display: grid; @@ -11,12 +11,10 @@ } .header { - align-self: flex-start; + align-self: flex-end; background-color: var(--Main-Grey-White); - display: grid; grid-area: header; - grid-template-columns: 1fr 24px; - padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x2); + padding: var(--Spacing-x3) var(--Spacing-x2); position: sticky; top: 0; z-index: 10; diff --git a/components/DatePicker/date-picker.module.css b/components/DatePicker/date-picker.module.css index ee9034648..985c9d63c 100644 --- a/components/DatePicker/date-picker.module.css +++ b/components/DatePicker/date-picker.module.css @@ -38,7 +38,7 @@ .hideWrapper { bottom: 0; left: 0; - overflow: auto; + overflow: hidden; position: fixed; right: 0; top: 100%; diff --git a/components/Forms/BookingWidget/schema.ts b/components/Forms/BookingWidget/schema.ts index 973ab6ad6..90f4f6712 100644 --- a/components/Forms/BookingWidget/schema.ts +++ b/components/Forms/BookingWidget/schema.ts @@ -4,12 +4,14 @@ import type { Location } from "@/types/trpc/routers/hotel/locations" export const guestRoomSchema = z.object({ adults: z.number().default(1), - child: z.array( - z.object({ - age: z.number().nonnegative(), - bed: z.number(), - }) - ), + child: z + .array( + z.object({ + age: z.number().nonnegative(), + bed: z.number(), + }) + ) + .default([]), }) export const guestRoomsSchema = z.array(guestRoomSchema) diff --git a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx index 83e3893d1..08ff83423 100644 --- a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx @@ -15,6 +15,11 @@ import { ChildInfoSelectorProps, } from "@/types/components/bookingWidget/guestsRoomsPicker" +const ageList = Array.from(Array(13).keys()).map((age) => ({ + label: age.toString(), + value: age, +})) + export default function ChildInfoSelector({ child = { age: -1, bed: -1 }, childrenInAdultsBed, @@ -28,11 +33,6 @@ export default function ChildInfoSelector({ const bedLabel = intl.formatMessage({ id: "Bed" }) const { setValue, formState } = useFormContext() - const ageList = Array.from(Array(13).keys()).map((age) => ({ - label: `${age}`, - value: age, - })) - function updateSelectedAge(age: number) { setValue(`rooms.${roomIndex}.child.${index}.age`, age, { shouldValidate: true, @@ -74,7 +74,8 @@ export default function ChildInfoSelector({ return availableBedTypes } - console.log("ALL TJHE ERORRORS", formState.errors) + //@ts-expect-error: formState is typed with FormValues + const roomErrors = formState.errors.rooms?.[roomIndex]?.child?.[index] return ( <>
@@ -110,12 +111,12 @@ export default function ChildInfoSelector({
- {/* {isValidated && child.age < 0 ? ( + {roomErrors ? ( {ageReqdErrMsg} - ) : null} */} + ) : null} ) } diff --git a/components/GuestsRoomsPicker/Form.tsx b/components/GuestsRoomsPicker/Form.tsx index 74f1dab6b..9288f80ec 100644 --- a/components/GuestsRoomsPicker/Form.tsx +++ b/components/GuestsRoomsPicker/Form.tsx @@ -39,65 +39,68 @@ export default function GuestsRoomsPickerDialog({ return ( <> -
- -
-
- {rooms.map((room, index) => { - const currentAdults = room.adults - const currentChildren = room.child - const childrenInAdultsBed = currentChildren.filter( - (child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED - ).length +
+
+ +
+
+ {rooms.map((room, index) => { + const currentAdults = room.adults + const currentChildren = room.child + const childrenInAdultsBed = + currentChildren.filter( + (child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED + ).length ?? 0 - return ( -
-
- - {roomLabel} {index + 1} - - - -
- -
- ) - })} -
- - {rooms.length < 4 ? ( - - ) : null} - + return ( +
+
+ + {roomLabel} {index + 1} + + + +
+ +
+ ) + })} +
+ + {rooms.length < 4 ? ( + + ) : null} + +
-
+
- + {({ close }) => } - + - + {({ close }) => } From 9189d588d1c256651954bca03ef30a5e79df0a2c Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Thu, 14 Nov 2024 11:41:56 +0100 Subject: [PATCH 04/16] fix: validation --- components/Forms/BookingWidget/schema.ts | 39 ++++++++++---- .../GuestsRoomsPicker/AdultSelector/index.tsx | 38 +++----------- .../ChildSelector/ChildInfoSelector.tsx | 52 ++++++++++++------- .../GuestsRoomsPicker/ChildSelector/index.tsx | 19 +++---- components/GuestsRoomsPicker/Form.tsx | 26 ++++++++-- components/GuestsRoomsPicker/index.tsx | 40 +++++++------- 6 files changed, 120 insertions(+), 94 deletions(-) diff --git a/components/Forms/BookingWidget/schema.ts b/components/Forms/BookingWidget/schema.ts index 90f4f6712..9d0c8f247 100644 --- a/components/Forms/BookingWidget/schema.ts +++ b/components/Forms/BookingWidget/schema.ts @@ -1,18 +1,37 @@ import { z } from "zod" +import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums" import type { Location } from "@/types/trpc/routers/hotel/locations" -export const guestRoomSchema = z.object({ - adults: z.number().default(1), - child: z - .array( - z.object({ - age: z.number().nonnegative(), - bed: z.number(), - }) +export const guestRoomSchema = z + .object({ + adults: z.number().default(1), + child: z + .array( + z.object({ + age: z.number().min(0, "Age is required"), + bed: z.number().min(0, "Bed choice is required"), + }) + ) + .default([]), + }) + .superRefine((value, ctx) => { + const childrenInAdultsBed = value.child.filter( + (c) => c.bed === ChildBedMapEnum.IN_ADULTS_BED ) - .default([]), -}) + if (value.adults < childrenInAdultsBed.length) { + const lastAdultBedIndex = value.child + .map((c) => c.bed) + .lastIndexOf(ChildBedMapEnum.IN_ADULTS_BED) + + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + "You cannot have more children in adults bed than adults in the room", + path: ["child", lastAdultBedIndex], + }) + } + }) export const guestRoomsSchema = z.array(guestRoomSchema) diff --git a/components/GuestsRoomsPicker/AdultSelector/index.tsx b/components/GuestsRoomsPicker/AdultSelector/index.tsx index d9069b6d0..008037300 100644 --- a/components/GuestsRoomsPicker/AdultSelector/index.tsx +++ b/components/GuestsRoomsPicker/AdultSelector/index.tsx @@ -9,44 +9,26 @@ import Counter from "../Counter" import styles from "./adult-selector.module.css" -import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums" -import { - Child, - SelectorProps, -} from "@/types/components/bookingWidget/guestsRoomsPicker" +import { SelectorProps } from "@/types/components/bookingWidget/guestsRoomsPicker" export default function AdultSelector({ roomIndex = 0, currentAdults, - currentChildren, - childrenInAdultsBed, }: SelectorProps) { + const name = `rooms.${roomIndex}.adults` const intl = useIntl() const adultsLabel = intl.formatMessage({ id: "Adults" }) const { setValue } = useFormContext() - function increaseAdultsCount(roomIndex: number) { + function increaseAdultsCount() { if (currentAdults < 6) { - setValue(`rooms.${roomIndex}.adults`, currentAdults + 1) + setValue(name, currentAdults + 1) } } - function decreaseAdultsCount(roomIndex: number) { + function decreaseAdultsCount() { 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`, - currentChildren[toUpdateIndex].age < 3 - ? ChildBedMapEnum.IN_CRIB - : ChildBedMapEnum.IN_EXTRA_BED - ) - } - } + setValue(name, currentAdults - 1) } } @@ -57,12 +39,8 @@ export default function AdultSelector({ { - decreaseAdultsCount(roomIndex) - }} - handleOnIncrease={() => { - increaseAdultsCount(roomIndex) - }} + handleOnDecrease={decreaseAdultsCount} + handleOnIncrease={increaseAdultsCount} disableDecrease={currentAdults == 1} disableIncrease={currentAdults == 6} /> diff --git a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx index 08ff83423..b5a34c168 100644 --- a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx @@ -15,9 +15,9 @@ import { ChildInfoSelectorProps, } from "@/types/components/bookingWidget/guestsRoomsPicker" -const ageList = Array.from(Array(13).keys()).map((age) => ({ - label: age.toString(), - value: age, +const ageList = [...Array(13)].map((_, i) => ({ + label: i.toString(), + value: i, })) export default function ChildInfoSelector({ @@ -27,22 +27,23 @@ export default function ChildInfoSelector({ index = 0, roomIndex = 0, }: ChildInfoSelectorProps) { + const ageFieldName = `rooms.${roomIndex}.child.${index}.age` + const bedFieldName = `rooms.${roomIndex}.child.${index}.bed` const intl = useIntl() const ageLabel = intl.formatMessage({ id: "Age" }) - const ageReqdErrMsg = intl.formatMessage({ id: "Child age is required" }) const bedLabel = intl.formatMessage({ id: "Bed" }) - const { setValue, formState } = useFormContext() - - function updateSelectedAge(age: number) { - setValue(`rooms.${roomIndex}.child.${index}.age`, age, { - shouldValidate: true, - }) - const availableBedTypes = getAvailableBeds(age) - updateSelectedBed(availableBedTypes[0].value) - } + const errorMessage = intl.formatMessage({ id: "Child age is required" }) + const { setValue, formState, register, trigger } = useFormContext() function updateSelectedBed(bed: number) { setValue(`rooms.${roomIndex}.child.${index}.bed`, bed) + trigger() + } + + function updateSelectedAge(age: number) { + setValue(`rooms.${roomIndex}.child.${index}.age`, age) + const availableBedTypes = getAvailableBeds(age) + updateSelectedBed(availableBedTypes[0].value) } const allBedTypes: ChildBed[] = [ @@ -76,6 +77,10 @@ export default function ChildInfoSelector({ //@ts-expect-error: formState is typed with FormValues const roomErrors = formState.errors.rooms?.[roomIndex]?.child?.[index] + + const ageError = roomErrors?.age + const bedError = roomErrors?.bed + return ( <>
@@ -89,13 +94,15 @@ export default function ChildInfoSelector({ onSelect={(key) => { updateSelectedAge(key as number) }} - name={`rooms.${roomIndex}.child.${index}.age`} placeholder={ageLabel} maxHeight={150} + {...register(ageFieldName, { + required: true, + })} />
- {child.age !== -1 ? ( + {child.age >= 0 ? ( From 8aa615dfc78fb3baabab12e613e86571c0f53ed1 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 10:53:16 +0100 Subject: [PATCH 07/16] feat(SW-892) Fixed bug with summary not adding pet charge on filter --- .../(standard)/select-rate/page.tsx | 2 +- .../RoomSelection/RoomCard/index.tsx | 2 +- .../SelectRate/RoomSelection/index.tsx | 4 +- .../SelectRate/Rooms/index.tsx | 37 +++++++++++++++++-- .../selectRate/roomSelection.ts | 10 +++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 816c937ae..dc5c7a6b5 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -18,7 +18,7 @@ import { setLang } from "@/i18n/serverContext" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" -import { LangParams, PageArgs } from "@/types/params" +import type { LangParams, PageArgs } from "@/types/params" export default async function SelectRatePage({ params, diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index 70f1c5aa6..4f3b398db 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -66,7 +66,7 @@ export default function RoomCard({ (room) => room.name === roomConfiguration.roomType ) - const { roomSize, occupancy, descriptions, images } = selectedRoom || {} + const { roomSize, occupancy, images } = selectedRoom || {} const mainImage = images?.[0] const freeCancelation = intl.formatMessage({ id: "Free cancellation" }) diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index 89c4dc9a2..ceb86c5f3 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -17,9 +17,9 @@ export default function RoomSelection({ user, packages, selectedPackages, + setRateSummary, + rateSummary, }: RoomSelectionProps) { - const [rateSummary, setRateSummary] = useState(null) - const router = useRouter() const searchParams = useSearchParams() const isUserLoggedIn = !!user diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 8e56555a2..abd5b2f86 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -12,7 +12,8 @@ import { RoomPackageCodeEnum, type RoomPackageCodes, } from "@/types/components/hotelReservation/selectRate/roomFilter" -import type { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection" +import type { SelectRateProps } from "@/types/components/hotelReservation/selectRate/roomSelection" +import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" import type { RoomConfiguration, RoomsAvailability, @@ -23,10 +24,10 @@ export default function Rooms({ roomCategories = [], user, packages, -}: Omit) { +}: SelectRateProps) { const visibleRooms: RoomConfiguration[] = filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations) - + const [rateSummary, setRateSummary] = useState(null) const [rooms, setRooms] = useState({ ...roomsAvailability, roomConfigurations: visibleRooms, @@ -48,6 +49,14 @@ export default function Rooms({ ...roomsAvailability, roomConfigurations: visibleRooms, }) + + if (!!rateSummary) { + setRateSummary({ + ...rateSummary, + features: [], + }) + } + return } @@ -57,8 +66,26 @@ export default function Rooms({ ) ) setRooms({ ...roomsAvailability, roomConfigurations: filteredRooms }) + + const petRoomPackage = + (filteredPackages.includes(RoomPackageCodeEnum.PET_ROOM) && + packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) || + undefined + + const features = filteredRooms.find((room) => + room.features.some( + (feature) => feature.code === RoomPackageCodeEnum.PET_ROOM + ) + )?.features + + if (!!rateSummary) { + setRateSummary({ + ...rateSummary, + features: petRoomPackage && features ? features : [], + }) + } }, - [roomsAvailability, visibleRooms] + [roomsAvailability, visibleRooms, rateSummary, packages] ) return ( @@ -74,6 +101,8 @@ export default function Rooms({ user={user} packages={packages} selectedPackages={selectedPackages} + setRateSummary={setRateSummary} + rateSummary={rateSummary} />
) diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts index 3e3a6117e..63b17ed6f 100644 --- a/types/components/hotelReservation/selectRate/roomSelection.ts +++ b/types/components/hotelReservation/selectRate/roomSelection.ts @@ -2,6 +2,7 @@ import type { RoomData } from "@/types/hotel" import type { SafeUser } from "@/types/user" import type { RoomsAvailability } from "@/server/routers/hotels/output" import type { RoomPackageCodes, RoomPackageData } from "./roomFilter" +import type { Rate } from "./selectRate" export interface RoomSelectionProps { roomsAvailability: RoomsAvailability @@ -9,4 +10,13 @@ export interface RoomSelectionProps { user: SafeUser packages: RoomPackageData selectedPackages: RoomPackageCodes[] + setRateSummary: (rateSummary: Rate) => void + rateSummary: Rate | null +} + +export interface SelectRateProps { + roomsAvailability: RoomsAvailability + roomCategories: RoomData[] + user: SafeUser + packages: RoomPackageData } From e12185b2e8737b5dc77d0997c18de03130e5a63a Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 10:53:42 +0100 Subject: [PATCH 08/16] feat(SW-892) Fixed issue with decimals on price --- .../FlexibilityOption/PriceList/utils.ts | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/utils.ts b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/utils.ts index 4043aa652..95825f585 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/utils.ts +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/utils.ts @@ -11,30 +11,38 @@ export function calculatePricesPerNight({ }: CalculatePricesPerNightProps) { const totalPublicLocalPricePerNight = publicLocalPrice ? petRoomLocalPrice - ? Number(publicLocalPrice.pricePerNight) + - Number(petRoomLocalPrice.price) / nights - : Number(publicLocalPrice.pricePerNight) + ? Math.floor( + Number(publicLocalPrice.pricePerNight) + + Number(petRoomLocalPrice.price) / nights + ) + : Math.floor(Number(publicLocalPrice.pricePerNight)) : undefined const totalMemberLocalPricePerNight = memberLocalPrice ? petRoomLocalPrice - ? Number(memberLocalPrice.pricePerNight) + - Number(petRoomLocalPrice.price) / nights - : Number(memberLocalPrice.pricePerNight) + ? Math.floor( + Number(memberLocalPrice.pricePerNight) + + Number(petRoomLocalPrice.price) / nights + ) + : Math.floor(Number(memberLocalPrice.pricePerNight)) : undefined const totalPublicRequestedPricePerNight = publicRequestedPrice ? petRoomRequestedPrice - ? Number(publicRequestedPrice.pricePerNight) + - Number(petRoomRequestedPrice.price) / nights - : Number(publicRequestedPrice.pricePerNight) + ? Math.floor( + Number(publicRequestedPrice.pricePerNight) + + Number(petRoomRequestedPrice.price) / nights + ) + : Math.floor(Number(publicRequestedPrice.pricePerNight)) : undefined const totalMemberRequestedPricePerNight = memberRequestedPrice ? petRoomRequestedPrice - ? Number(memberRequestedPrice.pricePerNight) + - Number(petRoomRequestedPrice.price) / nights - : Number(memberRequestedPrice.pricePerNight) + ? Math.floor( + Number(memberRequestedPrice.pricePerNight) + + Number(petRoomRequestedPrice.price) / nights + ) + : Math.floor(Number(memberRequestedPrice.pricePerNight)) : undefined return { From 87713d133222b3df2fc4ba8f80ab86a6a3bfc48f Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 11:00:50 +0100 Subject: [PATCH 09/16] feat(SW-892) Fixed active state on info icon --- components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx | 2 +- components/TempDesignSystem/Tooltip/tooltip.module.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx index d1f935fa2..2d11e1173 100644 --- a/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx +++ b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx @@ -47,7 +47,7 @@ export default function FilterChip({ {tooltipText && ( - + )} Date: Thu, 14 Nov 2024 14:52:30 +0100 Subject: [PATCH 10/16] feat(SW-892) Show toolTip on button hover --- .../SelectRate/RoomFilter/index.tsx | 52 ++++++++++++------- .../Form/FilterChip/_Chip/index.tsx | 11 ++-- types/components/form/filterChip.ts | 2 +- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index fb41f3cc7..a07e8353f 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -98,26 +98,38 @@ export default function RoomFilter({
- {filterOptions.map((option) => ( - - ))} + {filterOptions.map((option) => { + const { code, description } = option + const isPetRoom = code === RoomPackageCodeEnum.PET_ROOM + const isAllergyRoom = code === RoomPackageCodeEnum.ALLERGY_ROOM + const isDisabled = + (isAllergyRoom && petFriendly) || (isPetRoom && allergyFriendly) + + const checkboxChip = ( + + ) + + return isPetRoom ? ( + + {checkboxChip} + + ) : ( + checkboxChip + ) + })}
diff --git a/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx index 2d11e1173..12992bbc4 100644 --- a/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx +++ b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx @@ -3,7 +3,6 @@ import { useFormContext } from "react-hook-form" import { HeartIcon, InfoCircleIcon } from "@/components/Icons" import Caption from "@/components/TempDesignSystem/Text/Caption" -import { Tooltip } from "@/components/TempDesignSystem/Tooltip" import styles from "./chip.module.css" @@ -20,7 +19,7 @@ export default function FilterChip({ value, selected, disabled, - tooltipText, + hasTooltip, }: FilterChipProps) { const { register } = useFormContext() @@ -45,11 +44,11 @@ export default function FilterChip({ {label} - {tooltipText && ( - - - + + {hasTooltip && ( + )} + From f3037a1b3debcd63084bf8794ec1d777bb3014e1 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 10:01:59 +0100 Subject: [PATCH 11/16] feat(SW-827): Added bed types and icons --- components/Icons/BedSingle.tsx | 29 +++++++++++++++ components/Icons/KingBedSmall.tsx | 29 +++++++++++++++ components/Icons/index.tsx | 2 + components/SidePeeks/RoomSidePeek/bedIcon.ts | 37 +++++++++++++++++++ components/SidePeeks/RoomSidePeek/index.tsx | 28 ++++++++++---- .../RoomSidePeek/roomSidePeek.module.css | 6 +++ 6 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 components/Icons/BedSingle.tsx create mode 100644 components/Icons/KingBedSmall.tsx create mode 100644 components/SidePeeks/RoomSidePeek/bedIcon.ts diff --git a/components/Icons/BedSingle.tsx b/components/Icons/BedSingle.tsx new file mode 100644 index 000000000..0e9aaa68f --- /dev/null +++ b/components/Icons/BedSingle.tsx @@ -0,0 +1,29 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function BedSingleIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + ) +} diff --git a/components/Icons/KingBedSmall.tsx b/components/Icons/KingBedSmall.tsx new file mode 100644 index 000000000..711ce0f12 --- /dev/null +++ b/components/Icons/KingBedSmall.tsx @@ -0,0 +1,29 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function KingBedSmallIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + ) +} diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index fc6e5c069..1bc195717 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -9,6 +9,7 @@ export { default as ArrowRightIcon } from "./ArrowRight" export { default as BarIcon } from "./Bar" export { default as BathtubIcon } from "./Bathtub" export { default as BedDoubleIcon } from "./BedDouble" +export { default as BedSingleIcon } from "./BedSingle" export { default as BikingIcon } from "./Biking" export { default as BreakfastIcon } from "./Breakfast" export { default as BusinessIcon } from "./Business" @@ -77,6 +78,7 @@ export { default as IronIcon } from "./Iron" export { default as KayakingIcon } from "./Kayaking" export { default as KettleIcon } from "./Kettle" export { default as KingBedIcon } from "./KingBed" +export { default as KingBedSmallIcon } from "./KingBedSmall" export { default as LampIcon } from "./Lamp" export { default as LaundryMachineIcon } from "./LaundryMachine" export { default as LocalBarIcon } from "./LocalBar" diff --git a/components/SidePeeks/RoomSidePeek/bedIcon.ts b/components/SidePeeks/RoomSidePeek/bedIcon.ts new file mode 100644 index 000000000..41947bd77 --- /dev/null +++ b/components/SidePeeks/RoomSidePeek/bedIcon.ts @@ -0,0 +1,37 @@ +import { FC } from "react" + +import { + BedDoubleIcon, + BedSingleIcon, + KingBedSmallIcon, +} from "@/components/Icons" + +import { IconProps } from "@/types/components/icon" + +export function getBedIcon(name: string): FC | null { + const iconMappings = [ + { + icon: BedDoubleIcon, + texts: ["Queen"], + }, + { + icon: KingBedSmallIcon, + texts: ["King"], + }, + { + icon: KingBedSmallIcon, + texts: ["CustomOccupancy"], + }, + { + icon: BedSingleIcon, + texts: ["Twin"], + }, + { + icon: BedSingleIcon, + texts: ["Single"], + }, + ] + + const icon = iconMappings.find((icon) => icon.texts.includes(name)) + return icon ? icon.icon : BedSingleIcon +} diff --git a/components/SidePeeks/RoomSidePeek/index.tsx b/components/SidePeeks/RoomSidePeek/index.tsx index 34fc3afca..da46a6f42 100644 --- a/components/SidePeeks/RoomSidePeek/index.tsx +++ b/components/SidePeeks/RoomSidePeek/index.tsx @@ -1,11 +1,11 @@ import { useIntl } from "react-intl" import ImageGallery from "@/components/ImageGallery" -import Button from "@/components/TempDesignSystem/Button" import SidePeek from "@/components/TempDesignSystem/SidePeek" import Body from "@/components/TempDesignSystem/Text/Body" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import { getBedIcon } from "./bedIcon" import { getFacilityIcon } from "./facilityIcon" import styles from "./roomSidePeek.module.css" @@ -79,15 +79,27 @@ export default function RoomSidePeek({ {intl.formatMessage({ id: "booking.basedOnAvailability" })} - {/* TODO: Get data for bed options */} +
    + {room.roomTypes.map((roomType) => { + const BedIcon = getBedIcon(roomType.mainBed.type) + return ( +
  • + {BedIcon && ( + + )} + + {roomType.mainBed.description} + +
  • + ) + })} +
-
- -
) } diff --git a/components/SidePeeks/RoomSidePeek/roomSidePeek.module.css b/components/SidePeeks/RoomSidePeek/roomSidePeek.module.css index c76c57877..2e1fb5e6f 100644 --- a/components/SidePeeks/RoomSidePeek/roomSidePeek.module.css +++ b/components/SidePeeks/RoomSidePeek/roomSidePeek.module.css @@ -44,6 +44,12 @@ margin-bottom: var(--Spacing-x-half); } +.bedOptions li { + display: flex; + gap: var(--Spacing-x1); + margin-bottom: var(--Spacing-x-half); +} + .noIcon { margin-left: var(--Spacing-x4); } From 38c48006bec2028f0685852e2e63d3823a1f5132 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 10:02:36 +0100 Subject: [PATCH 12/16] feat(827): Added fallback on image if is error --- components/ImageGallery/imageGallery.module.css | 9 +++++---- components/ImageGallery/index.tsx | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/components/ImageGallery/imageGallery.module.css b/components/ImageGallery/imageGallery.module.css index 2ffdcf595..050f9be3b 100644 --- a/components/ImageGallery/imageGallery.module.css +++ b/components/ImageGallery/imageGallery.module.css @@ -27,16 +27,17 @@ .imagePlaceholder { height: 100%; min-height: 190px; + aspect-ratio: 16/9; width: 100%; background-color: #fff; background-image: linear-gradient(45deg, #000000 25%, transparent 25%), linear-gradient(-45deg, #000000 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #000000 75%), linear-gradient(-45deg, transparent 75%, #000000 75%); - background-size: 160px 160px; + background-size: 180px 180px; background-position: 0 0, - 0 80px, - 80px -80px, - -80px 0; + 0 90px, + 90px -90px, + -90px 0; } diff --git a/components/ImageGallery/index.tsx b/components/ImageGallery/index.tsx index eaeee684b..3fc10448b 100644 --- a/components/ImageGallery/index.tsx +++ b/components/ImageGallery/index.tsx @@ -20,9 +20,10 @@ export default function ImageGallery({ }: ImageGalleryProps) { const intl = useIntl() const [lightboxIsOpen, setLightboxIsOpen] = useState(false) + const [imageError, setImageError] = useState(false) const imageProps = fill ? { fill } : { height, width: height * 1.5 } - if (!images || images.length === 0) { + if (!images || images.length === 0 || imageError) { return
} @@ -38,6 +39,7 @@ export default function ImageGallery({ className={styles.image} src={images[0].imageSizes.medium} alt={images[0].metaData.altText} + onError={() => setImageError(true)} {...imageProps} />
From b9ff5dc920990257f77103e05e87984d4009b219 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 10:02:54 +0100 Subject: [PATCH 13/16] feat(SW-827): updated sidepeek width after design --- components/TempDesignSystem/SidePeek/sidePeek.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/TempDesignSystem/SidePeek/sidePeek.module.css b/components/TempDesignSystem/SidePeek/sidePeek.module.css index 68f588ab3..5fb93dc80 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.module.css +++ b/components/TempDesignSystem/SidePeek/sidePeek.module.css @@ -1,5 +1,5 @@ .modal { - --sidepeek-desktop-width: 600px; + --sidepeek-desktop-width: 560px; } @keyframes slide-in { from { From dc988e32fa9acb69c1ea3a7271fd5a825935cdfb Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 10:10:42 +0100 Subject: [PATCH 14/16] feat(SW-827): Added type --- components/SidePeeks/RoomSidePeek/bedIcon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/SidePeeks/RoomSidePeek/bedIcon.ts b/components/SidePeeks/RoomSidePeek/bedIcon.ts index 41947bd77..389045159 100644 --- a/components/SidePeeks/RoomSidePeek/bedIcon.ts +++ b/components/SidePeeks/RoomSidePeek/bedIcon.ts @@ -6,7 +6,7 @@ import { KingBedSmallIcon, } from "@/components/Icons" -import { IconProps } from "@/types/components/icon" +import type { IconProps } from "@/types/components/icon" export function getBedIcon(name: string): FC | null { const iconMappings = [ From b005af5bf1ffbc5e0d83e45210cee0bd3af5855a Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 11:05:36 +0100 Subject: [PATCH 15/16] feat(SW-827): Fixed fillRule and clipRule --- components/Icons/KingBedSmall.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/Icons/KingBedSmall.tsx b/components/Icons/KingBedSmall.tsx index 711ce0f12..6c3bc9be6 100644 --- a/components/Icons/KingBedSmall.tsx +++ b/components/Icons/KingBedSmall.tsx @@ -19,8 +19,8 @@ export default function KingBedSmallIcon({ {...props} > From ff1434cd45dfa39e079a54e734c61d7349efa865 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 14 Nov 2024 14:54:27 +0100 Subject: [PATCH 16/16] feat(SW-827) fixed fillRule and clipRule --- components/Icons/BedSingle.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/Icons/BedSingle.tsx b/components/Icons/BedSingle.tsx index 0e9aaa68f..9ff42333a 100644 --- a/components/Icons/BedSingle.tsx +++ b/components/Icons/BedSingle.tsx @@ -19,8 +19,8 @@ export default function BedSingleIcon({ {...props} >