Merged in feat/sw-2857-refactor-booking-flow-url-updates (pull request #2302)

feat(SW-2857): Refactor booking flow url updates

* Add support for removing parameters when using initial values in serializeSearchParams

* Don't manually write search params in rate store

* Booking is already from live search params so no need

* Fix input type in serializeBookingSearchParams


Approved-by: Linus Flood
This commit is contained in:
Anton Gunnarsson
2025-06-09 09:16:22 +00:00
parent 879a383b61
commit bff34b034e
12 changed files with 189 additions and 189 deletions

View File

@@ -1,5 +1,5 @@
"use client" "use client"
import { useRouter } from "next/navigation" import { useRouter, useSearchParams } from "next/navigation"
import { useSession } from "next-auth/react" import { useSession } from "next-auth/react"
import { useState, useTransition } from "react" import { useState, useTransition } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -32,7 +32,6 @@ export default function RateSummary() {
isFetchingPackages, isFetchingPackages,
rateSummary, rateSummary,
roomsAvailability, roomsAvailability,
searchParams,
} = useRatesStore((state) => ({ } = useRatesStore((state) => ({
bookingCode: state.booking.bookingCode, bookingCode: state.booking.bookingCode,
bookingRooms: state.booking.rooms, bookingRooms: state.booking.rooms,
@@ -43,7 +42,6 @@ export default function RateSummary() {
isFetchingPackages: state.rooms.some((room) => room.isFetchingPackages), isFetchingPackages: state.rooms.some((room) => room.isFetchingPackages),
rateSummary: state.rateSummary, rateSummary: state.rateSummary,
roomsAvailability: state.roomsAvailability, roomsAvailability: state.roomsAvailability,
searchParams: state.searchParams,
})) }))
const { data: session } = useSession() const { data: session } = useSession()
const isUserLoggedIn = isValidClientSession(session) const isUserLoggedIn = isValidClientSession(session)
@@ -51,7 +49,7 @@ export default function RateSummary() {
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const intl = useIntl() const intl = useIntl()
const router = useRouter() const router = useRouter()
const params = new URLSearchParams(searchParams) const params = useSearchParams()
const [_, startTransition] = useTransition() const [_, startTransition] = useTransition()
if (!roomsAvailability) { if (!roomsAvailability) {

View File

@@ -38,6 +38,9 @@ export default function Form({ close }: { close: () => void }) {
}) })
async function getFilteredRates(packages: PackageEnum[]) { async function getFilteredRates(packages: PackageEnum[]) {
const bookingCode = bookingRoom.rateCode
? bookingRoom.bookingCode
: booking.bookingCode
const filterRates = await utils.hotel.availability.selectRate.room.fetch({ const filterRates = await utils.hotel.availability.selectRate.room.fetch({
booking: { booking: {
fromDate: booking.fromDate, fromDate: booking.fromDate,
@@ -46,9 +49,7 @@ export default function Form({ close }: { close: () => void }) {
toDate: booking.toDate, toDate: booking.toDate,
room: { room: {
...bookingRoom, ...bookingRoom,
bookingCode: bookingRoom.rateCode bookingCode: bookingCode ?? undefined,
? bookingRoom.bookingCode
: booking.bookingCode,
packages, packages,
}, },
}, },

View File

@@ -33,6 +33,10 @@ export default function RoomPackageFilter() {
async function deleteSelectedPackage(code: PackageEnum) { async function deleteSelectedPackage(code: PackageEnum) {
removeSelectedPackage(code) removeSelectedPackage(code)
const bookingCode = bookingRoom.rateCode
? bookingRoom.bookingCode
: booking.bookingCode
const filterRates = await utils.hotel.availability.selectRate.room.fetch({ const filterRates = await utils.hotel.availability.selectRate.room.fetch({
booking: { booking: {
fromDate: booking.fromDate, fromDate: booking.fromDate,
@@ -41,9 +45,7 @@ export default function RoomPackageFilter() {
toDate: booking.toDate, toDate: booking.toDate,
room: { room: {
...bookingRoom, ...bookingRoom,
bookingCode: bookingRoom.rateCode bookingCode: bookingCode ?? undefined,
? bookingRoom.bookingCode
: booking.bookingCode,
packages: selectedPackages packages: selectedPackages
.filter((pkg) => pkg.code !== code) .filter((pkg) => pkg.code !== code)
.map((pkg) => pkg.code), .map((pkg) => pkg.code),

View File

@@ -22,39 +22,41 @@ export default function RatesProvider({
const searchParams = useSearchParams() const searchParams = useSearchParams()
const intl = useIntl() const intl = useIntl()
const store = useMemo( const modifyRateIndex = searchParams.has("modifyRateIndex")
() => ? Number(searchParams.get("modifyRateIndex"))
createRatesStore({ : undefined
booking,
hotelType, const store = useMemo(() => {
labels: { return createRatesStore({
accessibilityRoom: intl.formatMessage({
defaultMessage: "Accessible room",
}),
allergyRoom: intl.formatMessage({
defaultMessage: "Allergy-friendly room",
}),
petRoom: intl.formatMessage({
defaultMessage: "Pet-friendly room",
}),
},
pathname,
roomCategories,
roomsAvailability,
searchParams: new URLSearchParams(searchParams),
vat,
}),
[
booking, booking,
hotelType, hotelType,
intl, labels: {
accessibilityRoom: intl.formatMessage({
defaultMessage: "Accessible room",
}),
allergyRoom: intl.formatMessage({
defaultMessage: "Allergy-friendly room",
}),
petRoom: intl.formatMessage({
defaultMessage: "Pet-friendly room",
}),
},
pathname, pathname,
roomCategories, roomCategories,
roomsAvailability, roomsAvailability,
searchParams,
vat, vat,
] initialActiveRoom: modifyRateIndex,
) })
}, [
booking,
hotelType,
intl,
pathname,
roomCategories,
roomsAvailability,
modifyRateIndex,
vat,
])
return <RatesContext.Provider value={store}>{children}</RatesContext.Provider> return <RatesContext.Provider value={store}>{children}</RatesContext.Provider>
} }

View File

@@ -45,9 +45,9 @@ export function findProduct(
} }
export function findProductInRoom( export function findProductInRoom(
rateCode: string | undefined, rateCode: string | undefined | null,
room: RoomConfiguration, room: RoomConfiguration,
counterRateCode = "" counterRateCode: string | undefined | null
) { ) {
if (!rateCode) { if (!rateCode) {
return null return null
@@ -55,7 +55,7 @@ export function findProductInRoom(
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 || "")
) )
if (campaignProduct) { if (campaignProduct) {
return campaignProduct return campaignProduct
@@ -63,7 +63,7 @@ export function findProductInRoom(
} }
if (room.code.length) { if (room.code.length) {
const codeProduct = room.code.find((product) => const codeProduct = room.code.find((product) =>
findProduct(rateCode, product, counterRateCode) findProduct(rateCode, product, counterRateCode || "")
) )
if (codeProduct) { if (codeProduct) {
return codeProduct return codeProduct
@@ -79,7 +79,7 @@ export function findProductInRoom(
} }
if (room.regular.length) { if (room.regular.length) {
const regularProduct = room.regular.find((product) => const regularProduct = room.regular.find((product) =>
findProduct(rateCode, product, counterRateCode) findProduct(rateCode, product, counterRateCode || "")
) )
if (regularProduct) { if (regularProduct) {
return regularProduct return regularProduct
@@ -88,9 +88,9 @@ export function findProductInRoom(
} }
export function findSelectedRate( export function findSelectedRate(
rateCode: string | undefined, rateCode: string | undefined | null,
counterRateCode: string | undefined, counterRateCode: string | undefined | null,
roomTypeCode: string | undefined, roomTypeCode: string | undefined | null,
rooms: RoomConfiguration[] | AvailabilityError rooms: RoomConfiguration[] | AvailabilityError
) { ) {
if (!Array.isArray(rooms)) { if (!Array.isArray(rooms)) {
@@ -109,17 +109,6 @@ export function findSelectedRate(
}) })
} }
export function clearRoomSelectionFromUrl(
roomIdx: number,
searchParams: URLSearchParams
) {
searchParams.delete(`room[${roomIdx}].bookingCode`)
searchParams.delete(`room[${roomIdx}].counterratecode`)
searchParams.delete(`room[${roomIdx}].ratecode`)
searchParams.delete(`room[${roomIdx}].roomtype`)
return searchParams
}
export function findDefaultCurrency( export function findDefaultCurrency(
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
) { ) {

View File

@@ -5,9 +5,9 @@ import { create, useStore } from "zustand"
import { REDEMPTION } from "@/constants/booking" import { REDEMPTION } from "@/constants/booking"
import { RatesContext } from "@/contexts/Rates" import { RatesContext } from "@/contexts/Rates"
import { serializeBookingSearchParams } from "@/utils/url"
import { import {
clearRoomSelectionFromUrl,
findDefaultCurrency, findDefaultCurrency,
findProductInRoom, findProductInRoom,
findSelectedRate, findSelectedRate,
@@ -27,9 +27,16 @@ export function createRatesStore({
pathname, pathname,
roomCategories, roomCategories,
roomsAvailability, roomsAvailability,
searchParams, initialActiveRoom,
vat, vat,
}: InitialState) { }: InitialState) {
function updateUrl(booking: RatesState["booking"]) {
const searchParams = serializeBookingSearchParams(booking, {
initialSearchParams: new URLSearchParams(window.location.search),
})
window.history.replaceState({}, "", `${pathname}?${searchParams}`)
}
const packageOptions = [ const packageOptions = [
{ {
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM, code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
@@ -73,13 +80,8 @@ export function createRatesStore({
) )
if (!selectedRoom) { if (!selectedRoom) {
const updatedSearchParams = clearRoomSelectionFromUrl(idx, searchParams) booking.rooms[idx] = roomWithoutSelection(room)
searchParams = updatedSearchParams updateUrl(booking)
window.history.replaceState(
{},
"",
`${pathname}?${updatedSearchParams}`
)
continue continue
} }
@@ -108,8 +110,8 @@ export function createRatesStore({
} }
let activeRoom = rateSummary.length let activeRoom = rateSummary.length
if (searchParams.has("modifyRateIndex")) { if (initialActiveRoom) {
activeRoom = Number(searchParams.get("modifyRateIndex")) activeRoom = initialActiveRoom
} else if (rateSummary.length === booking.rooms.length) { } else if (rateSummary.length === booking.rooms.length) {
// Finds the first unselected room and sets that to active // Finds the first unselected room and sets that to active
// if no unselected rooms it will return -1 and close all rooms // if no unselected rooms it will return -1 and close all rooms
@@ -128,13 +130,11 @@ export function createRatesStore({
packageOptions, packageOptions,
hotelType, hotelType,
isRedemptionBooking, isRedemptionBooking,
pathname,
rateSummary, rateSummary,
roomConfigurations, roomConfigurations,
roomCategories, roomCategories,
roomsPackages, roomsPackages,
roomsAvailability, roomsAvailability,
searchParams,
vat, vat,
defaultCurrency, defaultCurrency,
rooms: booking.rooms.map((room, idx) => { rooms: booking.rooms.map((room, idx) => {
@@ -266,23 +266,14 @@ export function createRatesStore({
BookingCodeFilterEnum.Discounted BookingCodeFilterEnum.Discounted
} }
const searchParams = state.searchParams
if (filteredSelectedPackages.length) { if (filteredSelectedPackages.length) {
searchParams.set( state.booking.rooms[idx].packages =
`room[${idx}].packages`, filteredSelectedPackages.map((pkg) => pkg.code)
filteredSelectedPackages.map((pkg) => pkg.code).join(",")
)
} else { } else {
searchParams.delete(`room[${idx}].packages`) state.booking.rooms[idx].packages = null
} }
state.searchParams = searchParams updateUrl(state.booking)
window.history.replaceState(
{},
"",
`${state.pathname}?${searchParams}`
)
}) })
) )
}, },
@@ -300,36 +291,8 @@ export function createRatesStore({
BookingCodeFilterEnum.Discounted BookingCodeFilterEnum.Discounted
} }
const searchParams = state.searchParams state.booking.rooms[idx].packages = null
searchParams.delete(`room[${idx}].packages`) updateUrl(state.booking)
state.searchParams = searchParams
window.history.replaceState(
{},
"",
`${state.pathname}?${searchParams}`
)
})
)
},
removeSelectedRoom() {
return set(
produce((state: RatesState) => {
state.rateSummary[idx] = null
const searchParams = state.searchParams
searchParams.delete(`room[${idx}].counterratecode`)
searchParams.delete(`room[${idx}].ratecode`)
searchParams.delete(`room[${idx}].roomtype`)
state.searchParams = searchParams
window.history.replaceState(
{},
"",
`${state.pathname}?${searchParams}`
)
}) })
) )
}, },
@@ -408,43 +371,33 @@ export function createRatesStore({
state.rooms[idx].bookingRoom.bookingCode = state.rooms[idx].bookingRoom.bookingCode =
selectedRate.product.bookingCode selectedRate.product.bookingCode
const searchParams = new URLSearchParams(state.searchParams)
const counterratecode = isMemberRate const counterratecode = isMemberRate
? productRateCode ? productRateCode
: memberRateCode : memberRateCode
if (counterratecode) { if (counterratecode) {
searchParams.set( state.booking.rooms[idx].counterRateCode = counterratecode
`room[${idx}].counterratecode`,
counterratecode
)
} else { } else {
if (searchParams.has(`room[${idx}].counterratecode`)) { state.booking.rooms[idx].counterRateCode = null
searchParams.delete(`room[${idx}].counterratecode`)
}
} }
const rateCode = isMemberRate const rateCode = isMemberRate
? memberRateCode ? memberRateCode
: productRateCode : productRateCode
if (rateCode) { if (rateCode) {
searchParams.set(`room[${idx}].ratecode`, rateCode) state.booking.rooms[idx].rateCode = rateCode
} }
if (selectedRate.product.bookingCode) { if (selectedRate.product.bookingCode) {
searchParams.set( state.booking.rooms[idx].bookingCode =
`room[${idx}].bookingCode`,
selectedRate.product.bookingCode selectedRate.product.bookingCode
)
} else { } else {
if (searchParams.has(`room[${idx}].bookingCode`)) { if (state.booking.rooms[idx].bookingCode) {
searchParams.delete(`room[${idx}].bookingCode`) state.booking.rooms[idx].bookingCode = null
} }
} }
searchParams.set( state.booking.rooms[idx].roomTypeCode =
`room[${idx}].roomtype`,
selectedRate.roomTypeCode selectedRate.roomTypeCode
)
if (state.rateSummary.length === state.booking.rooms.length) { if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1 state.activeRoom = -1
@@ -452,13 +405,7 @@ export function createRatesStore({
state.activeRoom = idx + 1 state.activeRoom = idx + 1
} }
state.searchParams = searchParams updateUrl(state.booking)
window.history.replaceState(
{},
"",
`${state.pathname}?${searchParams}`
)
}) })
) )
}, },
@@ -479,23 +426,13 @@ export function createRatesStore({
BookingCodeFilterEnum.Discounted BookingCodeFilterEnum.Discounted
} }
const searchParams = state.searchParams
if (selectedPackages.length) { if (selectedPackages.length) {
searchParams.set( state.booking.rooms[idx].packages = selectedPackages
`room[${idx}].packages`,
selectedPackages.join(",")
)
} else { } else {
searchParams.delete(`room[${idx}].packages`) state.booking.rooms[idx].packages = null
} }
state.searchParams = searchParams updateUrl(state.booking)
window.history.replaceState(
{},
"",
`${state.pathname}?${searchParams}`
)
}) })
) )
}, },
@@ -519,36 +456,27 @@ export function createRatesStore({
rateSummaryRoom.packages = rateSummaryRoom.packages =
state.rooms[idx].selectedPackages state.rooms[idx].selectedPackages
} else { } else {
const searchParams = clearRoomSelectionFromUrl( state.booking.rooms[idx] = roomWithoutSelection(
idx, state.booking.rooms[idx]
state.searchParams
) )
state.searchParams = searchParams
state.rateSummary[idx] = null state.rateSummary[idx] = null
state.rooms[idx].selectedRate = null state.rooms[idx].selectedRate = null
window.history.replaceState( updateUrl(state.booking)
{},
"",
`${pathname}?${searchParams}`
)
} }
} }
} else { } else {
state.rooms[idx].rooms = [] state.rooms[idx].rooms = []
if (state.rateSummary[idx]) { if (state.rateSummary[idx]) {
const searchParams = clearRoomSelectionFromUrl( state.booking.rooms[idx] = roomWithoutSelection(
idx, state.booking.rooms[idx]
state.searchParams
) )
state.searchParams = searchParams
state.rateSummary[idx] = null state.rateSummary[idx] = null
state.rooms[idx].selectedRate = null state.rooms[idx].selectedRate = null
window.history.replaceState(
{}, updateUrl(state.booking)
"",
`${pathname}?${searchParams}`
)
} }
} }
}) })
@@ -587,3 +515,15 @@ export function useRatesStore<T>(selector: (store: RatesState) => T) {
return useStore(store, selector) return useStore(store, selector)
} }
function roomWithoutSelection(
room: RatesState["booking"]["rooms"][number]
): RatesState["booking"]["rooms"][number] {
return {
...room,
rateCode: null,
counterRateCode: null,
roomTypeCode: null,
bookingCode: null,
}
}

View File

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

View File

@@ -14,12 +14,12 @@ export interface Child {
export interface Room { export interface Room {
adults: number adults: number
bookingCode?: string
childrenInRoom?: Child[] childrenInRoom?: Child[]
counterRateCode?: string bookingCode?: string | null
packages?: PackageEnum[] counterRateCode?: string | null
rateCode?: string packages?: PackageEnum[] | null
roomTypeCode?: string rateCode?: string | null
roomTypeCode?: string | null
} }
export type SelectRateBooking = { export type SelectRateBooking = {

View File

@@ -27,7 +27,6 @@ interface Actions {
modifyRate: () => void modifyRate: () => void
removeSelectedPackage: (code: PackageEnum) => void removeSelectedPackage: (code: PackageEnum) => void
removeSelectedPackages: () => void removeSelectedPackages: () => void
removeSelectedRoom: () => void
selectFilter: (filter: BookingCodeFilterEnum) => void selectFilter: (filter: BookingCodeFilterEnum) => void
selectPackages: (codes: PackageEnum[]) => void selectPackages: (codes: PackageEnum[]) => void
selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void
@@ -58,14 +57,12 @@ export interface RatesState {
hotelType: string | undefined hotelType: string | undefined
isRedemptionBooking: boolean isRedemptionBooking: boolean
packageOptions: DefaultFilterOptions[] packageOptions: DefaultFilterOptions[]
pathname: string
rateSummary: Array<Rate | null> rateSummary: Array<Rate | null>
rooms: SelectedRoom[] rooms: SelectedRoom[]
roomCategories: Room[] roomCategories: Room[]
roomConfigurations: RoomConfiguration[][] roomConfigurations: RoomConfiguration[][]
roomsPackages: Package[][] roomsPackages: Package[][]
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
searchParams: URLSearchParams
vat: number vat: number
defaultCurrency: CurrencyEnum defaultCurrency: CurrencyEnum
} }
@@ -73,14 +70,10 @@ export interface RatesState {
export interface InitialState export interface InitialState
extends Pick< extends Pick<
RatesState, RatesState,
| "booking" "booking" | "hotelType" | "roomCategories" | "roomsAvailability" | "vat"
| "hotelType"
| "pathname"
| "roomCategories"
| "roomsAvailability"
| "searchParams"
| "vat"
> { > {
initialActiveRoom?: number
pathname: string
labels: { labels: {
accessibilityRoom: string accessibilityRoom: string
allergyRoom: string allergyRoom: string

View File

@@ -450,6 +450,53 @@ describe("Serialize search params", () => {
"city=stockholm&hotel=456&filter[0]=1831&filter[1]=1383&packages=ABC" "city=stockholm&hotel=456&filter[0]=1831&filter[1]=1383&packages=ABC"
) )
}) })
test("with initial search params and removing existing parameter", () => {
const initialSearchParams = new URLSearchParams(
"city=stockholm&hotel=123&filters=123,456,789"
)
const obj = {
hotel: null,
filters: ["123", "789"],
}
const result = serializeSearchParams(obj, {
initialSearchParams,
typeHints: {
filters: "COMMA_SEPARATED_ARRAY",
},
})
expect(decodeURIComponent(result.toString())).toEqual(
"city=stockholm&filters=123,789"
)
})
test("with initial search params and removing values in array", () => {
const initialSearchParams = new URLSearchParams(
"room[0].adults=1&room[0].rateCode=ABC&room[1].adults=2&room[2].adults=3&room[3].adults=4"
)
const obj = {
room: [
{
adults: 1,
rateCode: null,
},
{
adults: 3,
},
{
adults: 4,
},
],
}
const result = serializeSearchParams(obj, {
initialSearchParams,
})
expect(decodeURIComponent(result.toString())).toEqual(
"room[0].adults=1&room[1].adults=3&room[2].adults=4"
)
})
}) })
describe("Parse serialized search params", () => { describe("Parse serialized search params", () => {

View File

@@ -150,7 +150,11 @@ type SerializeOptions = {
* @param obj - The object to serialize * @param obj - The object to serialize
* @param options.keyRenameMap - Optional mapping of keys to rename, ie { "oldKey": "newKey" } * @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 * @param options.typeHints - Optional type hints to force certain keys to be treated as comma separated arrays
* @param options.initialSearchParams - Optional initial URL search parameters to merge with the serialized object
* @returns URLSearchParams - The serialized URL search parameters * @returns URLSearchParams - The serialized URL search parameters
*
* To force a key to be removed when merging with initialSearchParams, set its value to `null` in the object.
* Arrays are not merged, they will always replace existing values.
*/ */
export function serializeSearchParams( export function serializeSearchParams(
obj: Record<string, any>, obj: Record<string, any>,
@@ -173,23 +177,31 @@ export function serializeSearchParams(
const value = obj[key] const value = obj[key]
const renamedKey = keyRenameMap[key] || key const renamedKey = keyRenameMap[key] || key
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
if (value === null) {
params.delete(paramKey)
continue
}
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (typeHints[key] === "COMMA_SEPARATED_ARRAY") { if (typeHints[key] === "COMMA_SEPARATED_ARRAY") {
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
params.set(paramKey, value.join(",")) params.set(paramKey, value.join(","))
continue continue
} }
// If an array value already exists (from initialSearchParams),
// we need to first remove it since it can't be merged.
deleteAllKeysStartingWith(params, renamedKey)
value.forEach((item, index) => { value.forEach((item, index) => {
const indexedKey = `${renamedKey}[${index}]` const indexedKey = `${renamedKey}[${index}]`
const paramKey = prefix ? `${prefix}.${indexedKey}` : indexedKey const arrayKey = prefix ? `${prefix}.${indexedKey}` : indexedKey
buildParams(item, paramKey)
buildParams(item, arrayKey)
}) })
continue continue
} }
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
if (typeof value === "object" && value !== null) { if (typeof value === "object" && value !== null) {
buildParams(value, paramKey) buildParams(value, paramKey)
continue continue
@@ -207,3 +219,12 @@ export function serializeSearchParams(
function isRecord(value: unknown): value is Record<string, unknown> { function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null return typeof value === "object" && value !== null
} }
function deleteAllKeysStartingWith(searchParams: URLSearchParams, key: string) {
const keysToDelete = Array.from(searchParams.keys()).filter(
(k) => k.startsWith(key) || k === key
)
for (const k of keysToDelete) {
searchParams.delete(k)
}
}

View File

@@ -238,7 +238,11 @@ const reversedKeyRenameMap = Object.fromEntries(
Object.entries(keyRenameMap).map(([key, value]) => [value, key]) Object.entries(keyRenameMap).map(([key, value]) => [value, key])
) )
export function serializeBookingSearchParams( export function serializeBookingSearchParams(
obj: { [key: string]: any }, obj:
| BookingWidgetSearchData
| SelectHotelBooking
| SelectRateBooking
| DetailsBooking,
{ initialSearchParams }: { initialSearchParams?: URLSearchParams } = {} { initialSearchParams }: { initialSearchParams?: URLSearchParams } = {}
) { ) {
return serializeSearchParams(obj, { return serializeSearchParams(obj, {