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
303 lines
8.0 KiB
TypeScript
303 lines
8.0 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { bookingSearchTypes } from "@/constants/booking"
|
|
import { Lang } from "@/constants/languages"
|
|
|
|
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 {
|
|
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, "/")
|
|
}
|
|
|
|
export function removeTrailingSlash(pathname: string) {
|
|
if (pathname.endsWith("/")) {
|
|
// Remove the trailing slash
|
|
return pathname.slice(0, -1)
|
|
}
|
|
return pathname
|
|
}
|
|
|
|
type PartialRoom = { rooms?: Partial<Room>[] }
|
|
|
|
export type SelectHotelParams<T> = Omit<T, "hotel"> & {
|
|
hotelId: string
|
|
} & PartialRoom
|
|
|
|
export function searchParamsToRecord(searchParams: URLSearchParams) {
|
|
return Object.fromEntries(searchParams.entries())
|
|
}
|
|
|
|
const keyRenameMap = {
|
|
room: "rooms",
|
|
ratecode: "rateCode",
|
|
counterratecode: "counterRateCode",
|
|
roomtype: "roomTypeCode",
|
|
fromdate: "fromDate",
|
|
todate: "toDate",
|
|
hotel: "hotelId",
|
|
child: "childrenInRoom",
|
|
searchtype: "searchType",
|
|
}
|
|
const typeHints = {
|
|
filters: "COMMA_SEPARATED_ARRAY",
|
|
packages: "COMMA_SEPARATED_ARRAY",
|
|
} as const
|
|
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)
|
|
|
|
export function parseBookingWidgetSearchParams(
|
|
searchParams: NextSearchParams
|
|
): BookingWidgetSearchData {
|
|
try {
|
|
const result = parseSearchParams(searchParams, {
|
|
keyRenameMap,
|
|
typeHints,
|
|
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(),
|
|
}),
|
|
})
|
|
|
|
return result
|
|
} catch (error) {
|
|
console.log("[URL] Error parsing search params for booking widget:", error)
|
|
return {}
|
|
}
|
|
}
|
|
|
|
export function parseSelectHotelSearchParams(
|
|
searchParams: NextSearchParams
|
|
): SelectHotelBooking | null {
|
|
try {
|
|
const result = parseSearchParams(searchParams, {
|
|
keyRenameMap,
|
|
typeHints,
|
|
schema: z.object({
|
|
city: z.string().optional(),
|
|
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(),
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
|
|
return result
|
|
} catch (error) {
|
|
console.log("[URL] Error parsing search params for select hotel:", error)
|
|
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function parseSelectRateSearchParams(
|
|
searchParams: NextSearchParams
|
|
): SelectRateBooking | null {
|
|
try {
|
|
const result = parseSearchParams(searchParams, {
|
|
keyRenameMap,
|
|
typeHints,
|
|
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,
|
|
typeHints,
|
|
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:
|
|
| BookingWidgetSearchData
|
|
| SelectHotelBooking
|
|
| SelectRateBooking
|
|
| DetailsBooking,
|
|
{ initialSearchParams }: { initialSearchParams?: URLSearchParams } = {}
|
|
) {
|
|
return serializeSearchParams(obj, {
|
|
keyRenameMap: reversedKeyRenameMap,
|
|
initialSearchParams,
|
|
typeHints,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Returns the TLD (top-level domain) for a given language.
|
|
* @param lang - The language to get the TLD for
|
|
* @returns The TLD for the given language
|
|
*/
|
|
export function getTldForLanguage(lang: Lang): string {
|
|
switch (lang) {
|
|
case Lang.sv:
|
|
return "se"
|
|
case Lang.no:
|
|
return "no"
|
|
case Lang.da:
|
|
return "dk"
|
|
case Lang.fi:
|
|
return "fi"
|
|
case Lang.de:
|
|
return "de"
|
|
default:
|
|
return "com"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a URL with the correct TLD (top-level domain) based on lang, for current web.
|
|
* @param params - Object containing path, lang, and baseUrl
|
|
* @param params.path - The path to append to the URL
|
|
* @param params.lang - The language to use for TLD
|
|
* @param params.baseUrl - The base URL to use (e.g. https://www.scandichotels.com)
|
|
* @returns The complete URL with language-specific TLD
|
|
*/
|
|
export function getCurrentWebUrl({
|
|
path,
|
|
lang,
|
|
baseUrl = "https://www.scandichotels.com", // Fallback for ephemeral environments (e.g. deploy previews).
|
|
}: {
|
|
path: string
|
|
lang: Lang
|
|
baseUrl?: string
|
|
}): string {
|
|
const tld = getTldForLanguage(lang)
|
|
const url = new URL(path, baseUrl)
|
|
|
|
if (tld !== "com") {
|
|
url.host = url.host.replace(".com", `.${tld}`)
|
|
}
|
|
|
|
return url.toString()
|
|
}
|