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

@@ -9,7 +9,7 @@ import { detailsStorageName } from "@/stores/enter-details"
import LoadingSpinner from "@/components/LoadingSpinner"
import { trackPaymentEvent } from "@/utils/tracking"
import { trackEvent } from "@/utils/tracking/base"
import { convertObjToSearchParams } from "@/utils/url"
import { serializeBookingSearchParams } from "@/utils/url"
import { clearGlaSessionStorage, readGlaFromSessionStorage } from "./helpers"
@@ -33,9 +33,11 @@ export default function HandleErrorCallback({
if (bookingData) {
const detailsStorage: PersistedState = JSON.parse(bookingData)
const searchParams = convertObjToSearchParams(
const searchParams = serializeBookingSearchParams(
detailsStorage.booking,
searchObject
{
initialSearchParams: searchObject,
}
)
const glaSessionData = readGlaFromSessionStorage()

View File

@@ -7,14 +7,13 @@ import { useSearchHistory } from "@/hooks/useSearchHistory"
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 { Room } from "@/types/providers/details/room"
import type { Lang } from "@/constants/languages"
import type { SelectHotelParams } from "@/utils/url"
interface TrackingWrapperProps {
booking: SelectHotelParams<SelectRateSearchParams>
booking: DetailsBooking
hotel: Hotel
rooms: Room[]
isMember: boolean

View File

@@ -7,8 +7,8 @@ import { getSpecialRoomType } from "@/utils/specialRoomType"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
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 type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import {
TrackingChannelEnum,
type TrackingSDKAncillaries,
@@ -25,10 +25,9 @@ import type {
Product,
} from "@/types/trpc/routers/hotel/roomAvailability"
import type { Lang } from "@/constants/languages"
import type { SelectHotelParams } from "@/utils/url"
export function getTracking(
booking: SelectHotelParams<SelectRateSearchParams>,
booking: DetailsBooking,
hotel: Hotel,
rooms: Room[],
isMember: boolean,

View File

@@ -37,23 +37,25 @@ export async function SelectHotelMapContainer({
bookingCode,
childrenInRoom,
city,
cityName,
hotel: isAlternativeFor,
noOfRooms,
redemption,
selectHotelParams,
} = searchDetails
if (!city) {
return notFound()
}
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 hotelPins = getHotelPins(hotels)
const filterList = getFiltersFromHotels(hotels)
@@ -62,8 +64,8 @@ export async function SelectHotelMapContainer({
hotel: { address: hotels?.[0]?.hotel?.address.streetAddress },
})
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(
(hotel) => hotel.availability.productType?.redemptions?.length
@@ -83,11 +85,11 @@ export async function SelectHotelMapContainer({
adultsInRoom,
childrenInRoom,
hotels.length,
selectHotelParams.hotelId,
booking.hotelId,
noOfRooms,
hotels?.[0]?.hotel.address.country,
hotels?.[0]?.hotel.address.city,
selectHotelParams.city,
cityName,
bookingCode,
isBookingCodeRateAvailable,
redemption,

View File

@@ -18,7 +18,7 @@ export function getTracking(
adultsInRoom: number[],
childrenInRoom: ChildrenInRoom,
hotelsResult: number,
hotelId: string,
hotelId: string | undefined,
noOfRooms: number,
country: string | undefined,
hotelCity: string | undefined,

View File

@@ -14,17 +14,13 @@ import type {
HotelFilter,
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import type {
AlternativeHotelsSearchParams,
SelectHotelSearchParams,
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { AdditionalData, Hotel } from "@/types/hotel"
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
import type {
HotelLocation,
Location,
} from "@/types/trpc/routers/hotel/locations"
import type { SelectHotelParams } from "@/utils/url"
interface AvailabilityResponse {
availability: HotelsAvailabilityItem[]
@@ -162,19 +158,32 @@ function sortAndFilterHotelsByAvailability(
].flat()
}
export async function getHotels(
booking: SelectHotelParams<
SelectHotelSearchParams | AlternativeHotelsSearchParams
>,
isAlternativeFor: HotelLocation | null,
bookingCode: string | undefined,
city: Location,
type GetHotelsInput = {
fromDate: string
toDate: string
rooms: {
adults: number
childrenInRoom?: Child[]
}[]
isAlternativeFor: HotelLocation | null
bookingCode: string | undefined
city: Location
redemption: boolean
) {
}
export async function getHotels({
rooms,
fromDate,
toDate,
isAlternativeFor,
bookingCode,
city,
redemption,
}: GetHotelsInput) {
let availableHotelsResponse: SettledResult = []
if (isAlternativeFor) {
availableHotelsResponse = await Promise.allSettled(
booking.rooms.map(async (room) => {
rooms.map(async (room) => {
return fetchAlternativeHotels(isAlternativeFor.id, {
adults: room.adults,
bookingCode,
@@ -182,14 +191,14 @@ export async function getHotels(
? generateChildrenString(room.childrenInRoom)
: undefined,
redemption,
roomStayEndDate: booking.toDate,
roomStayStartDate: booking.fromDate,
roomStayEndDate: toDate,
roomStayStartDate: fromDate,
})
})
)
} else if (bookingCode) {
availableHotelsResponse = await Promise.allSettled(
booking.rooms.map(async (room) => {
rooms.map(async (room) => {
return fetchBookingCodeAvailableHotels({
adults: room.adults,
bookingCode,
@@ -197,14 +206,14 @@ export async function getHotels(
? generateChildrenString(room.childrenInRoom)
: undefined,
cityId: city.id,
roomStayStartDate: booking.fromDate,
roomStayEndDate: booking.toDate,
roomStayStartDate: fromDate,
roomStayEndDate: toDate,
})
})
)
} else {
availableHotelsResponse = await Promise.allSettled(
booking.rooms.map(
rooms.map(
async (room) =>
await fetchAvailableHotels({
adults: room.adults,
@@ -213,8 +222,8 @@ export async function getHotels(
: undefined,
cityId: city.id,
redemption,
roomStayEndDate: booking.toDate,
roomStayStartDate: booking.fromDate,
roomStayEndDate: toDate,
roomStayStartDate: fromDate,
})
)
)

View File

@@ -17,7 +17,7 @@ export function getTracking(
adultsInRoom: number[],
childrenInRoom: ChildrenInRoom,
hotelsResult: number,
hotelId: string,
hotelId: string | undefined,
noOfRooms: number,
country: string | undefined,
hotelCity: string | undefined,

View File

@@ -72,7 +72,7 @@ export default function Summary({
)
const showDiscounted = containsBookingCodeRate || isMember
const priceDetailsRooms = mapToPrice(rateSummary, booking, isMember)
const priceDetailsRooms = mapToPrice(rateSummary, booking.rooms, isMember)
return (
<section className={styles.summary}>

View File

@@ -1,12 +1,12 @@
import type {
Rate,
SelectRateSearchParams,
Room as SelectRateRoom,
} from "@/types/components/hotelReservation/selectRate/selectRate"
import type { Room } from "@/components/HotelReservation/PriceDetailsModal/PriceDetailsTable"
export function mapToPrice(
rooms: (Rate | null)[],
booking: SelectRateSearchParams,
bookingRooms: SelectRateRoom[],
isUserLoggedIn: boolean
) {
return rooms
@@ -43,7 +43,7 @@ export function mapToPrice(
}
}
const bookingRoom = booking.rooms[idx]
const bookingRoom = bookingRooms[idx]
return {
adults: bookingRoom.adults,
bedType: undefined,

View File

@@ -1,6 +1,6 @@
"use client"
import { useSearchParams } from "next/navigation"
import { notFound, useSearchParams } from "next/navigation"
import { useIntl } from "react-intl"
import { trpc } from "@/lib/trpc/client"
@@ -9,7 +9,7 @@ import { selectRateRoomsAvailabilityInputSchema } from "@/server/routers/hotels/
import Alert from "@/components/TempDesignSystem/Alert"
import useLang from "@/hooks/useLang"
import RatesProvider from "@/providers/RatesProvider"
import { convertSearchParamsToObj, searchParamsToRecord } from "@/utils/url"
import { parseSelectRateSearchParams, searchParamsToRecord } from "@/utils/url"
import RateSummary from "./RateSummary"
import Rooms from "./Rooms"
@@ -18,7 +18,6 @@ import { RoomsContainerSkeleton } from "./RoomsContainerSkeleton"
import styles from "./index.module.css"
import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer"
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import { AlertTypeEnum } from "@/types/enums/alert"
export function RoomsContainer({
@@ -30,10 +29,12 @@ export function RoomsContainer({
const intl = useIntl()
const searchParams = useSearchParams()
const booking = convertSearchParamsToObj<SelectRateSearchParams>(
const booking = parseSelectRateSearchParams(
searchParamsToRecord(searchParams)
)
if (!booking) return notFound()
const bookingInput = selectRateRoomsAvailabilityInputSchema.safeParse({
booking,
lang,

View File

@@ -7,12 +7,11 @@ import { REDEMPTION } from "@/constants/booking"
import TrackingSDK from "@/components/TrackingSDK"
import useLang from "@/hooks/useLang"
import { convertSearchParamsToObj, searchParamsToRecord } from "@/utils/url"
import { parseSelectRateSearchParams, searchParamsToRecord } from "@/utils/url"
import { getValidDates } from "../getValidDates"
import { getTracking } from "./tracking"
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { ChildrenInRoom } from "@/utils/hotelSearchDetails"
export default function Tracking({
@@ -34,16 +33,13 @@ export default function Tracking({
}) {
const lang = useLang()
const params = useSearchParams()
const selectRateParams = convertSearchParamsToObj<SelectRateSearchParams>(
searchParamsToRecord(params)
)
const booking = parseSelectRateSearchParams(searchParamsToRecord(params))
const { fromDate, toDate } = getValidDates(
selectRateParams.fromDate,
selectRateParams.toDate
)
if (!booking) return null
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 departureDate = toDate.toDate()

View File

@@ -12,16 +12,15 @@ import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert"
import AvailabilityError from "./AvailabilityError"
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 { SelectHotelParams } from "@/utils/url"
export default async function SelectRatePage({
lang,
booking,
}: {
lang: Lang
booking: SelectHotelParams<SelectRateSearchParams>
booking: SelectRateBooking
}) {
const searchDetails = await getHotelSearchDetails(booking)
if (!searchDetails?.hotel) {