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:
@@ -1,10 +1,20 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { bookingSearchTypes } from "@/constants/booking"
|
||||
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 {
|
||||
Child,
|
||||
Room,
|
||||
SelectRateBooking,
|
||||
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||
import type { NextSearchParams } from "@/types/params"
|
||||
|
||||
export function removeMultipleSlashes(pathname: string) {
|
||||
return pathname.replaceAll(/\/\/+/g, "/")
|
||||
@@ -20,151 +30,213 @@ export function removeTrailingSlash(pathname: string) {
|
||||
|
||||
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"> & {
|
||||
hotelId: string
|
||||
} & 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) {
|
||||
return Object.fromEntries(searchParams.entries())
|
||||
}
|
||||
|
||||
export function convertSearchParamsToObj<T extends PartialRoom>(
|
||||
searchParams: Record<string, string>
|
||||
): SelectHotelParams<T> {
|
||||
const searchParamsObject = Object.entries(searchParams).reduce<
|
||||
SelectHotelParams<T>
|
||||
>((acc, [key, value]) => {
|
||||
// The params are sometimes indexed with a number (for ex: `room[0].adults`),
|
||||
// so we need to split them by . or []
|
||||
const keys = key.replace(/\]/g, "").split(/\[|\./)
|
||||
const firstKey = getKeyFromSearchParam(keys[0])
|
||||
const keyRenameMap = {
|
||||
room: "rooms",
|
||||
ratecode: "rateCode",
|
||||
counterratecode: "counterRateCode",
|
||||
roomtype: "roomTypeCode",
|
||||
fromdate: "fromDate",
|
||||
todate: "toDate",
|
||||
hotel: "hotelId",
|
||||
child: "childrenInRoom",
|
||||
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
|
||||
if (firstKey === "rooms") {
|
||||
// Rooms are always indexed with a number, so we need to extract the index
|
||||
const index = Number(keys[1])
|
||||
const roomObject =
|
||||
acc.rooms && Array.isArray(acc.rooms) ? acc.rooms : (acc.rooms = [])
|
||||
export function parseBookingWidgetSearchParams(
|
||||
searchParams: NextSearchParams
|
||||
): BookingWidgetSearchData {
|
||||
try {
|
||||
const result = parseSearchParams(searchParams, {
|
||||
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
|
||||
|
||||
if (!roomObject[index]) {
|
||||
roomObject[index] = {}
|
||||
}
|
||||
|
||||
// Adults should be converted to a number
|
||||
if (roomObjectKey === "adults") {
|
||||
roomObject[index].adults = Number(value)
|
||||
|
||||
// Child is an array, so we need to handle it separately
|
||||
} else if (roomObjectKey === "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
|
||||
return result
|
||||
} catch (error) {
|
||||
console.log("[URL] Error parsing search params for booking widget:", error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export function convertObjToSearchParams<T>(
|
||||
bookingData: T & PartialRoom,
|
||||
intitalSearchParams = {} as URLSearchParams
|
||||
) {
|
||||
const bookingSearchParams = new URLSearchParams(intitalSearchParams)
|
||||
Object.entries(bookingData).forEach(([key, value]) => {
|
||||
if (key === "rooms") {
|
||||
value.forEach((item, index) => {
|
||||
if (item?.adults) {
|
||||
bookingSearchParams.set(
|
||||
`room[${index}].adults`,
|
||||
item.adults.toString()
|
||||
)
|
||||
}
|
||||
if (item?.childrenInRoom) {
|
||||
item.childrenInRoom.forEach((child, childIndex) => {
|
||||
bookingSearchParams.set(
|
||||
`room[${index}].child[${childIndex}].age`,
|
||||
child.age.toString()
|
||||
)
|
||||
bookingSearchParams.set(
|
||||
`room[${index}].child[${childIndex}].bed`,
|
||||
child.bed.toString()
|
||||
)
|
||||
export function parseSelectHotelSearchParams(
|
||||
searchParams: NextSearchParams
|
||||
): SelectHotelBooking | null {
|
||||
try {
|
||||
const result = parseSearchParams(searchParams, {
|
||||
keyRenameMap,
|
||||
schema: z.object({
|
||||
city: z.string(),
|
||||
hotelId: z.string().optional(),
|
||||
fromDate: z.string(),
|
||||
toDate: z.string(),
|
||||
bookingCode: z.string().optional(),
|
||||
searchType: searchTypeSchema,
|
||||
rooms: z.array(
|
||||
z.object({
|
||||
adults: adultsSchema,
|
||||
childrenInRoom: z
|
||||
.array(
|
||||
z.object({
|
||||
bed: childBedSchema,
|
||||
age: childAgeSchema,
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
}
|
||||
if (item?.roomTypeCode) {
|
||||
bookingSearchParams.set(`room[${index}].roomtype`, item.roomTypeCode)
|
||||
}
|
||||
if (item?.rateCode) {
|
||||
bookingSearchParams.set(`room[${index}].ratecode`, item.rateCode)
|
||||
}
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
if (item?.counterRateCode) {
|
||||
bookingSearchParams.set(
|
||||
`room[${index}].counterratecode`,
|
||||
item.counterRateCode
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
console.log("[URL] Error parsing search params for select hotel:", error)
|
||||
|
||||
if (item.packages && item.packages.length > 0) {
|
||||
bookingSearchParams.set(
|
||||
`room[${index}].packages`,
|
||||
item.packages.join(",")
|
||||
)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
bookingSearchParams.set(getSearchParamFromKey(key), value.toString())
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function parseSelectRateSearchParams(
|
||||
searchParams: NextSearchParams
|
||||
): SelectRateBooking | null {
|
||||
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().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