From 43c25aea9538bae882e77c065ca83a2b26d1a8bf Mon Sep 17 00:00:00 2001 From: Michael Zetterberg Date: Tue, 25 Feb 2025 13:29:39 +0100 Subject: [PATCH] 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 --- .../components/BookingWidget/Client.tsx | 110 +++++++++--------- .../server/routers/hotels/output.ts | 9 ++ .../routers/hotels/schemas/location/hotel.ts | 2 +- 3 files changed, 66 insertions(+), 55 deletions(-) diff --git a/apps/scandic-web/components/BookingWidget/Client.tsx b/apps/scandic-web/components/BookingWidget/Client.tsx index 69bdd1c42..50a2ad8b2 100644 --- a/apps/scandic-web/components/BookingWidget/Client.tsx +++ b/apps/scandic-web/components/BookingWidget/Client.tsx @@ -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( - 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( + 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({ 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, diff --git a/apps/scandic-web/server/routers/hotels/output.ts b/apps/scandic-web/server/routers/hotels/output.ts index 17ff87888..17384c3b8 100644 --- a/apps/scandic-web/server/routers/hotels/output.ts +++ b/apps/scandic-web/server/routers/hotels/output.ts @@ -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) diff --git a/apps/scandic-web/server/routers/hotels/schemas/location/hotel.ts b/apps/scandic-web/server/routers/hotels/schemas/location/hotel.ts index 01a407ab3..df28c4932 100644 --- a/apps/scandic-web/server/routers/hotels/schemas/location/hotel.ts +++ b/apps/scandic-web/server/routers/hotels/schemas/location/hotel.ts @@ -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