From 4de3f4717cfc40d2623dbe7623296fca4f2e80f8 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 26 Nov 2024 10:27:47 +0100 Subject: [PATCH 1/5] fix(SW-978): Fallback if no room is selected in URL --- .../hotelreservation/(standard)/select-hotel/page.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index 3954be688..1d3ea38f6 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -61,6 +61,14 @@ export default async function SelectHotelPage({ const selectHotelParams = new URLSearchParams(searchParams) const selectHotelParamsObject = getHotelReservationQueryParams(selectHotelParams) + + if ( + !selectHotelParamsObject.room || + selectHotelParamsObject.room.length === 0 + ) { + return notFound() + } + const adults = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms const children = selectHotelParamsObject.room[0].child ? generateChildrenString(selectHotelParamsObject.room[0].child) From 3d78bdd671a369f2b0bba3d03a0adf431fd2cd86 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 26 Nov 2024 13:55:44 +0100 Subject: [PATCH 2/5] fix(SW-978) Checks for null data on hotels --- .../select-hotel/@modal/(.)map/page.tsx | 13 ++++++---- .../(standard)/select-hotel/page.tsx | 9 +++++-- .../(standard)/select-hotel/utils.ts | 20 ++++++++++++---- .../HotelReservation/HotelCard/index.tsx | 24 ++++++++++--------- .../HotelCardDialogListing/index.tsx | 2 +- .../HotelCardDialogListing/utils.ts | 2 ++ lib/trpc/memoizedRequests/index.ts | 2 +- server/routers/hotels/input.ts | 2 +- .../selectHotel/hotelCardListingProps.ts | 4 ++++ .../hotelReservation/selectHotel/map.ts | 2 +- 10 files changed, 54 insertions(+), 26 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx index 2579fd597..40ce894bc 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx @@ -14,6 +14,7 @@ import { setLang } from "@/i18n/serverContext" import { fetchAvailableHotels, getFiltersFromHotels } from "../../utils" +import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import type { LangParams, PageArgs } from "@/types/params" @@ -52,11 +53,15 @@ export default async function SelectHotelMapPage({ children, }) - const hotelPins = getHotelPins(hotels) - const filterList = getFiltersFromHotels(hotels) + const validHotels = hotels.filter( + (hotel): hotel is HotelData => hotel !== null + ) + + const hotelPins = getHotelPins(validHotels) + const filterList = getFiltersFromHotels(validHotels) const cityCoordinates = await getCityCoordinates({ city: city.name, - hotel: { address: hotels[0].hotelData.address.streetAddress }, + hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress }, }) return ( @@ -65,7 +70,7 @@ export default async function SelectHotelMapPage({ apiKey={googleMapsApiKey} hotelPins={hotelPins} mapId={googleMapId} - hotels={hotels} + hotels={validHotels} filterList={filterList} cityCoordinates={cityCoordinates} /> diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index 1d3ea38f6..ec281141b 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -33,6 +33,7 @@ import { setLang } from "@/i18n/serverContext" import styles from "./page.module.css" +import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import { AlertTypeEnum } from "@/types/enums/alert" import { LangParams, PageArgs } from "@/types/params" @@ -82,7 +83,11 @@ export default async function SelectHotelPage({ children, }) - const filterList = getFiltersFromHotels(hotels) + const validHotels = hotels.filter( + (hotel): hotel is HotelData => hotel !== null + ) + + const filterList = getFiltersFromHotels(validHotels) const breadcrumbs = [ { title: intl.formatMessage({ id: "Home" }), @@ -177,7 +182,7 @@ export default async function SelectHotelPage({ })} /> )} - + diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts index bd41510ce..fe9f0ab84 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts @@ -1,10 +1,14 @@ import { getHotelData } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" +import { badRequestError } from "@/server/errors/trpc" import { getLang } from "@/i18n/serverContext" import type { AvailabilityInput } from "@/types/components/hotelReservation/selectHotel/availabilityInput" -import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" +import type { + HotelData, + NullableHotelData, +} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { CategorizedFilters, Filter, @@ -30,10 +34,10 @@ const hotelFacilitiesFilterNames = [ export async function fetchAvailableHotels( input: AvailabilityInput -): Promise { +): Promise { const availableHotels = await serverClient().hotel.availability.hotels(input) - if (!availableHotels) throw new Error() + if (!availableHotels) return [] const language = getLang() @@ -43,7 +47,7 @@ export async function fetchAvailableHotels( language, }) - if (!hotelData) throw new Error() + if (!hotelData) return { hotelData: null, price: hotel.productType } return { hotelData: hotelData.data.attributes, @@ -55,7 +59,13 @@ export async function fetchAvailableHotels( } export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters { - const filters = hotels.flatMap((hotel) => hotel.hotelData.detailedFacilities) + if (hotels.length === 0) + return { facilityFilters: [], surroundingsFilters: [] } + + const filters = hotels.flatMap((hotel) => { + if (!hotel.hotelData) return [] + return hotel.hotelData.detailedFacilities + }) const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))] const filterList: Filter[] = uniqueFilterIds diff --git a/components/HotelReservation/HotelCard/index.tsx b/components/HotelReservation/HotelCard/index.tsx index f6fbbcf12..5c2f788ee 100644 --- a/components/HotelReservation/HotelCard/index.tsx +++ b/components/HotelReservation/HotelCard/index.tsx @@ -41,18 +41,11 @@ function HotelCard({ const { hotelData } = hotel const { price } = hotel - const amenities = hotelData.detailedFacilities.slice(0, 5) - - const classNames = hotelCardVariants({ - type, - state, - }) - const handleMouseEnter = useCallback(() => { - if (onHotelCardHover) { + if (onHotelCardHover && hotelData) { onHotelCardHover(hotelData.name) } - }, [onHotelCardHover, hotelData.name]) + }, [onHotelCardHover, hotelData]) const handleMouseLeave = useCallback(() => { if (onHotelCardHover) { @@ -60,6 +53,15 @@ function HotelCard({ } }, [onHotelCardHover]) + if (!hotel || !hotelData) return null + + const amenities = hotelData.detailedFacilities.slice(0, 5) + + const classNames = hotelCardVariants({ + type, + state, + }) + return (
{hotelData.name} diff --git a/components/HotelReservation/HotelCardDialogListing/index.tsx b/components/HotelReservation/HotelCardDialogListing/index.tsx index 123cc4ced..7e8750ec2 100644 --- a/components/HotelReservation/HotelCardDialogListing/index.tsx +++ b/components/HotelReservation/HotelCardDialogListing/index.tsx @@ -17,7 +17,7 @@ export default function HotelCardDialogListing({ activeCard, onActiveCardChange, }: HotelCardDialogListingProps) { - const hotelsPinData = getHotelPins(hotels) + const hotelsPinData = hotels ? getHotelPins(hotels) : [] const activeCardRef = useRef(null) const observerRef = useRef(null) const dialogRef = useRef(null) diff --git a/components/HotelReservation/HotelCardDialogListing/utils.ts b/components/HotelReservation/HotelCardDialogListing/utils.ts index 2299b3f35..1a0e05ad8 100644 --- a/components/HotelReservation/HotelCardDialogListing/utils.ts +++ b/components/HotelReservation/HotelCardDialogListing/utils.ts @@ -2,6 +2,8 @@ import type { HotelData } from "@/types/components/hotelReservation/selectHotel/ import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" export function getHotelPins(hotels: HotelData[]): HotelPin[] { + if (hotels.length === 0) return [] + return hotels.map((hotel) => ({ coordinates: { lat: hotel.hotelData.location.latitude, diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index 7db2e66da..2bf35c264 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -144,7 +144,7 @@ export const getBookingConfirmation = cache( export const getCityCoordinates = cache( async function getMemoizedCityCoordinates(input: { city: string - hotel: { address: string } + hotel: { address: string | undefined } }) { return serverClient().hotel.map.city(input) } diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index a4e47caf3..4a66009b3 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -77,6 +77,6 @@ export const getRoomPackagesInputSchema = z.object({ export const getCityCoordinatesInputSchema = z.object({ city: z.string(), hotel: z.object({ - address: z.string(), + address: z.string().optional(), }), }) diff --git a/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts b/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts index 68a6174ed..eb84b6977 100644 --- a/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts +++ b/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts @@ -18,3 +18,7 @@ export type HotelData = { hotelData: Hotel price: ProductType } + +export interface NullableHotelData extends Omit { + hotelData: HotelData["hotelData"] | null +} diff --git a/types/components/hotelReservation/selectHotel/map.ts b/types/components/hotelReservation/selectHotel/map.ts index 4ec21f86f..19490464d 100644 --- a/types/components/hotelReservation/selectHotel/map.ts +++ b/types/components/hotelReservation/selectHotel/map.ts @@ -56,7 +56,7 @@ export interface HotelCardDialogProps { } export interface HotelCardDialogListingProps { - hotels: HotelData[] + hotels: HotelData[] | null activeCard: string | null | undefined onActiveCardChange: (hotelName: string | null) => void } From 558efba23d4a0f1f1e7d53967c854f674e99b73f Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Fri, 29 Nov 2024 09:38:49 +0100 Subject: [PATCH 3/5] Reset the error and refresh the page when the search params change, to support the booking widget that is using router.push to navigate to the booking flow page --- app/[lang]/(live)/error.tsx | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/app/[lang]/(live)/error.tsx b/app/[lang]/(live)/error.tsx index 1b1970475..a39495eb7 100644 --- a/app/[lang]/(live)/error.tsx +++ b/app/[lang]/(live)/error.tsx @@ -1,7 +1,12 @@ "use client" // Error components must be Client Components -import { useParams, usePathname } from "next/navigation" -import { useEffect } from "react" +import { + useParams, + usePathname, + useRouter, + useSearchParams, +} from "next/navigation" +import { startTransition, useEffect, useRef } from "react" import { useIntl } from "react-intl" import { login } from "@/constants/routes/handleAuth" @@ -15,11 +20,17 @@ import { LangParams } from "@/types/params" export default function Error({ error, + reset, }: { error: Error & { digest?: string } + reset: () => void }) { const intl = useIntl() const params = useParams() + const router = useRouter() + const searchParams = useSearchParams() + const currentSearchParamsRef = useRef() + const isFirstLoadRef = useRef(true) useEffect(() => { // Log the error to an error reporting service @@ -31,6 +42,23 @@ export default function Error({ } }, [error, params.lang]) + useEffect(() => { + // This is to reset the error and refresh the page when the search params change, to support the booking widget that is using router.push to navigate to the booking flow page + const currentSearchParams = searchParams.toString() + + if ( + currentSearchParamsRef.current !== currentSearchParams && + !isFirstLoadRef.current + ) { + startTransition(() => { + reset() + router.refresh() + }) + } + isFirstLoadRef.current = false + currentSearchParamsRef.current = currentSearchParams + }, [searchParams, reset, router]) + const pathname = usePathname() const lang = findLang(pathname) From 289d43b326379a142bb2443ef97613f605536d4a Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Fri, 29 Nov 2024 09:56:25 +0100 Subject: [PATCH 4/5] fix(SW-978): Small fixes --- .../select-hotel/@modal/(.)map/page.tsx | 2 +- .../(standard)/select-hotel/page.tsx | 4 +++- .../(standard)/select-hotel/utils.ts | 1 - .../HotelCardDialogListing/index.tsx | 2 +- components/Maps/StaticMap/index.tsx | 23 ++++++++++++++++--- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx index 40ce894bc..600afbb38 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx @@ -14,7 +14,7 @@ import { setLang } from "@/i18n/serverContext" import { fetchAvailableHotels, getFiltersFromHotels } from "../../utils" -import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" +import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import type { LangParams, PageArgs } from "@/types/params" diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index ec281141b..d0b079f5d 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -33,7 +33,7 @@ import { setLang } from "@/i18n/serverContext" import styles from "./page.module.css" -import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" +import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import { AlertTypeEnum } from "@/types/enums/alert" import { LangParams, PageArgs } from "@/types/params" @@ -112,6 +112,8 @@ export default async function SelectHotelPage({ const isAllUnavailable = hotels.every((hotel) => hotel.price === undefined) + console.log("searchParams.city", searchParams.city) + return ( <>
diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts index fe9f0ab84..7960d3314 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts @@ -1,6 +1,5 @@ import { getHotelData } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" -import { badRequestError } from "@/server/errors/trpc" import { getLang } from "@/i18n/serverContext" diff --git a/components/HotelReservation/HotelCardDialogListing/index.tsx b/components/HotelReservation/HotelCardDialogListing/index.tsx index 7e8750ec2..4f9e83b36 100644 --- a/components/HotelReservation/HotelCardDialogListing/index.tsx +++ b/components/HotelReservation/HotelCardDialogListing/index.tsx @@ -77,7 +77,7 @@ export default function HotelCardDialogListing({ return (
- {hotelsPinData?.length && + {!!hotelsPinData?.length && hotelsPinData.map((data) => { const isActive = data.name === activeCard return ( diff --git a/components/Maps/StaticMap/index.tsx b/components/Maps/StaticMap/index.tsx index 7c80aa01f..6db5eada3 100644 --- a/components/Maps/StaticMap/index.tsx +++ b/components/Maps/StaticMap/index.tsx @@ -5,6 +5,25 @@ import { getUrlWithSignature } from "@/utils/map" import { StaticMapProps } from "@/types/components/maps/staticMap" +function getCenter({ + coordinates, + city, + country, +}: { + coordinates?: { lat: number; lng: number } + city?: string + country?: string +}): string | undefined { + switch (true) { + case !!coordinates: + return `${coordinates.lat},${coordinates.lng}` + case !!country: + return `${city}, ${country}` + default: + return city + } +} + export default function StaticMap({ city, country, @@ -19,9 +38,7 @@ export default function StaticMap({ const key = env.GOOGLE_STATIC_MAP_KEY const secret = env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET const baseUrl = "https://maps.googleapis.com/maps/api/staticmap" - const center = coordinates - ? `${coordinates.lat},${coordinates.lng}` - : `${city}, ${country}` + const center = getCenter({ coordinates, city, country }) if (!center) { return null From f60a77ec262e6e567791ea4174a3cce96c9bf989 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Fri, 29 Nov 2024 14:30:15 +0100 Subject: [PATCH 5/5] Removed console log --- .../(public)/hotelreservation/(standard)/select-hotel/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index d0b079f5d..662d051d7 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -112,8 +112,6 @@ export default async function SelectHotelPage({ const isAllUnavailable = hotels.every((hotel) => hotel.price === undefined) - console.log("searchParams.city", searchParams.city) - return ( <>