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:
Anton Gunnarsson
2025-06-02 13:38:01 +00:00
parent 81887c83ff
commit 03468ad824
49 changed files with 1257 additions and 444 deletions

View File

@@ -3,19 +3,21 @@ import { notFound } from "next/navigation"
import { env } from "@/env/server"
import StartPage from "@/components/ContentType/StartPage"
import { parseBookingWidgetSearchParams } from "@/utils/url"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { PageArgs } from "@/types/params"
import type { NextSearchParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/generateMetadata"
export default async function StartPagePage(
props: PageArgs<{}, BookingWidgetSearchData>
props: PageArgs<{}, NextSearchParams>
) {
const searchParams = await props.searchParams
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return notFound()
}
return <StartPage searchParams={searchParams} />
const booking = parseBookingWidgetSearchParams(searchParams)
return <StartPage booking={booking} />
}

View File

@@ -1,22 +1,23 @@
import { notFound } from "next/navigation"
import { Suspense } from "react"
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
import { MapContainer } from "@/components/MapContainer"
import { convertSearchParamsToObj } from "@/utils/url"
import { parseSelectHotelSearchParams } from "@/utils/url"
import styles from "./page.module.css"
import type { AlternativeHotelsSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import type { LangParams, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function SelectHotelMapPage(
props: PageArgs<LangParams, AlternativeHotelsSearchParams>
props: PageArgs<LangParams, NextSearchParams>
) {
const searchParams = await props.searchParams
const booking =
convertSearchParamsToObj<AlternativeHotelsSearchParams>(searchParams)
const booking = parseSelectHotelSearchParams(searchParams)
if (!booking) return notFound()
return (
<div className={styles.main}>

View File

@@ -13,23 +13,19 @@ import { getTracking } from "@/components/HotelReservation/SelectHotel/tracking"
import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n"
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
import { convertSearchParamsToObj } from "@/utils/url"
import { parseSelectHotelSearchParams } from "@/utils/url"
import type {
AlternativeHotelsSearchParams,
SelectHotelSearchParams,
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { LangParams, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function AlternativeHotelsPage(
props: PageArgs<LangParams, AlternativeHotelsSearchParams>
props: PageArgs<LangParams, NextSearchParams>
) {
const searchParams = await props.searchParams
const params = await props.params
const booking =
convertSearchParamsToObj<AlternativeHotelsSearchParams>(searchParams)
const booking = parseSelectHotelSearchParams(searchParams)
if (!booking) return notFound()
const searchDetails = await getHotelSearchDetails(booking, true)
@@ -42,10 +38,10 @@ export default async function AlternativeHotelsPage(
bookingCode,
childrenInRoom,
city,
cityName,
hotel: isAlternativeFor,
noOfRooms,
redemption,
selectHotelParams,
} = searchDetails
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
@@ -59,16 +55,18 @@ export default async function AlternativeHotelsPage(
// TODO: This needs to be refactored into its
// own functions
const hotels = await getHotels(
selectHotelParams,
const hotels = await getHotels({
fromDate: booking.fromDate,
toDate: booking.toDate,
rooms: booking.rooms,
isAlternativeFor,
bookingCode,
city,
!!redemption
)
redemption: !!redemption,
})
const arrivalDate = new Date(selectHotelParams.fromDate)
const departureDate = new Date(selectHotelParams.toDate)
const arrivalDate = new Date(booking.fromDate)
const departureDate = new Date(booking.toDate)
const isRedemptionAvailability = redemption
? hotels.some(
@@ -92,11 +90,11 @@ export default async function AlternativeHotelsPage(
adultsInRoom,
childrenInRoom,
hotels?.length ?? 0,
selectHotelParams.hotelId,
booking.hotelId,
noOfRooms,
hotels?.[0]?.hotel.address.country,
hotels?.[0]?.hotel.address.city,
selectHotelParams.city,
cityName,
bookingCode,
isBookingCodeRateAvailable,
redemption,

View File

@@ -20,15 +20,14 @@ import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDeta
import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert"
import RoomProvider from "@/providers/Details/RoomProvider"
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
import { convertSearchParamsToObj } from "@/utils/url"
import { parseDetailsSearchParams } from "@/utils/url"
import styles from "./page.module.css"
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { LangParams, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function DetailsPage(
props: PageArgs<LangParams, SelectRateSearchParams>
props: PageArgs<LangParams, NextSearchParams>
) {
const searchParams = await props.searchParams
const params = await props.params
@@ -37,11 +36,12 @@ export default async function DetailsPage(
const selectRoomParams = new URLSearchParams(searchParams)
const { errorCode, ...booking } =
convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
if ("modifyRateIndex" in booking) {
const booking = parseDetailsSearchParams(searchParams)
if (!booking) return notFound()
if (selectRoomParams.has("modifyRateIndex")) {
selectRoomParams.delete("modifyRateIndex")
delete booking.modifyRateIndex
}
if (

View File

@@ -1,24 +1,25 @@
import stringify from "json-stable-stringify-without-jsonify"
import { notFound } from "next/navigation"
import { Suspense } from "react"
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
import { MapContainer } from "@/components/MapContainer"
import { convertSearchParamsToObj } from "@/utils/url"
import { parseSelectHotelSearchParams } from "@/utils/url"
import styles from "./page.module.css"
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import type { LangParams, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function SelectHotelMapPage(
props: PageArgs<LangParams, SelectHotelSearchParams>
props: PageArgs<LangParams, NextSearchParams>
) {
const searchParams = await props.searchParams
const suspenseKey = stringify(searchParams)
const booking =
convertSearchParamsToObj<SelectHotelSearchParams>(searchParams)
const booking = parseSelectHotelSearchParams(searchParams)
if (!booking) return notFound()
return (
<div className={styles.main}>

View File

@@ -12,19 +12,19 @@ import { getHotels } from "@/components/HotelReservation/SelectHotel/helpers"
import { getTracking } from "@/components/HotelReservation/SelectHotel/tracking"
import TrackingSDK from "@/components/TrackingSDK"
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, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function SelectHotelPage(
props: PageArgs<LangParams, SelectHotelSearchParams>
props: PageArgs<LangParams, NextSearchParams>
) {
const searchParams = await props.searchParams
const params = await props.params
const booking =
convertSearchParamsToObj<SelectHotelSearchParams>(searchParams)
const booking = parseSelectHotelSearchParams(searchParams)
if (!booking) return notFound()
const searchDetails = await getHotelSearchDetails(booking)
@@ -35,9 +35,9 @@ export default async function SelectHotelPage(
bookingCode,
childrenInRoom,
city,
cityName,
noOfRooms,
redemption,
selectHotelParams,
} = searchDetails
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
@@ -49,16 +49,18 @@ export default async function SelectHotelPage(
}
}
const hotels = await getHotels(
selectHotelParams,
null,
const hotels = await getHotels({
fromDate: booking.fromDate,
toDate: booking.toDate,
rooms: booking.rooms,
isAlternativeFor: null,
bookingCode,
city,
!!redemption
)
redemption: !!redemption,
})
const arrivalDate = new Date(selectHotelParams.fromDate)
const departureDate = new Date(selectHotelParams.toDate)
const arrivalDate = new Date(booking.fromDate)
const departureDate = new Date(booking.toDate)
const isRedemptionAvailability = redemption
? hotels.some(
@@ -82,11 +84,11 @@ export default async function SelectHotelPage(
adultsInRoom,
childrenInRoom,
hotels?.length ?? 0,
selectHotelParams.hotelId,
booking.hotelId,
noOfRooms,
hotels?.[0]?.hotel.address.country,
hotels?.[0]?.hotel.address.city,
selectHotelParams.city,
cityName,
bookingCode,
isBookingCodeRateAvailable,
redemption,

View File

@@ -3,10 +3,9 @@ import { notFound } from "next/navigation"
import { combineRegExps, rateTypeRegex, REDEMPTION } from "@/constants/booking"
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, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
const singleRoomRateTypes = combineRegExps(
[rateTypeRegex.ARB, rateTypeRegex.VOUCHER],
@@ -14,11 +13,13 @@ const singleRoomRateTypes = combineRegExps(
)
export default async function SelectRatePage(
props: PageArgs<LangParams & { section: string }, SelectRateSearchParams>
props: PageArgs<LangParams & { section: string }, NextSearchParams>
) {
const params = await props.params
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 isRedemption = booking.searchType === REDEMPTION

View File

@@ -2,12 +2,14 @@ import { env } from "@/env/server"
import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests"
import { BookingWidget } from "@/components/BookingWidget"
import { parseBookingWidgetSearchParams } from "@/utils/url"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { PageArgs } from "@/types/params"
import type { NextSearchParams, PageArgs } from "@/types/params"
export default async function BookingWidgetDestinationCityPage(props: PageArgs<{}, BookingWidgetSearchData>) {
const searchParams = await props.searchParams;
export default async function BookingWidgetDestinationCityPage(
props: PageArgs<{}, NextSearchParams>
) {
const searchParams = await props.searchParams
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return null
}
@@ -20,5 +22,7 @@ export default async function BookingWidgetDestinationCityPage(props: PageArgs<{
city: pageData?.city.name ?? "",
}
return <BookingWidget bookingWidgetSearchParams={bookingWidgetSearchParams} />
const booking = parseBookingWidgetSearchParams(bookingWidgetSearchParams)
return <BookingWidget booking={booking} />
}

View File

@@ -3,20 +3,18 @@ import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
import { BookingWidget } from "@/components/BookingWidget"
import { getLang } from "@/i18n/serverContext"
import { parseBookingWidgetSearchParams } from "@/utils/url"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { PageArgs } from "@/types/params"
import type { NextSearchParams, PageArgs } from "@/types/params"
export default async function BookingWidgetHotelPage(
props: PageArgs<{}, BookingWidgetSearchData & { subpage?: string }>
props: PageArgs<{}, NextSearchParams & { subpage?: string }>
) {
const searchParams = await props.searchParams
if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return null
}
const { bookingCode, subpage } = searchParams
const hotelPageData = await getHotelPage()
const hotelData = await getHotel({
hotelId: hotelPageData?.hotel_page_id || "",
@@ -24,6 +22,7 @@ export default async function BookingWidgetHotelPage(
isCardOnlyPayment: false,
})
const subpage = searchParams.subpage
const isMeetingSubpage =
subpage && hotelData?.additionalData.meetingRooms.nameInUrl === subpage
@@ -32,10 +31,12 @@ export default async function BookingWidgetHotelPage(
}
const bookingWidgetSearchParams = {
bookingCode: bookingCode ?? "",
bookingCode: searchParams.bookingCode ?? "",
hotel: hotelData?.hotel.id ?? "",
city: hotelData?.hotel.cityName ?? "",
}
return <BookingWidget bookingWidgetSearchParams={bookingWidgetSearchParams} />
const booking = parseBookingWidgetSearchParams(bookingWidgetSearchParams)
return <BookingWidget booking={booking} />
}

View File

@@ -1,12 +1,14 @@
import { BookingWidget } from "@/components/BookingWidget"
import { parseBookingWidgetSearchParams } from "@/utils/url"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { LangParams, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function BookingWidgetPage(
props: PageArgs<LangParams, BookingWidgetSearchData>
props: PageArgs<LangParams, NextSearchParams>
) {
const searchParams = await props.searchParams
return <BookingWidget bookingWidgetSearchParams={searchParams} />
const booking = parseBookingWidgetSearchParams(searchParams)
return <BookingWidget booking={booking} />
}

View File

@@ -1,16 +1,20 @@
import { env } from "@/env/server"
import { BookingWidget } from "@/components/BookingWidget"
import { parseBookingWidgetSearchParams } from "@/utils/url"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { LangParams, PageArgs } from "@/types/params"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function BookingWidgetPage(props: PageArgs<LangParams, BookingWidgetSearchData>) {
const params = await props.params;
const searchParams = await props.searchParams;
export default async function BookingWidgetPage(
props: PageArgs<LangParams, NextSearchParams>
) {
const params = await props.params
const searchParams = await props.searchParams
if (!env.isLangLive(params.lang)) {
return null
}
return <BookingWidget bookingWidgetSearchParams={searchParams} />
const booking = parseBookingWidgetSearchParams(searchParams)
return <BookingWidget booking={booking} />
}