Merged in fix/refactor-booking-flow-search-params (pull request #2148)
Fix: refactor booking flow search params * wip: apply codemod and upgrade swc plugin * wip: design-system to react 19, fix issues from async (search)params * Prepare new parse function for booking flow search params * Prepare serialize function for booking flow search params * Improve handling of comma separated arrays * Slightly refactor for readability * Next abstracts URLSearchParams so handle the abstraction instead * Refactor booking widget to use new search params parsing * Rename search param functions * Refactor select-hotel to use new search param parser * Use new search params parser in select-rate and details * Fix hotelId type * Avoid passing down search params into BookingWidget components * More updates to use new types instead of SearchParams<T> * Remove types SelectHotelSearchParams and AlternativeSelectHotelSearchParams * Fix parseBookingWidgetSearchParams return type * Add error handling to booking search param parsers * Fix modifyRateIndex handling in details page * Clean up * Refactor booking widget search param serializing to util function * Move start page booking widget search param parsing to page * Use new search param serializer in HandleErrorCallback * Delete convertSearchParamsToObj & convertObjToSearchParams Approved-by: Michael Zetterberg
This commit is contained in:
@@ -3,19 +3,21 @@ import { notFound } from "next/navigation"
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import StartPage from "@/components/ContentType/StartPage"
|
import StartPage from "@/components/ContentType/StartPage"
|
||||||
|
import { parseBookingWidgetSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export { generateMetadata } from "@/utils/generateMetadata"
|
export { generateMetadata } from "@/utils/generateMetadata"
|
||||||
|
|
||||||
export default async function StartPagePage(
|
export default async function StartPagePage(
|
||||||
props: PageArgs<{}, BookingWidgetSearchData>
|
props: PageArgs<{}, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
|
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
return <StartPage searchParams={searchParams} />
|
const booking = parseBookingWidgetSearchParams(searchParams)
|
||||||
|
|
||||||
|
return <StartPage booking={booking} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
|
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
|
||||||
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
|
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
|
||||||
import { MapContainer } from "@/components/MapContainer"
|
import { MapContainer } from "@/components/MapContainer"
|
||||||
import { convertSearchParamsToObj } from "@/utils/url"
|
import { parseSelectHotelSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import type { AlternativeHotelsSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function SelectHotelMapPage(
|
export default async function SelectHotelMapPage(
|
||||||
props: PageArgs<LangParams, AlternativeHotelsSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
|
|
||||||
const booking =
|
const booking = parseSelectHotelSearchParams(searchParams)
|
||||||
convertSearchParamsToObj<AlternativeHotelsSearchParams>(searchParams)
|
|
||||||
|
if (!booking) return notFound()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.main}>
|
<div className={styles.main}>
|
||||||
|
|||||||
@@ -13,23 +13,19 @@ import { getTracking } from "@/components/HotelReservation/SelectHotel/tracking"
|
|||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
|
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
|
||||||
import { convertSearchParamsToObj } from "@/utils/url"
|
import { parseSelectHotelSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type {
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
AlternativeHotelsSearchParams,
|
|
||||||
SelectHotelSearchParams,
|
|
||||||
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
|
||||||
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function AlternativeHotelsPage(
|
export default async function AlternativeHotelsPage(
|
||||||
props: PageArgs<LangParams, AlternativeHotelsSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const params = await props.params
|
const params = await props.params
|
||||||
|
|
||||||
const booking =
|
const booking = parseSelectHotelSearchParams(searchParams)
|
||||||
convertSearchParamsToObj<AlternativeHotelsSearchParams>(searchParams)
|
|
||||||
|
if (!booking) return notFound()
|
||||||
|
|
||||||
const searchDetails = await getHotelSearchDetails(booking, true)
|
const searchDetails = await getHotelSearchDetails(booking, true)
|
||||||
|
|
||||||
@@ -42,10 +38,10 @@ export default async function AlternativeHotelsPage(
|
|||||||
bookingCode,
|
bookingCode,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
city,
|
city,
|
||||||
|
cityName,
|
||||||
hotel: isAlternativeFor,
|
hotel: isAlternativeFor,
|
||||||
noOfRooms,
|
noOfRooms,
|
||||||
redemption,
|
redemption,
|
||||||
selectHotelParams,
|
|
||||||
} = searchDetails
|
} = searchDetails
|
||||||
|
|
||||||
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
|
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
|
||||||
@@ -59,16 +55,18 @@ export default async function AlternativeHotelsPage(
|
|||||||
|
|
||||||
// TODO: This needs to be refactored into its
|
// TODO: This needs to be refactored into its
|
||||||
// own functions
|
// own functions
|
||||||
const hotels = await getHotels(
|
const hotels = await getHotels({
|
||||||
selectHotelParams,
|
fromDate: booking.fromDate,
|
||||||
|
toDate: booking.toDate,
|
||||||
|
rooms: booking.rooms,
|
||||||
isAlternativeFor,
|
isAlternativeFor,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
city,
|
city,
|
||||||
!!redemption
|
redemption: !!redemption,
|
||||||
)
|
})
|
||||||
|
|
||||||
const arrivalDate = new Date(selectHotelParams.fromDate)
|
const arrivalDate = new Date(booking.fromDate)
|
||||||
const departureDate = new Date(selectHotelParams.toDate)
|
const departureDate = new Date(booking.toDate)
|
||||||
|
|
||||||
const isRedemptionAvailability = redemption
|
const isRedemptionAvailability = redemption
|
||||||
? hotels.some(
|
? hotels.some(
|
||||||
@@ -92,11 +90,11 @@ export default async function AlternativeHotelsPage(
|
|||||||
adultsInRoom,
|
adultsInRoom,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
hotels?.length ?? 0,
|
hotels?.length ?? 0,
|
||||||
selectHotelParams.hotelId,
|
booking.hotelId,
|
||||||
noOfRooms,
|
noOfRooms,
|
||||||
hotels?.[0]?.hotel.address.country,
|
hotels?.[0]?.hotel.address.country,
|
||||||
hotels?.[0]?.hotel.address.city,
|
hotels?.[0]?.hotel.address.city,
|
||||||
selectHotelParams.city,
|
cityName,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
isBookingCodeRateAvailable,
|
isBookingCodeRateAvailable,
|
||||||
redemption,
|
redemption,
|
||||||
|
|||||||
@@ -20,15 +20,14 @@ import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDeta
|
|||||||
import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert"
|
import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert"
|
||||||
import RoomProvider from "@/providers/Details/RoomProvider"
|
import RoomProvider from "@/providers/Details/RoomProvider"
|
||||||
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
||||||
import { convertSearchParamsToObj } from "@/utils/url"
|
import { parseDetailsSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function DetailsPage(
|
export default async function DetailsPage(
|
||||||
props: PageArgs<LangParams, SelectRateSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const params = await props.params
|
const params = await props.params
|
||||||
@@ -37,11 +36,12 @@ export default async function DetailsPage(
|
|||||||
|
|
||||||
const selectRoomParams = new URLSearchParams(searchParams)
|
const selectRoomParams = new URLSearchParams(searchParams)
|
||||||
|
|
||||||
const { errorCode, ...booking } =
|
const booking = parseDetailsSearchParams(searchParams)
|
||||||
convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
|
|
||||||
if ("modifyRateIndex" in booking) {
|
if (!booking) return notFound()
|
||||||
|
|
||||||
|
if (selectRoomParams.has("modifyRateIndex")) {
|
||||||
selectRoomParams.delete("modifyRateIndex")
|
selectRoomParams.delete("modifyRateIndex")
|
||||||
delete booking.modifyRateIndex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import stringify from "json-stable-stringify-without-jsonify"
|
import stringify from "json-stable-stringify-without-jsonify"
|
||||||
|
import { notFound } from "next/navigation"
|
||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
|
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
|
||||||
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
|
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
|
||||||
import { MapContainer } from "@/components/MapContainer"
|
import { MapContainer } from "@/components/MapContainer"
|
||||||
import { convertSearchParamsToObj } from "@/utils/url"
|
import { parseSelectHotelSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function SelectHotelMapPage(
|
export default async function SelectHotelMapPage(
|
||||||
props: PageArgs<LangParams, SelectHotelSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const suspenseKey = stringify(searchParams)
|
const suspenseKey = stringify(searchParams)
|
||||||
|
|
||||||
const booking =
|
const booking = parseSelectHotelSearchParams(searchParams)
|
||||||
convertSearchParamsToObj<SelectHotelSearchParams>(searchParams)
|
|
||||||
|
if (!booking) return notFound()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.main}>
|
<div className={styles.main}>
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ import { getHotels } from "@/components/HotelReservation/SelectHotel/helpers"
|
|||||||
import { getTracking } from "@/components/HotelReservation/SelectHotel/tracking"
|
import { getTracking } from "@/components/HotelReservation/SelectHotel/tracking"
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
|
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
|
||||||
import { convertSearchParamsToObj } from "@/utils/url"
|
import { parseSelectHotelSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function SelectHotelPage(
|
export default async function SelectHotelPage(
|
||||||
props: PageArgs<LangParams, SelectHotelSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const params = await props.params
|
const params = await props.params
|
||||||
|
|
||||||
const booking =
|
const booking = parseSelectHotelSearchParams(searchParams)
|
||||||
convertSearchParamsToObj<SelectHotelSearchParams>(searchParams)
|
|
||||||
|
if (!booking) return notFound()
|
||||||
|
|
||||||
const searchDetails = await getHotelSearchDetails(booking)
|
const searchDetails = await getHotelSearchDetails(booking)
|
||||||
|
|
||||||
@@ -35,9 +35,9 @@ export default async function SelectHotelPage(
|
|||||||
bookingCode,
|
bookingCode,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
city,
|
city,
|
||||||
|
cityName,
|
||||||
noOfRooms,
|
noOfRooms,
|
||||||
redemption,
|
redemption,
|
||||||
selectHotelParams,
|
|
||||||
} = searchDetails
|
} = searchDetails
|
||||||
|
|
||||||
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
|
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
|
||||||
@@ -49,16 +49,18 @@ export default async function SelectHotelPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hotels = await getHotels(
|
const hotels = await getHotels({
|
||||||
selectHotelParams,
|
fromDate: booking.fromDate,
|
||||||
null,
|
toDate: booking.toDate,
|
||||||
|
rooms: booking.rooms,
|
||||||
|
isAlternativeFor: null,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
city,
|
city,
|
||||||
!!redemption
|
redemption: !!redemption,
|
||||||
)
|
})
|
||||||
|
|
||||||
const arrivalDate = new Date(selectHotelParams.fromDate)
|
const arrivalDate = new Date(booking.fromDate)
|
||||||
const departureDate = new Date(selectHotelParams.toDate)
|
const departureDate = new Date(booking.toDate)
|
||||||
|
|
||||||
const isRedemptionAvailability = redemption
|
const isRedemptionAvailability = redemption
|
||||||
? hotels.some(
|
? hotels.some(
|
||||||
@@ -82,11 +84,11 @@ export default async function SelectHotelPage(
|
|||||||
adultsInRoom,
|
adultsInRoom,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
hotels?.length ?? 0,
|
hotels?.length ?? 0,
|
||||||
selectHotelParams.hotelId,
|
booking.hotelId,
|
||||||
noOfRooms,
|
noOfRooms,
|
||||||
hotels?.[0]?.hotel.address.country,
|
hotels?.[0]?.hotel.address.country,
|
||||||
hotels?.[0]?.hotel.address.city,
|
hotels?.[0]?.hotel.address.city,
|
||||||
selectHotelParams.city,
|
cityName,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
isBookingCodeRateAvailable,
|
isBookingCodeRateAvailable,
|
||||||
redemption,
|
redemption,
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import { notFound } from "next/navigation"
|
|||||||
import { combineRegExps, rateTypeRegex, REDEMPTION } from "@/constants/booking"
|
import { combineRegExps, rateTypeRegex, REDEMPTION } from "@/constants/booking"
|
||||||
|
|
||||||
import SelectRate from "@/components/HotelReservation/SelectRate"
|
import SelectRate from "@/components/HotelReservation/SelectRate"
|
||||||
import { convertSearchParamsToObj } from "@/utils/url"
|
import { parseSelectRateSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
const singleRoomRateTypes = combineRegExps(
|
const singleRoomRateTypes = combineRegExps(
|
||||||
[rateTypeRegex.ARB, rateTypeRegex.VOUCHER],
|
[rateTypeRegex.ARB, rateTypeRegex.VOUCHER],
|
||||||
@@ -14,11 +13,13 @@ const singleRoomRateTypes = combineRegExps(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export default async function SelectRatePage(
|
export default async function SelectRatePage(
|
||||||
props: PageArgs<LangParams & { section: string }, SelectRateSearchParams>
|
props: PageArgs<LangParams & { section: string }, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const params = await props.params
|
const params = await props.params
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
|
const booking = parseSelectRateSearchParams(searchParams)
|
||||||
|
|
||||||
|
if (!booking) return notFound()
|
||||||
|
|
||||||
const isMultiRoom = booking.rooms.length > 1
|
const isMultiRoom = booking.rooms.length > 1
|
||||||
const isRedemption = booking.searchType === REDEMPTION
|
const isRedemption = booking.searchType === REDEMPTION
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { env } from "@/env/server"
|
|||||||
import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests"
|
import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import { BookingWidget } from "@/components/BookingWidget"
|
import { BookingWidget } from "@/components/BookingWidget"
|
||||||
|
import { parseBookingWidgetSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function BookingWidgetDestinationCityPage(props: PageArgs<{}, BookingWidgetSearchData>) {
|
export default async function BookingWidgetDestinationCityPage(
|
||||||
const searchParams = await props.searchParams;
|
props: PageArgs<{}, NextSearchParams>
|
||||||
|
) {
|
||||||
|
const searchParams = await props.searchParams
|
||||||
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
|
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -20,5 +22,7 @@ export default async function BookingWidgetDestinationCityPage(props: PageArgs<{
|
|||||||
city: pageData?.city.name ?? "",
|
city: pageData?.city.name ?? "",
|
||||||
}
|
}
|
||||||
|
|
||||||
return <BookingWidget bookingWidgetSearchParams={bookingWidgetSearchParams} />
|
const booking = parseBookingWidgetSearchParams(bookingWidgetSearchParams)
|
||||||
|
|
||||||
|
return <BookingWidget booking={booking} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,18 @@ import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
|||||||
|
|
||||||
import { BookingWidget } from "@/components/BookingWidget"
|
import { BookingWidget } from "@/components/BookingWidget"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
import { parseBookingWidgetSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function BookingWidgetHotelPage(
|
export default async function BookingWidgetHotelPage(
|
||||||
props: PageArgs<{}, BookingWidgetSearchData & { subpage?: string }>
|
props: PageArgs<{}, NextSearchParams & { subpage?: string }>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
|
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bookingCode, subpage } = searchParams
|
|
||||||
|
|
||||||
const hotelPageData = await getHotelPage()
|
const hotelPageData = await getHotelPage()
|
||||||
const hotelData = await getHotel({
|
const hotelData = await getHotel({
|
||||||
hotelId: hotelPageData?.hotel_page_id || "",
|
hotelId: hotelPageData?.hotel_page_id || "",
|
||||||
@@ -24,6 +22,7 @@ export default async function BookingWidgetHotelPage(
|
|||||||
isCardOnlyPayment: false,
|
isCardOnlyPayment: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const subpage = searchParams.subpage
|
||||||
const isMeetingSubpage =
|
const isMeetingSubpage =
|
||||||
subpage && hotelData?.additionalData.meetingRooms.nameInUrl === subpage
|
subpage && hotelData?.additionalData.meetingRooms.nameInUrl === subpage
|
||||||
|
|
||||||
@@ -32,10 +31,12 @@ export default async function BookingWidgetHotelPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bookingWidgetSearchParams = {
|
const bookingWidgetSearchParams = {
|
||||||
bookingCode: bookingCode ?? "",
|
bookingCode: searchParams.bookingCode ?? "",
|
||||||
hotel: hotelData?.hotel.id ?? "",
|
hotel: hotelData?.hotel.id ?? "",
|
||||||
city: hotelData?.hotel.cityName ?? "",
|
city: hotelData?.hotel.cityName ?? "",
|
||||||
}
|
}
|
||||||
|
|
||||||
return <BookingWidget bookingWidgetSearchParams={bookingWidgetSearchParams} />
|
const booking = parseBookingWidgetSearchParams(bookingWidgetSearchParams)
|
||||||
|
|
||||||
|
return <BookingWidget booking={booking} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { BookingWidget } from "@/components/BookingWidget"
|
import { BookingWidget } from "@/components/BookingWidget"
|
||||||
|
import { parseBookingWidgetSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function BookingWidgetPage(
|
export default async function BookingWidgetPage(
|
||||||
props: PageArgs<LangParams, BookingWidgetSearchData>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
|
|
||||||
return <BookingWidget bookingWidgetSearchParams={searchParams} />
|
const booking = parseBookingWidgetSearchParams(searchParams)
|
||||||
|
|
||||||
|
return <BookingWidget booking={booking} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import { BookingWidget } from "@/components/BookingWidget"
|
import { BookingWidget } from "@/components/BookingWidget"
|
||||||
|
import { parseBookingWidgetSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function BookingWidgetPage(props: PageArgs<LangParams, BookingWidgetSearchData>) {
|
export default async function BookingWidgetPage(
|
||||||
const params = await props.params;
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
const searchParams = await props.searchParams;
|
) {
|
||||||
|
const params = await props.params
|
||||||
|
const searchParams = await props.searchParams
|
||||||
if (!env.isLangLive(params.lang)) {
|
if (!env.isLangLive(params.lang)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return <BookingWidget bookingWidgetSearchParams={searchParams} />
|
const booking = parseBookingWidgetSearchParams(searchParams)
|
||||||
|
|
||||||
|
return <BookingWidget booking={booking} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import useLang from "@/hooks/useLang"
|
|||||||
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 { convertSearchParamsToObj } from "@/utils/url"
|
|
||||||
|
|
||||||
import MobileToggleButton, {
|
import MobileToggleButton, {
|
||||||
MobileToggleButtonSkeleton,
|
MobileToggleButtonSkeleton,
|
||||||
@@ -35,12 +34,11 @@ import type {
|
|||||||
BookingCodeSchema,
|
BookingCodeSchema,
|
||||||
BookingWidgetClientProps,
|
BookingWidgetClientProps,
|
||||||
BookingWidgetSchema,
|
BookingWidgetSchema,
|
||||||
BookingWidgetSearchData,
|
|
||||||
} from "@/types/components/bookingWidget"
|
} from "@/types/components/bookingWidget"
|
||||||
|
|
||||||
export default function BookingWidgetClient({
|
export default function BookingWidgetClient({
|
||||||
type,
|
type,
|
||||||
bookingWidgetSearchParams,
|
data,
|
||||||
pageSettingsBookingCodePromise,
|
pageSettingsBookingCodePromise,
|
||||||
}: BookingWidgetClientProps) {
|
}: BookingWidgetClientProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
@@ -50,22 +48,19 @@ export default function BookingWidgetClient({
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
const params = convertSearchParamsToObj<BookingWidgetSearchData>(
|
const shouldFetchAutoComplete = !!data.hotelId || !!data.city
|
||||||
bookingWidgetSearchParams
|
|
||||||
)
|
|
||||||
|
|
||||||
const shouldFetchAutoComplete = !!params.hotelId || !!params.city
|
const { data: destinationsData, isPending } =
|
||||||
|
trpc.autocomplete.destinations.useQuery(
|
||||||
const { data, isPending } = trpc.autocomplete.destinations.useQuery(
|
{
|
||||||
{
|
lang,
|
||||||
lang,
|
query: "",
|
||||||
query: "",
|
includeTypes: ["hotels", "cities"],
|
||||||
includeTypes: ["hotels", "cities"],
|
selectedHotelId: data.hotelId ? data.hotelId.toString() : undefined,
|
||||||
selectedHotelId: params.hotelId,
|
selectedCity: data.city,
|
||||||
selectedCity: params.city,
|
},
|
||||||
},
|
{ enabled: shouldFetchAutoComplete }
|
||||||
{ enabled: shouldFetchAutoComplete }
|
)
|
||||||
)
|
|
||||||
const shouldShowSkeleton = shouldFetchAutoComplete && isPending
|
const shouldShowSkeleton = shouldFetchAutoComplete && isPending
|
||||||
|
|
||||||
useStickyPosition({
|
useStickyPosition({
|
||||||
@@ -76,8 +71,8 @@ export default function BookingWidgetClient({
|
|||||||
const now = dt()
|
const now = dt()
|
||||||
// if fromDate or toDate is undefined, dt will return value that represents the same as 'now' above.
|
// if fromDate or toDate is undefined, dt will return value that represents the same as 'now' above.
|
||||||
// this is fine as isDateParamValid will catch this and default the values accordingly.
|
// this is fine as isDateParamValid will catch this and default the values accordingly.
|
||||||
let fromDate = dt(params.fromDate)
|
let fromDate = dt(data.fromDate)
|
||||||
let toDate = dt(params.toDate)
|
let toDate = dt(data.toDate)
|
||||||
|
|
||||||
const isDateParamValid =
|
const isDateParamValid =
|
||||||
fromDate.isValid() &&
|
fromDate.isValid() &&
|
||||||
@@ -91,17 +86,18 @@ export default function BookingWidgetClient({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let selectedLocation =
|
let selectedLocation =
|
||||||
data?.currentSelection.hotel ?? data?.currentSelection.city
|
destinationsData?.currentSelection.hotel ??
|
||||||
|
destinationsData?.currentSelection.city
|
||||||
|
|
||||||
// if bookingCode is not provided in the search params,
|
// if bookingCode is not provided in the search params,
|
||||||
// we will fetch it from the page settings stored in Contentstack.
|
// we will fetch it from the page settings stored in Contentstack.
|
||||||
const selectedBookingCode =
|
const selectedBookingCode =
|
||||||
params.bookingCode ||
|
data.bookingCode ||
|
||||||
(pageSettingsBookingCodePromise !== null
|
(pageSettingsBookingCodePromise !== null
|
||||||
? use(pageSettingsBookingCodePromise)
|
? use(pageSettingsBookingCodePromise)
|
||||||
: "")
|
: "")
|
||||||
|
|
||||||
const defaultRoomsData: BookingWidgetSchema["rooms"] = params.rooms?.map(
|
const defaultRoomsData: BookingWidgetSchema["rooms"] = data.rooms?.map(
|
||||||
(room) => ({
|
(room) => ({
|
||||||
adults: room.adults,
|
adults: room.adults,
|
||||||
childrenInRoom: room.childrenInRoom || [],
|
childrenInRoom: room.childrenInRoom || [],
|
||||||
@@ -112,7 +108,7 @@ export default function BookingWidgetClient({
|
|||||||
childrenInRoom: [],
|
childrenInRoom: [],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const hotelId = isNaN(+params.hotelId) ? undefined : +params.hotelId
|
const hotelId = data.hotelId ? parseInt(data.hotelId) : undefined
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
search: selectedLocation?.name ?? "",
|
search: selectedLocation?.name ?? "",
|
||||||
@@ -126,9 +122,9 @@ export default function BookingWidgetClient({
|
|||||||
value: selectedBookingCode,
|
value: selectedBookingCode,
|
||||||
remember: false,
|
remember: false,
|
||||||
},
|
},
|
||||||
redemption: params?.searchType === REDEMPTION,
|
redemption: data.searchType === REDEMPTION,
|
||||||
rooms: defaultRoomsData,
|
rooms: defaultRoomsData,
|
||||||
city: params.city || undefined,
|
city: data.city || undefined,
|
||||||
hotel: hotelId,
|
hotel: hotelId,
|
||||||
},
|
},
|
||||||
shouldFocusError: false,
|
shouldFocusError: false,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { FloatingBookingWidgetClient } from "./FloatingBookingWidgetClient"
|
|||||||
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
|
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
|
||||||
|
|
||||||
export async function FloatingBookingWidget({
|
export async function FloatingBookingWidget({
|
||||||
bookingWidgetSearchParams,
|
booking,
|
||||||
}: Omit<BookingWidgetProps, "type">) {
|
}: Omit<BookingWidgetProps, "type">) {
|
||||||
const isHidden = await isBookingWidgetHidden()
|
const isHidden = await isBookingWidgetHidden()
|
||||||
|
|
||||||
@@ -17,13 +17,13 @@ export async function FloatingBookingWidget({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pageSettingsBookingCodePromise: Promise<string> | null = null
|
let pageSettingsBookingCodePromise: Promise<string> | null = null
|
||||||
if (!bookingWidgetSearchParams.bookingCode) {
|
if (!booking.bookingCode) {
|
||||||
pageSettingsBookingCodePromise = getPageSettingsBookingCode()
|
pageSettingsBookingCodePromise = getPageSettingsBookingCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FloatingBookingWidgetClient
|
<FloatingBookingWidgetClient
|
||||||
bookingWidgetSearchParams={bookingWidgetSearchParams}
|
data={booking}
|
||||||
pageSettingsBookingCodePromise={pageSettingsBookingCodePromise}
|
pageSettingsBookingCodePromise={pageSettingsBookingCodePromise}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ export async function BookingWidget(props: BookingWidgetProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function InternalBookingWidget({
|
async function InternalBookingWidget({ type, booking }: BookingWidgetProps) {
|
||||||
type,
|
|
||||||
bookingWidgetSearchParams,
|
|
||||||
}: BookingWidgetProps) {
|
|
||||||
const isHidden = await isBookingWidgetHidden()
|
const isHidden = await isBookingWidgetHidden()
|
||||||
|
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
@@ -28,14 +25,14 @@ async function InternalBookingWidget({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pageSettingsBookingCodePromise: Promise<string> | null = null
|
let pageSettingsBookingCodePromise: Promise<string> | null = null
|
||||||
if (!bookingWidgetSearchParams.bookingCode) {
|
if (!booking.bookingCode) {
|
||||||
pageSettingsBookingCodePromise = getPageSettingsBookingCode()
|
pageSettingsBookingCodePromise = getPageSettingsBookingCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BookingWidgetClient
|
<BookingWidgetClient
|
||||||
type={type}
|
type={type}
|
||||||
bookingWidgetSearchParams={bookingWidgetSearchParams}
|
data={booking}
|
||||||
pageSettingsBookingCodePromise={pageSettingsBookingCodePromise}
|
pageSettingsBookingCodePromise={pageSettingsBookingCodePromise}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import TrackingSDK from "@/components/TrackingSDK"
|
|||||||
|
|
||||||
import styles from "./startPage.module.css"
|
import styles from "./startPage.module.css"
|
||||||
|
|
||||||
|
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
||||||
import { BlocksEnums } from "@/types/enums/blocks"
|
import { BlocksEnums } from "@/types/enums/blocks"
|
||||||
|
|
||||||
export default async function StartPage({
|
export default async function StartPage({
|
||||||
searchParams,
|
booking,
|
||||||
}: {
|
}: {
|
||||||
searchParams: { [key: string]: string }
|
booking: BookingWidgetSearchData
|
||||||
}) {
|
}) {
|
||||||
const content = await getStartPage()
|
const content = await getStartPage()
|
||||||
if (!content) {
|
if (!content) {
|
||||||
@@ -30,7 +31,7 @@ export default async function StartPage({
|
|||||||
<Title color="white" textAlign="center">
|
<Title color="white" textAlign="center">
|
||||||
{header.heading}
|
{header.heading}
|
||||||
</Title>
|
</Title>
|
||||||
<FloatingBookingWidget bookingWidgetSearchParams={searchParams} />
|
<FloatingBookingWidget booking={booking} />
|
||||||
</div>
|
</div>
|
||||||
{header.hero_image ? (
|
{header.hero_image ? (
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { REDEMPTION } from "@/constants/booking"
|
|||||||
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 { serializeBookingSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import FormContent, { BookingWidgetFormContentSkeleton } from "./FormContent"
|
import FormContent, { BookingWidgetFormContentSkeleton } from "./FormContent"
|
||||||
import { bookingWidgetVariants } from "./variants"
|
import { bookingWidgetVariants } from "./variants"
|
||||||
@@ -38,7 +38,7 @@ export default function Form({ type, onClose }: BookingWidgetFormProps) {
|
|||||||
|
|
||||||
function onSubmit(data: BookingWidgetSchema) {
|
function onSubmit(data: BookingWidgetSchema) {
|
||||||
const bookingFlowPage = data.hotel ? selectRate(lang) : selectHotel(lang)
|
const bookingFlowPage = data.hotel ? selectRate(lang) : selectHotel(lang)
|
||||||
const bookingWidgetParams = convertObjToSearchParams({
|
const bookingWidgetParams = serializeBookingSearchParams({
|
||||||
rooms: data.rooms,
|
rooms: data.rooms,
|
||||||
...data.date,
|
...data.date,
|
||||||
...(data.city ? { city: data.city } : {}),
|
...(data.city ? { city: data.city } : {}),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { detailsStorageName } from "@/stores/enter-details"
|
|||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
import { trackPaymentEvent } from "@/utils/tracking"
|
import { trackPaymentEvent } from "@/utils/tracking"
|
||||||
import { trackEvent } from "@/utils/tracking/base"
|
import { trackEvent } from "@/utils/tracking/base"
|
||||||
import { convertObjToSearchParams } from "@/utils/url"
|
import { serializeBookingSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import { clearGlaSessionStorage, readGlaFromSessionStorage } from "./helpers"
|
import { clearGlaSessionStorage, readGlaFromSessionStorage } from "./helpers"
|
||||||
|
|
||||||
@@ -33,9 +33,11 @@ export default function HandleErrorCallback({
|
|||||||
|
|
||||||
if (bookingData) {
|
if (bookingData) {
|
||||||
const detailsStorage: PersistedState = JSON.parse(bookingData)
|
const detailsStorage: PersistedState = JSON.parse(bookingData)
|
||||||
const searchParams = convertObjToSearchParams(
|
const searchParams = serializeBookingSearchParams(
|
||||||
detailsStorage.booking,
|
detailsStorage.booking,
|
||||||
searchObject
|
{
|
||||||
|
initialSearchParams: searchObject,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const glaSessionData = readGlaFromSessionStorage()
|
const glaSessionData = readGlaFromSessionStorage()
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import { useSearchHistory } from "@/hooks/useSearchHistory"
|
|||||||
|
|
||||||
import { getTracking } from "./tracking"
|
import { getTracking } from "./tracking"
|
||||||
|
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { DetailsBooking } from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
import type { Hotel } from "@/types/hotel"
|
import type { Hotel } from "@/types/hotel"
|
||||||
import type { Room } from "@/types/providers/details/room"
|
import type { Room } from "@/types/providers/details/room"
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
import type { SelectHotelParams } from "@/utils/url"
|
|
||||||
|
|
||||||
interface TrackingWrapperProps {
|
interface TrackingWrapperProps {
|
||||||
booking: SelectHotelParams<SelectRateSearchParams>
|
booking: DetailsBooking
|
||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
rooms: Room[]
|
rooms: Room[]
|
||||||
isMember: boolean
|
isMember: boolean
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { getSpecialRoomType } from "@/utils/specialRoomType"
|
|||||||
|
|
||||||
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
||||||
import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast"
|
import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast"
|
||||||
|
import type { DetailsBooking } from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
||||||
import {
|
import {
|
||||||
TrackingChannelEnum,
|
TrackingChannelEnum,
|
||||||
type TrackingSDKAncillaries,
|
type TrackingSDKAncillaries,
|
||||||
@@ -25,10 +25,9 @@ import type {
|
|||||||
Product,
|
Product,
|
||||||
} from "@/types/trpc/routers/hotel/roomAvailability"
|
} from "@/types/trpc/routers/hotel/roomAvailability"
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
import type { SelectHotelParams } from "@/utils/url"
|
|
||||||
|
|
||||||
export function getTracking(
|
export function getTracking(
|
||||||
booking: SelectHotelParams<SelectRateSearchParams>,
|
booking: DetailsBooking,
|
||||||
hotel: Hotel,
|
hotel: Hotel,
|
||||||
rooms: Room[],
|
rooms: Room[],
|
||||||
isMember: boolean,
|
isMember: boolean,
|
||||||
|
|||||||
@@ -37,23 +37,25 @@ export async function SelectHotelMapContainer({
|
|||||||
bookingCode,
|
bookingCode,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
city,
|
city,
|
||||||
|
cityName,
|
||||||
hotel: isAlternativeFor,
|
hotel: isAlternativeFor,
|
||||||
noOfRooms,
|
noOfRooms,
|
||||||
redemption,
|
redemption,
|
||||||
selectHotelParams,
|
|
||||||
} = searchDetails
|
} = searchDetails
|
||||||
|
|
||||||
if (!city) {
|
if (!city) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const hotels = await getHotels(
|
const hotels = await getHotels({
|
||||||
selectHotelParams,
|
fromDate: booking.fromDate,
|
||||||
|
toDate: booking.toDate,
|
||||||
|
rooms: booking.rooms,
|
||||||
isAlternativeFor,
|
isAlternativeFor,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
city,
|
city,
|
||||||
!!redemption
|
redemption: !!redemption,
|
||||||
)
|
})
|
||||||
|
|
||||||
const hotelPins = getHotelPins(hotels)
|
const hotelPins = getHotelPins(hotels)
|
||||||
const filterList = getFiltersFromHotels(hotels)
|
const filterList = getFiltersFromHotels(hotels)
|
||||||
@@ -62,8 +64,8 @@ export async function SelectHotelMapContainer({
|
|||||||
hotel: { address: hotels?.[0]?.hotel?.address.streetAddress },
|
hotel: { address: hotels?.[0]?.hotel?.address.streetAddress },
|
||||||
})
|
})
|
||||||
|
|
||||||
const arrivalDate = new Date(selectHotelParams.fromDate)
|
const arrivalDate = new Date(booking.fromDate)
|
||||||
const departureDate = new Date(selectHotelParams.toDate)
|
const departureDate = new Date(booking.toDate)
|
||||||
const isRedemptionAvailability = redemption
|
const isRedemptionAvailability = redemption
|
||||||
? hotels.some(
|
? hotels.some(
|
||||||
(hotel) => hotel.availability.productType?.redemptions?.length
|
(hotel) => hotel.availability.productType?.redemptions?.length
|
||||||
@@ -83,11 +85,11 @@ export async function SelectHotelMapContainer({
|
|||||||
adultsInRoom,
|
adultsInRoom,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
hotels.length,
|
hotels.length,
|
||||||
selectHotelParams.hotelId,
|
booking.hotelId,
|
||||||
noOfRooms,
|
noOfRooms,
|
||||||
hotels?.[0]?.hotel.address.country,
|
hotels?.[0]?.hotel.address.country,
|
||||||
hotels?.[0]?.hotel.address.city,
|
hotels?.[0]?.hotel.address.city,
|
||||||
selectHotelParams.city,
|
cityName,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
isBookingCodeRateAvailable,
|
isBookingCodeRateAvailable,
|
||||||
redemption,
|
redemption,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function getTracking(
|
|||||||
adultsInRoom: number[],
|
adultsInRoom: number[],
|
||||||
childrenInRoom: ChildrenInRoom,
|
childrenInRoom: ChildrenInRoom,
|
||||||
hotelsResult: number,
|
hotelsResult: number,
|
||||||
hotelId: string,
|
hotelId: string | undefined,
|
||||||
noOfRooms: number,
|
noOfRooms: number,
|
||||||
country: string | undefined,
|
country: string | undefined,
|
||||||
hotelCity: string | undefined,
|
hotelCity: string | undefined,
|
||||||
|
|||||||
@@ -14,17 +14,13 @@ import type {
|
|||||||
HotelFilter,
|
HotelFilter,
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
||||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||||
import type {
|
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
AlternativeHotelsSearchParams,
|
|
||||||
SelectHotelSearchParams,
|
|
||||||
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
|
||||||
import type { AdditionalData, Hotel } from "@/types/hotel"
|
import type { AdditionalData, Hotel } from "@/types/hotel"
|
||||||
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
||||||
import type {
|
import type {
|
||||||
HotelLocation,
|
HotelLocation,
|
||||||
Location,
|
Location,
|
||||||
} from "@/types/trpc/routers/hotel/locations"
|
} from "@/types/trpc/routers/hotel/locations"
|
||||||
import type { SelectHotelParams } from "@/utils/url"
|
|
||||||
|
|
||||||
interface AvailabilityResponse {
|
interface AvailabilityResponse {
|
||||||
availability: HotelsAvailabilityItem[]
|
availability: HotelsAvailabilityItem[]
|
||||||
@@ -162,19 +158,32 @@ function sortAndFilterHotelsByAvailability(
|
|||||||
].flat()
|
].flat()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHotels(
|
type GetHotelsInput = {
|
||||||
booking: SelectHotelParams<
|
fromDate: string
|
||||||
SelectHotelSearchParams | AlternativeHotelsSearchParams
|
toDate: string
|
||||||
>,
|
rooms: {
|
||||||
isAlternativeFor: HotelLocation | null,
|
adults: number
|
||||||
bookingCode: string | undefined,
|
childrenInRoom?: Child[]
|
||||||
city: Location,
|
}[]
|
||||||
|
isAlternativeFor: HotelLocation | null
|
||||||
|
bookingCode: string | undefined
|
||||||
|
city: Location
|
||||||
redemption: boolean
|
redemption: boolean
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
export async function getHotels({
|
||||||
|
rooms,
|
||||||
|
fromDate,
|
||||||
|
toDate,
|
||||||
|
isAlternativeFor,
|
||||||
|
bookingCode,
|
||||||
|
city,
|
||||||
|
redemption,
|
||||||
|
}: GetHotelsInput) {
|
||||||
let availableHotelsResponse: SettledResult = []
|
let availableHotelsResponse: SettledResult = []
|
||||||
if (isAlternativeFor) {
|
if (isAlternativeFor) {
|
||||||
availableHotelsResponse = await Promise.allSettled(
|
availableHotelsResponse = await Promise.allSettled(
|
||||||
booking.rooms.map(async (room) => {
|
rooms.map(async (room) => {
|
||||||
return fetchAlternativeHotels(isAlternativeFor.id, {
|
return fetchAlternativeHotels(isAlternativeFor.id, {
|
||||||
adults: room.adults,
|
adults: room.adults,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
@@ -182,14 +191,14 @@ export async function getHotels(
|
|||||||
? generateChildrenString(room.childrenInRoom)
|
? generateChildrenString(room.childrenInRoom)
|
||||||
: undefined,
|
: undefined,
|
||||||
redemption,
|
redemption,
|
||||||
roomStayEndDate: booking.toDate,
|
roomStayEndDate: toDate,
|
||||||
roomStayStartDate: booking.fromDate,
|
roomStayStartDate: fromDate,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
} else if (bookingCode) {
|
} else if (bookingCode) {
|
||||||
availableHotelsResponse = await Promise.allSettled(
|
availableHotelsResponse = await Promise.allSettled(
|
||||||
booking.rooms.map(async (room) => {
|
rooms.map(async (room) => {
|
||||||
return fetchBookingCodeAvailableHotels({
|
return fetchBookingCodeAvailableHotels({
|
||||||
adults: room.adults,
|
adults: room.adults,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
@@ -197,14 +206,14 @@ export async function getHotels(
|
|||||||
? generateChildrenString(room.childrenInRoom)
|
? generateChildrenString(room.childrenInRoom)
|
||||||
: undefined,
|
: undefined,
|
||||||
cityId: city.id,
|
cityId: city.id,
|
||||||
roomStayStartDate: booking.fromDate,
|
roomStayStartDate: fromDate,
|
||||||
roomStayEndDate: booking.toDate,
|
roomStayEndDate: toDate,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
availableHotelsResponse = await Promise.allSettled(
|
availableHotelsResponse = await Promise.allSettled(
|
||||||
booking.rooms.map(
|
rooms.map(
|
||||||
async (room) =>
|
async (room) =>
|
||||||
await fetchAvailableHotels({
|
await fetchAvailableHotels({
|
||||||
adults: room.adults,
|
adults: room.adults,
|
||||||
@@ -213,8 +222,8 @@ export async function getHotels(
|
|||||||
: undefined,
|
: undefined,
|
||||||
cityId: city.id,
|
cityId: city.id,
|
||||||
redemption,
|
redemption,
|
||||||
roomStayEndDate: booking.toDate,
|
roomStayEndDate: toDate,
|
||||||
roomStayStartDate: booking.fromDate,
|
roomStayStartDate: fromDate,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function getTracking(
|
|||||||
adultsInRoom: number[],
|
adultsInRoom: number[],
|
||||||
childrenInRoom: ChildrenInRoom,
|
childrenInRoom: ChildrenInRoom,
|
||||||
hotelsResult: number,
|
hotelsResult: number,
|
||||||
hotelId: string,
|
hotelId: string | undefined,
|
||||||
noOfRooms: number,
|
noOfRooms: number,
|
||||||
country: string | undefined,
|
country: string | undefined,
|
||||||
hotelCity: string | undefined,
|
hotelCity: string | undefined,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function Summary({
|
|||||||
)
|
)
|
||||||
const showDiscounted = containsBookingCodeRate || isMember
|
const showDiscounted = containsBookingCodeRate || isMember
|
||||||
|
|
||||||
const priceDetailsRooms = mapToPrice(rateSummary, booking, isMember)
|
const priceDetailsRooms = mapToPrice(rateSummary, booking.rooms, isMember)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.summary}>
|
<section className={styles.summary}>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type {
|
import type {
|
||||||
Rate,
|
Rate,
|
||||||
SelectRateSearchParams,
|
Room as SelectRateRoom,
|
||||||
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import type { Room } from "@/components/HotelReservation/PriceDetailsModal/PriceDetailsTable"
|
import type { Room } from "@/components/HotelReservation/PriceDetailsModal/PriceDetailsTable"
|
||||||
|
|
||||||
export function mapToPrice(
|
export function mapToPrice(
|
||||||
rooms: (Rate | null)[],
|
rooms: (Rate | null)[],
|
||||||
booking: SelectRateSearchParams,
|
bookingRooms: SelectRateRoom[],
|
||||||
isUserLoggedIn: boolean
|
isUserLoggedIn: boolean
|
||||||
) {
|
) {
|
||||||
return rooms
|
return rooms
|
||||||
@@ -43,7 +43,7 @@ export function mapToPrice(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookingRoom = booking.rooms[idx]
|
const bookingRoom = bookingRooms[idx]
|
||||||
return {
|
return {
|
||||||
adults: bookingRoom.adults,
|
adults: bookingRoom.adults,
|
||||||
bedType: undefined,
|
bedType: undefined,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation"
|
import { notFound, useSearchParams } from "next/navigation"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
@@ -9,7 +9,7 @@ import { selectRateRoomsAvailabilityInputSchema } from "@/server/routers/hotels/
|
|||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import RatesProvider from "@/providers/RatesProvider"
|
import RatesProvider from "@/providers/RatesProvider"
|
||||||
import { convertSearchParamsToObj, searchParamsToRecord } from "@/utils/url"
|
import { parseSelectRateSearchParams, searchParamsToRecord } from "@/utils/url"
|
||||||
|
|
||||||
import RateSummary from "./RateSummary"
|
import RateSummary from "./RateSummary"
|
||||||
import Rooms from "./Rooms"
|
import Rooms from "./Rooms"
|
||||||
@@ -18,7 +18,6 @@ import { RoomsContainerSkeleton } from "./RoomsContainerSkeleton"
|
|||||||
import styles from "./index.module.css"
|
import styles from "./index.module.css"
|
||||||
|
|
||||||
import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer"
|
import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer"
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
export function RoomsContainer({
|
export function RoomsContainer({
|
||||||
@@ -30,10 +29,12 @@ export function RoomsContainer({
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
const booking = convertSearchParamsToObj<SelectRateSearchParams>(
|
const booking = parseSelectRateSearchParams(
|
||||||
searchParamsToRecord(searchParams)
|
searchParamsToRecord(searchParams)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!booking) return notFound()
|
||||||
|
|
||||||
const bookingInput = selectRateRoomsAvailabilityInputSchema.safeParse({
|
const bookingInput = selectRateRoomsAvailabilityInputSchema.safeParse({
|
||||||
booking,
|
booking,
|
||||||
lang,
|
lang,
|
||||||
|
|||||||
@@ -7,12 +7,11 @@ import { REDEMPTION } from "@/constants/booking"
|
|||||||
|
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { convertSearchParamsToObj, searchParamsToRecord } from "@/utils/url"
|
import { parseSelectRateSearchParams, searchParamsToRecord } from "@/utils/url"
|
||||||
|
|
||||||
import { getValidDates } from "../getValidDates"
|
import { getValidDates } from "../getValidDates"
|
||||||
import { getTracking } from "./tracking"
|
import { getTracking } from "./tracking"
|
||||||
|
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
||||||
import type { ChildrenInRoom } from "@/utils/hotelSearchDetails"
|
import type { ChildrenInRoom } from "@/utils/hotelSearchDetails"
|
||||||
|
|
||||||
export default function Tracking({
|
export default function Tracking({
|
||||||
@@ -34,16 +33,13 @@ export default function Tracking({
|
|||||||
}) {
|
}) {
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const params = useSearchParams()
|
const params = useSearchParams()
|
||||||
const selectRateParams = convertSearchParamsToObj<SelectRateSearchParams>(
|
const booking = parseSelectRateSearchParams(searchParamsToRecord(params))
|
||||||
searchParamsToRecord(params)
|
|
||||||
)
|
|
||||||
|
|
||||||
const { fromDate, toDate } = getValidDates(
|
if (!booking) return null
|
||||||
selectRateParams.fromDate,
|
|
||||||
selectRateParams.toDate
|
|
||||||
)
|
|
||||||
|
|
||||||
const { rooms, searchType, bookingCode, city: paramCity } = selectRateParams
|
const { fromDate, toDate } = getValidDates(booking.fromDate, booking.toDate)
|
||||||
|
|
||||||
|
const { rooms, searchType, bookingCode, city: paramCity } = booking
|
||||||
|
|
||||||
const arrivalDate = fromDate.toDate()
|
const arrivalDate = fromDate.toDate()
|
||||||
const departureDate = toDate.toDate()
|
const departureDate = toDate.toDate()
|
||||||
|
|||||||
@@ -12,16 +12,15 @@ import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert"
|
|||||||
import AvailabilityError from "./AvailabilityError"
|
import AvailabilityError from "./AvailabilityError"
|
||||||
import Tracking from "./Tracking"
|
import Tracking from "./Tracking"
|
||||||
|
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
import type { SelectHotelParams } from "@/utils/url"
|
|
||||||
|
|
||||||
export default async function SelectRatePage({
|
export default async function SelectRatePage({
|
||||||
lang,
|
lang,
|
||||||
booking,
|
booking,
|
||||||
}: {
|
}: {
|
||||||
lang: Lang
|
lang: Lang
|
||||||
booking: SelectHotelParams<SelectRateSearchParams>
|
booking: SelectRateBooking
|
||||||
}) {
|
}) {
|
||||||
const searchDetails = await getHotelSearchDetails(booking)
|
const searchDetails = await getHotelSearchDetails(booking)
|
||||||
if (!searchDetails?.hotel) {
|
if (!searchDetails?.hotel) {
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import BedTwinIcon from "@scandic-hotels/design-system/Icons/BedTwinIcon"
|
|||||||
import BedWallExtraIcon from "@scandic-hotels/design-system/Icons/BedWallExtraIcon"
|
import BedWallExtraIcon from "@scandic-hotels/design-system/Icons/BedWallExtraIcon"
|
||||||
|
|
||||||
import type { IconProps } from "@scandic-hotels/design-system/Icons"
|
import type { IconProps } from "@scandic-hotels/design-system/Icons"
|
||||||
|
import type { JSX } from "react"
|
||||||
import type { JSX } from "react";
|
|
||||||
|
|
||||||
export enum BookingStatusEnum {
|
export enum BookingStatusEnum {
|
||||||
BookingCompleted = "BookingCompleted",
|
BookingCompleted = "BookingCompleted",
|
||||||
@@ -39,6 +38,7 @@ export enum ChildBedTypeEnum {
|
|||||||
export const FamilyAndFriendsCodes = ["D000029555", "D000029271", "D000029195"]
|
export const FamilyAndFriendsCodes = ["D000029555", "D000029271", "D000029195"]
|
||||||
|
|
||||||
export const REDEMPTION = "redemption"
|
export const REDEMPTION = "redemption"
|
||||||
|
export const bookingSearchTypes = [REDEMPTION] as const
|
||||||
export const SEARCHTYPE = "searchtype"
|
export const SEARCHTYPE = "searchtype"
|
||||||
|
|
||||||
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"
|
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { detailsStorageName } from "."
|
|||||||
|
|
||||||
import { type RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
import { type RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
import type { Price } from "@/types/components/hotelReservation/price"
|
import type { Price } from "@/types/components/hotelReservation/price"
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import { CurrencyEnum } from "@/types/enums/currency"
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
import type { Package } from "@/types/requests/packages"
|
import type { Package } from "@/types/requests/packages"
|
||||||
import type { PersistedState, RoomState } from "@/types/stores/enter-details"
|
import type { PersistedState, RoomState } from "@/types/stores/enter-details"
|
||||||
@@ -28,8 +28,8 @@ export function extractGuestFromUser(user: NonNullable<SafeUser>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function checkIsSameBooking(
|
export function checkIsSameBooking(
|
||||||
prev: SelectRateSearchParams & { errorCode?: string },
|
prev: SelectRateBooking & { errorCode?: string },
|
||||||
next: SelectRateSearchParams & { errorCode?: string }
|
next: SelectRateBooking & { errorCode?: string }
|
||||||
) {
|
) {
|
||||||
const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev
|
const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,14 @@ export function findProduct(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function findProductInRoom(
|
export function findProductInRoom(
|
||||||
rateCode: string,
|
rateCode: string | undefined,
|
||||||
room: RoomConfiguration,
|
room: RoomConfiguration,
|
||||||
counterRateCode = ""
|
counterRateCode = ""
|
||||||
) {
|
) {
|
||||||
|
if (!rateCode) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
if (room.campaign.length) {
|
if (room.campaign.length) {
|
||||||
const campaignProduct = room.campaign.find((product) =>
|
const campaignProduct = room.campaign.find((product) =>
|
||||||
findProduct(rateCode, product, counterRateCode)
|
findProduct(rateCode, product, counterRateCode)
|
||||||
@@ -84,14 +88,19 @@ export function findProductInRoom(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function findSelectedRate(
|
export function findSelectedRate(
|
||||||
rateCode: string,
|
rateCode: string | undefined,
|
||||||
counterRateCode: string,
|
counterRateCode: string | undefined,
|
||||||
roomTypeCode: string,
|
roomTypeCode: string | undefined,
|
||||||
rooms: RoomConfiguration[] | AvailabilityError
|
rooms: RoomConfiguration[] | AvailabilityError
|
||||||
) {
|
) {
|
||||||
if (!Array.isArray(rooms)) {
|
if (!Array.isArray(rooms)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!rateCode) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return rooms.find((room) => {
|
return rooms.find((room) => {
|
||||||
if (room.roomTypeCode !== roomTypeCode) {
|
if (room.roomTypeCode !== roomTypeCode) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
import { create } from "zustand"
|
import { create } from "zustand"
|
||||||
|
|
||||||
import { convertSearchParamsToObj, searchParamsToRecord } from "@/utils/url"
|
import { parseSelectRateSearchParams, searchParamsToRecord } from "@/utils/url"
|
||||||
|
|
||||||
import { checkIsSameBooking } from "./enter-details/helpers"
|
import { checkIsSameBooking } from "./enter-details/helpers"
|
||||||
|
|
||||||
import type { ReadonlyURLSearchParams } from "next/navigation"
|
import type { ReadonlyURLSearchParams } from "next/navigation"
|
||||||
|
|
||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
||||||
|
|
||||||
interface TrackingStoreState {
|
interface TrackingStoreState {
|
||||||
initialStartTime: number
|
initialStartTime: number
|
||||||
setInitialPageLoadTime: (time: number) => void
|
setInitialPageLoadTime: (time: number) => void
|
||||||
@@ -81,14 +79,15 @@ const useTrackingStore = create<TrackingStoreState>((set, get) => ({
|
|||||||
if (!currentPath?.match(/^\/(da|de|en|fi|no|sv)\/(hotelreservation)/))
|
if (!currentPath?.match(/^\/(da|de|en|fi|no|sv)\/(hotelreservation)/))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
const previousParamsObject =
|
const previousParamsObject = parseSelectRateSearchParams(
|
||||||
convertSearchParamsToObj<SelectRateSearchParams>(
|
searchParamsToRecord(previousParams)
|
||||||
searchParamsToRecord(previousParams)
|
)
|
||||||
)
|
const currentParamsObject = parseSelectRateSearchParams(
|
||||||
const currentParamsObject =
|
searchParamsToRecord(currentParams)
|
||||||
convertSearchParamsToObj<SelectRateSearchParams>(
|
)
|
||||||
searchParamsToRecord(currentParams)
|
|
||||||
)
|
if (!previousParamsObject && !currentParamsObject) return false
|
||||||
|
if (!previousParamsObject || !currentParamsObject) return true
|
||||||
|
|
||||||
const isSameBooking = checkIsSameBooking(
|
const isSameBooking = checkIsSameBooking(
|
||||||
previousParamsObject,
|
previousParamsObject,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
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 {
|
import type {
|
||||||
bookingCodeSchema,
|
bookingCodeSchema,
|
||||||
bookingWidgetSchema,
|
bookingWidgetSchema,
|
||||||
} from "@/components/Forms/BookingWidget/schema"
|
} from "@/components/Forms/BookingWidget/schema"
|
||||||
import type { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants"
|
import type { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants"
|
||||||
|
import type { BookingSearchType } from "../hotelReservation/booking"
|
||||||
import type { GuestsRoom } from "./guestsRoomsPicker"
|
import type { GuestsRoom } from "./guestsRoomsPicker"
|
||||||
|
|
||||||
export type BookingWidgetSchema = z.output<typeof bookingWidgetSchema>
|
export type BookingWidgetSchema = z.output<typeof bookingWidgetSchema>
|
||||||
@@ -14,12 +14,12 @@ export type BookingCodeSchema = z.output<typeof bookingCodeSchema>
|
|||||||
|
|
||||||
export type BookingWidgetSearchData = {
|
export type BookingWidgetSearchData = {
|
||||||
city?: string
|
city?: string
|
||||||
hotel?: string
|
hotelId?: string
|
||||||
fromDate?: string
|
fromDate?: string
|
||||||
toDate?: string
|
toDate?: string
|
||||||
rooms?: GuestsRoom[]
|
rooms?: GuestsRoom[]
|
||||||
bookingCode?: string
|
bookingCode?: string
|
||||||
searchType?: "redemption"
|
searchType?: BookingSearchType
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BookingWidgetType = VariantProps<
|
export type BookingWidgetType = VariantProps<
|
||||||
@@ -28,16 +28,12 @@ export type BookingWidgetType = VariantProps<
|
|||||||
|
|
||||||
export interface BookingWidgetProps {
|
export interface BookingWidgetProps {
|
||||||
type?: BookingWidgetType
|
type?: BookingWidgetType
|
||||||
bookingWidgetSearchParams: Awaited<
|
booking: BookingWidgetSearchData
|
||||||
SearchParams<BookingWidgetSearchData>["searchParams"]
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BookingWidgetClientProps {
|
export interface BookingWidgetClientProps {
|
||||||
type?: BookingWidgetType
|
type?: BookingWidgetType
|
||||||
bookingWidgetSearchParams: Awaited<
|
data: BookingWidgetSearchData
|
||||||
SearchParams<BookingWidgetSearchData>["searchParams"]
|
|
||||||
>
|
|
||||||
pageSettingsBookingCodePromise: Promise<string> | null
|
pageSettingsBookingCodePromise: Promise<string> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import type { bookingSearchTypes } from "@/constants/booking"
|
||||||
|
|
||||||
|
export type BookingSearchType = (typeof bookingSearchTypes)[number]
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { z } from "zod"
|
import type { z } from "zod"
|
||||||
|
|
||||||
|
import type { PackageEnum } from "@/types/requests/packages"
|
||||||
import type { Product } from "@/types/trpc/routers/hotel/roomAvailability"
|
import type { Product } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||||
import type { SafeUser } from "@/types/user"
|
import type { SafeUser } from "@/types/user"
|
||||||
import type { getMultiroomDetailsSchema } from "@/components/HotelReservation/EnterDetails/Details/Multiroom/schema"
|
import type { getMultiroomDetailsSchema } from "@/components/HotelReservation/EnterDetails/Details/Multiroom/schema"
|
||||||
@@ -8,7 +9,9 @@ import type {
|
|||||||
signedInDetailsSchema,
|
signedInDetailsSchema,
|
||||||
} from "@/components/HotelReservation/EnterDetails/Details/RoomOne/schema"
|
} from "@/components/HotelReservation/EnterDetails/Details/RoomOne/schema"
|
||||||
import type { productTypePointsSchema } from "@/server/routers/hotels/schemas/productTypePrice"
|
import type { productTypePointsSchema } from "@/server/routers/hotels/schemas/productTypePrice"
|
||||||
|
import type { BookingSearchType } from "../booking"
|
||||||
import type { Price } from "../price"
|
import type { Price } from "../price"
|
||||||
|
import type { Child } from "../selectRate/selectRate"
|
||||||
|
|
||||||
export type DetailsSchema = z.output<typeof guestDetailsSchema>
|
export type DetailsSchema = z.output<typeof guestDetailsSchema>
|
||||||
export type MultiroomDetailsSchema = z.output<
|
export type MultiroomDetailsSchema = z.output<
|
||||||
@@ -31,3 +34,21 @@ export type JoinScandicFriendsCardProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type RoomRate = Product
|
export type RoomRate = Product
|
||||||
|
|
||||||
|
export type DetailsBooking = {
|
||||||
|
hotelId: string
|
||||||
|
fromDate: string
|
||||||
|
toDate: string
|
||||||
|
city?: string
|
||||||
|
bookingCode?: string
|
||||||
|
searchType?: BookingSearchType
|
||||||
|
rooms: {
|
||||||
|
adults: number
|
||||||
|
rateCode: string
|
||||||
|
roomTypeCode: string
|
||||||
|
bookingCode?: string
|
||||||
|
childrenInRoom?: Child[]
|
||||||
|
counterRateCode?: string
|
||||||
|
packages?: PackageEnum[]
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,12 +5,8 @@ import type { Amenities } from "@/types/hotel"
|
|||||||
import type { ProductTypeCheque } from "@/types/trpc/routers/hotel/availability"
|
import type { ProductTypeCheque } from "@/types/trpc/routers/hotel/availability"
|
||||||
import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers"
|
import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers"
|
||||||
import type { imageSchema } from "@/server/routers/hotels/schemas/image"
|
import type { imageSchema } from "@/server/routers/hotels/schemas/image"
|
||||||
import type { SelectHotelParams } from "@/utils/url"
|
|
||||||
import type { CategorizedFilters } from "./hotelFilters"
|
import type { CategorizedFilters } from "./hotelFilters"
|
||||||
import type {
|
import type { SelectHotelBooking } from "./selectHotel"
|
||||||
AlternativeHotelsSearchParams,
|
|
||||||
SelectHotelSearchParams,
|
|
||||||
} from "./selectHotelSearchParams"
|
|
||||||
|
|
||||||
export interface HotelListingProps {
|
export interface HotelListingProps {
|
||||||
hotels: HotelResponse[]
|
hotels: HotelResponse[]
|
||||||
@@ -76,8 +72,6 @@ export interface HotelCardDialogListingProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SelectHotelMapContainerProps = {
|
export type SelectHotelMapContainerProps = {
|
||||||
booking: SelectHotelParams<
|
booking: SelectHotelBooking
|
||||||
SelectHotelSearchParams | AlternativeHotelsSearchParams
|
|
||||||
>
|
|
||||||
isAlternativeHotels?: boolean
|
isAlternativeHotels?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { Hotel } from "@/types/hotel"
|
import type { Hotel } from "@/types/hotel"
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { BookingSearchType } from "../booking"
|
||||||
import type { SelectHotelParams } from "@/utils/url"
|
import type { Child } from "../selectRate/selectRate"
|
||||||
import type { SidePeekEnum } from "../sidePeek"
|
import type { SidePeekEnum } from "../sidePeek"
|
||||||
import type { AlternativeHotelsSearchParams } from "./selectHotelSearchParams"
|
|
||||||
|
|
||||||
export enum AvailabilityEnum {
|
export enum AvailabilityEnum {
|
||||||
Available = "Available",
|
Available = "Available",
|
||||||
@@ -20,8 +19,15 @@ export interface ContactProps {
|
|||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectHotelProps {
|
export type SelectHotelBooking = {
|
||||||
booking: SelectHotelParams<AlternativeHotelsSearchParams>
|
hotelId?: string
|
||||||
lang: Lang
|
city?: string
|
||||||
isAlternativeHotels?: boolean
|
fromDate: string
|
||||||
|
toDate: string
|
||||||
|
rooms: {
|
||||||
|
adults: number
|
||||||
|
childrenInRoom?: Child[]
|
||||||
|
}[]
|
||||||
|
bookingCode?: string
|
||||||
|
searchType?: BookingSearchType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { Room } from "../selectRate/selectRate"
|
|
||||||
|
|
||||||
export interface SelectHotelSearchParams {
|
|
||||||
city: string
|
|
||||||
fromDate: string
|
|
||||||
toDate: string
|
|
||||||
rooms: Pick<Room, "adults" | "childrenInRoom">[]
|
|
||||||
bookingCode: string
|
|
||||||
searchType?: "redemption"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlternativeHotelsSearchParams {
|
|
||||||
hotel: string
|
|
||||||
fromDate: string
|
|
||||||
toDate: string
|
|
||||||
rooms: Pick<Room, "adults" | "childrenInRoom">[]
|
|
||||||
bookingCode: string
|
|
||||||
searchType?: "redemption"
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
RoomConfiguration,
|
RoomConfiguration,
|
||||||
} from "@/types/trpc/routers/hotel/roomAvailability"
|
} from "@/types/trpc/routers/hotel/roomAvailability"
|
||||||
import type { ChildBedMapEnum } from "../../bookingWidget/enums"
|
import type { ChildBedMapEnum } from "../../bookingWidget/enums"
|
||||||
|
import type { BookingSearchType } from "../booking"
|
||||||
|
|
||||||
export interface Child {
|
export interface Child {
|
||||||
bed: ChildBedMapEnum
|
bed: ChildBedMapEnum
|
||||||
@@ -15,21 +16,20 @@ export interface Room {
|
|||||||
adults: number
|
adults: number
|
||||||
bookingCode?: string
|
bookingCode?: string
|
||||||
childrenInRoom?: Child[]
|
childrenInRoom?: Child[]
|
||||||
counterRateCode: string
|
counterRateCode?: string
|
||||||
packages?: PackageEnum[]
|
packages?: PackageEnum[]
|
||||||
rateCode: string
|
rateCode?: string
|
||||||
roomTypeCode: string
|
roomTypeCode?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectRateSearchParams {
|
export type SelectRateBooking = {
|
||||||
bookingCode?: string
|
bookingCode?: string
|
||||||
city?: string
|
city?: string
|
||||||
errorCode?: string
|
|
||||||
fromDate: string
|
fromDate: string
|
||||||
hotelId: string
|
hotelId: string
|
||||||
rooms: Room[]
|
rooms: Room[]
|
||||||
|
searchType?: BookingSearchType
|
||||||
toDate: string
|
toDate: string
|
||||||
searchType?: "redemption"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Rate = {
|
export type Rate = {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import type { CurrencyEnum } from "@/types/enums/currency"
|
import type { CurrencyEnum } from "@/types/enums/currency"
|
||||||
import type { Packages } from "@/types/requests/packages"
|
import type { Packages } from "@/types/requests/packages"
|
||||||
import type { RoomState } from "@/types/stores/enter-details"
|
import type { RoomState } from "@/types/stores/enter-details"
|
||||||
import type { RoomPrice, RoomRate } from "./enterDetails/details"
|
import type {
|
||||||
|
DetailsBooking,
|
||||||
|
RoomPrice,
|
||||||
|
RoomRate,
|
||||||
|
} from "./enterDetails/details"
|
||||||
import type { Price } from "./price"
|
import type { Price } from "./price"
|
||||||
import type { Child, SelectRateSearchParams } from "./selectRate/selectRate"
|
import type { Child, SelectRateBooking } from "./selectRate/selectRate"
|
||||||
|
|
||||||
export type RoomsData = {
|
export type RoomsData = {
|
||||||
rateDetails: string[] | undefined
|
rateDetails: string[] | undefined
|
||||||
@@ -20,7 +24,7 @@ export interface SummaryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EnterDetailsSummaryProps {
|
export interface EnterDetailsSummaryProps {
|
||||||
booking: SelectRateSearchParams
|
booking: DetailsBooking
|
||||||
isMember: boolean
|
isMember: boolean
|
||||||
totalPrice: Price
|
totalPrice: Price
|
||||||
vat: number
|
vat: number
|
||||||
@@ -30,7 +34,7 @@ export interface EnterDetailsSummaryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectRateSummaryProps {
|
export interface SelectRateSummaryProps {
|
||||||
booking: SelectRateSearchParams
|
booking: SelectRateBooking
|
||||||
isMember: boolean
|
isMember: boolean
|
||||||
totalPrice: Price
|
totalPrice: Price
|
||||||
vat: number
|
vat: number
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
import type { PageContentTypeEnum } from "./requests/contentType"
|
import type { PageContentTypeEnum } from "./requests/contentType"
|
||||||
|
|
||||||
|
export type NextSearchParams = { [key: string]: string | string[] | undefined }
|
||||||
|
|
||||||
export type SearchParams<S = {}> = {
|
export type SearchParams<S = {}> = {
|
||||||
searchParams: Promise<S & { [key: string]: string }>
|
searchParams: Promise<S & { [key: string]: string }>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Room } from "@/types/providers/details/room"
|
import type { Room } from "@/types/providers/details/room"
|
||||||
import type { SafeUser } from "@/types/user"
|
import type { SafeUser } from "@/types/user"
|
||||||
import type { BreakfastPackages } from "../components/hotelReservation/breakfast"
|
import type { BreakfastPackages } from "../components/hotelReservation/breakfast"
|
||||||
import type { SelectRateSearchParams } from "../components/hotelReservation/selectRate/selectRate"
|
import type { DetailsBooking } from "../components/hotelReservation/enterDetails/details"
|
||||||
|
|
||||||
export interface DetailsProviderProps extends React.PropsWithChildren {
|
export interface DetailsProviderProps extends React.PropsWithChildren {
|
||||||
booking: SelectRateSearchParams
|
booking: DetailsBooking
|
||||||
breakfastPackages: BreakfastPackages
|
breakfastPackages: BreakfastPackages
|
||||||
rooms: Room[]
|
rooms: Room[]
|
||||||
searchParamsStr: string
|
searchParamsStr: string
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Room } from "@/types/hotel"
|
import type { Room } from "@/types/hotel"
|
||||||
import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
|
import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||||
import type { SelectRateSearchParams } from "../components/hotelReservation/selectRate/selectRate"
|
import type { SelectRateBooking } from "../components/hotelReservation/selectRate/selectRate"
|
||||||
import type { AvailabilityError } from "../stores/rates"
|
import type { AvailabilityError } from "../stores/rates"
|
||||||
|
|
||||||
export interface RatesProviderProps extends React.PropsWithChildren {
|
export interface RatesProviderProps extends React.PropsWithChildren {
|
||||||
booking: SelectRateSearchParams
|
booking: SelectRateBooking
|
||||||
hotelType: string | undefined
|
hotelType: string | undefined
|
||||||
roomCategories: Room[]
|
roomCategories: Room[]
|
||||||
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
|
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
|
||||||
|
|||||||
@@ -7,19 +7,17 @@ import type {
|
|||||||
BedTypeSelection,
|
BedTypeSelection,
|
||||||
} from "@/types/components/hotelReservation/enterDetails/bedType"
|
} from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||||
import type {
|
import type {
|
||||||
|
DetailsBooking,
|
||||||
DetailsSchema,
|
DetailsSchema,
|
||||||
MultiroomDetailsSchema,
|
MultiroomDetailsSchema,
|
||||||
RoomPrice,
|
RoomPrice,
|
||||||
RoomRate,
|
RoomRate,
|
||||||
SignedInDetailsSchema,
|
SignedInDetailsSchema,
|
||||||
} from "@/types/components/hotelReservation/enterDetails/details"
|
} from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
import type { CurrencyEnum } from "@/types/enums/currency"
|
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import type { StepEnum } from "@/types/enums/step"
|
import type { StepEnum } from "@/types/enums/step"
|
||||||
import type { Price } from "../components/hotelReservation/price"
|
import type { Price } from "../components/hotelReservation/price"
|
||||||
import type {
|
import type { CurrencyEnum } from "../enums/currency"
|
||||||
Child,
|
|
||||||
SelectRateSearchParams,
|
|
||||||
} from "../components/hotelReservation/selectRate/selectRate"
|
|
||||||
import type { Packages } from "../requests/packages"
|
import type { Packages } from "../requests/packages"
|
||||||
|
|
||||||
export interface InitialRoomData {
|
export interface InitialRoomData {
|
||||||
@@ -78,7 +76,7 @@ export interface RoomState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type InitialState = {
|
export type InitialState = {
|
||||||
booking: SelectRateSearchParams
|
booking: DetailsBooking
|
||||||
rooms: InitialRoomData[]
|
rooms: InitialRoomData[]
|
||||||
vat: number
|
vat: number
|
||||||
}
|
}
|
||||||
@@ -92,7 +90,7 @@ export interface DetailsState {
|
|||||||
addPreSubmitCallback: (name: string, callback: () => void) => void
|
addPreSubmitCallback: (name: string, callback: () => void) => void
|
||||||
}
|
}
|
||||||
availableBeds: Record<string, number>
|
availableBeds: Record<string, number>
|
||||||
booking: SelectRateSearchParams
|
booking: DetailsBooking
|
||||||
breakfastPackages: BreakfastPackages
|
breakfastPackages: BreakfastPackages
|
||||||
canProceedToPayment: boolean
|
canProceedToPayment: boolean
|
||||||
isSubmitting: boolean
|
isSubmitting: boolean
|
||||||
@@ -107,6 +105,6 @@ export interface DetailsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PersistedState = {
|
export type PersistedState = {
|
||||||
booking: SelectRateSearchParams
|
booking: DetailsBooking
|
||||||
rooms: RoomState[]
|
rooms: RoomState[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { DefaultFilterOptions } from "@/types/components/hotelReservation/s
|
|||||||
import type {
|
import type {
|
||||||
Rate,
|
Rate,
|
||||||
Room as RoomBooking,
|
Room as RoomBooking,
|
||||||
SelectRateSearchParams,
|
SelectRateBooking,
|
||||||
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import type { CurrencyEnum } from "@/types/enums/currency"
|
import type { CurrencyEnum } from "@/types/enums/currency"
|
||||||
import type { Room } from "@/types/hotel"
|
import type { Room } from "@/types/hotel"
|
||||||
@@ -54,7 +54,7 @@ export interface SelectedRoom {
|
|||||||
|
|
||||||
export interface RatesState {
|
export interface RatesState {
|
||||||
activeRoom: number
|
activeRoom: number
|
||||||
booking: SelectRateSearchParams
|
booking: SelectRateBooking
|
||||||
hotelType: string | undefined
|
hotelType: string | undefined
|
||||||
isRedemptionBooking: boolean
|
isRedemptionBooking: boolean
|
||||||
packageOptions: DefaultFilterOptions[]
|
packageOptions: DefaultFilterOptions[]
|
||||||
|
|||||||
@@ -5,16 +5,9 @@ import { getLocations } from "@/lib/trpc/memoizedRequests"
|
|||||||
|
|
||||||
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
||||||
import { safeTry } from "@/utils/safeTry"
|
import { safeTry } from "@/utils/safeTry"
|
||||||
import { type SelectHotelParams } from "@/utils/url"
|
|
||||||
|
|
||||||
import type {
|
import type { BookingSearchType } from "@/types/components/hotelReservation/booking"
|
||||||
AlternativeHotelsSearchParams,
|
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
SelectHotelSearchParams,
|
|
||||||
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
|
||||||
import type {
|
|
||||||
Child,
|
|
||||||
SelectRateSearchParams,
|
|
||||||
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
||||||
import {
|
import {
|
||||||
type HotelLocation,
|
type HotelLocation,
|
||||||
isHotelLocation,
|
isHotelLocation,
|
||||||
@@ -24,41 +17,44 @@ import {
|
|||||||
export type ChildrenInRoom = (Child[] | null)[] | null
|
export type ChildrenInRoom = (Child[] | null)[] | null
|
||||||
export type ChildrenInRoomString = (string | null)[] | null
|
export type ChildrenInRoomString = (string | null)[] | null
|
||||||
|
|
||||||
interface HotelSearchDetails<T> {
|
interface HotelSearchDetails {
|
||||||
adultsInRoom: number[]
|
adultsInRoom: number[]
|
||||||
bookingCode?: string
|
bookingCode?: string
|
||||||
childrenInRoom: ChildrenInRoom
|
childrenInRoom: ChildrenInRoom
|
||||||
childrenInRoomString: ChildrenInRoomString
|
childrenInRoomString: ChildrenInRoomString
|
||||||
city: Location | null
|
city: Location | null
|
||||||
|
cityName: string | undefined
|
||||||
hotel: HotelLocation | null
|
hotel: HotelLocation | null
|
||||||
noOfRooms: number
|
noOfRooms: number
|
||||||
redemption?: boolean
|
redemption?: boolean
|
||||||
selectHotelParams: SelectHotelParams<T> & { city: string | undefined }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHotelSearchDetails<
|
export async function getHotelSearchDetails(
|
||||||
T extends
|
params: {
|
||||||
| SelectHotelSearchParams
|
hotelId?: string
|
||||||
| SelectRateSearchParams
|
city?: string
|
||||||
| AlternativeHotelsSearchParams,
|
rooms?: {
|
||||||
>(
|
adults: number
|
||||||
selectHotelParams: SelectHotelParams<T>,
|
childrenInRoom?: Child[]
|
||||||
|
}[]
|
||||||
|
bookingCode?: string
|
||||||
|
searchType?: BookingSearchType
|
||||||
|
},
|
||||||
isAlternativeHotels?: boolean
|
isAlternativeHotels?: boolean
|
||||||
): Promise<HotelSearchDetails<T> | null> {
|
): Promise<HotelSearchDetails | null> {
|
||||||
const [locations, error] = await safeTry(getLocations())
|
const [locations, error] = await safeTry(getLocations())
|
||||||
if (!locations || error) {
|
if (!locations || error) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const hotel =
|
const hotel = params.hotelId
|
||||||
("hotelId" in selectHotelParams &&
|
? ((locations.find(
|
||||||
(locations.find(
|
|
||||||
(location) =>
|
(location) =>
|
||||||
isHotelLocation(location) &&
|
isHotelLocation(location) &&
|
||||||
"operaId" in location &&
|
"operaId" in location &&
|
||||||
location.operaId === selectHotelParams.hotelId
|
location.operaId === params.hotelId
|
||||||
) as HotelLocation | undefined)) ||
|
) as HotelLocation | undefined) ?? null)
|
||||||
null
|
: null
|
||||||
|
|
||||||
if (isAlternativeHotels && !hotel) {
|
if (isAlternativeHotels && !hotel) {
|
||||||
return notFound()
|
return notFound()
|
||||||
@@ -66,16 +62,13 @@ export async function getHotelSearchDetails<
|
|||||||
|
|
||||||
const cityName = isAlternativeHotels
|
const cityName = isAlternativeHotels
|
||||||
? hotel?.relationships.city.name
|
? hotel?.relationships.city.name
|
||||||
: "city" in selectHotelParams
|
: params.city
|
||||||
? (selectHotelParams.city as string | undefined)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const city =
|
const city = cityName
|
||||||
(typeof cityName === "string" &&
|
? (locations.find(
|
||||||
locations.find(
|
|
||||||
(location) => location.name.toLowerCase() === cityName.toLowerCase()
|
(location) => location.name.toLowerCase() === cityName.toLowerCase()
|
||||||
)) ||
|
) ?? null)
|
||||||
null
|
: null
|
||||||
|
|
||||||
if (!city && !hotel) return notFound()
|
if (!city && !hotel) return notFound()
|
||||||
if (isAlternativeHotels && (!city || !hotel)) return notFound()
|
if (isAlternativeHotels && (!city || !hotel)) return notFound()
|
||||||
@@ -84,7 +77,7 @@ export async function getHotelSearchDetails<
|
|||||||
let childrenInRoom: ChildrenInRoom = null
|
let childrenInRoom: ChildrenInRoom = null
|
||||||
let childrenInRoomString: ChildrenInRoomString = null
|
let childrenInRoomString: ChildrenInRoomString = null
|
||||||
|
|
||||||
const { rooms } = selectHotelParams
|
const { rooms } = params
|
||||||
|
|
||||||
if (rooms?.length) {
|
if (rooms?.length) {
|
||||||
adultsInRoom = rooms.map((room) => room.adults ?? 0)
|
adultsInRoom = rooms.map((room) => room.adults ?? 0)
|
||||||
@@ -97,13 +90,13 @@ export async function getHotelSearchDetails<
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
adultsInRoom,
|
adultsInRoom,
|
||||||
bookingCode: selectHotelParams.bookingCode ?? undefined,
|
bookingCode: params.bookingCode ?? undefined,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
childrenInRoomString,
|
childrenInRoomString,
|
||||||
city,
|
city,
|
||||||
|
cityName,
|
||||||
hotel,
|
hotel,
|
||||||
noOfRooms: rooms?.length ?? 0,
|
noOfRooms: rooms?.length ?? 0,
|
||||||
redemption: selectHotelParams.searchType === REDEMPTION,
|
redemption: params.searchType === REDEMPTION,
|
||||||
selectHotelParams: { city: cityName, ...selectHotelParams },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
509
apps/scandic-web/utils/searchParams.test.ts
Normal file
509
apps/scandic-web/utils/searchParams.test.ts
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
import { describe, expect, test } from "@jest/globals"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { parseSearchParams, serializeSearchParams } from "./searchParams"
|
||||||
|
|
||||||
|
describe("Parse search params", () => {
|
||||||
|
test("with flat values", () => {
|
||||||
|
const searchParams = getSearchParams("city=stockholm&hotel=123")
|
||||||
|
const result = parseSearchParams(searchParams)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
city: "stockholm",
|
||||||
|
hotel: "123",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with comma separated array", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"filter=1831,1383,971,1607&packages=ABC,XYZ"
|
||||||
|
)
|
||||||
|
const result = parseSearchParams(searchParams, {
|
||||||
|
typeHints: {
|
||||||
|
packages: "COMMA_SEPARATED_ARRAY",
|
||||||
|
filter: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
filter: ["1831", "1383", "971", "1607"],
|
||||||
|
packages: ["ABC", "XYZ"],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with comma separated array with single value", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"details.packages=ABC&filter=1831&rooms[0].packages=XYZ"
|
||||||
|
)
|
||||||
|
const result = parseSearchParams(searchParams, {
|
||||||
|
typeHints: {
|
||||||
|
filter: "COMMA_SEPARATED_ARRAY",
|
||||||
|
packages: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
filter: ["1831"],
|
||||||
|
details: {
|
||||||
|
packages: ["ABC"],
|
||||||
|
},
|
||||||
|
rooms: [
|
||||||
|
{
|
||||||
|
packages: ["XYZ"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with nested object", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"room.details.adults=1&room.ratecode=ABC&room.details.children=2&room.filters=1,2,3,4"
|
||||||
|
)
|
||||||
|
const result = parseSearchParams(searchParams, {
|
||||||
|
typeHints: {
|
||||||
|
filters: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(result).toEqual({
|
||||||
|
room: {
|
||||||
|
ratecode: "ABC",
|
||||||
|
filters: ["1", "2", "3", "4"],
|
||||||
|
details: {
|
||||||
|
adults: "1",
|
||||||
|
children: "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with array of objects", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"room[0].adults=1&room[0].ratecode=ABC&room[1].adults=2&room[1].ratecode=DEF"
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = parseSearchParams(searchParams)
|
||||||
|
expect(result).toEqual({
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
ratecode: "ABC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
ratecode: "DEF",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with array defined out of order", () => {
|
||||||
|
const searchParams = getSearchParams("room[1].adults=1&room[0].adults=2")
|
||||||
|
|
||||||
|
const result = parseSearchParams(searchParams)
|
||||||
|
expect(result).toEqual({
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with nested array of objects", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"room[0].adults=1&room[0].child[0].age=2&room[1].adults=2&room[1].child[0].age=3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = parseSearchParams(searchParams)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
age: "2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
age: "3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("can handle array syntax with primitive values", () => {
|
||||||
|
const searchParams = getSearchParams("room[1]=1&room[0]=2")
|
||||||
|
const result = parseSearchParams(searchParams)
|
||||||
|
expect(result).toEqual({
|
||||||
|
room: ["2", "1"],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("can rename search param keys", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"city=stockholm&hotel=123&room[0].adults=1&room[0].child[0].age=2&room[1].adults=2&room[1].child[0].age=3"
|
||||||
|
)
|
||||||
|
const result = parseSearchParams(searchParams, {
|
||||||
|
keyRenameMap: {
|
||||||
|
hotel: "hotelId",
|
||||||
|
room: "rooms",
|
||||||
|
age: "childAge",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
city: "stockholm",
|
||||||
|
hotelId: "123",
|
||||||
|
rooms: [
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
childAge: "2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
childAge: "3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with schema validation", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"room[0].adults=1&room[0].child[0].age=2&room[1].adults=2&room[1].child[0].age=3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = parseSearchParams(searchParams, {
|
||||||
|
schema: z.object({
|
||||||
|
room: z.array(
|
||||||
|
z.object({
|
||||||
|
adults: z.string(),
|
||||||
|
child: z.array(
|
||||||
|
z.object({
|
||||||
|
age: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
expect(result).toEqual({
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
age: "2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
age: "3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("throws when schema validation fails", () => {
|
||||||
|
const searchParams = getSearchParams("city=stockholm")
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
parseSearchParams(searchParams, {
|
||||||
|
schema: z.object({
|
||||||
|
city: z.string(),
|
||||||
|
hotel: z.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with value coercion", () => {
|
||||||
|
const searchParams = getSearchParams(
|
||||||
|
"room[0].adults=1&room[0].enabled=true"
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = parseSearchParams(searchParams, {
|
||||||
|
schema: z.object({
|
||||||
|
room: z.array(
|
||||||
|
z.object({
|
||||||
|
adults: z.coerce.number(),
|
||||||
|
enabled: z.coerce.boolean(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
expect(result).toEqual({
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: 1,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Serialize search params", () => {
|
||||||
|
test("with flat values", () => {
|
||||||
|
const obj = {
|
||||||
|
city: "stockholm",
|
||||||
|
hotel: "123",
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj)
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"city=stockholm&hotel=123"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with comma separated array", () => {
|
||||||
|
const obj = {
|
||||||
|
filter: ["1831", "1383", "971", "1607"],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj, {
|
||||||
|
typeHints: {
|
||||||
|
filter: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"filter=1831,1383,971,1607"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with comma separated array with single value", () => {
|
||||||
|
const obj = {
|
||||||
|
details: {
|
||||||
|
packages: ["ABC"],
|
||||||
|
},
|
||||||
|
filter: ["1831"],
|
||||||
|
rooms: [
|
||||||
|
{
|
||||||
|
packages: ["XYZ"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj, {
|
||||||
|
typeHints: {
|
||||||
|
filter: "COMMA_SEPARATED_ARRAY",
|
||||||
|
packages: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"details.packages=ABC&filter=1831&rooms[0].packages=XYZ"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with nested object", () => {
|
||||||
|
const obj = {
|
||||||
|
room: {
|
||||||
|
ratecode: "ABC",
|
||||||
|
filters: ["1", "2", "3", "4"],
|
||||||
|
details: {
|
||||||
|
adults: "1",
|
||||||
|
children: "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj, {
|
||||||
|
typeHints: {
|
||||||
|
filters: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"room.ratecode=ABC&room.filters=1,2,3,4&room.details.adults=1&room.details.children=2"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with array of objects", () => {
|
||||||
|
const obj = {
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
ratecode: "ABC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
ratecode: "DEF",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj)
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"room[0].adults=1&room[0].ratecode=ABC&room[1].adults=2&room[1].ratecode=DEF"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with nested array of objects", () => {
|
||||||
|
const obj = {
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
age: "2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
age: "3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj)
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"room[0].adults=1&room[0].child[0].age=2&room[1].adults=2&room[1].child[0].age=3"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("can handle array syntax with primitive values", () => {
|
||||||
|
const obj = {
|
||||||
|
room: ["2", "1"],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj)
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual("room[0]=2&room[1]=1")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("can rename search param keys", () => {
|
||||||
|
const obj = {
|
||||||
|
city: "stockholm",
|
||||||
|
hotelId: "123",
|
||||||
|
rooms: [
|
||||||
|
{
|
||||||
|
adults: "1",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
childAge: "2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: "2",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
childAge: "3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj, {
|
||||||
|
keyRenameMap: {
|
||||||
|
hotelId: "hotel",
|
||||||
|
rooms: "room",
|
||||||
|
childAge: "age",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"city=stockholm&hotel=123&room[0].adults=1&room[0].child[0].age=2&room[1].adults=2&room[1].child[0].age=3"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with initial search params", () => {
|
||||||
|
const initialSearchParams = new URLSearchParams("city=stockholm&hotel=123")
|
||||||
|
const obj = {
|
||||||
|
hotel: "456",
|
||||||
|
filter: ["1831", "1383"],
|
||||||
|
packages: ["ABC"],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj, {
|
||||||
|
initialSearchParams,
|
||||||
|
typeHints: {
|
||||||
|
packages: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"city=stockholm&hotel=456&filter[0]=1831&filter[1]=1383&packages=ABC"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Parse serialized search params", () => {
|
||||||
|
test("should return the same object", () => {
|
||||||
|
const obj = {
|
||||||
|
city: "stockholm",
|
||||||
|
hotelId: "123",
|
||||||
|
filter: ["1831", "1383", "971", "1607"],
|
||||||
|
details: {
|
||||||
|
packages: ["ABC"],
|
||||||
|
},
|
||||||
|
rooms: [
|
||||||
|
{
|
||||||
|
packages: ["XYZ"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchParams = serializeSearchParams(obj, {
|
||||||
|
keyRenameMap: {
|
||||||
|
hotelId: "hotel",
|
||||||
|
rooms: "room",
|
||||||
|
},
|
||||||
|
typeHints: {
|
||||||
|
filter: "COMMA_SEPARATED_ARRAY",
|
||||||
|
packages: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchParamsObj = searchParamsToObject(searchParams)
|
||||||
|
const result = parseSearchParams(searchParamsObj, {
|
||||||
|
keyRenameMap: {
|
||||||
|
hotel: "hotelId",
|
||||||
|
room: "rooms",
|
||||||
|
},
|
||||||
|
typeHints: {
|
||||||
|
filter: "COMMA_SEPARATED_ARRAY",
|
||||||
|
packages: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Simulates what Next does behind the scenes for search params
|
||||||
|
const getSearchParams = (input: string) => {
|
||||||
|
const searchParams = new URLSearchParams(input)
|
||||||
|
return searchParamsToObject(searchParams)
|
||||||
|
}
|
||||||
|
const searchParamsToObject = (searchParams: URLSearchParams) => {
|
||||||
|
const obj: Record<string, any> = {}
|
||||||
|
for (const [key, value] of searchParams.entries()) {
|
||||||
|
obj[key] = value
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
209
apps/scandic-web/utils/searchParams.ts
Normal file
209
apps/scandic-web/utils/searchParams.ts
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import type { z } from "zod"
|
||||||
|
|
||||||
|
import type { NextSearchParams } from "@/types/params"
|
||||||
|
|
||||||
|
type ParseOptions<T extends z.ZodRawShape> = {
|
||||||
|
keyRenameMap?: Record<string, string>
|
||||||
|
typeHints?: Record<string, "COMMA_SEPARATED_ARRAY">
|
||||||
|
schema?: z.ZodObject<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseOptionsWithSchema<T extends z.ZodRawShape> = ParseOptions<T> & {
|
||||||
|
schema: z.ZodObject<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
// This ensures that the return type is correct when a schema is provided
|
||||||
|
export function parseSearchParams<T extends z.ZodRawShape>(
|
||||||
|
searchParams: NextSearchParams,
|
||||||
|
options: ParseOptionsWithSchema<T>
|
||||||
|
): z.infer<typeof options.schema>
|
||||||
|
export function parseSearchParams<T extends z.ZodRawShape>(
|
||||||
|
searchParams: NextSearchParams,
|
||||||
|
options?: ParseOptions<T>
|
||||||
|
): Record<string, any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses URL search parameters into a structured object.
|
||||||
|
* This function can handle nested objects, arrays, and type validation/transformation using Zod schema.
|
||||||
|
*
|
||||||
|
* @param searchParams - The object to parse
|
||||||
|
* @param options.keyRenameMap - Optional mapping of keys to rename, ie { "oldKey": "newKey" }
|
||||||
|
* @param options.typeHints - Optional type hints to force certain keys to be treated as arrays
|
||||||
|
* @param options.schema - Pass a Zod schema to validate and transform the parsed search parameters and get a typed return value
|
||||||
|
*
|
||||||
|
* Supported formats:
|
||||||
|
* - Objects: `user.name=John&user.age=30`
|
||||||
|
* - Arrays: `tags[0]=javascript&tags[1]=typescript`
|
||||||
|
* - Arrays of objects: `tags[0].name=javascript&tags[0].age=30`
|
||||||
|
* - Nested arrays: `tags[0].languages[0]=javascript&tags[0].languages[1]=typescript`
|
||||||
|
* - Comma-separated arrays: `tags=javascript,typescript`
|
||||||
|
*
|
||||||
|
* For comma-separated arrays you must use the `typeHints`
|
||||||
|
* option to inform the parser that the key should be treated as an array.
|
||||||
|
*/
|
||||||
|
export function parseSearchParams<T extends z.ZodRawShape>(
|
||||||
|
searchParams: NextSearchParams,
|
||||||
|
options?: ParseOptions<T>
|
||||||
|
) {
|
||||||
|
const entries = Object.entries(searchParams)
|
||||||
|
|
||||||
|
const buildObject = getBuilder(options || {})
|
||||||
|
|
||||||
|
const resultObject: Record<string, any> = {}
|
||||||
|
for (const [key, value] of entries) {
|
||||||
|
const paths = key.split(".")
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
throw new Error(
|
||||||
|
`Arrays from duplicate keys (?a=1&a=2) are not yet supported.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buildObject(resultObject, paths, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.schema) {
|
||||||
|
return options.schema.parse(resultObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultObject
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a higher-order function to avoid passing the options
|
||||||
|
// object every time we recursively call the builder
|
||||||
|
function getBuilder<T extends z.ZodRawShape>(options: ParseOptions<T>) {
|
||||||
|
const keyRenameMap = options.keyRenameMap || {}
|
||||||
|
const typeHints = options.typeHints || {}
|
||||||
|
|
||||||
|
return function buildNestedObject(
|
||||||
|
obj: Record<string, any>,
|
||||||
|
paths: string[],
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
if (paths.length === 0) return
|
||||||
|
|
||||||
|
const path = paths[0]
|
||||||
|
const remainingPaths = paths.slice(1)
|
||||||
|
|
||||||
|
// Extract the key name and optional array index
|
||||||
|
const match = path.match(/^([^\[]+)(?:\[(\d+)\])?$/)
|
||||||
|
if (!match) return
|
||||||
|
const key = keyRenameMap[match[1]] || match[1]
|
||||||
|
const index = match[2] ? parseInt(match[2]) : null
|
||||||
|
|
||||||
|
const forceCommaSeparatedArray = typeHints[key] === "COMMA_SEPARATED_ARRAY"
|
||||||
|
const hasIndex = index !== null
|
||||||
|
|
||||||
|
// If we've reached the last path, set the value
|
||||||
|
if (remainingPaths.length === 0) {
|
||||||
|
// This is either an array or a value that is
|
||||||
|
// forced to be an array by the typeHints
|
||||||
|
if (hasIndex || forceCommaSeparatedArray) {
|
||||||
|
if (isNotArray(obj[key])) obj[key] = []
|
||||||
|
|
||||||
|
if (!hasIndex || forceCommaSeparatedArray) {
|
||||||
|
obj[key] = value.split(",")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[key][index] = value
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[key] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasIndex) {
|
||||||
|
// If the key is an array, ensure array and element at index exists
|
||||||
|
if (isNotArray(obj[key])) obj[key] = []
|
||||||
|
if (!obj[key][index]) obj[key][index] = {}
|
||||||
|
|
||||||
|
buildNestedObject(obj[key][index], remainingPaths, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, it should be an object
|
||||||
|
if (!obj[key]) obj[key] = {}
|
||||||
|
buildNestedObject(obj[key], remainingPaths, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotArray(value: any) {
|
||||||
|
return !value || typeof value !== "object" || !Array.isArray(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SerializeOptions = {
|
||||||
|
keyRenameMap?: Record<string, string>
|
||||||
|
typeHints?: Record<string, "COMMA_SEPARATED_ARRAY">
|
||||||
|
initialSearchParams?: URLSearchParams
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes an object into URL search parameters.
|
||||||
|
*
|
||||||
|
* @param obj - The object to serialize
|
||||||
|
* @param options.keyRenameMap - Optional mapping of keys to rename, ie { "oldKey": "newKey" }
|
||||||
|
* @param options.typeHints - Optional type hints to force certain keys to be treated as comma separated arrays
|
||||||
|
* @returns URLSearchParams - The serialized URL search parameters
|
||||||
|
*/
|
||||||
|
export function serializeSearchParams(
|
||||||
|
obj: Record<string, any>,
|
||||||
|
options?: SerializeOptions
|
||||||
|
): URLSearchParams {
|
||||||
|
const params = new URLSearchParams(options?.initialSearchParams)
|
||||||
|
|
||||||
|
const keyRenameMap = options?.keyRenameMap || {}
|
||||||
|
const typeHints = options?.typeHints || {}
|
||||||
|
|
||||||
|
function buildParams(obj: unknown, prefix: string) {
|
||||||
|
if (obj === null || obj === undefined) return
|
||||||
|
|
||||||
|
if (!isRecord(obj)) {
|
||||||
|
params.set(prefix, String(obj))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
const value = obj[key]
|
||||||
|
|
||||||
|
const renamedKey = keyRenameMap[key] || key
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (typeHints[key] === "COMMA_SEPARATED_ARRAY") {
|
||||||
|
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
|
||||||
|
params.set(paramKey, value.join(","))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value.forEach((item, index) => {
|
||||||
|
const indexedKey = `${renamedKey}[${index}]`
|
||||||
|
const paramKey = prefix ? `${prefix}.${indexedKey}` : indexedKey
|
||||||
|
buildParams(item, paramKey)
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
buildParams(value, paramKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
params.set(paramKey, String(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildParams(obj, "")
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null
|
||||||
|
}
|
||||||
@@ -1,10 +1,20 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { bookingSearchTypes } from "@/constants/booking"
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
import type { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
import { parseSearchParams, serializeSearchParams } from "./searchParams"
|
||||||
|
|
||||||
|
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
||||||
|
import type { DetailsBooking } from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
|
import type { SelectHotelBooking } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||||
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
import type {
|
import type {
|
||||||
Child,
|
|
||||||
Room,
|
Room,
|
||||||
|
SelectRateBooking,
|
||||||
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
|
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||||
|
import type { NextSearchParams } from "@/types/params"
|
||||||
|
|
||||||
export function removeMultipleSlashes(pathname: string) {
|
export function removeMultipleSlashes(pathname: string) {
|
||||||
return pathname.replaceAll(/\/\/+/g, "/")
|
return pathname.replaceAll(/\/\/+/g, "/")
|
||||||
@@ -20,151 +30,213 @@ export function removeTrailingSlash(pathname: string) {
|
|||||||
|
|
||||||
type PartialRoom = { rooms?: Partial<Room>[] }
|
type PartialRoom = { rooms?: Partial<Room>[] }
|
||||||
|
|
||||||
const keyedSearchParams = new Map([
|
|
||||||
["room", "rooms"],
|
|
||||||
["ratecode", "rateCode"],
|
|
||||||
["counterratecode", "counterRateCode"],
|
|
||||||
["roomtype", "roomTypeCode"],
|
|
||||||
["fromdate", "fromDate"],
|
|
||||||
["todate", "toDate"],
|
|
||||||
["hotel", "hotelId"],
|
|
||||||
["child", "childrenInRoom"],
|
|
||||||
["searchtype", "searchType"],
|
|
||||||
])
|
|
||||||
|
|
||||||
export type SelectHotelParams<T> = Omit<T, "hotel"> & {
|
export type SelectHotelParams<T> = Omit<T, "hotel"> & {
|
||||||
hotelId: string
|
hotelId: string
|
||||||
} & PartialRoom
|
} & PartialRoom
|
||||||
|
|
||||||
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 searchParamsToRecord(searchParams: URLSearchParams) {
|
export function searchParamsToRecord(searchParams: URLSearchParams) {
|
||||||
return Object.fromEntries(searchParams.entries())
|
return Object.fromEntries(searchParams.entries())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertSearchParamsToObj<T extends PartialRoom>(
|
const keyRenameMap = {
|
||||||
searchParams: Record<string, string>
|
room: "rooms",
|
||||||
): SelectHotelParams<T> {
|
ratecode: "rateCode",
|
||||||
const searchParamsObject = Object.entries(searchParams).reduce<
|
counterratecode: "counterRateCode",
|
||||||
SelectHotelParams<T>
|
roomtype: "roomTypeCode",
|
||||||
>((acc, [key, value]) => {
|
fromdate: "fromDate",
|
||||||
// The params are sometimes indexed with a number (for ex: `room[0].adults`),
|
todate: "toDate",
|
||||||
// so we need to split them by . or []
|
hotel: "hotelId",
|
||||||
const keys = key.replace(/\]/g, "").split(/\[|\./)
|
child: "childrenInRoom",
|
||||||
const firstKey = getKeyFromSearchParam(keys[0])
|
searchtype: "searchType",
|
||||||
|
}
|
||||||
|
const adultsSchema = z.coerce.number().min(1).max(6).catch(0)
|
||||||
|
const childAgeSchema = z.coerce.number().catch(-1)
|
||||||
|
const childBedSchema = z.coerce.number().catch(-1)
|
||||||
|
const searchTypeSchema = z.enum(bookingSearchTypes).optional().catch(undefined)
|
||||||
|
|
||||||
// Room is a special case since it is an array, so we need to handle it separately
|
export function parseBookingWidgetSearchParams(
|
||||||
if (firstKey === "rooms") {
|
searchParams: NextSearchParams
|
||||||
// Rooms are always indexed with a number, so we need to extract the index
|
): BookingWidgetSearchData {
|
||||||
const index = Number(keys[1])
|
try {
|
||||||
const roomObject =
|
const result = parseSearchParams(searchParams, {
|
||||||
acc.rooms && Array.isArray(acc.rooms) ? acc.rooms : (acc.rooms = [])
|
keyRenameMap,
|
||||||
|
schema: z.object({
|
||||||
|
city: z.string().optional(),
|
||||||
|
hotelId: z.string().optional(),
|
||||||
|
fromDate: z.string().optional(),
|
||||||
|
toDate: z.string().optional(),
|
||||||
|
bookingCode: z.string().optional(),
|
||||||
|
searchType: searchTypeSchema,
|
||||||
|
rooms: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
adults: adultsSchema,
|
||||||
|
childrenInRoom: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
bed: childBedSchema,
|
||||||
|
age: childAgeSchema,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.default([]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
const roomObjectKey = getKeyFromSearchParam(keys[2]) as keyof Room
|
return result
|
||||||
|
} catch (error) {
|
||||||
if (!roomObject[index]) {
|
console.log("[URL] Error parsing search params for booking widget:", error)
|
||||||
roomObject[index] = {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 === "childrenInRoom") {
|
|
||||||
const childIndex = Number(keys[3])
|
|
||||||
const childKey = keys[4] as keyof Child
|
|
||||||
|
|
||||||
if (
|
|
||||||
!("childrenInRoom" in roomObject[index]) ||
|
|
||||||
!Array.isArray(roomObject[index].childrenInRoom)
|
|
||||||
) {
|
|
||||||
roomObject[index].childrenInRoom = []
|
|
||||||
}
|
|
||||||
|
|
||||||
roomObject[index].childrenInRoom![childIndex] = {
|
|
||||||
...roomObject[index].childrenInRoom![childIndex],
|
|
||||||
[childKey]: Number(value),
|
|
||||||
}
|
|
||||||
} else if (roomObjectKey === "packages") {
|
|
||||||
roomObject[index].packages = value.split(",") as RoomPackageCodeEnum[]
|
|
||||||
} else {
|
|
||||||
roomObject[index][roomObjectKey] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return { ...acc, [firstKey]: value }
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc
|
|
||||||
}, {} as SelectHotelParams<T>)
|
|
||||||
|
|
||||||
return searchParamsObject
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertObjToSearchParams<T>(
|
export function parseSelectHotelSearchParams(
|
||||||
bookingData: T & PartialRoom,
|
searchParams: NextSearchParams
|
||||||
intitalSearchParams = {} as URLSearchParams
|
): SelectHotelBooking | null {
|
||||||
) {
|
try {
|
||||||
const bookingSearchParams = new URLSearchParams(intitalSearchParams)
|
const result = parseSearchParams(searchParams, {
|
||||||
Object.entries(bookingData).forEach(([key, value]) => {
|
keyRenameMap,
|
||||||
if (key === "rooms") {
|
schema: z.object({
|
||||||
value.forEach((item, index) => {
|
city: z.string(),
|
||||||
if (item?.adults) {
|
hotelId: z.string().optional(),
|
||||||
bookingSearchParams.set(
|
fromDate: z.string(),
|
||||||
`room[${index}].adults`,
|
toDate: z.string(),
|
||||||
item.adults.toString()
|
bookingCode: z.string().optional(),
|
||||||
)
|
searchType: searchTypeSchema,
|
||||||
}
|
rooms: z.array(
|
||||||
if (item?.childrenInRoom) {
|
z.object({
|
||||||
item.childrenInRoom.forEach((child, childIndex) => {
|
adults: adultsSchema,
|
||||||
bookingSearchParams.set(
|
childrenInRoom: z
|
||||||
`room[${index}].child[${childIndex}].age`,
|
.array(
|
||||||
child.age.toString()
|
z.object({
|
||||||
)
|
bed: childBedSchema,
|
||||||
bookingSearchParams.set(
|
age: childAgeSchema,
|
||||||
`room[${index}].child[${childIndex}].bed`,
|
})
|
||||||
child.bed.toString()
|
)
|
||||||
)
|
.optional(),
|
||||||
})
|
})
|
||||||
}
|
),
|
||||||
if (item?.roomTypeCode) {
|
}),
|
||||||
bookingSearchParams.set(`room[${index}].roomtype`, item.roomTypeCode)
|
})
|
||||||
}
|
|
||||||
if (item?.rateCode) {
|
|
||||||
bookingSearchParams.set(`room[${index}].ratecode`, item.rateCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item?.counterRateCode) {
|
return result
|
||||||
bookingSearchParams.set(
|
} catch (error) {
|
||||||
`room[${index}].counterratecode`,
|
console.log("[URL] Error parsing search params for select hotel:", error)
|
||||||
item.counterRateCode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.packages && item.packages.length > 0) {
|
return null
|
||||||
bookingSearchParams.set(
|
}
|
||||||
`room[${index}].packages`,
|
}
|
||||||
item.packages.join(",")
|
|
||||||
)
|
export function parseSelectRateSearchParams(
|
||||||
}
|
searchParams: NextSearchParams
|
||||||
})
|
): SelectRateBooking | null {
|
||||||
} else {
|
try {
|
||||||
bookingSearchParams.set(getSearchParamFromKey(key), value.toString())
|
const result = parseSearchParams(searchParams, {
|
||||||
}
|
keyRenameMap,
|
||||||
|
schema: z.object({
|
||||||
|
city: z.string().optional(),
|
||||||
|
hotelId: z.string(),
|
||||||
|
fromDate: z.string(),
|
||||||
|
toDate: z.string(),
|
||||||
|
searchType: searchTypeSchema,
|
||||||
|
bookingCode: z.string().optional(),
|
||||||
|
rooms: z.array(
|
||||||
|
z.object({
|
||||||
|
adults: adultsSchema,
|
||||||
|
bookingCode: z.string().optional(),
|
||||||
|
counterRateCode: z.string().optional(),
|
||||||
|
rateCode: z.string().optional(),
|
||||||
|
roomTypeCode: z.string().optional(),
|
||||||
|
packages: z
|
||||||
|
.array(
|
||||||
|
z.nativeEnum({
|
||||||
|
...BreakfastPackageEnum,
|
||||||
|
...RoomPackageCodeEnum,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
childrenInRoom: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
bed: childBedSchema,
|
||||||
|
age: childAgeSchema,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[URL] Error parsing search params for select rate:", error)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseDetailsSearchParams(
|
||||||
|
searchParams: NextSearchParams
|
||||||
|
): DetailsBooking | null {
|
||||||
|
const packageEnum = {
|
||||||
|
...BreakfastPackageEnum,
|
||||||
|
...RoomPackageCodeEnum,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = parseSearchParams(searchParams, {
|
||||||
|
keyRenameMap,
|
||||||
|
schema: z.object({
|
||||||
|
city: z.string().optional(),
|
||||||
|
hotelId: z.string(),
|
||||||
|
fromDate: z.string(),
|
||||||
|
toDate: z.string(),
|
||||||
|
searchType: searchTypeSchema,
|
||||||
|
bookingCode: z.string().optional(),
|
||||||
|
rooms: z.array(
|
||||||
|
z.object({
|
||||||
|
adults: adultsSchema,
|
||||||
|
bookingCode: z.string().optional(),
|
||||||
|
counterRateCode: z.string().optional(),
|
||||||
|
rateCode: z.string(),
|
||||||
|
roomTypeCode: z.string(),
|
||||||
|
packages: z.array(z.nativeEnum(packageEnum)).optional(),
|
||||||
|
childrenInRoom: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
bed: childBedSchema,
|
||||||
|
age: childAgeSchema,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[URL] Error parsing search params for details:", error)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reversedKeyRenameMap = Object.fromEntries(
|
||||||
|
Object.entries(keyRenameMap).map(([key, value]) => [value, key])
|
||||||
|
)
|
||||||
|
export function serializeBookingSearchParams(
|
||||||
|
obj: { [key: string]: any },
|
||||||
|
{ initialSearchParams }: { initialSearchParams?: URLSearchParams } = {}
|
||||||
|
) {
|
||||||
|
return serializeSearchParams(obj, {
|
||||||
|
keyRenameMap: reversedKeyRenameMap,
|
||||||
|
initialSearchParams,
|
||||||
})
|
})
|
||||||
|
|
||||||
return bookingSearchParams
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user