Merged in fix/SW-978-error-page-gdansk (pull request #1002)

Fix/SW-978 error page gdansk

Approved-by: Niclas Edenvin
This commit is contained in:
Pontus Dreij
2024-12-02 11:20:06 +00:00
12 changed files with 112 additions and 32 deletions

View File

@@ -14,6 +14,7 @@ import { setLang } from "@/i18n/serverContext"
import { fetchAvailableHotels, getFiltersFromHotels } from "../../utils"
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"
@@ -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}
/>

View File

@@ -33,6 +33,7 @@ import { setLang } from "@/i18n/serverContext"
import styles from "./page.module.css"
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"
@@ -61,6 +62,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)
@@ -74,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" }),
@@ -169,7 +182,7 @@ export default async function SelectHotelPage({
})}
/>
)}
<HotelCardListing hotelData={hotels} />
<HotelCardListing hotelData={validHotels} />
</div>
</main>
</>

View File

@@ -4,7 +4,10 @@ import { serverClient } from "@/lib/trpc/server"
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 +33,10 @@ const hotelFacilitiesFilterNames = [
export async function fetchAvailableHotels(
input: AvailabilityInput
): Promise<HotelData[]> {
): Promise<NullableHotelData[]> {
const availableHotels = await serverClient().hotel.availability.hotels(input)
if (!availableHotels) throw new Error()
if (!availableHotels) return []
const language = getLang()
@@ -43,7 +46,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 +58,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

View File

@@ -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<LangParams>()
const router = useRouter()
const searchParams = useSearchParams()
const currentSearchParamsRef = useRef<string>()
const isFirstLoadRef = useRef<boolean>(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)

View File

@@ -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 (
<article
className={classNames}
@@ -82,8 +84,8 @@ function HotelCard({
<section className={styles.hotelInformation}>
<div className={styles.titleContainer}>
<HotelLogo
hotelId={hotel.hotelData.operaId}
hotelType={hotel.hotelData.hotelType}
hotelId={hotelData.operaId}
hotelType={hotelData.hotelType}
/>
<Subtitle textTransform="capitalize" color="uiTextHighContrast">
{hotelData.name}

View File

@@ -17,7 +17,7 @@ export default function HotelCardDialogListing({
activeCard,
onActiveCardChange,
}: HotelCardDialogListingProps) {
const hotelsPinData = getHotelPins(hotels)
const hotelsPinData = hotels ? getHotelPins(hotels) : []
const activeCardRef = useRef<HTMLDivElement | null>(null)
const observerRef = useRef<IntersectionObserver | null>(null)
const dialogRef = useRef<HTMLDivElement>(null)
@@ -77,7 +77,7 @@ export default function HotelCardDialogListing({
return (
<div className={styles.hotelCardDialogListing} ref={dialogRef}>
{hotelsPinData?.length &&
{!!hotelsPinData?.length &&
hotelsPinData.map((data) => {
const isActive = data.name === activeCard
return (

View File

@@ -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,

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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(),
}),
})

View File

@@ -18,3 +18,7 @@ export type HotelData = {
hotelData: Hotel
price: ProductType
}
export interface NullableHotelData extends Omit<HotelData, "hotelData"> {
hotelData: HotelData["hotelData"] | null
}

View File

@@ -56,7 +56,7 @@ export interface HotelCardDialogProps {
}
export interface HotelCardDialogListingProps {
hotels: HotelData[]
hotels: HotelData[] | null
activeCard: string | null | undefined
onActiveCardChange: (hotelName: string | null) => void
}