sub-task/ SW-695 Prefill Guests data in booking widget
This commit is contained in:
@@ -9,6 +9,7 @@ import Form from "@/components/Forms/BookingWidget"
|
||||
import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
|
||||
import { CloseLargeIcon } from "@/components/Icons"
|
||||
import { debounce } from "@/utils/debounce"
|
||||
import { getFormattedUrlQueryParams } from "@/utils/url"
|
||||
|
||||
import getHotelReservationQueryParams from "../HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import MobileToggleButton from "./MobileToggleButton"
|
||||
@@ -18,6 +19,7 @@ import styles from "./bookingWidget.module.css"
|
||||
import type {
|
||||
BookingWidgetClientProps,
|
||||
BookingWidgetSchema,
|
||||
BookingWidgetSearchParams,
|
||||
} from "@/types/components/bookingWidget"
|
||||
import type { Location } from "@/types/trpc/routers/hotel/locations"
|
||||
|
||||
@@ -36,12 +38,14 @@ export default function BookingWidgetClient({
|
||||
? JSON.parse(sessionStorageSearchData)
|
||||
: undefined
|
||||
|
||||
const bookingWidgetSearchParams = searchParams
|
||||
? new URLSearchParams(searchParams)
|
||||
: undefined
|
||||
const bookingWidgetSearchData = bookingWidgetSearchParams
|
||||
? getHotelReservationQueryParams(bookingWidgetSearchParams)
|
||||
: undefined
|
||||
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined =
|
||||
searchParams
|
||||
? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
|
||||
adults: "number",
|
||||
age: "number",
|
||||
bed: "number",
|
||||
}) as BookingWidgetSearchParams)
|
||||
: undefined
|
||||
|
||||
const getLocationObj = (destination: string): Location | undefined => {
|
||||
if (destination) {
|
||||
@@ -83,7 +87,7 @@ export default function BookingWidgetClient({
|
||||
// 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.
|
||||
fromDate: isDateParamValid
|
||||
? bookingWidgetSearchData?.fromDate.toString()
|
||||
? bookingWidgetSearchData?.fromDate?.toString()
|
||||
: dt().utc().format("YYYY-MM-DD"),
|
||||
toDate: isDateParamValid
|
||||
? bookingWidgetSearchData?.toDate?.toString()
|
||||
@@ -92,10 +96,10 @@ export default function BookingWidgetClient({
|
||||
bookingCode: "",
|
||||
redemption: false,
|
||||
voucher: false,
|
||||
rooms: [
|
||||
rooms: bookingWidgetSearchData?.room ?? [
|
||||
{
|
||||
adults: 1,
|
||||
children: [],
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -54,6 +54,12 @@ export default function MobileToggleButton({
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
const totalChildren = rooms.reduce((acc, room) => {
|
||||
if (room.child) {
|
||||
acc = acc + room.child.length
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
return (
|
||||
<div className={styles.complete} onClick={openMobileSearch} role="button">
|
||||
<div>
|
||||
@@ -62,7 +68,7 @@ export default function MobileToggleButton({
|
||||
{`${selectedFromDate} - ${selectedToDate} (${intl.formatMessage(
|
||||
{ id: "booking.nights" },
|
||||
{ totalNights: nights }
|
||||
)}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`}
|
||||
)}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${intl.formatMessage({ id: "booking.children" }, { totalChildren })}, ${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`}
|
||||
</Caption>
|
||||
</div>
|
||||
<div className={styles.icon}>
|
||||
|
||||
@@ -7,6 +7,7 @@ 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"
|
||||
@@ -29,6 +30,8 @@ export default function FormContent({
|
||||
|
||||
const nights = dt(selectedDate.toDate).diff(dt(selectedDate.fromDate), "days")
|
||||
|
||||
const selectedGuests = useWatch({ name: "rooms" })
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.input}>
|
||||
@@ -51,7 +54,9 @@ export default function FormContent({
|
||||
{rooms}
|
||||
</Caption>
|
||||
</label>
|
||||
<GuestsRoomsPickerForm />
|
||||
<GuestsRoomsProvider selectedGuests={selectedGuests}>
|
||||
<GuestsRoomsPickerForm name="rooms" />
|
||||
</GuestsRoomsProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.voucherContainer}>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function Form({ locations, type }: BookingWidgetFormProps) {
|
||||
data.rooms.forEach((room, index) => {
|
||||
bookingWidgetParams.set(`room[${index}].adults`, room.adults.toString())
|
||||
|
||||
room.children.forEach((child, childIndex) => {
|
||||
room.child.forEach((child, childIndex) => {
|
||||
bookingWidgetParams.set(
|
||||
`room[${index}].child[${childIndex}].age`,
|
||||
child.age.toString()
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
|
||||
|
||||
export const guestRoomSchema = z.object({
|
||||
adults: z.number().default(1),
|
||||
children: z.array(
|
||||
child: z.array(
|
||||
z.object({
|
||||
age: z.number().nonnegative(),
|
||||
bed: z.number(),
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function AdultSelector({ roomIndex = 0 }: AdultSelectorProps) {
|
||||
const intl = useIntl()
|
||||
const adultsLabel = intl.formatMessage({ id: "Adults" })
|
||||
const { setValue } = useFormContext()
|
||||
const { adults, children, childrenInAdultsBed } = useGuestsRoomsStore(
|
||||
const { adults, child, childrenInAdultsBed } = useGuestsRoomsStore(
|
||||
(state) => state.rooms[roomIndex]
|
||||
)
|
||||
const increaseAdults = useGuestsRoomsStore((state) => state.increaseAdults)
|
||||
@@ -39,13 +39,13 @@ export default function AdultSelector({ roomIndex = 0 }: AdultSelectorProps) {
|
||||
decreaseAdults(roomIndex)
|
||||
setValue(`rooms.${roomIndex}.adults`, adults - 1)
|
||||
if (childrenInAdultsBed > adults) {
|
||||
const toUpdateIndex = children.findIndex(
|
||||
const toUpdateIndex = child.findIndex(
|
||||
(child: Child) => child.bed == BedTypeEnum.IN_ADULTS_BED
|
||||
)
|
||||
if (toUpdateIndex != -1) {
|
||||
setValue(
|
||||
`rooms.${roomIndex}.children.${toUpdateIndex}.bed`,
|
||||
children[toUpdateIndex].age < 3
|
||||
child[toUpdateIndex].age < 3
|
||||
? BedTypeEnum.IN_CRIB
|
||||
: BedTypeEnum.IN_EXTRA_BED
|
||||
)
|
||||
|
||||
@@ -26,7 +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, trigger } = useFormContext()
|
||||
const { setValue } = useFormContext()
|
||||
const { adults, childrenInAdultsBed } = useGuestsRoomsStore(
|
||||
(state) => state.rooms[roomIndex]
|
||||
)
|
||||
@@ -51,10 +51,11 @@ export default function ChildInfoSelector({
|
||||
|
||||
function updateSelectedAge(age: number) {
|
||||
updateChildAge(age, roomIndex, index)
|
||||
setValue(`rooms.${roomIndex}.children.${index}.age`, age)
|
||||
setValue(`rooms.${roomIndex}.child.${index}.age`, age, {
|
||||
shouldValidate: true,
|
||||
})
|
||||
const availableBedTypes = getAvailableBeds(age)
|
||||
updateSelectedBed(availableBedTypes[0].value)
|
||||
trigger("rooms")
|
||||
}
|
||||
|
||||
function updateSelectedBed(bed: number) {
|
||||
@@ -64,7 +65,7 @@ export default function ChildInfoSelector({
|
||||
decreaseChildInAdultsBed(roomIndex)
|
||||
}
|
||||
updateChildBed(bed, roomIndex, index)
|
||||
setValue(`rooms.${roomIndex}.children.${index}.bed`, bed)
|
||||
setValue(`rooms.${roomIndex}.child.${index}.bed`, bed)
|
||||
}
|
||||
|
||||
const allBedTypes: ChildBed[] = [
|
||||
@@ -109,8 +110,9 @@ export default function ChildInfoSelector({
|
||||
onSelect={(key) => {
|
||||
updateSelectedAge(key as number)
|
||||
}}
|
||||
name={`rooms.${roomIndex}.children.${index}.age`}
|
||||
name={`rooms.${roomIndex}.child.${index}.age`}
|
||||
placeholder={ageLabel}
|
||||
maxHeight={150}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -123,7 +125,7 @@ export default function ChildInfoSelector({
|
||||
onSelect={(key) => {
|
||||
updateSelectedBed(key as number)
|
||||
}}
|
||||
name={`rooms.${roomIndex}.children.${index}.age`}
|
||||
name={`rooms.${roomIndex}.child.${index}.age`}
|
||||
placeholder={bedLabel}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -19,9 +19,7 @@ export default function ChildSelector({ roomIndex = 0 }: ChildSelectorProps) {
|
||||
const intl = useIntl()
|
||||
const childrenLabel = intl.formatMessage({ id: "Children" })
|
||||
const { setValue, trigger } = useFormContext<BookingWidgetSchema>()
|
||||
const children = useGuestsRoomsStore(
|
||||
(state) => state.rooms[roomIndex].children
|
||||
)
|
||||
const children = useGuestsRoomsStore((state) => state.rooms[roomIndex].child)
|
||||
const increaseChildren = useGuestsRoomsStore(
|
||||
(state) => state.increaseChildren
|
||||
)
|
||||
@@ -32,18 +30,22 @@ export default function ChildSelector({ roomIndex = 0 }: ChildSelectorProps) {
|
||||
function increaseChildrenCount(roomIndex: number) {
|
||||
if (children.length < 5) {
|
||||
increaseChildren(roomIndex)
|
||||
setValue(`rooms.${roomIndex}.children.${children.length}`, {
|
||||
age: -1,
|
||||
bed: -1,
|
||||
})
|
||||
trigger("rooms")
|
||||
setValue(
|
||||
`rooms.${roomIndex}.child.${children.length}`,
|
||||
{
|
||||
age: -1,
|
||||
bed: -1,
|
||||
},
|
||||
{ shouldValidate: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
function decreaseChildrenCount(roomIndex: number) {
|
||||
if (children.length > 0) {
|
||||
const newChildrenList = decreaseChildren(roomIndex)
|
||||
setValue(`rooms.${roomIndex}.children`, newChildrenList)
|
||||
trigger("rooms")
|
||||
setValue(`rooms.${roomIndex}.child`, newChildrenList, {
|
||||
shouldValidate: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
|
||||
@@ -12,9 +13,14 @@ import GuestsRoomsPicker from "./GuestsRoomsPicker"
|
||||
|
||||
import styles from "./guests-rooms-picker.module.css"
|
||||
|
||||
export default function GuestsRoomsPickerForm() {
|
||||
export default function GuestsRoomsPickerForm({
|
||||
name = "rooms",
|
||||
}: {
|
||||
name: string
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const { setValue } = useFormContext()
|
||||
const { rooms, adultCount, childCount, setIsValidated } = useGuestsRoomsStore(
|
||||
(state) => ({
|
||||
rooms: state.rooms,
|
||||
@@ -32,10 +38,11 @@ export default function GuestsRoomsPickerForm() {
|
||||
if (guestRoomsValidData.success) {
|
||||
setIsOpen(false)
|
||||
setIsValidated(false)
|
||||
setValue(name, guestRoomsValidData.data, { shouldValidate: true })
|
||||
} else {
|
||||
setIsValidated(true)
|
||||
}
|
||||
}, [rooms, setIsValidated, setIsOpen])
|
||||
}, [rooms, name, setValue, setIsValidated, setIsOpen])
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(evt: Event) {
|
||||
|
||||
@@ -1,28 +1,12 @@
|
||||
import { getFormattedUrlQueryParams } from "@/utils/url"
|
||||
|
||||
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
|
||||
function getHotelReservationQueryParams(searchParams: URLSearchParams) {
|
||||
const searchParamsObject: Record<string, unknown> = Array.from(
|
||||
searchParams.entries()
|
||||
).reduce<Record<string, unknown>>(
|
||||
(acc, [key, value]) => {
|
||||
const keys = key.replace(/\]/g, "").split(/\[|\./) // Split keys by '[' or '.'
|
||||
keys.reduce((nestedAcc, k, i) => {
|
||||
if (i === keys.length - 1) {
|
||||
// Convert value to number if the key is 'adults' or 'age'
|
||||
;(nestedAcc as Record<string, unknown>)[k] =
|
||||
k === "adults" || k === "age" ? Number(value) : value
|
||||
} else {
|
||||
if (!nestedAcc[k]) {
|
||||
nestedAcc[k] = isNaN(Number(keys[i + 1])) ? {} : [] // Initialize as object or array
|
||||
}
|
||||
}
|
||||
return nestedAcc[k] as Record<string, unknown>
|
||||
}, acc)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
)
|
||||
return searchParamsObject as SelectRateSearchParams
|
||||
return getFormattedUrlQueryParams(searchParams, {
|
||||
adults: "number",
|
||||
age: "number",
|
||||
}) as SelectRateSearchParams
|
||||
}
|
||||
|
||||
export default getHotelReservationQueryParams
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function Select({
|
||||
required = false,
|
||||
tabIndex,
|
||||
value,
|
||||
maxHeight,
|
||||
}: SelectProps) {
|
||||
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
|
||||
|
||||
@@ -81,6 +82,7 @@ export default function Select({
|
||||
* on the container as well as to not overflow it at any time.
|
||||
*/
|
||||
UNSTABLE_portalContainer={rootDiv}
|
||||
maxHeight={maxHeight}
|
||||
>
|
||||
<ListBox className={styles.listBox}>
|
||||
{items.map((item) => (
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface SelectProps
|
||||
onSelect: (key: Key) => void
|
||||
placeholder?: string
|
||||
value?: string | number
|
||||
maxHeight?: number
|
||||
}
|
||||
|
||||
export type SelectPortalContainer = HTMLDivElement | undefined
|
||||
|
||||
Reference in New Issue
Block a user