refactor: url management in hotel reservation flow

This commit is contained in:
Christel Westerberg
2025-01-13 14:26:38 +01:00
parent 23ff0970e9
commit b2935114e3
48 changed files with 407 additions and 418 deletions

View File

@@ -1,7 +1,8 @@
.page { .page {
background-color: var(--Base-Background-Primary-Normal); background-color: var(--Base-Background-Primary-Normal);
min-height: 50dvh; min-height: 50dvh;
max-width: var(--max-width); max-width: var(--max-width-page);
margin: 0 auto;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@@ -20,8 +20,13 @@ export default async function SelectHotelMapPage({
setLang(params.lang) setLang(params.lang)
const searchDetails = await getHotelSearchDetails({ searchParams }) const searchDetails = await getHotelSearchDetails({ searchParams })
if (!searchDetails) return notFound() if (!searchDetails) return notFound()
const { city, adultsInRoom, childrenInRoom, childrenInRoomArray } = const {
searchDetails city,
adultsInRoom,
childrenInRoom,
childrenInRoomArray,
selectHotelParams,
} = searchDetails
if (!city) return notFound() if (!city) return notFound()
@@ -34,7 +39,7 @@ export default async function SelectHotelMapPage({
> >
<SelectHotelMapContainer <SelectHotelMapContainer
city={city} city={city}
searchParams={searchParams} selectHotelParams={selectHotelParams}
adultsInRoom={adultsInRoom} adultsInRoom={adultsInRoom}
childrenInRoom={childrenInRoom} childrenInRoom={childrenInRoom}
child={childrenInRoomArray} child={childrenInRoomArray}

View File

@@ -3,7 +3,6 @@ import { Suspense } from "react"
import SelectHotel from "@/components/HotelReservation/SelectHotel" import SelectHotel from "@/components/HotelReservation/SelectHotel"
import { SelectHotelSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelSkeleton" import { SelectHotelSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelSkeleton"
import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { getHotelSearchDetails } from "../utils" import { getHotelSearchDetails } from "../utils"
@@ -20,7 +19,7 @@ export default async function SelectHotelPage({
if (!searchDetails) return notFound() if (!searchDetails) return notFound()
const { const {
city, city,
urlSearchParams, selectHotelParams,
adultsInRoom, adultsInRoom,
childrenInRoom, childrenInRoom,
childrenInRoomArray, childrenInRoomArray,
@@ -29,8 +28,7 @@ export default async function SelectHotelPage({
if (!city) return notFound() if (!city) return notFound()
const reservationParams = { const reservationParams = {
selectHotelParams: urlSearchParams, selectHotelParams,
searchParams,
adultsInRoom, adultsInRoom,
childrenInRoom, childrenInRoom,
childrenInRoomArray, childrenInRoomArray,

View File

@@ -29,7 +29,8 @@ export default async function SelectRatePage({
setLang(params.lang) setLang(params.lang)
const searchDetails = await getHotelSearchDetails({ searchParams }) const searchDetails = await getHotelSearchDetails({ searchParams })
if (!searchDetails) return notFound() if (!searchDetails) return notFound()
const { hotel, adultsInRoom, childrenInRoomArray } = searchDetails const { hotel, adultsInRoom, childrenInRoomArray, selectHotelParams } =
searchDetails
if (!hotel) return notFound() if (!hotel) return notFound()
@@ -39,12 +40,12 @@ export default async function SelectRatePage({
}) })
const { fromDate, toDate } = getValidDates( const { fromDate, toDate } = getValidDates(
searchParams.fromDate, selectHotelParams.fromDate,
searchParams.toDate selectHotelParams.toDate
) )
const arrivalDate = new Date(searchParams.fromDate) const arrivalDate = fromDate.toDate()
const departureDate = new Date(searchParams.toDate) const departureDate = toDate.toDate()
const pageTrackingData: TrackingSDKPageData = { const pageTrackingData: TrackingSDKPageData = {
pageId: "select-rate", pageId: "select-rate",
@@ -57,7 +58,7 @@ export default async function SelectRatePage({
} }
const hotelsTrackingData: TrackingSDKHotelInfo = { const hotelsTrackingData: TrackingSDKHotelInfo = {
searchTerm: searchParams.city ?? hotel?.name, searchTerm: selectHotelParams.city ?? hotel?.name,
arrivalDate: format(arrivalDate, "yyyy-MM-dd"), arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
departureDate: format(departureDate, "yyyy-MM-dd"), departureDate: format(departureDate, "yyyy-MM-dd"),
noOfAdults: adultsInRoom, noOfAdults: adultsInRoom,

View File

@@ -21,21 +21,19 @@ import SectionAccordion from "@/components/HotelReservation/EnterDetails/Section
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom" import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop" import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop"
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile" import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
import { import { generateChildrenString } from "@/components/HotelReservation/utils"
generateChildrenString,
getQueryParamsForEnterDetails,
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import EnterDetailsProvider from "@/providers/EnterDetailsProvider" import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
import { convertSearchParamsToObj } from "@/utils/url"
import EnterDetailsTracking from "./enterDetailsTracking" import EnterDetailsTracking from "./enterDetailsTracking"
import styles from "./page.module.css" import styles from "./page.module.css"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums" import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import { type SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import { import {
TrackingChannelEnum, TrackingChannelEnum,
type TrackingSDKHotelInfo, type TrackingSDKHotelInfo,
@@ -61,10 +59,10 @@ export default async function StepPage({
const selectRoomParams = new URLSearchParams(searchParams) const selectRoomParams = new URLSearchParams(searchParams)
// Deleting step to avoid double searchparams after rewrite // Deleting step to avoid double searchparams after rewrite
selectRoomParams.delete("step") selectRoomParams.delete("step")
const booking = getQueryParamsForEnterDetails(selectRoomParams) const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
const { const {
hotel: hotelId, hotelId,
rooms: [ rooms: [
{ adults, children, roomTypeCode, rateCode, packages: packageCodes }, { adults, children, roomTypeCode, rateCode, packages: packageCodes },
], // TODO: Handle multiple rooms ], // TODO: Handle multiple rooms
@@ -153,8 +151,8 @@ export default async function StepPage({
} }
: undefined : undefined
const arrivalDate = new Date(searchParams.fromDate) const arrivalDate = new Date(fromDate)
const departureDate = new Date(searchParams.toDate) const departureDate = new Date(toDate)
const hotelAttributes = hotelData?.data.attributes const hotelAttributes = hotelData?.data.attributes
const initialPageTrackingData: TrackingSDKPageData = { const initialPageTrackingData: TrackingSDKPageData = {

View File

@@ -2,10 +2,8 @@ import { notFound } from "next/navigation"
import { getLocations } from "@/lib/trpc/memoizedRequests" import { getLocations } from "@/lib/trpc/memoizedRequests"
import { import { generateChildrenString } from "@/components/HotelReservation/utils"
generateChildrenString, import { convertSearchParamsToObj } from "@/utils/url"
getHotelReservationQueryParams,
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import type { import type {
@@ -14,62 +12,59 @@ import type {
} from "@/types/components/hotelReservation/selectRate/selectRate" } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { Location } from "@/types/trpc/routers/hotel/locations" import type { Location } from "@/types/trpc/routers/hotel/locations"
interface HotelSearchDetails { interface HotelSearchDetails<T> {
city: Location | null city: Location | null
hotel: Location | null hotel: Location | null
urlSearchParams?: URLSearchParams selectHotelParams: T
adultsInRoom: number adultsInRoom: number
childrenInRoom?: string childrenInRoom?: string
childrenInRoomArray?: Child[] childrenInRoomArray?: Child[]
} }
export async function getHotelSearchDetails({ export async function getHotelSearchDetails<
T extends SelectHotelSearchParams | SelectRateSearchParams,
>({
searchParams, searchParams,
}: { }: {
searchParams: searchParams: T & {
| (SelectHotelSearchParams & {
[key: string]: string [key: string]: string
}) }
| (SelectRateSearchParams & { }): Promise<HotelSearchDetails<T> | null> {
[key: string]: string const selectHotelParams = convertSearchParamsToObj<T>(searchParams)
})
}): Promise<HotelSearchDetails | null> {
const locations = await getLocations() const locations = await getLocations()
if (!locations || "error" in locations) return null if (!locations || "error" in locations) return null
const city = locations.data.find( const city = locations.data.find(
(location) => (location) =>
location.name.toLowerCase() === searchParams.city?.toLowerCase() location.name.toLowerCase() === selectHotelParams.city?.toLowerCase()
) )
const hotel = locations.data.find( const hotel = locations.data.find(
(location) => (location) =>
"operaId" in location && location.operaId == searchParams.hotel "operaId" in location && location.operaId == selectHotelParams.hotelId
) )
if (!city && !hotel) return notFound() if (!city && !hotel) return notFound()
const urlSearchParams = new URLSearchParams(searchParams)
const searchParamsObject = getHotelReservationQueryParams(urlSearchParams)
let adultsInRoom = 1 let adultsInRoom = 1
let childrenInRoom: string | undefined = undefined let childrenInRoom: string | undefined = undefined
let childrenInRoomArray: Child[] | undefined = undefined let childrenInRoomArray: Child[] | undefined = undefined
if (searchParamsObject.room && searchParamsObject.room.length > 0) { const { rooms } = selectHotelParams
adultsInRoom = searchParamsObject.room[0].adults // TODO: Handle multiple rooms
childrenInRoom = searchParamsObject.room[0].child if (rooms && rooms.length > 0) {
? generateChildrenString(searchParamsObject.room[0].child) adultsInRoom = rooms[0].adults // TODO: Handle multiple rooms
: undefined // TODO: Handle multiple rooms childrenInRoom = rooms[0].children
childrenInRoomArray = searchParamsObject.room[0].child ? generateChildrenString(rooms[0].children)
? searchParamsObject.room[0].child
: undefined // TODO: Handle multiple rooms : undefined // TODO: Handle multiple rooms
childrenInRoomArray = rooms[0].children ? rooms[0].children : undefined // TODO: Handle multiple rooms
} }
return { return {
city: city ?? null, city: city ?? null,
hotel: hotel ?? null, hotel: hotel ?? null,
urlSearchParams, selectHotelParams,
adultsInRoom, adultsInRoom,
childrenInRoom, childrenInRoom,
childrenInRoomArray, childrenInRoomArray,

View File

@@ -4,21 +4,20 @@ import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests"
import BookingWidget, { preload } from "@/components/BookingWidget" import BookingWidget, { preload } from "@/components/BookingWidget"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { ContentTypeParams, PageArgs } from "@/types/params" import type { ContentTypeParams, PageArgs } from "@/types/params"
import { PageContentTypeEnum } from "@/types/requests/contentType" import { PageContentTypeEnum } from "@/types/requests/contentType"
export default async function BookingWidgetPage({ export default async function BookingWidgetPage({
params, params,
searchParams, searchParams,
}: PageArgs<ContentTypeParams, URLSearchParams>) { }: PageArgs<ContentTypeParams, BookingWidgetSearchData>) {
if (!env.ENABLE_BOOKING_WIDGET) { if (!env.ENABLE_BOOKING_WIDGET) {
return null return null
} }
preload() preload()
const urlParams = new URLSearchParams()
if (params.contentType === PageContentTypeEnum.hotelPage) { if (params.contentType === PageContentTypeEnum.hotelPage) {
const hotelPageData = await getHotelPage() const hotelPageData = await getHotelPage()
@@ -26,10 +25,14 @@ export default async function BookingWidgetPage({
hotelId: hotelPageData?.hotel_page_id || "", hotelId: hotelPageData?.hotel_page_id || "",
language: getLang(), language: getLang(),
}) })
urlParams.set("hotel", hotelData?.data?.id || "")
urlParams.set("city", hotelData?.data?.attributes?.cityName || "")
return <BookingWidget searchParams={urlParams} /> const hotelPageParams = {
hotel: hotelData?.data?.id || "",
city: hotelData?.data?.attributes?.cityName || "",
} }
return <BookingWidget searchParams={searchParams} />
return <BookingWidget bookingWidgetSearchParams={hotelPageParams} />
}
return <BookingWidget bookingWidgetSearchParams={searchParams} />
} }

View File

@@ -2,16 +2,17 @@ import { env } from "@/env/server"
import BookingWidget, { preload } from "@/components/BookingWidget" import BookingWidget, { preload } from "@/components/BookingWidget"
import type { PageArgs } from "@/types/params" import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { LangParams, PageArgs } from "@/types/params"
export default async function BookingWidgetPage({ export default async function BookingWidgetPage({
searchParams, searchParams,
}: PageArgs<{}, URLSearchParams>) { }: PageArgs<LangParams, BookingWidgetSearchData>) {
if (!env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH) { if (!env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH) {
return null return null
} }
preload() preload()
return <BookingWidget searchParams={searchParams} /> return <BookingWidget bookingWidgetSearchParams={searchParams} />
} }

View File

@@ -3,11 +3,12 @@ import { serverClient } from "@/lib/trpc/server"
import BookingWidget, { preload } from "@/components/BookingWidget" import BookingWidget, { preload } from "@/components/BookingWidget"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { PageArgs } from "@/types/params" import type { PageArgs } from "@/types/params"
export default async function BookingWidgetPage({ export default async function BookingWidgetPage({
searchParams, searchParams,
}: PageArgs<{}, URLSearchParams>) { }: PageArgs<{}, BookingWidgetSearchData>) {
if (!env.ENABLE_BOOKING_WIDGET) { if (!env.ENABLE_BOOKING_WIDGET) {
return null return null
} }
@@ -22,5 +23,5 @@ export default async function BookingWidgetPage({
return null return null
} }
return <BookingWidget searchParams={searchParams} /> return <BookingWidget bookingWidgetSearchParams={searchParams} />
} }

View File

@@ -4,7 +4,6 @@ import "@scandic-hotels/design-system/style.css"
import Script from "next/script" import Script from "next/script"
import TokenRefresher from "@/components/Auth/TokenRefresher" import TokenRefresher from "@/components/Auth/TokenRefresher"
import BookingWidget from "@/components/BookingWidget"
import CookieBotConsent from "@/components/CookieBot" import CookieBotConsent from "@/components/CookieBot"
import AdobeScript from "@/components/Current/AdobeScript" import AdobeScript from "@/components/Current/AdobeScript"
import Footer from "@/components/Current/Footer" import Footer from "@/components/Current/Footer"

View File

@@ -14,7 +14,7 @@ import { CloseLargeIcon } from "@/components/Icons"
import useStickyPosition from "@/hooks/useStickyPosition" import useStickyPosition from "@/hooks/useStickyPosition"
import { debounce } from "@/utils/debounce" import { debounce } from "@/utils/debounce"
import isValidJson from "@/utils/isValidJson" import isValidJson from "@/utils/isValidJson"
import { getFormattedUrlQueryParams } from "@/utils/url" import { convertSearchParamsToObj } from "@/utils/url"
import MobileToggleButton, { import MobileToggleButton, {
MobileToggleButtonSkeleton, MobileToggleButtonSkeleton,
@@ -25,14 +25,14 @@ import styles from "./bookingWidget.module.css"
import type { import type {
BookingWidgetClientProps, BookingWidgetClientProps,
BookingWidgetSchema, BookingWidgetSchema,
BookingWidgetSearchParams, BookingWidgetSearchData,
} from "@/types/components/bookingWidget" } from "@/types/components/bookingWidget"
import type { Location } from "@/types/trpc/routers/hotel/locations" import type { Location } from "@/types/trpc/routers/hotel/locations"
export default function BookingWidgetClient({ export default function BookingWidgetClient({
locations, locations,
type, type,
searchParams, bookingWidgetSearchParams,
}: BookingWidgetClientProps) { }: BookingWidgetClientProps) {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const bookingWidgetRef = useRef(null) const bookingWidgetRef = useRef(null)
@@ -41,13 +41,10 @@ export default function BookingWidgetClient({
name: StickyElementNameEnum.BOOKING_WIDGET, name: StickyElementNameEnum.BOOKING_WIDGET,
}) })
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined = const bookingWidgetSearchData = bookingWidgetSearchParams
searchParams ? convertSearchParamsToObj<BookingWidgetSearchData>(
? getFormattedUrlQueryParams(new URLSearchParams(searchParams), { bookingWidgetSearchParams
adults: "number", )
age: "number",
bed: "number",
})
: undefined : undefined
const getLocationObj = (destination: string): Location | undefined => { const getLocationObj = (destination: string): Location | undefined => {
@@ -85,13 +82,13 @@ export default function BookingWidgetClient({
) )
: undefined : undefined
const defaultRoomsData = bookingWidgetSearchData?.room?.map((room) => ({ const defaultRoomsData = bookingWidgetSearchData?.rooms?.map((room) => ({
adults: room.adults, adults: room.adults,
child: room.child ?? [], children: room.children ?? [],
})) ?? [ })) ?? [
{ {
adults: 1, adults: 1,
child: [], children: [],
}, },
] ]

View File

@@ -49,8 +49,8 @@ export default function MobileToggleButton({
return acc return acc
}, 0) }, 0)
const totalChildren = rooms.reduce((acc, room) => { const totalChildren = rooms.reduce((acc, room) => {
if (room.child) { if (room.children) {
acc = acc + room.child.length acc = acc + room.children.length
} }
return acc return acc
}, 0) }, 0)

View File

@@ -10,7 +10,7 @@ export function preload() {
export default async function BookingWidget({ export default async function BookingWidget({
type, type,
searchParams, bookingWidgetSearchParams,
}: BookingWidgetProps) { }: BookingWidgetProps) {
const locations = await getLocations() const locations = await getLocations()
const siteConfig = await getSiteConfig() const siteConfig = await getSiteConfig()
@@ -23,7 +23,7 @@ export default async function BookingWidget({
<BookingWidgetClient <BookingWidgetClient
locations={locations.data} locations={locations.data}
type={type} type={type}
searchParams={searchParams} bookingWidgetSearchParams={bookingWidgetSearchParams}
/> />
) )
} }

View File

@@ -10,6 +10,10 @@
} }
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
.section {
max-width: var(--max-width-page);
}
.form { .form {
align-self: flex-start; align-self: flex-start;
} }

View File

@@ -6,6 +6,7 @@ import { useFormContext } from "react-hook-form"
import { selectHotel, selectRate } from "@/constants/routes/hotelReservation" import { selectHotel, selectRate } from "@/constants/routes/hotelReservation"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { convertObjToSearchParams } from "@/utils/url"
import FormContent, { BookingWidgetFormContentSkeleton } from "./FormContent" import FormContent, { BookingWidgetFormContentSkeleton } from "./FormContent"
import { bookingWidgetVariants } from "./variants" import { bookingWidgetVariants } from "./variants"
@@ -37,26 +38,14 @@ export default function Form({
const bookingFlowPage = const bookingFlowPage =
locationData.type == "cities" ? selectHotel(lang) : selectRate(lang) locationData.type == "cities" ? selectHotel(lang) : selectRate(lang)
const bookingWidgetParams = new URLSearchParams(data.date) const bookingWidgetParams = convertObjToSearchParams({
rooms: data.rooms,
if (locationData.type == "cities") ...data.date,
bookingWidgetParams.set("city", locationData.name) ...(locationData.type == "cities"
else bookingWidgetParams.set("hotel", locationData.operaId || "") ? { city: locationData.name }
: { hotel: locationData.operaId || "" }),
data.rooms.forEach((room, index) => {
bookingWidgetParams.set(`room[${index}].adults`, room.adults.toString())
room.child.forEach((child, childIndex) => {
bookingWidgetParams.set(
`room[${index}].child[${childIndex}].age`,
child.age.toString()
)
bookingWidgetParams.set(
`room[${index}].child[${childIndex}].bed`,
child.bed.toString()
)
})
}) })
onClose() onClose()
router.push(`${bookingFlowPage}?${bookingWidgetParams.toString()}`) router.push(`${bookingFlowPage}?${bookingWidgetParams.toString()}`)
} }

View File

@@ -6,7 +6,7 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
export const guestRoomSchema = z export const guestRoomSchema = z
.object({ .object({
adults: z.number().default(1), adults: z.number().default(1),
child: z children: z
.array( .array(
z.object({ z.object({
age: z.number().min(0, "Age is required"), age: z.number().min(0, "Age is required"),
@@ -16,11 +16,11 @@ export const guestRoomSchema = z
.default([]), .default([]),
}) })
.superRefine((value, ctx) => { .superRefine((value, ctx) => {
const childrenInAdultsBed = value.child.filter( const childrenInAdultsBed = value.children.filter(
(c) => c.bed === ChildBedMapEnum.IN_ADULTS_BED (c) => c.bed === ChildBedMapEnum.IN_ADULTS_BED
) )
if (value.adults < childrenInAdultsBed.length) { if (value.adults < childrenInAdultsBed.length) {
const lastAdultBedIndex = value.child const lastAdultBedIndex = value.children
.map((c) => c.bed) .map((c) => c.bed)
.lastIndexOf(ChildBedMapEnum.IN_ADULTS_BED) .lastIndexOf(ChildBedMapEnum.IN_ADULTS_BED)

View File

@@ -29,8 +29,8 @@ export default function ChildInfoSelector({
index = 0, index = 0,
roomIndex = 0, roomIndex = 0,
}: ChildInfoSelectorProps) { }: ChildInfoSelectorProps) {
const ageFieldName = `rooms.${roomIndex}.child.${index}.age` const ageFieldName = `rooms.${roomIndex}.children.${index}.age`
const bedFieldName = `rooms.${roomIndex}.child.${index}.bed` const bedFieldName = `rooms.${roomIndex}.children.${index}.bed`
const intl = useIntl() const intl = useIntl()
const ageLabel = intl.formatMessage({ id: "Age" }) const ageLabel = intl.formatMessage({ id: "Age" })
const bedLabel = intl.formatMessage({ id: "Bed" }) const bedLabel = intl.formatMessage({ id: "Bed" })
@@ -38,11 +38,11 @@ export default function ChildInfoSelector({
const { setValue, formState } = useFormContext() const { setValue, formState } = useFormContext()
function updateSelectedBed(bed: number) { function updateSelectedBed(bed: number) {
setValue(`rooms.${roomIndex}.child.${index}.bed`, bed) setValue(`rooms.${roomIndex}.children.${index}.bed`, bed)
} }
function updateSelectedAge(age: number) { function updateSelectedAge(age: number) {
setValue(`rooms.${roomIndex}.child.${index}.age`, age) setValue(`rooms.${roomIndex}.children.${index}.age`, age)
const availableBedTypes = getAvailableBeds(age) const availableBedTypes = getAvailableBeds(age)
updateSelectedBed(availableBedTypes[0].value) updateSelectedBed(availableBedTypes[0].value)
} }
@@ -77,7 +77,7 @@ export default function ChildInfoSelector({
} }
//@ts-expect-error: formState is typed with FormValues //@ts-expect-error: formState is typed with FormValues
const roomErrors = formState.errors.rooms?.[roomIndex]?.child?.[index] const roomErrors = formState.errors.rooms?.[roomIndex]?.children?.[index]
const ageError = roomErrors?.age const ageError = roomErrors?.age
const bedError = roomErrors?.bed const bedError = roomErrors?.bed

View File

@@ -24,7 +24,7 @@ export default function ChildSelector({
function increaseChildrenCount(roomIndex: number) { function increaseChildrenCount(roomIndex: number) {
if (currentChildren.length < 5) { if (currentChildren.length < 5) {
setValue(`rooms.${roomIndex}.child.${currentChildren.length}`, { setValue(`rooms.${roomIndex}.children.${currentChildren.length}`, {
age: undefined, age: undefined,
bed: undefined, bed: undefined,
}) })
@@ -33,7 +33,7 @@ export default function ChildSelector({
function decreaseChildrenCount(roomIndex: number) { function decreaseChildrenCount(roomIndex: number) {
if (currentChildren.length > 0) { if (currentChildren.length > 0) {
currentChildren.pop() currentChildren.pop()
setValue(`rooms.${roomIndex}.child`, currentChildren) setValue(`rooms.${roomIndex}.children`, currentChildren)
} }
} }

View File

@@ -48,7 +48,7 @@ export default function GuestsRoomsPickerDialog({
}, [trigger, onClose]) }, [trigger, onClose])
const handleAddRoom = useCallback(() => { const handleAddRoom = useCallback(() => {
setValue("rooms", [...roomsValue, { adults: 1, child: [] }], { setValue("rooms", [...roomsValue, { adults: 1, children: [] }], {
shouldValidate: true, shouldValidate: true,
}) })
}, [roomsValue, setValue]) }, [roomsValue, setValue])

View File

@@ -25,7 +25,7 @@ export function GuestsRoom({
const intl = useIntl() const intl = useIntl()
const roomLabel = intl.formatMessage({ id: "Room" }) const roomLabel = intl.formatMessage({ id: "Room" })
const childrenInAdultsBed = room.child.filter( const childrenInAdultsBed = room.children.filter(
(child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED (child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED
).length ).length
@@ -38,13 +38,13 @@ export function GuestsRoom({
<AdultSelector <AdultSelector
roomIndex={index} roomIndex={index}
currentAdults={room.adults} currentAdults={room.adults}
currentChildren={room.child} currentChildren={room.children}
childrenInAdultsBed={childrenInAdultsBed} childrenInAdultsBed={childrenInAdultsBed}
/> />
<ChildSelector <ChildSelector
roomIndex={index} roomIndex={index}
currentAdults={room.adults} currentAdults={room.adults}
currentChildren={room.child} currentChildren={room.children}
childrenInAdultsBed={childrenInAdultsBed} childrenInAdultsBed={childrenInAdultsBed}
/> />
{index !== 0 && ( {index !== 0 && (

View File

@@ -28,7 +28,8 @@ export default function GuestsRoomsPickerForm() {
const [isDesktop, setIsDesktop] = useState(true) const [isDesktop, setIsDesktop] = useState(true)
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [containerHeight, setContainerHeight] = useState(0) const [containerHeight, setContainerHeight] = useState(0)
const childCount = rooms[0] ? rooms[0].child.length : 0 // ToDo Update for multiroom later const childCount =
rooms[0] && rooms[0].children ? rooms[0].children.length : 0 // ToDo Update for multiroom later
const htmlElement = const htmlElement =
typeof window !== "undefined" ? document.querySelector("body") : null typeof window !== "undefined" ? document.querySelector("body") : null
@@ -153,7 +154,7 @@ function Trigger({
) )
) )
if (rooms.some((room) => room.child.length > 0)) { if (rooms.some((room) => room.children.length > 0)) {
parts.push( parts.push(
intl.formatMessage( intl.formatMessage(
{ {
@@ -161,7 +162,7 @@ function Trigger({
}, },
{ {
totalChildren: rooms.reduce( totalChildren: rooms.reduce(
(acc, room) => acc + room.child.length, (acc, room) => acc + room.children.length,
0 0
), ),
} }

View File

@@ -6,7 +6,7 @@ import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { invertedBedTypeMap } from "../SelectRate/RoomSelection/utils" import { invertedBedTypeMap } from "../utils"
import Confirmation from "./Confirmation" import Confirmation from "./Confirmation"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"

View File

@@ -5,9 +5,9 @@ import { useEffect } from "react"
import { detailsStorageName } from "@/stores/enter-details" import { detailsStorageName } from "@/stores/enter-details"
import { createQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import LoadingSpinner from "@/components/LoadingSpinner" import LoadingSpinner from "@/components/LoadingSpinner"
import { trackPaymentEvent } from "@/utils/tracking" import { trackPaymentEvent } from "@/utils/tracking"
import { convertObjToSearchParams } from "@/utils/url"
import type { PersistedState } from "@/types/stores/enter-details" import type { PersistedState } from "@/types/stores/enter-details"
@@ -29,7 +29,7 @@ export default function PaymentCallback({
if (bookingData) { if (bookingData) {
const detailsStorage: PersistedState = JSON.parse(bookingData) const detailsStorage: PersistedState = JSON.parse(bookingData)
const searchParams = createQueryParamsForEnterDetails( const searchParams = convertObjToSearchParams(
detailsStorage.booking, detailsStorage.booking,
searchObject searchObject
) )
@@ -37,13 +37,13 @@ export default function PaymentCallback({
if (status === "cancel") { if (status === "cancel") {
trackPaymentEvent({ trackPaymentEvent({
event: "paymentCancel", event: "paymentCancel",
hotelId: detailsStorage.booking.hotel, hotelId: detailsStorage.booking.hotelId,
}) })
} }
if (status === "error") { if (status === "error") {
trackPaymentEvent({ trackPaymentEvent({
event: "paymentFail", event: "paymentFail",
hotelId: detailsStorage.booking.hotel, hotelId: detailsStorage.booking.hotelId,
errorMessage, errorMessage,
}) })
} }

View File

@@ -33,7 +33,7 @@ import { usePaymentFailedToast } from "@/hooks/booking/usePaymentFailedToast"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { trackPaymentEvent } from "@/utils/tracking" import { trackPaymentEvent } from "@/utils/tracking"
import { bedTypeMap } from "../../SelectRate/RoomSelection/utils" import { bedTypeMap } from "../../utils"
import PriceChangeDialog from "../PriceChangeDialog" import PriceChangeDialog from "../PriceChangeDialog"
import GuaranteeDetails from "./GuaranteeDetails" import GuaranteeDetails from "./GuaranteeDetails"
import PaymentOption from "./PaymentOption" import PaymentOption from "./PaymentOption"
@@ -87,7 +87,7 @@ export default function PaymentClient({
newPrice: number newPrice: number
} | null>() } | null>()
const { toDate, fromDate, rooms, hotel } = booking const { toDate, fromDate, rooms, hotelId } = booking
usePaymentFailedToast() usePaymentFailedToast()
@@ -171,14 +171,14 @@ export default function PaymentClient({
trackPaymentEvent({ trackPaymentEvent({
event: "paymentFail", event: "paymentFail",
hotelId: hotel, hotelId,
method: currentPaymentMethod, method: currentPaymentMethod,
isSavedCreditCard, isSavedCreditCard,
smsEnable, smsEnable,
errorMessage, errorMessage,
}) })
}, },
[intl, methods, savedCreditCards, hotel] [intl, methods, savedCreditCards, hotelId]
) )
useEffect(() => { useEffect(() => {
@@ -226,7 +226,7 @@ export default function PaymentClient({
trackPaymentEvent({ trackPaymentEvent({
event: "paymentAttemptStart", event: "paymentAttemptStart",
hotelId: hotel, hotelId,
method: paymentMethod, method: paymentMethod,
isSavedCreditCard: !!savedCreditCard, isSavedCreditCard: !!savedCreditCard,
smsEnable: data.smsConfirmation, smsEnable: data.smsConfirmation,
@@ -234,7 +234,7 @@ export default function PaymentClient({
initiateBooking.mutate({ initiateBooking.mutate({
language: lang, language: lang,
hotelId: hotel, hotelId,
checkInDate: fromDate, checkInDate: fromDate,
checkOutDate: toDate, checkOutDate: toDate,
rooms: rooms.map((room) => ({ rooms: rooms.map((room) => ({
@@ -294,7 +294,7 @@ export default function PaymentClient({
savedCreditCards, savedCreditCards,
lang, lang,
initiateBooking, initiateBooking,
hotel, hotelId,
fromDate, fromDate,
toDate, toDate,
rooms, rooms,
@@ -347,7 +347,7 @@ export default function PaymentClient({
] ]
} }
cardNumber={savedCreditCard.truncatedNumber} cardNumber={savedCreditCard.truncatedNumber}
hotelId={hotel} hotelId={hotelId}
/> />
))} ))}
</div> </div>
@@ -364,7 +364,7 @@ export default function PaymentClient({
name="paymentMethod" name="paymentMethod"
value={PaymentMethodEnum.card} value={PaymentMethodEnum.card}
label={intl.formatMessage({ id: "Credit card" })} label={intl.formatMessage({ id: "Credit card" })}
hotelId={hotel} hotelId={hotelId}
/> />
{availablePaymentOptions.map((paymentMethod) => ( {availablePaymentOptions.map((paymentMethod) => (
<PaymentOption <PaymentOption
@@ -374,7 +374,7 @@ export default function PaymentClient({
label={ label={
PAYMENT_METHOD_TITLES[paymentMethod as PaymentMethodEnum] PAYMENT_METHOD_TITLES[paymentMethod as PaymentMethodEnum]
} }
hotelId={hotel} hotelId={hotelId}
/> />
))} ))}
</div> </div>

View File

@@ -9,6 +9,7 @@ import {
getFiltersFromHotels, getFiltersFromHotels,
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils" } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
import TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
import { getLang } from "@/i18n/serverContext"
import { safeTry } from "@/utils/safeTry" import { safeTry } from "@/utils/safeTry"
import { getHotelPins } from "../../HotelCardDialogListing/utils" import { getHotelPins } from "../../HotelCardDialogListing/utils"
@@ -33,19 +34,20 @@ function isValidHotelData(hotel: NullableHotelData): hotel is HotelData {
export async function SelectHotelMapContainer({ export async function SelectHotelMapContainer({
city, city,
searchParams, selectHotelParams,
adultsInRoom, adultsInRoom,
childrenInRoom, childrenInRoom,
child, child,
}: SelectHotelMapContainerProps) { }: SelectHotelMapContainerProps) {
const lang = getLang()
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
const fetchAvailableHotelsPromise = safeTry( const fetchAvailableHotelsPromise = safeTry(
fetchAvailableHotels({ fetchAvailableHotels({
cityId: city.id, cityId: city.id,
roomStayStartDate: searchParams.fromDate, roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: searchParams.toDate, roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom, adults: adultsInRoom,
children: childrenInRoom, children: childrenInRoom,
}) })
@@ -62,12 +64,12 @@ export async function SelectHotelMapContainer({
hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress }, hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress },
}) })
const arrivalDate = new Date(searchParams.fromDate) const arrivalDate = new Date(selectHotelParams.fromDate)
const departureDate = new Date(searchParams.toDate) const departureDate = new Date(selectHotelParams.toDate)
const pageTrackingData: TrackingSDKPageData = { const pageTrackingData: TrackingSDKPageData = {
pageId: "select-hotel", pageId: "select-hotel",
domainLanguage: searchParams.lang as Lang, domainLanguage: lang,
channel: TrackingChannelEnum["hotelreservation"], channel: TrackingChannelEnum["hotelreservation"],
pageName: "hotelreservation|select-hotel|mapview", pageName: "hotelreservation|select-hotel|mapview",
siteSections: "hotelreservation|select-hotel|mapview", siteSections: "hotelreservation|select-hotel|mapview",
@@ -77,7 +79,7 @@ export async function SelectHotelMapContainer({
const hotelsTrackingData: TrackingSDKHotelInfo = { const hotelsTrackingData: TrackingSDKHotelInfo = {
availableResults: validHotels.length, availableResults: validHotels.length,
searchTerm: searchParams.city, searchTerm: selectHotelParams.city,
arrivalDate: format(arrivalDate, "yyyy-MM-dd"), arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
departureDate: format(departureDate, "yyyy-MM-dd"), departureDate: format(departureDate, "yyyy-MM-dd"),
noOfAdults: adultsInRoom, noOfAdults: adultsInRoom,

View File

@@ -20,6 +20,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { safeTry } from "@/utils/safeTry" import { safeTry } from "@/utils/safeTry"
import { convertObjToSearchParams } from "@/utils/url"
import HotelCardListing from "../HotelCardListing" import HotelCardListing from "../HotelCardListing"
import HotelCount from "./HotelCount" import HotelCount from "./HotelCount"
@@ -46,7 +47,6 @@ export default async function SelectHotel({
}: SelectHotelProps) { }: SelectHotelProps) {
const { const {
selectHotelParams, selectHotelParams,
searchParams,
adultsInRoom, adultsInRoom,
childrenInRoom, childrenInRoom,
childrenInRoomArray, childrenInRoomArray,
@@ -57,8 +57,8 @@ export default async function SelectHotel({
const hotelsPromise = safeTry( const hotelsPromise = safeTry(
fetchAvailableHotels({ fetchAvailableHotels({
cityId: city.id, cityId: city.id,
roomStayStartDate: searchParams.fromDate, roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: searchParams.toDate, roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom, adults: adultsInRoom,
children: childrenInRoom, children: childrenInRoom,
}) })
@@ -66,8 +66,8 @@ export default async function SelectHotel({
const [hotels] = await hotelsPromise const [hotels] = await hotelsPromise
const arrivalDate = new Date(searchParams.fromDate) const arrivalDate = new Date(selectHotelParams.fromDate)
const departureDate = new Date(searchParams.toDate) const departureDate = new Date(selectHotelParams.toDate)
const isCityWithCountry = (city: any): city is { country: string } => const isCityWithCountry = (city: any): city is { country: string } =>
"country" in city "country" in city
@@ -76,6 +76,8 @@ export default async function SelectHotel({
hotels?.filter((hotel): hotel is HotelData => hotel !== null) || [] hotels?.filter((hotel): hotel is HotelData => hotel !== null) || []
const filterList = getFiltersFromHotels(validHotels) const filterList = getFiltersFromHotels(validHotels)
const searchParams = convertObjToSearchParams(selectHotelParams)
const breadcrumbs = [ const breadcrumbs = [
{ {
title: intl.formatMessage({ id: "Home" }), title: intl.formatMessage({ id: "Home" }),
@@ -89,7 +91,7 @@ export default async function SelectHotel({
}, },
{ {
title: intl.formatMessage({ id: "Select hotel" }), title: intl.formatMessage({ id: "Select hotel" }),
href: `${selectHotel(params.lang)}/?${selectHotelParams}`, href: `${selectHotel(params.lang)}/?${searchParams.toString()}`,
uid: "select-hotel", uid: "select-hotel",
}, },
{ {
@@ -112,7 +114,7 @@ export default async function SelectHotel({
const hotelsTrackingData: TrackingSDKHotelInfo = { const hotelsTrackingData: TrackingSDKHotelInfo = {
availableResults: validHotels.length, availableResults: validHotels.length,
searchTerm: searchParams.city, searchTerm: selectHotelParams.city,
arrivalDate: format(arrivalDate, "yyyy-MM-dd"), arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
departureDate: format(departureDate, "yyyy-MM-dd"), departureDate: format(departureDate, "yyyy-MM-dd"),
noOfAdults: adultsInRoom, noOfAdults: adultsInRoom,
@@ -158,13 +160,13 @@ export default async function SelectHotel({
> >
<div className={styles.mapContainer}> <div className={styles.mapContainer}>
<StaticMap <StaticMap
city={searchParams.city} city={selectHotelParams.city}
country={isCityWithCountry(city) ? city.country : undefined} country={isCityWithCountry(city) ? city.country : undefined}
width={340} width={340}
height={180} height={180}
zoomLevel={11} zoomLevel={11}
mapType="roadmap" mapType="roadmap"
altText={`Map of ${searchParams.city} city center`} altText={`Map of ${selectHotelParams.city} city center`}
/> />
<Button wrapping size="medium" intent="text" theme="base"> <Button wrapping size="medium" intent="text" theme="base">
{intl.formatMessage({ id: "See map" })} {intl.formatMessage({ id: "See map" })}
@@ -179,12 +181,12 @@ export default async function SelectHotel({
) : ( ) : (
<div className={styles.mapContainer}> <div className={styles.mapContainer}>
<StaticMap <StaticMap
city={searchParams.city} city={selectHotelParams.city}
width={340} width={340}
height={180} height={180}
zoomLevel={11} zoomLevel={11}
mapType="roadmap" mapType="roadmap"
altText={`Map of ${searchParams.city} city center`} altText={`Map of ${selectHotelParams.city} city center`}
/> />
</div> </div>
)} )}

View File

@@ -5,7 +5,7 @@ import Alert from "@/components/TempDesignSystem/Alert"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { safeTry } from "@/utils/safeTry" import { safeTry } from "@/utils/safeTry"
import { generateChildrenString } from "../RoomSelection/utils" import { generateChildrenString } from "../../utils"
import styles from "./NoRoomsAlert.module.css" import styles from "./NoRoomsAlert.module.css"

View File

@@ -8,14 +8,13 @@ import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts" import { useMediaQuery } from "usehooks-ts"
import { z } from "zod" import { z } from "zod"
import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
import { InfoCircleIcon } from "@/components/Icons" import { InfoCircleIcon } from "@/components/Icons"
import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox" import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import { Tooltip } from "@/components/TempDesignSystem/Tooltip" import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
import { getIconForFeatureCode } from "../utils"
import styles from "./roomFilter.module.css" import styles from "./roomFilter.module.css"
import { import {

View File

@@ -5,13 +5,13 @@ import { useIntl } from "react-intl"
import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek" import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek"
import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption" import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption"
import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
import { ErrorCircleIcon } from "@/components/Icons" import { ErrorCircleIcon } from "@/components/Icons"
import ImageGallery from "@/components/ImageGallery" import ImageGallery from "@/components/ImageGallery"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIconForFeatureCode } from "../../utils"
import { cardVariants } from "./cardVariants" import { cardVariants } from "./cardVariants"
import styles from "./roomCard.module.css" import styles from "./roomCard.module.css"

View File

@@ -2,9 +2,10 @@
import { usePathname, useRouter, useSearchParams } from "next/navigation" import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useMemo } from "react" import { useMemo } from "react"
import { convertObjToSearchParams } from "@/utils/url"
import RateSummary from "./RateSummary" import RateSummary from "./RateSummary"
import RoomCard from "./RoomCard" import RoomCard from "./RoomCard"
import { getHotelReservationQueryParams } from "./utils"
import styles from "./roomSelection.module.css" import styles from "./roomSelection.module.css"
@@ -26,28 +27,22 @@ export default function RoomSelection({
const { roomConfigurations, rateDefinitions } = roomsAvailability const { roomConfigurations, rateDefinitions } = roomsAvailability
const queryParams = useMemo(() => { const queryParams = useMemo(() => {
const params = new URLSearchParams(searchParams) // TODO: handle multiple rooms
const searchParamsObject = getHotelReservationQueryParams(searchParams) const newSearchParams = convertObjToSearchParams(
{
searchParamsObject.room.forEach((item, index) => { rooms: [
if (rateSummary?.roomTypeCode) { {
params.set(`room[${index}].roomtype`, rateSummary.roomTypeCode) roomTypeCode: rateSummary?.roomTypeCode,
} rateCode: rateSummary?.public.rateCode,
if (rateSummary?.public?.rateCode) { counterRateCode: rateSummary?.member?.rateCode,
params.set(`room[${index}].ratecode`, rateSummary.public.rateCode) packages: selectedPackages,
} },
if (rateSummary?.member?.rateCode) { ],
params.set( },
`room[${index}].counterratecode`, searchParams
rateSummary.member.rateCode
) )
}
selectedPackages.length > 0
? params.set(`room[${index}].packages`, selectedPackages.join(","))
: params.delete(`room[${index}].packages`)
})
return params return newSearchParams
}, [searchParams, rateSummary, selectedPackages]) }, [searchParams, rateSummary, selectedPackages])
function handleSubmit(e: React.FormEvent<HTMLFormElement>) { function handleSubmit(e: React.FormEvent<HTMLFormElement>) {

View File

@@ -1,113 +0,0 @@
import { ChildBedTypeEnum } from "@/constants/booking"
import { getFormattedUrlQueryParams } from "@/utils/url"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
import type { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type {
Child,
SelectRateSearchParams,
} from "@/types/components/hotelReservation/selectRate/selectRate"
export function getHotelReservationQueryParams(searchParams: URLSearchParams) {
return getFormattedUrlQueryParams(searchParams, {
adults: "number",
age: "number",
bed: ChildBedMapEnum,
}) as SelectRateSearchParams
}
export const bedTypeMap: Record<number, ChildBedTypeEnum> = {
[ChildBedMapEnum.IN_ADULTS_BED]: ChildBedTypeEnum.ParentsBed,
[ChildBedMapEnum.IN_CRIB]: ChildBedTypeEnum.Crib,
[ChildBedMapEnum.IN_EXTRA_BED]: ChildBedTypeEnum.ExtraBed,
[ChildBedMapEnum.UNKNOWN]: ChildBedTypeEnum.Unknown,
}
export const invertedBedTypeMap: Record<ChildBedTypeEnum, string> = {
[ChildBedTypeEnum.ParentsBed]: ChildBedMapEnum[ChildBedMapEnum.IN_ADULTS_BED],
[ChildBedTypeEnum.Crib]: ChildBedMapEnum[ChildBedMapEnum.IN_CRIB],
[ChildBedTypeEnum.ExtraBed]: ChildBedMapEnum[ChildBedMapEnum.IN_EXTRA_BED],
[ChildBedTypeEnum.Unknown]: ChildBedMapEnum[ChildBedMapEnum.UNKNOWN],
}
export function generateChildrenString(children: Child[]): string {
return `[${children
.map((child) => {
const age = child.age
const bedType = bedTypeMap[parseInt(child.bed.toString())]
return `${age}:${bedType}`
})
.join(",")}]`
}
export function getQueryParamsForEnterDetails(
searchParams: URLSearchParams
): BookingData {
const selectRoomParamsObject = getHotelReservationQueryParams(searchParams)
const { room } = selectRoomParamsObject
return {
fromDate: selectRoomParamsObject.fromDate,
toDate: selectRoomParamsObject.toDate,
hotel: selectRoomParamsObject.hotel,
rooms: room?.map((room) => ({
adults: room.adults, // TODO: Handle multiple rooms
children: room.child, // TODO: Handle multiple rooms and children
roomTypeCode: room.roomtype,
rateCode: room.ratecode,
packages: room.packages?.split(",") as RoomPackageCodeEnum[],
counterRateCode: room.counterratecode,
})),
}
}
export function createQueryParamsForEnterDetails(
bookingData: BookingData,
intitalSearchParams: URLSearchParams
) {
const { hotel, fromDate, toDate, rooms } = bookingData
const bookingSearchParams = new URLSearchParams({ hotel, fromDate, toDate })
const searchParams = new URLSearchParams([
...intitalSearchParams,
...bookingSearchParams,
])
searchParams.set(`hotel`, hotel)
rooms.forEach((item, index) => {
if (item?.adults) {
searchParams.set(`room[${index}].adults`, item.adults.toString())
}
if (item?.children) {
item.children.forEach((child, childIndex) => {
searchParams.set(
`room[${index}].child[${childIndex}].age`,
child.age.toString()
)
searchParams.set(
`room[${index}].child[${childIndex}].bed`,
child.bed.toString()
)
})
}
if (item?.roomTypeCode) {
searchParams.set(`room[${index}].roomtype`, item.roomTypeCode)
}
if (item?.rateCode) {
searchParams.set(`room[${index}].ratecode`, item.rateCode)
}
if (item?.counterRateCode) {
searchParams.set(`room[${index}].counterratecode`, item.counterRateCode)
}
if (item.packages && item.packages.length > 0) {
searchParams.set(`room[${index}].packages`, item.packages.join(","))
}
})
return searchParams
}

View File

@@ -9,7 +9,7 @@ import { auth } from "@/auth"
import { safeTry } from "@/utils/safeTry" import { safeTry } from "@/utils/safeTry"
import { isValidSession } from "@/utils/session" import { isValidSession } from "@/utils/session"
import { generateChildrenString } from "../RoomSelection/utils" import { generateChildrenString } from "../../utils"
import Rooms from "." import Rooms from "."
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"

View File

@@ -1,19 +0,0 @@
import { AllergyIcon, PetsIcon, WheelchairIcon } from "@/components/Icons"
import {
RoomPackageCodeEnum,
type RoomPackageCodes,
} from "@/types/components/hotelReservation/selectRate/roomFilter"
export function getIconForFeatureCode(featureCode: RoomPackageCodes) {
switch (featureCode) {
case RoomPackageCodeEnum.ACCESSIBILITY_ROOM:
return WheelchairIcon
case RoomPackageCodeEnum.ALLERGY_ROOM:
return AllergyIcon
case RoomPackageCodeEnum.PET_ROOM:
return PetsIcon
default:
return PetsIcon
}
}

View File

@@ -0,0 +1,47 @@
import { ChildBedTypeEnum } from "@/constants/booking"
import { AllergyIcon, PetsIcon, WheelchairIcon } from "@/components/Icons"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import {
RoomPackageCodeEnum,
type RoomPackageCodes,
} from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
export function getIconForFeatureCode(featureCode: RoomPackageCodes) {
switch (featureCode) {
case RoomPackageCodeEnum.ACCESSIBILITY_ROOM:
return WheelchairIcon
case RoomPackageCodeEnum.ALLERGY_ROOM:
return AllergyIcon
case RoomPackageCodeEnum.PET_ROOM:
return PetsIcon
default:
return PetsIcon
}
}
export const bedTypeMap: Record<number, ChildBedTypeEnum> = {
[ChildBedMapEnum.IN_ADULTS_BED]: ChildBedTypeEnum.ParentsBed,
[ChildBedMapEnum.IN_CRIB]: ChildBedTypeEnum.Crib,
[ChildBedMapEnum.IN_EXTRA_BED]: ChildBedTypeEnum.ExtraBed,
[ChildBedMapEnum.UNKNOWN]: ChildBedTypeEnum.Unknown,
}
export const invertedBedTypeMap: Record<ChildBedTypeEnum, string> = {
[ChildBedTypeEnum.ParentsBed]: ChildBedMapEnum[ChildBedMapEnum.IN_ADULTS_BED],
[ChildBedTypeEnum.Crib]: ChildBedMapEnum[ChildBedMapEnum.IN_CRIB],
[ChildBedTypeEnum.ExtraBed]: ChildBedMapEnum[ChildBedMapEnum.IN_EXTRA_BED],
[ChildBedTypeEnum.Unknown]: ChildBedMapEnum[ChildBedMapEnum.UNKNOWN],
}
export function generateChildrenString(children: Child[]): string {
return `[${children
.map((child) => {
const age = child.age
const bedType = bedTypeMap[parseInt(child.bed.toString())]
return `${age}:${bedType}`
})
.join(",")}]`
}

View File

@@ -23,14 +23,14 @@ export const middleware: NextMiddleware = (request) => {
const { searchParams } = url const { searchParams } = url
if ( if (
legacyDatePattern.test(searchParams.get("fromDate")!) || legacyDatePattern.test(searchParams.get("fromdate")!) ||
legacyDatePattern.test(searchParams.get("toDate")!) legacyDatePattern.test(searchParams.get("todate")!)
) { ) {
const fromDate = searchParams.get("fromDate")! const fromDate = searchParams.get("fromdate")!
url.searchParams.set("fromDate", normalizeDate(fromDate)) url.searchParams.set("fromdate", normalizeDate(fromDate))
const toDate = searchParams.get("toDate")! const toDate = searchParams.get("todate")!
url.searchParams.set("toDate", normalizeDate(toDate)) url.searchParams.set("todate", normalizeDate(toDate))
return NextResponse.redirect(url) return NextResponse.redirect(url)
} else { } else {
const headers = new Headers(request.headers) const headers = new Headers(request.headers)
@@ -43,5 +43,5 @@ export const middleware: NextMiddleware = (request) => {
export const matcher: MiddlewareMatcher = (request) => { export const matcher: MiddlewareMatcher = (request) => {
const { searchParams } = request.nextUrl const { searchParams } = request.nextUrl
return searchParams.has("fromDate") || searchParams.has("toDate") return searchParams.has("fromdate") || searchParams.has("todate")
} }

View File

@@ -112,14 +112,14 @@ const nextConfig = {
}, },
{ {
// ---------------------------------------- // ----------------------------------------
// fromDate param missing // fromdate param missing
// ---------------------------------------- // ----------------------------------------
source: source:
"/:lang/hotelreservation/(select-bed|breakfast|details|payment)", "/:lang/hotelreservation/(select-bed|breakfast|details|payment)",
destination: "/:lang/hotelreservation/select-rate", destination: "/:lang/hotelreservation/select-rate",
missing: [ missing: [
{ {
key: "fromDate", key: "fromdate",
type: "query", type: "query",
value: undefined, value: undefined,
}, },
@@ -128,14 +128,14 @@ const nextConfig = {
}, },
{ {
// ---------------------------------------- // ----------------------------------------
// fromDate param has to be a date // fromdate param has to be a date
// ---------------------------------------- // ----------------------------------------
source: source:
"/:lang/hotelreservation/(select-bed|breakfast|details|payment)", "/:lang/hotelreservation/(select-bed|breakfast|details|payment)",
destination: "/:lang/hotelreservation/select-rate", destination: "/:lang/hotelreservation/select-rate",
missing: [ missing: [
{ {
key: "fromDate", key: "fromdate",
type: "query", type: "query",
value: "^([12]\\d{3}-(0[1-9]|1[0-2])-(0?[1-9]|[12]\\d|3[01]))$", value: "^([12]\\d{3}-(0[1-9]|1[0-2])-(0?[1-9]|[12]\\d|3[01]))$",
}, },
@@ -144,14 +144,14 @@ const nextConfig = {
}, },
{ {
// ---------------------------------------- // ----------------------------------------
// toDate param missing // todate param missing
// ---------------------------------------- // ----------------------------------------
source: source:
"/:lang/hotelreservation/(select-bed|breakfast|details|payment)", "/:lang/hotelreservation/(select-bed|breakfast|details|payment)",
destination: "/:lang/hotelreservation/select-rate", destination: "/:lang/hotelreservation/select-rate",
missing: [ missing: [
{ {
key: "toDate", key: "todate",
type: "query", type: "query",
value: undefined, value: undefined,
}, },
@@ -160,14 +160,14 @@ const nextConfig = {
}, },
{ {
// ---------------------------------------- // ----------------------------------------
// toDate param has to be a date // todate param has to be a date
// ---------------------------------------- // ----------------------------------------
source: source:
"/:lang/hotelreservation/(select-bed|breakfast|details|payment)", "/:lang/hotelreservation/(select-bed|breakfast|details|payment)",
destination: "/:lang/hotelreservation/select-rate", destination: "/:lang/hotelreservation/select-rate",
missing: [ missing: [
{ {
key: "toDate", key: "todate",
type: "query", type: "query",
value: "^([12]\\d{3}-(0[1-9]|1[0-2])-(0?[1-9]|[12]\\d|3[01]))$", value: "^([12]\\d{3}-(0[1-9]|1[0-2])-(0?[1-9]|[12]\\d|3[01]))$",
}, },

View File

@@ -8,15 +8,13 @@ import { arrayMerge } from "@/utils/merge"
import { detailsStorageName } from "." import { detailsStorageName } from "."
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import { CurrencyEnum } from "@/types/enums/currency" import { CurrencyEnum } from "@/types/enums/currency"
import type { StepEnum } from "@/types/enums/step" import type { StepEnum } from "@/types/enums/step"
import type { import type {
DetailsState, DetailsState,
PersistedState, PersistedState,
PersistedStatePart, PersistedStatePart,
Price,
RoomPrice,
RoomRate, RoomRate,
} from "@/types/stores/enter-details" } from "@/types/stores/enter-details"
import type { SafeUser } from "@/types/user" import type { SafeUser } from "@/types/user"
@@ -55,7 +53,10 @@ export function navigate(step: StepEnum, searchParams: string) {
window.history.pushState({ step }, "", `${step}?${searchParams}`) window.history.pushState({ step }, "", `${step}?${searchParams}`)
} }
export function checkIsSameRoom(prev: BookingData, next: BookingData) { export function checkIsSameRoom(
prev: SelectRateSearchParams,
next: SelectRateSearchParams
) {
const { rooms: prevRooms, ...prevBooking } = prev const { rooms: prevRooms, ...prevBooking } = prev
const prevRoomsWithoutRateCodes = prevRooms.map( const prevRoomsWithoutRateCodes = prevRooms.map(

View File

@@ -29,7 +29,7 @@ jest.mock("@/lib/api", () => ({
})) }))
const booking = { const booking = {
hotel: "123", hotelId: "123",
fromDate: "2100-01-01", fromDate: "2100-01-01",
toDate: "2100-01-02", toDate: "2100-01-02",
rooms: [ rooms: [

View File

@@ -1,17 +1,11 @@
import type { Child, Room } from "../hotelReservation/selectRate/selectRate"
export type ChildBed = { export type ChildBed = {
label: string label: string
value: number value: number
} }
export type Child = { export type TGuestsRoom = Required<Pick<Room, "adults" | "children">>
age: number
bed: number
}
export type TGuestsRoom = {
adults: number
child: Child[]
}
export type GuestsRoomPickerProps = { export type GuestsRoomPickerProps = {
index: number index: number

View File

@@ -1,6 +1,7 @@
import type { VariantProps } from "class-variance-authority" import type { VariantProps } from "class-variance-authority"
import type { z } from "zod" import type { z } from "zod"
import type { SearchParams } from "@/types/params"
import type { Locations } from "@/types/trpc/routers/hotel/locations" import type { Locations } from "@/types/trpc/routers/hotel/locations"
import type { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" import type { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
import type { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants" import type { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants"
@@ -8,12 +9,12 @@ import type { TGuestsRoom } from "./guestsRoomsPicker"
export type BookingWidgetSchema = z.output<typeof bookingWidgetSchema> export type BookingWidgetSchema = z.output<typeof bookingWidgetSchema>
export type BookingWidgetSearchParams = { export type BookingWidgetSearchData = {
city?: string city?: string
hotel?: string hotel?: string
fromDate?: string fromDate?: string
toDate?: string toDate?: string
room?: TGuestsRoom[] rooms?: TGuestsRoom[]
} }
export type BookingWidgetType = VariantProps< export type BookingWidgetType = VariantProps<
@@ -22,13 +23,13 @@ export type BookingWidgetType = VariantProps<
export interface BookingWidgetProps { export interface BookingWidgetProps {
type?: BookingWidgetType type?: BookingWidgetType
searchParams?: URLSearchParams bookingWidgetSearchParams: SearchParams<BookingWidgetSearchData>["searchParams"]
} }
export interface BookingWidgetClientProps { export interface BookingWidgetClientProps {
locations: Locations locations: Locations
type?: BookingWidgetType type?: BookingWidgetType
searchParams?: URLSearchParams bookingWidgetSearchParams: SearchParams<BookingWidgetSearchData>["searchParams"]
} }
export interface BookingWidgetToggleButtonProps { export interface BookingWidgetToggleButtonProps {

View File

@@ -1,17 +0,0 @@
import type { RoomPackageCodeEnum } from "../selectRate/roomFilter"
import type { Child } from "../selectRate/selectRate"
interface Room {
adults: number
roomTypeCode: string
rateCode: string
counterRateCode?: string
children?: Child[]
packages?: RoomPackageCodeEnum[]
}
export interface BookingData {
hotel: string
fromDate: string
toDate: string
rooms: Room[]
}

View File

@@ -3,7 +3,7 @@ import type { z } from "zod"
import type { Coordinates } from "@/types/components/maps/coordinates" import type { Coordinates } from "@/types/components/maps/coordinates"
import type { Location } from "@/types/trpc/routers/hotel/locations" import type { Location } from "@/types/trpc/routers/hotel/locations"
import type { imageSchema } from "@/server/routers/hotels/schemas/image" import type { imageSchema } from "@/server/routers/hotels/schemas/image"
import type { Child } from "../../bookingWidget/guestsRoomsPicker" import type { Child } from "../selectRate/selectRate"
import type { HotelData } from "./hotelCardListingProps" import type { HotelData } from "./hotelCardListingProps"
import type { CategorizedFilters, Filter } from "./hotelFilters" import type { CategorizedFilters, Filter } from "./hotelFilters"
import type { SelectHotelSearchParams } from "./selectHotelSearchParams" import type { SelectHotelSearchParams } from "./selectHotelSearchParams"
@@ -66,7 +66,7 @@ export interface HotelCardDialogListingProps {
export type SelectHotelMapContainerProps = { export type SelectHotelMapContainerProps = {
city: Location city: Location
searchParams: SelectHotelSearchParams selectHotelParams: SelectHotelSearchParams
adultsInRoom: number adultsInRoom: number
childrenInRoom: string | undefined childrenInRoom: string | undefined
child: Child[] | undefined child: Child[] | undefined

View File

@@ -46,8 +46,7 @@ export interface SelectHotelProps {
lang: Lang lang: Lang
} }
reservationParams: { reservationParams: {
selectHotelParams: URLSearchParams | undefined selectHotelParams: SelectHotelSearchParams
searchParams: SelectHotelSearchParams
adultsInRoom: number adultsInRoom: number
childrenInRoom: string | undefined childrenInRoom: string | undefined
childrenInRoomArray: Child[] | undefined childrenInRoomArray: Child[] | undefined

View File

@@ -1,17 +1,9 @@
interface Child { import type { Room } from "../selectRate/selectRate"
bed: string
age: number
}
interface Room {
adults: number
child?: Child[]
}
export interface SelectHotelSearchParams { export interface SelectHotelSearchParams {
city: string city: string
fromDate: string fromDate: string
toDate: string toDate: string
room: Room[] rooms: Pick<Room, "adults" | "children">[]
[key: string]: string | string[] | Room[] [key: string]: string | string[] | Pick<Room, "adults" | "children">[]
} }

View File

@@ -1,26 +1,27 @@
import type { Product, RoomConfiguration } from "@/server/routers/hotels/output" import type { Product, RoomConfiguration } from "@/server/routers/hotels/output"
import type { ChildBedMapEnum } from "../../bookingWidget/enums" import type { ChildBedMapEnum } from "../../bookingWidget/enums"
import type { RoomPackageCodeEnum } from "./roomFilter"
export interface Child { export interface Child {
bed: ChildBedMapEnum bed: ChildBedMapEnum
age: number age: number
} }
interface Room { export interface Room {
adults: number adults: number
roomtype: string roomTypeCode: string
ratecode: string rateCode: string
counterratecode: string counterRateCode: string
child?: Child[] children?: Child[]
packages?: string packages?: RoomPackageCodeEnum[]
} }
export interface SelectRateSearchParams { export interface SelectRateSearchParams {
hotel: string city?: string
hotelId: string
fromDate: string fromDate: string
toDate: string toDate: string
room: Room[] rooms: Room[]
[key: string]: string | string[] | Room[]
} }
export interface Rate { export interface Rate {

View File

@@ -1,13 +1,12 @@
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType" import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
import type { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
import type { StepEnum } from "@/types/enums/step" import type { StepEnum } from "@/types/enums/step"
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability" import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
import type { SafeUser } from "@/types/user" import type { SafeUser } from "@/types/user"
import type { SelectRateSearchParams } from "../components/hotelReservation/selectRate/selectRate"
import type { Packages } from "../requests/packages" import type { Packages } from "../requests/packages"
export interface DetailsProviderProps extends React.PropsWithChildren { export interface DetailsProviderProps extends React.PropsWithChildren {
booking: BookingData booking: SelectRateSearchParams
bedTypes: BedTypeSelection[] bedTypes: BedTypeSelection[]
showBreakfastStep: boolean showBreakfastStep: boolean
packages: Packages | null packages: Packages | null

View File

@@ -1,11 +1,11 @@
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType" import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
import type { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast" import type { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
import type { import type {
DetailsSchema, DetailsSchema,
SignedInDetailsSchema, SignedInDetailsSchema,
} from "@/types/components/hotelReservation/enterDetails/details" } from "@/types/components/hotelReservation/enterDetails/details"
import type { StepEnum } from "@/types/enums/step" import type { StepEnum } from "@/types/enums/step"
import type { SelectRateSearchParams } from "../components/hotelReservation/selectRate/selectRate"
import type { DetailsProviderProps } from "../providers/enter-details" import type { DetailsProviderProps } from "../providers/enter-details"
import type { Packages } from "../requests/packages" import type { Packages } from "../requests/packages"
@@ -26,7 +26,7 @@ export interface Price {
export interface FormValues { export interface FormValues {
bedType: BedTypeSchema | undefined bedType: BedTypeSchema | undefined
booking: BookingData booking: SelectRateSearchParams
breakfast: BreakfastPackage | false | undefined breakfast: BreakfastPackage | false | undefined
guest: DetailsSchema | SignedInDetailsSchema guest: DetailsSchema | SignedInDetailsSchema
} }
@@ -46,7 +46,7 @@ export interface DetailsState {
updateSeachParamString: (searchParamString: string) => void updateSeachParamString: (searchParamString: string) => void
} }
bedType: BedTypeSchema | undefined bedType: BedTypeSchema | undefined
booking: BookingData booking: SelectRateSearchParams
breakfast: BreakfastPackage | false | undefined breakfast: BreakfastPackage | false | undefined
currentStep: StepEnum currentStep: StepEnum
formValues: FormValues formValues: FormValues

View File

@@ -1,3 +1,9 @@
import type { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type {
Child,
Room,
} from "@/types/components/hotelReservation/selectRate/selectRate"
export function removeMultipleSlashes(pathname: string) { export function removeMultipleSlashes(pathname: string) {
return pathname.replaceAll(/\/\/+/g, "/") return pathname.replaceAll(/\/\/+/g, "/")
} }
@@ -10,35 +16,142 @@ export function removeTrailingSlash(pathname: string) {
return pathname return pathname
} }
export function getFormattedUrlQueryParams( type PartialRoom = { rooms?: Partial<Room>[] }
searchParams: URLSearchParams,
dataTypes: Record<string, unknown> const keyedSearchParams = new Map([
["room", "rooms"],
["ratecode", "rateCode"],
["counterratecode", "counterRateCode"],
["roomtype", "roomTypeCode"],
["fromdate", "fromDate"],
["todate", "toDate"],
["hotel", "hotelId"],
["child", "children"],
])
export function getKeyFromSearchParam(key: string): string {
return keyedSearchParams.get(key) || key
}
export function getSearchParamFromKey(key: string): string {
for (const [mapKey, mapValue] of keyedSearchParams.entries()) {
if (mapValue === key) {
return mapKey
}
}
return key
}
export function convertSearchParamsToObj<T extends PartialRoom>(
searchParams: Record<string, string>
) { ) {
const searchParamsObject: Record<string, unknown> = Array.from( const searchParamsObject = Object.entries(searchParams).reduce<
searchParams.entries() T & PartialRoom
).reduce<Record<string, unknown>>( >((acc, [key, value]) => {
(acc, [key, value]) => { // The params are sometimes indexed with a number (for ex: `room[0].adults`),
const keys = key.replace(/\]/g, "").split(/\[|\./) // Split keys by '[' or '.' // so we need to split them by . or []
keys.reduce((nestedAcc, k, i) => { const keys = key.replace(/\]/g, "").split(/\[|\./)
if (i === keys.length - 1) { const firstKey = getKeyFromSearchParam(keys[0]) as keyof T
if (dataTypes[k] == "number") {
;(nestedAcc as Record<string, unknown>)[k] = Number(value) // Room is a special case since it is an array, so we need to handle it separately
} else if (dataTypes[k] == "boolean") { if (firstKey === "rooms") {
;(nestedAcc as Record<string, unknown>)[k] = // Rooms are always indexed with a number, so we need to extract the index
value.toLowerCase() === "true" const index = Number(keys[1])
const roomObject =
acc.rooms && Array.isArray(acc.rooms) ? acc.rooms : (acc.rooms = [])
const roomObjectKey = getKeyFromSearchParam(keys[2]) as keyof Room
if (!roomObject[index]) {
roomObject[index] = {}
}
// Adults should be converted to a number
if (roomObjectKey === "adults") {
roomObject[index].adults = Number(value)
// Child is an array, so we need to handle it separately
} else if (roomObjectKey === "children") {
const childIndex = Number(keys[3])
const childKey = keys[4] as keyof Child
if (
!("children" in roomObject[index]) ||
!Array.isArray(roomObject[index].children)
) {
roomObject[index].children = []
}
roomObject[index].children![childIndex] = {
...roomObject[index].children![childIndex],
[childKey]: Number(value),
}
} else if (roomObjectKey === "packages") {
roomObject[index].packages = value.split(",") as RoomPackageCodeEnum[]
} else { } else {
;(nestedAcc as Record<string, unknown>)[k] = value roomObject[index][roomObjectKey] = value
} }
} else { } else {
if (!nestedAcc[k]) { acc[firstKey] = value as T[keyof T]
nestedAcc[k] = isNaN(Number(keys[i + 1])) ? {} : [] // Initialize as object or array
} }
}
return nestedAcc[k] as Record<string, unknown>
}, acc)
return acc return acc
}, }, {} as T)
{} as Record<string, unknown>
)
return searchParamsObject return searchParamsObject
} }
export function convertObjToSearchParams<T>(
bookingData: T & PartialRoom,
intitalSearchParams = {} as URLSearchParams
) {
const bookingSearchParams = new URLSearchParams(intitalSearchParams)
Object.entries(bookingData).forEach(([key, value]) => {
if (key === "rooms") {
value.forEach((item, index) => {
if (item?.adults) {
bookingSearchParams.set(
`room[${index}].adults`,
item.adults.toString()
)
}
if (item?.children) {
item.children.forEach((child, childIndex) => {
bookingSearchParams.set(
`room[${index}].child[${childIndex}].age`,
child.age.toString()
)
bookingSearchParams.set(
`room[${index}].child[${childIndex}].bed`,
child.bed.toString()
)
})
}
if (item?.roomTypeCode) {
bookingSearchParams.set(`room[${index}].roomtype`, item.roomTypeCode)
}
if (item?.rateCode) {
bookingSearchParams.set(`room[${index}].ratecode`, item.rateCode)
}
if (item?.counterRateCode) {
bookingSearchParams.set(
`room[${index}].counterratecode`,
item.counterRateCode
)
}
if (item.packages && item.packages.length > 0) {
bookingSearchParams.set(
`room[${index}].packages`,
item.packages.join(",")
)
}
})
} else {
bookingSearchParams.set(getSearchParamFromKey(key), value.toString())
}
})
return bookingSearchParams
}