fix: we showed duplicate rooms because every bed represents a room

This commit is contained in:
Simon Emanuelsson
2025-03-19 10:01:05 +01:00
parent 200ed55a2c
commit cf91d3d947
12 changed files with 356 additions and 234 deletions

View File

@@ -17,94 +17,89 @@ import styles from "./hotelInfoCard.module.css"
import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCard" import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCard"
export default async function HotelInfoCard({ hotelData }: HotelInfoCardProps) { export default async function HotelInfoCard({ hotel }: HotelInfoCardProps) {
const hotel = hotelData?.hotel
const intl = await getIntl() const intl = await getIntl()
const sortedFacilities = hotel?.detailedFacilities const sortedFacilities = hotel.detailedFacilities
.sort((a, b) => b.sortOrder - a.sortOrder) .sort((a, b) => b.sortOrder - a.sortOrder)
.slice(0, 5) .slice(0, 5)
const galleryImages = mapApiImagesToGalleryImages(hotel?.galleryImages || []) const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
return ( return (
<article className={styles.container}> <article className={styles.container}>
{hotel && ( <section className={styles.wrapper}>
<section className={styles.wrapper}> <div className={styles.imageWrapper}>
<div className={styles.imageWrapper}> <ImageGallery title={hotel.name} images={galleryImages} fill />
<ImageGallery title={hotel.name} images={galleryImages} fill /> {hotel.ratings?.tripAdvisor && (
{hotel.ratings?.tripAdvisor && ( <TripAdvisorChip rating={hotel.ratings.tripAdvisor.rating} />
<TripAdvisorChip rating={hotel.ratings.tripAdvisor.rating} /> )}
)} </div>
</div> <div className={styles.hotelContent}>
<div className={styles.hotelContent}> <div className={styles.hotelInformation}>
<div className={styles.hotelInformation}> <Title as="h2" textTransform="uppercase">
<Title as="h2" textTransform="uppercase"> {hotel.name}
{hotel.name} </Title>
</Title> <div className={styles.hotelAddressDescription}>
<div className={styles.hotelAddressDescription}> <Caption color="uiTextMediumContrast">
<Caption color="uiTextMediumContrast"> {intl.formatMessage(
{intl.formatMessage( {
{ id: "{address}, {city} ∙ {distanceToCityCenterInKm} km to city center",
id: "{address}, {city} ∙ {distanceToCityCenterInKm} km to city center", },
}, {
{ address: hotel.address.streetAddress,
address: hotel.address.streetAddress, city: hotel.address.city,
city: hotel.address.city, distanceToCityCenterInKm: getSingleDecimal(
distanceToCityCenterInKm: getSingleDecimal( hotel.location.distanceToCentre / 1000
hotel.location.distanceToCentre / 1000 ),
), }
} )}
)} </Caption>
</Caption> <Body color="uiTextHighContrast">
<Body color="uiTextHighContrast"> {hotel.hotelContent.texts.descriptions?.medium}
{hotel.hotelContent.texts.descriptions?.medium} </Body>
</Body>
</div>
</div>
<Divider color="subtle" variant="vertical" />
<div className={styles.facilities}>
<div className={styles.facilityList}>
<Body textTransform="bold" className={styles.facilityTitle}>
{intl.formatMessage({ id: "At the hotel" })}
</Body>
{sortedFacilities?.map((facility) => {
const IconComponent = mapFacilityToIcon(facility.id)
return (
<div className={styles.facilitiesItem} key={facility.id}>
{IconComponent && (
<IconComponent
className={styles.facilitiesIcon}
color="grey80"
/>
)}
<Body color="uiTextHighContrast">{facility.name}</Body>
</div>
)
})}
</div>
<ReadMore
label={intl.formatMessage({ id: "See all amenities" })}
hotelId={hotel.operaId}
hotel={hotel}
showCTA={false}
/>
</div> </div>
</div> </div>
</section> <Divider color="subtle" variant="vertical" />
)} <div className={styles.facilities}>
{hotel?.specialAlerts.map((alert) => { <div className={styles.facilityList}>
return ( <Body textTransform="bold" className={styles.facilityTitle}>
<div className={styles.hotelAlert} key={`wrapper_${alert.id}`}> {intl.formatMessage({ id: "At the hotel" })}
<Alert </Body>
key={alert.id} {sortedFacilities?.map((facility) => {
type={alert.type} const IconComponent = mapFacilityToIcon(facility.id)
heading={alert.heading} return (
text={alert.text} <div className={styles.facilitiesItem} key={facility.id}>
{IconComponent && (
<IconComponent
className={styles.facilitiesIcon}
color="grey80"
/>
)}
<Body color="uiTextHighContrast">{facility.name}</Body>
</div>
)
})}
</div>
<ReadMore
label={intl.formatMessage({ id: "See all amenities" })}
hotelId={hotel.operaId}
hotel={hotel}
showCTA={false}
/> />
</div> </div>
) </div>
})} </section>
{hotel.specialAlerts.map((alert) => (
<div className={styles.hotelAlert} key={`wrapper_${alert.id}`}>
<Alert
key={alert.id}
type={alert.type}
heading={alert.heading}
text={alert.text}
/>
</div>
))}
</article> </article>
) )
} }

