fix: improve handling of booking widget params from search params

now we are defensive in parsing the location
if parsing fails the not found is now displayed
This commit is contained in:
Michael Zetterberg
2025-02-25 13:29:39 +01:00
parent 15fa01cbb2
commit 43c25aea95
3 changed files with 66 additions and 55 deletions

View File

@@ -1,4 +1,5 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useEffect, useRef, useState } from "react"
import { FormProvider, useForm } from "react-hook-form"
@@ -30,6 +31,25 @@ import type {
} from "@/types/components/bookingWidget"
import type { Location } from "@/types/trpc/routers/hotel/locations"
function getLocationObj(locations: Location[], destination: string) {
try {
const location = locations.find((location) => {
if (location.type === "hotels") {
return location.operaId === destination
} else if (location.type === "cities") {
return location.name.toLowerCase() === destination.toLowerCase()
}
})
if (location) {
return location
}
} catch (_) {
// ignore any errors
}
return null
}
export default function BookingWidgetClient({
locations,
type,
@@ -37,80 +57,62 @@ export default function BookingWidgetClient({
}: BookingWidgetClientProps) {
const [isOpen, setIsOpen] = useState(false)
const bookingWidgetRef = useRef(null)
useStickyPosition({
ref: bookingWidgetRef,
name: StickyElementNameEnum.BOOKING_WIDGET,
})
const bookingWidgetSearchData = bookingWidgetSearchParams
? convertSearchParamsToObj<BookingWidgetSearchData>(
bookingWidgetSearchParams
)
: undefined
const getLocationObj = (destination: string): Location | undefined => {
if (destination) {
const location: Location | undefined = locations.find((location) => {
return (
location.name.toLowerCase() === destination.toLowerCase() ||
//@ts-ignore (due to operaId not property error)
(location.operaId && location.operaId == destination)
)
})
return location
}
return undefined
}
const reqFromDate = bookingWidgetSearchData?.fromDate?.toString()
const reqToDate = bookingWidgetSearchData?.toDate?.toString()
const parsedFromDate = reqFromDate ? dt(reqFromDate) : undefined
const parsedToDate = reqToDate ? dt(reqToDate) : undefined
const params = convertSearchParamsToObj<BookingWidgetSearchData>(
bookingWidgetSearchParams
)
const now = dt()
// if fromDate or toDate is undefined, dt will return value that represents the same as 'now' above.
// this is fine as isDateParamValid will catch this and default the values accordingly.
let fromDate = dt(params.fromDate)
let toDate = dt(params.toDate)
const isDateParamValid =
parsedFromDate &&
parsedToDate &&
parsedFromDate.isSameOrAfter(now, "day") &&
parsedToDate.isAfter(parsedFromDate)
fromDate.isValid() &&
toDate.isValid() &&
fromDate.isSameOrAfter(now, "day") &&
toDate.isAfter(fromDate)
const selectedLocation = bookingWidgetSearchData
? getLocationObj(
(bookingWidgetSearchData.hotelId ??
bookingWidgetSearchData.city) as string
)
: undefined
if (!isDateParamValid) {
fromDate = now
toDate = now.add(1, "day")
}
const selectedBookingCode = bookingWidgetSearchData
? bookingWidgetSearchData.bookingCode
: ""
let selectedLocation: Location | null = null
const defaultRoomsData: BookingWidgetSchema["rooms"] =
bookingWidgetSearchData?.rooms?.map((room) => ({
if (params.hotelId) {
selectedLocation = getLocationObj(locations, params.hotelId)
} else if (params.city) {
selectedLocation = getLocationObj(locations, params.city)
}
const selectedBookingCode = params.bookingCode ?? ""
const defaultRoomsData: BookingWidgetSchema["rooms"] = params.rooms?.map(
(room) => ({
adults: room.adults,
childrenInRoom: room.childrenInRoom ?? [],
})) ?? [
{
adults: 1,
childrenInRoom: [],
},
]
})
) ?? [
{
adults: 1,
childrenInRoom: [],
},
]
const methods = useForm<BookingWidgetSchema>({
defaultValues: {
search: selectedLocation?.name ?? "",
location: selectedLocation ? JSON.stringify(selectedLocation) : undefined,
date: {
// UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507
// This is specifically to handle timezones falling in different dates.
fromDate: isDateParamValid
? parsedFromDate.format("YYYY-MM-DD")
: now.utc().format("YYYY-MM-DD"),
toDate: isDateParamValid
? parsedToDate.format("YYYY-MM-DD")
: now.utc().add(1, "day").format("YYYY-MM-DD"),
fromDate: fromDate.format("YYYY-MM-DD"),
toDate: toDate.format("YYYY-MM-DD"),
},
bookingCode: {
value: selectedBookingCode,

View File

@@ -317,12 +317,21 @@ export const locationsSchema = z.object({
},
},
type: location.type,
operaId: location.attributes.operaId ?? "",
}
})
)
.transform((data) =>
data
.filter((node) => !!node)
.filter((node) => {
if (node.type === "hotels") {
if (!node.operaId) {
return false
}
}
return true
})
.sort((a, b) => {
if (a.type === b.type) {
return a.name.localeCompare(b.name)

View File

@@ -13,7 +13,7 @@ export const locationHotelSchema = z.object({
.optional(),
keyWords: z.array(z.string()).optional(),
name: z.string().optional().default(""),
operaId: z.string().optional(),
operaId: z.coerce.string().optional(),
}),
id: z.string().optional().default(""),
relationships: z