diff --git a/app/[lang]/(live)/@bookingwidget/page.tsx b/app/[lang]/(live)/@bookingwidget/page.tsx index 16bc85731..2d43c7773 100644 --- a/app/[lang]/(live)/@bookingwidget/page.tsx +++ b/app/[lang]/(live)/@bookingwidget/page.tsx @@ -3,13 +3,11 @@ import { serverClient } from "@/lib/trpc/server" import BookingWidget, { preload } from "@/components/BookingWidget" -import { BookingWidgetSearchParams } from "@/types/components/bookingWidget" -import { LangParams, PageArgs } from "@/types/params" +import { BookingWidgetPageProps } from "@/types/components/bookingWidget" export default async function BookingWidgetPage({ - params, searchParams, -}: PageArgs) { +}: BookingWidgetPageProps) { if (env.HIDE_FOR_NEXT_RELEASE) { return null } diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index fda8683c9..6a594199e 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -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: [], }, ], }, diff --git a/components/BookingWidget/MobileToggleButton/index.tsx b/components/BookingWidget/MobileToggleButton/index.tsx index df84cd65d..3bc438c41 100644 --- a/components/BookingWidget/MobileToggleButton/index.tsx +++ b/components/BookingWidget/MobileToggleButton/index.tsx @@ -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 (
@@ -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 })}`}
diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index eca1634d8..002edb44a 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -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 ( <>
@@ -51,7 +54,9 @@ export default function FormContent({ {rooms} - + + +
diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx index 270a38b04..8c9ccb48e 100644 --- a/components/Forms/BookingWidget/index.tsx +++ b/components/Forms/BookingWidget/index.tsx @@ -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() diff --git a/components/Forms/BookingWidget/schema.ts b/components/Forms/BookingWidget/schema.ts index aa42b542d..973ab6ad6 100644 --- a/components/Forms/BookingWidget/schema.ts +++ b/components/Forms/BookingWidget/schema.ts @@ -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(), diff --git a/components/GuestsRoomsPicker/AdultSelector/index.tsx b/components/GuestsRoomsPicker/AdultSelector/index.tsx index 72d60ebaf..06dbd56c3 100644 --- a/components/GuestsRoomsPicker/AdultSelector/index.tsx +++ b/components/GuestsRoomsPicker/AdultSelector/index.tsx @@ -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 ) diff --git a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx index 107bfd8b6..f219293ab 100644 --- a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx @@ -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} />
@@ -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} diff --git a/components/GuestsRoomsPicker/ChildSelector/index.tsx b/components/GuestsRoomsPicker/ChildSelector/index.tsx index 827bcd2e6..22d594397 100644 --- a/components/GuestsRoomsPicker/ChildSelector/index.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/index.tsx @@ -19,9 +19,7 @@ export default function ChildSelector({ roomIndex = 0 }: ChildSelectorProps) { const intl = useIntl() const childrenLabel = intl.formatMessage({ id: "Children" }) const { setValue, trigger } = useFormContext() - 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, + }) } } diff --git a/components/GuestsRoomsPicker/Provider/GuestsRoomsProvider.tsx b/components/GuestsRoomsPicker/Provider/GuestsRoomsProvider.tsx new file mode 100644 index 000000000..5a85102e7 --- /dev/null +++ b/components/GuestsRoomsPicker/Provider/GuestsRoomsProvider.tsx @@ -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() + if (!initialStore.current) { + initialStore.current = initGuestsRoomsState(selectedGuests) + } + + return ( + + {children} + + ) +} diff --git a/components/GuestsRoomsPicker/index.tsx b/components/GuestsRoomsPicker/index.tsx index 8533f79bd..090fc3803 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 { 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) { diff --git a/components/HotelReservation/SelectRate/RoomSelection/utils.ts b/components/HotelReservation/SelectRate/RoomSelection/utils.ts index e47a0da70..0b1ab884a 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/utils.ts +++ b/components/HotelReservation/SelectRate/RoomSelection/utils.ts @@ -1,28 +1,12 @@ +import { getFormattedUrlQueryParams } from "@/utils/url" + import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" function getHotelReservationQueryParams(searchParams: URLSearchParams) { - const searchParamsObject: Record = Array.from( - searchParams.entries() - ).reduce>( - (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)[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 - }, acc) - return acc - }, - {} as Record - ) - return searchParamsObject as SelectRateSearchParams + return getFormattedUrlQueryParams(searchParams, { + adults: "number", + age: "number", + }) as SelectRateSearchParams } export default getHotelReservationQueryParams diff --git a/components/TempDesignSystem/Select/index.tsx b/components/TempDesignSystem/Select/index.tsx index d506c146c..5575dbd33 100644 --- a/components/TempDesignSystem/Select/index.tsx +++ b/components/TempDesignSystem/Select/index.tsx @@ -34,6 +34,7 @@ export default function Select({ required = false, tabIndex, value, + maxHeight, }: SelectProps) { const [rootDiv, setRootDiv] = useState(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} > {items.map((item) => ( diff --git a/components/TempDesignSystem/Select/select.ts b/components/TempDesignSystem/Select/select.ts index cac1e69cc..706ed71fd 100644 --- a/components/TempDesignSystem/Select/select.ts +++ b/components/TempDesignSystem/Select/select.ts @@ -9,6 +9,7 @@ export interface SelectProps onSelect: (key: Key) => void placeholder?: string value?: string | number + maxHeight?: number } export type SelectPortalContainer = HTMLDivElement | undefined diff --git a/stores/guests-rooms.ts b/stores/guests-rooms.ts index 2866184b0..bc305db61 100644 --- a/stores/guests-rooms.ts +++ b/stores/guests-rooms.ts @@ -1,22 +1,28 @@ "use client" import { produce } from "immer" -import { create } from "zustand" +import { createContext, useContext } from "react" +import { create, useStore } from "zustand" import { BedTypeEnum } from "@/types/components/bookingWidget/enums" -import { Child } from "@/types/components/bookingWidget/guestsRoomsPicker" +import { + Child, + GuestsRoom, +} from "@/types/components/bookingWidget/guestsRoomsPicker" -interface GuestsRooms { - rooms: [ - { - adults: number - children: Child[] - childrenInAdultsBed: number - }, - ] +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 @@ -30,115 +36,192 @@ interface GuestsRooms { setIsValidated: (isValidated: boolean) => void } -export const useGuestsRoomsStore = create((set, get) => ({ - rooms: [ - { - adults: 1, - children: [], - childrenInAdultsBed: 0, - }, - ], - adultCount: 1, - childCount: 0, - isValidated: false, - increaseAdults: (roomIndex) => - set( - produce((state: GuestsRooms) => { - state.rooms[roomIndex].adults = state.rooms[roomIndex].adults + 1 - state.adultCount = state.adultCount + 1 - }) - ), - decreaseAdults: (roomIndex) => - set( - produce((state: GuestsRooms) => { - 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].children.findIndex( - (child) => child.bed == BedTypeEnum.IN_ADULTS_BED - ) - if (toUpdateIndex != -1) { - state.rooms[roomIndex].children[toUpdateIndex].bed = - state.rooms[roomIndex].children[toUpdateIndex].age < 3 - ? BedTypeEnum.IN_CRIB - : BedTypeEnum.IN_EXTRA_BED - state.rooms[roomIndex].childrenInAdultsBed = - state.rooms[roomIndex].adults - } - } - }) - ), - increaseChildren: (roomIndex) => - set( - produce((state: GuestsRooms) => { - state.rooms[roomIndex].children.push({ - age: -1, - bed: -1, +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(BedTypeEnum.IN_ADULTS_BED) + } else if (child.age <= 5) { + room.childrenInAdultsBed = room.childrenInAdultsBed - 1 + } + if (child.age < 3) { + allowedBedTypes.push(BedTypeEnum.IN_CRIB) + } + if (child.age > 2) { + allowedBedTypes.push(BedTypeEnum.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 == BedTypeEnum.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 }) - state.childCount = state.childCount + 1 - }) - ), - decreaseChildren: (roomIndex) => { - set( - produce((state: GuestsRooms) => { - const roomChildren = state.rooms[roomIndex].children - if ( - roomChildren.length && - roomChildren[roomChildren.length - 1].bed == BedTypeEnum.IN_ADULTS_BED - ) { + ), + 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 == BedTypeEnum.IN_ADULTS_BED + ) + if (toUpdateIndex != -1) { + state.rooms[roomIndex].child[toUpdateIndex].bed = + state.rooms[roomIndex].child[toUpdateIndex].age < 3 + ? BedTypeEnum.IN_CRIB + : BedTypeEnum.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 == + BedTypeEnum.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 - } - state.rooms[roomIndex].children.pop() - state.childCount = state.childCount - 1 - }) - ) - return get().rooms[roomIndex].children - }, - updateChildAge: (age, roomIndex, childIndex) => - set( - produce((state: GuestsRooms) => { - state.rooms[roomIndex].children[childIndex].age = age - }) - ), - updateChildBed: (bed, roomIndex, childIndex) => - set( - produce((state: GuestsRooms) => { - state.rooms[roomIndex].children[childIndex].bed = bed - }) - ), - increaseChildInAdultsBed: (roomIndex) => - set( - produce((state: GuestsRooms) => { - state.rooms[roomIndex].childrenInAdultsBed = - state.rooms[roomIndex].childrenInAdultsBed + 1 - }) - ), - decreaseChildInAdultsBed: (roomIndex) => - set( - produce((state: GuestsRooms) => { - state.rooms[roomIndex].childrenInAdultsBed = - state.rooms[roomIndex].childrenInAdultsBed - 1 - }) - ), - increaseRoom: () => - set( - produce((state: GuestsRooms) => { - state.rooms.push({ - adults: 1, - children: [], - childrenInAdultsBed: 0, }) - }) - ), - decreaseRoom: (roomIndex) => - set( - produce((state: GuestsRooms) => { - state.rooms.splice(roomIndex, 1) - }) - ), - setIsValidated: (isValidated) => set(() => ({ isValidated })), -})) + ), + 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/types/components/bookingWidget/guestsRoomsPicker.ts b/types/components/bookingWidget/guestsRoomsPicker.ts index 5b3b7b3e2..61e8f7d7a 100644 --- a/types/components/bookingWidget/guestsRoomsPicker.ts +++ b/types/components/bookingWidget/guestsRoomsPicker.ts @@ -10,7 +10,7 @@ export type Child = { export type GuestsRoom = { adults: number - children: Child[] + child: Child[] } export interface GuestsRoomsPickerProps { diff --git a/types/components/bookingWidget/index.ts b/types/components/bookingWidget/index.ts index 8652b63da..a6230b0b2 100644 --- a/types/components/bookingWidget/index.ts +++ b/types/components/bookingWidget/index.ts @@ -4,6 +4,8 @@ import { z } from "zod" import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" import { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants" +import { GuestsRoom } from "./guestsRoomsPicker" + import type { Locations } from "@/types/trpc/routers/hotel/locations" export type BookingWidgetSchema = z.output @@ -13,22 +15,27 @@ export type BookingWidgetSearchParams = { hotel?: string fromDate?: string toDate?: string - room?: string + room?: GuestsRoom[] + [key: string]: string | string[] | GuestsRoom[] | undefined } export type BookingWidgetType = VariantProps< typeof bookingWidgetVariants >["type"] +export interface BookingWidgetPageProps { + searchParams?: URLSearchParams +} + export interface BookingWidgetProps { type?: BookingWidgetType - searchParams?: BookingWidgetSearchParams + searchParams?: URLSearchParams } export interface BookingWidgetClientProps { locations: Locations type?: BookingWidgetType - searchParams?: BookingWidgetSearchParams + searchParams?: URLSearchParams } export interface BookingWidgetToggleButtonProps { diff --git a/utils/url.ts b/utils/url.ts index d9be2c491..4366fa181 100644 --- a/utils/url.ts +++ b/utils/url.ts @@ -9,3 +9,36 @@ export function removeTrailingSlash(pathname: string) { } return pathname } + +export function getFormattedUrlQueryParams( + searchParams: URLSearchParams, + dataTypes: Record +) { + const searchParamsObject: Record = Array.from( + searchParams.entries() + ).reduce>( + (acc, [key, value]) => { + const keys = key.replace(/\]/g, "").split(/\[|\./) // Split keys by '[' or '.' + keys.reduce((nestedAcc, k, i) => { + if (i === keys.length - 1) { + if (dataTypes[k] == "number") { + ;(nestedAcc as Record)[k] = Number(value) + } else if (dataTypes[k] == "boolean") { + ;(nestedAcc as Record)[k] = + value.toLowerCase() === "true" + } else { + ;(nestedAcc as Record)[k] = value + } + } else { + if (!nestedAcc[k]) { + nestedAcc[k] = isNaN(Number(keys[i + 1])) ? {} : [] // Initialize as object or array + } + } + return nestedAcc[k] as Record + }, acc) + return acc + }, + {} as Record + ) + return searchParamsObject +}