View File

@@ -69,8 +69,6 @@ function getBreakfastMessage(
export default function RoomCard({ roomConfiguration }: RoomCardProps) { export default function RoomCard({ roomConfiguration }: RoomCardProps) {
const intl = useIntl() const intl = useIntl()
const lessThanFiveRoomsLeft =
roomConfiguration.roomsLeft > 0 && roomConfiguration.roomsLeft < 5
const searchParams = useSearchParams() const searchParams = useSearchParams()
const bookingCode = searchParams.get("bookingCode") const bookingCode = searchParams.get("bookingCode")
@@ -85,6 +83,8 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
})) }))
const { isMainRoom, roomAvailability, roomNr, selectedPackage } = const { isMainRoom, roomAvailability, roomNr, selectedPackage } =
useRoomContext() useRoomContext()
const showLowInventory =
roomConfiguration.roomsLeft > 0 && roomConfiguration.roomsLeft < 5
if (!roomAvailability || !("rateDefinitions" in roomAvailability)) { if (!roomAvailability || !("rateDefinitions" in roomAvailability)) {
return null return null
@@ -177,7 +177,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
<li className={classNames}> <li className={classNames}>
<div className={styles.imageContainer}> <div className={styles.imageContainer}>
<div className={styles.chipContainer}> <div className={styles.chipContainer}>
{lessThanFiveRoomsLeft ? ( {showLowInventory ? (
<span className={styles.chip}> <span className={styles.chip}>
<Footnote color="burgundy" textTransform="uppercase"> <Footnote color="burgundy" textTransform="uppercase">
{intl.formatMessage( {intl.formatMessage(
@@ -211,16 +211,16 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
<Caption color="uiTextMediumContrast"> <Caption color="uiTextMediumContrast">
{occupancy.max === occupancy.min {occupancy.max === occupancy.min
? intl.formatMessage( ? intl.formatMessage(
{ id: "{guests, plural, one {# guest} other {# guests}}" }, { id: "{guests, plural, one {# guest} other {# guests}}" },
{ guests: occupancy.max } { guests: occupancy.max }
) )
: intl.formatMessage( : intl.formatMessage(
{ id: "{min}-{max} guests" }, { id: "{min}-{max} guests" },
{ {
min: occupancy.min, min: occupancy.min,
max: occupancy.max, max: occupancy.max,
} }
)} )}
</Caption> </Caption>
)} )}
<RoomSize roomSize={roomSize} /> <RoomSize roomSize={roomSize} />
@@ -293,7 +293,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
title={rateTitle} title={rateTitle}
rateTitle={ rateTitle={
product.public && product.public &&
product.public?.rateType !== RateTypeEnum.Regular product.public?.rateType !== RateTypeEnum.Regular
? rateDefinition?.title ? rateDefinition?.title
: undefined : undefined
} }

View File

@@ -17,7 +17,6 @@ export function RoomsContainer({
childArray, childArray,
fromDate, fromDate,
hotelData, hotelData,
hotelId,
isUserLoggedIn, isUserLoggedIn,
toDate, toDate,
}: RoomsContainerProps) { }: RoomsContainerProps) {
@@ -29,7 +28,7 @@ export function RoomsContainer({
const { data: roomsAvailability, isPending: isLoadingAvailability } = const { data: roomsAvailability, isPending: isLoadingAvailability } =
useRoomsAvailability( useRoomsAvailability(
adultArray, adultArray,
hotelId, hotelData.hotel.id,
fromDateString, fromDateString,
toDateString, toDateString,
lang, lang,
@@ -42,7 +41,7 @@ export function RoomsContainer({
childArray, childArray,
fromDateString, fromDateString,
toDateString, toDateString,
hotelId, hotelData.hotel.id,
lang lang
) )
@@ -50,10 +49,6 @@ export function RoomsContainer({
return <RoomsContainerSkeleton /> return <RoomsContainerSkeleton />
} }
if (!hotelData?.hotel) {
return null
}
if (packages === null) { if (packages === null) {
// TODO: Log packages error // TODO: Log packages error
console.error("[RoomsContainer] unable to fetch packages") console.error("[RoomsContainer] unable to fetch packages")

View File

@@ -5,9 +5,7 @@ import { Suspense } from "react"
import { getHotel } from "@/lib/trpc/memoizedRequests" import { getHotel } from "@/lib/trpc/memoizedRequests"
import { auth } from "@/auth" import { auth } from "@/auth"
import HotelInfoCard, { import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard"
HotelInfoCardSkeleton,
} from "@/components/HotelReservation/SelectRate/HotelInfoCard"
import { RoomsContainer } from "@/components/HotelReservation/SelectRate/RoomsContainer" import { RoomsContainer } from "@/components/HotelReservation/SelectRate/RoomsContainer"
import TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
@@ -33,17 +31,21 @@ export default async function SelectRatePage({
const { adultsInRoom, childrenInRoom, hotel, noOfRooms, selectHotelParams } = const { adultsInRoom, childrenInRoom, hotel, noOfRooms, selectHotelParams } =
searchDetails searchDetails
const { fromDate, toDate } = getValidDates(
selectHotelParams.fromDate,
selectHotelParams.toDate
)
const hotelData = await getHotel({ const hotelData = await getHotel({
hotelId: hotel.id, hotelId: hotel.id,
isCardOnlyPayment: false, isCardOnlyPayment: false,
language: params.lang, language: params.lang,
}) })
if (!hotelData) {
return notFound()
}
const { fromDate, toDate } = getValidDates(
selectHotelParams.fromDate,
selectHotelParams.toDate
)
const session = await auth() const session = await auth()
const isUserLoggedIn = isValidSession(session) const isUserLoggedIn = isValidSession(session)
@@ -59,20 +61,17 @@ export default async function SelectRatePage({
hotel.id, hotel.id,
hotel.name, hotel.name,
noOfRooms, noOfRooms,
hotelData?.hotel.address.country, hotelData.hotel.address.country,
hotelData?.hotel.address.city, hotelData.hotel.address.city,
selectHotelParams.city selectHotelParams.city
) )
const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams) const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
const hotelId = +hotel.id
const suspenseKey = stringify(searchParams) const suspenseKey = stringify(searchParams)
return ( return (
<> <>
<Suspense fallback={<HotelInfoCardSkeleton />}> <HotelInfoCard hotel={hotelData.hotel} />
<HotelInfoCard hotelData={hotelData} />
</Suspense>
<RoomsContainer <RoomsContainer
adultArray={adultsInRoom} adultArray={adultsInRoom}
@@ -80,7 +79,6 @@ export default async function SelectRatePage({
childArray={childrenInRoom} childArray={childrenInRoom}
fromDate={arrivalDate} fromDate={arrivalDate}
hotelData={hotelData} hotelData={hotelData}
hotelId={hotelId}
isUserLoggedIn={isUserLoggedIn} isUserLoggedIn={isUserLoggedIn}
toDate={departureDate} toDate={departureDate}
/> />

View File

@@ -6,38 +6,22 @@ import type { ChildrenInRoom } from "@/utils/hotelSearchDetails"
export function useRoomsAvailability( export function useRoomsAvailability(
adultsCount: number[], adultsCount: number[],
hotelId: number, hotelId: string,
fromDateString: string, fromDateString: string,
toDateString: string, toDateString: string,
lang: Lang, lang: Lang,
childArray: ChildrenInRoom, childArray: ChildrenInRoom,
bookingCode?: string bookingCode?: string
) { ) {
const roomsAvailability = return trpc.hotel.availability.roomsCombinedAvailability.useQuery({
trpc.hotel.availability.roomsCombinedAvailability.useQuery({ adultsCount,
adultsCount, bookingCode,
bookingCode, childArray,
childArray, hotelId,
hotelId, lang,
lang, roomStayEndDate: toDateString,
roomStayEndDate: toDateString, roomStayStartDate: fromDateString,
roomStayStartDate: fromDateString,
})
const data = roomsAvailability.data?.map((ra) => {
if (ra.status === "fulfilled") {
return ra.value
}
return {
details: ra.reason,
error: "request_failure",
}
}) })
return {
...roomsAvailability,
data,
}
} }
export function useHotelPackages( export function useHotelPackages(
@@ -45,14 +29,14 @@ export function useHotelPackages(
childArray: ChildrenInRoom, childArray: ChildrenInRoom,
fromDateString: string, fromDateString: string,
toDateString: string, toDateString: string,
hotelId: number, hotelId: string,
lang: Lang lang: Lang
) { ) {
return trpc.hotel.packages.get.useQuery({ return trpc.hotel.packages.get.useQuery({
adults: adultArray[0], // Using the first adult count adults: adultArray[0], // Using the first adult count
children: childArray?.[0]?.length, // Using the first children count children: childArray?.[0]?.length, // Using the first children count
endDate: toDateString, endDate: toDateString,
hotelId: hotelId.toString(), hotelId,
packageCodes: [ packageCodes: [
RoomPackageCodeEnum.ACCESSIBILITY_ROOM, RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
RoomPackageCodeEnum.PET_ROOM, RoomPackageCodeEnum.PET_ROOM,

View File

@@ -29,6 +29,7 @@ export default function BookingConfirmationProvider({
rooms, rooms,
vat, vat,
} }
storeRef.current = createBookingConfirmationStore(initialData) storeRef.current = createBookingConfirmationStore(initialData)
} }

View File

@@ -40,7 +40,7 @@ export const roomsCombinedAvailabilityInputSchema = z.object({
.nullable() .nullable()
) )
.nullish(), .nullish(),
hotelId: z.number(), hotelId: z.string(),
lang: z.nativeEnum(Lang), lang: z.nativeEnum(Lang),
rateCode: z.string().optional(), rateCode: z.string().optional(),
roomStayEndDate: z.string(), roomStayEndDate: z.string(),

View File

@@ -33,6 +33,7 @@ import type {
import type { import type {
Product, Product,
RateDefinition, RateDefinition,
RoomConfiguration,
} from "@/types/trpc/routers/hotel/roomAvailability" } from "@/types/trpc/routers/hotel/roomAvailability"
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html // NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
@@ -145,6 +146,142 @@ const statusLookup = {
[AvailabilityEnum.NotAvailable]: 2, [AvailabilityEnum.NotAvailable]: 2,
} }
function sortRoomConfigs(a: RoomConfiguration, b: RoomConfiguration) {
// @ts-expect-error - array indexing
return statusLookup[a.status] - statusLookup[b.status]
}
export const roomsCombinedAvailabilitySchema = z
.object({
data: z.object({
attributes: z.object({
checkInDate: z.string(),
checkOutDate: z.string(),
hotelId: z.number(),
mustBeGuaranteed: z.boolean().optional(),
occupancy: occupancySchema.optional(),
rateDefinitions: z.array(rateDefinitionSchema),
roomConfigurations: z
.array(roomConfigurationSchema)
.transform((data) => {
// Initial sort to guarantee if one bed is NotAvailable and whereas
// the other is Available to make sure data is added to the correct
// roomConfig
const configs = data.sort(sortRoomConfigs)
const roomConfigs = new Map<string, RoomConfiguration>()
for (const roomConfig of configs) {
if (roomConfigs.has(roomConfig.roomType)) {
const currentRoomConf = roomConfigs.get(roomConfig.roomType)
if (currentRoomConf) {
currentRoomConf.features = roomConfig.features.reduce(
(feats, feature) => {
const currentFeatureIndex = feats.findIndex(
(f) => f.code === feature.code
)
if (currentFeatureIndex !== -1) {
feats[currentFeatureIndex].inventory =
feats[currentFeatureIndex].inventory +
feature.inventory
} else {
feats.push(feature)
}
return feats
},
currentRoomConf.features
)
currentRoomConf.roomsLeft =
currentRoomConf.roomsLeft + roomConfig.roomsLeft
roomConfigs.set(currentRoomConf.roomType, currentRoomConf)
}
} else {
roomConfigs.set(roomConfig.roomType, roomConfig)
}
}
return Array.from(roomConfigs.values())
}),
}),
relationships: relationshipsSchema.optional(),
type: z.string().optional(),
}),
})
.transform(({ data: { attributes } }) => {
const rateDefinitions = attributes.rateDefinitions
const cancellationRuleLookup = rateDefinitions.reduce((acc, val) => {
// @ts-expect-error - index of cancellationRule TS
acc[val.rateCode] = cancellationRules[val.cancellationRule]
return acc
}, {})
const roomConfigurations = attributes.roomConfigurations
.map((room) => {
if (room.products.length) {
room.breakfastIncludedInAllRatesMember = room.products.every(
(product) =>
everyRateHasBreakfastIncluded(product, rateDefinitions, "member")
)
room.breakfastIncludedInAllRatesPublic = room.products.every(
(product) =>
everyRateHasBreakfastIncluded(product, rateDefinitions, "public")
)
room.products = room.products.map((product) => {
const publicRate = product.public
if (publicRate?.rateCode) {
const publicRateDefinition = rateDefinitions.find(
(rateDefinition) =>
rateDefinition.rateCode === publicRate.rateCode
)
if (publicRateDefinition) {
const rate = getRate(publicRateDefinition)
if (rate) {
product.rate = rate
if (rate === "flex") {
product.isFlex = true
}
}
}
}
const memberRate = product.member
if (memberRate?.rateCode) {
const memberRateDefinition = rateDefinitions.find(
(rate) => rate.rateCode === memberRate.rateCode
)
if (memberRateDefinition) {
const rate = getRate(memberRateDefinition)
if (rate) {
product.rate = rate
if (rate === "flex") {
product.isFlex = true
}
}
}
}
return product
})
// CancellationRule is the same for public and member per product
// Sorting to guarantee order based on rate
room.products = room.products.sort(
(a, b) =>
// @ts-expect-error - index
cancellationRuleLookup[a.public?.rateCode || a.member?.rateCode] -
// @ts-expect-error - index
cancellationRuleLookup[b.public?.rateCode || b.member?.rateCode]
)
}
return room
})
.sort(sortRoomConfigs)
return {
...attributes,
roomConfigurations,
}
})
export const roomsAvailabilitySchema = z export const roomsAvailabilitySchema = z
.object({ .object({
data: z.object({ data: z.object({

View File

@@ -50,6 +50,7 @@ import {
packagesSchema, packagesSchema,
ratesSchema, ratesSchema,
roomsAvailabilitySchema, roomsAvailabilitySchema,
roomsCombinedAvailabilitySchema,
} from "./output" } from "./output"
import tempRatesData from "./tempRatesData.json" import tempRatesData from "./tempRatesData.json"
import { import {
@@ -495,95 +496,110 @@ export const hotelQueryRouter = router({
roomsCombinedAvailability: serviceProcedure roomsCombinedAvailability: serviceProcedure
.input(roomsCombinedAvailabilityInputSchema) .input(roomsCombinedAvailabilityInputSchema)
.query(async ({ input, ctx }) => { .query(
const { lang } = input async ({
const apiLang = toApiLang(lang) ctx,
const { input: {
adultsCount, adultsCount,
bookingCode, bookingCode,
childArray, childArray,
hotelId, hotelId,
rateCode, lang,
roomStayEndDate, rateCode,
roomStayStartDate, roomStayEndDate,
} = input roomStayStartDate,
},
}) => {
const apiLang = toApiLang(lang)
const metricsData = { const metricsData = {
hotelId, hotelId,
roomStayStartDate, roomStayStartDate,
roomStayEndDate, roomStayEndDate,
adultsCount, adultsCount,
childArray: childArray ? JSON.stringify(childArray) : undefined, childArray: childArray ? JSON.stringify(childArray) : undefined,
bookingCode, bookingCode,
} }
metrics.roomsCombinedAvailability.counter.add(1, metricsData) metrics.roomsCombinedAvailability.counter.add(1, metricsData)
console.info( console.info(
"api.hotels.roomsCombinedAvailability start", "api.hotels.roomsCombinedAvailability start",
JSON.stringify({ query: { hotelId, params: metricsData } }) JSON.stringify({ query: { hotelId, params: metricsData } })
) )
const availabilityResponses = await Promise.allSettled( const availabilityResponses = await Promise.allSettled(
adultsCount.map(async (adultCount: number, idx: number) => { adultsCount.map(async (adultCount: number, idx: number) => {
const kids = childArray?.[idx] const kids = childArray?.[idx]
const params: Record<string, string | number | undefined> = { const params: Record<string, string | number | undefined> = {
roomStayStartDate, roomStayStartDate,
roomStayEndDate, roomStayEndDate,
adults: adultCount, adults: adultCount,
...(kids?.length && { ...(kids?.length && {
children: generateChildrenString(kids), children: generateChildrenString(kids),
}), }),
...(bookingCode && { bookingCode }), ...(bookingCode && { bookingCode }),
language: apiLang, language: apiLang,
}
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotel(hotelId.toString()),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
console.error("Failed API call", { params, text })
return { error: "http_error", details: text }
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
roomsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
console.error("Validation error", {
params,
error: validateAvailabilityData.error,
})
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
return {
error: "validation_error",
details: validateAvailabilityData.error,
} }
}
if (rateCode) { const apiResponse = await api.get(
validateAvailabilityData.data.mustBeGuaranteed = api.endpoints.v1.Availability.hotel(hotelId.toString()),
validateAvailabilityData.data.rateDefinitions.find( {
(rate) => rate.rateCode === rateCode headers: {
)?.mustBeGuaranteed Authorization: `Bearer ${ctx.serviceToken}`,
} },
},
params
)
return validateAvailabilityData.data if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
console.error("Failed API call", { params, text })
return { error: "http_error", details: text }
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
roomsCombinedAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
console.error("Validation error", {
params,
error: validateAvailabilityData.error,
})
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
return {
error: "validation_error",
details: validateAvailabilityData.error,
}
}
if (rateCode) {
validateAvailabilityData.data.mustBeGuaranteed =
validateAvailabilityData.data.rateDefinitions.find(
(rate) => rate.rateCode === rateCode
)?.mustBeGuaranteed
}
return validateAvailabilityData.data
})
)
metrics.roomsCombinedAvailability.success.add(1, metricsData)
const data = availabilityResponses.map((availability) => {
if (availability.status === "fulfilled") {
return availability.value
}
return {
details: availability.reason,
error: "request_failure",
}
}) })
)
metrics.roomsCombinedAvailability.success.add(1, metricsData) return data
return availabilityResponses }
}), ),
room: serviceProcedure room: serviceProcedure
.input(selectedRoomAvailabilityInputSchema) .input(selectedRoomAvailabilityInputSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@@ -771,9 +787,9 @@ export const hotelQueryRouter = router({
type: matchingRoom.mainBed.type, type: matchingRoom.mainBed.type,
extraBed: matchingRoom.fixedExtraBed extraBed: matchingRoom.fixedExtraBed
? { ? {
type: matchingRoom.fixedExtraBed.type, type: matchingRoom.fixedExtraBed.type,
description: matchingRoom.fixedExtraBed.description, description: matchingRoom.fixedExtraBed.description,
} }
: undefined, : undefined,
} }
} }
@@ -1102,9 +1118,9 @@ export const hotelQueryRouter = router({
return hotelData return hotelData
? { ? {
...hotelData, ...hotelData,
url: hotelPage?.url ?? null, url: hotelPage?.url ?? null,
} }
: null : null
}) })
) )

View File

@@ -45,8 +45,5 @@ export const roomConfigurationSchema = z
} }
} }
// Creating a new objekt since data is frozen (readony) return data
// and can cause errors to be thrown if trying to manipulate
// object elsewhere
return { ...data }
}) })

View File

@@ -1,5 +1,5 @@
import type { HotelData } from "@/types/hotel" import type { Hotel } from "@/types/hotel"
export interface HotelInfoCardProps { export interface HotelInfoCardProps {
hotelData: HotelData | null hotel: Hotel
} }

View File

@@ -8,8 +8,7 @@ export interface RoomsContainerProps {
bookingCode?: string bookingCode?: string
childArray: ChildrenInRoom childArray: ChildrenInRoom
fromDate: Date fromDate: Date
hotelData: HotelData | null hotelData: HotelData
hotelId: number
isUserLoggedIn: boolean isUserLoggedIn: boolean
toDate: Date toDate: Date
} }