Merged in feat/refactor-select-rate (pull request #1402)

Select-rate: refactor - converted RoomsContainer into a client component

* feat/select-rate - refactor and fixed duplicate key warning

* Rooms as client component

* Fixed lang in input

* It works

* Cleanup

* Cleanup

* PR fixes


Approved-by: Joakim Jäderberg
This commit is contained in:
Linus Flood
2025-02-25 08:40:36 +00:00
parent cf3268bda3
commit e2749f5593
11 changed files with 226 additions and 273 deletions

View File

@@ -2,6 +2,7 @@ import { z } from "zod"
import { Lang } from "@/constants/languages"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { Country } from "@/types/enums/country"
@@ -23,14 +24,22 @@ export const getHotelsByHotelIdsAvailabilityInputSchema = z.object({
bookingCode: z.string().optional().default(""),
})
export const roomsAvailabilityInputSchema = z.object({
export const roomsCombinedAvailabilityInputSchema = z.object({
hotelId: z.number(),
roomStayStartDate: z.string(),
roomStayEndDate: z.string(),
adults: z.number(),
children: z.string().optional(),
uniqueAdultsCount: z.array(z.number()),
childArray: z
.array(
z.object({
bed: z.nativeEnum(ChildBedMapEnum),
age: z.number(),
})
)
.optional(),
bookingCode: z.string().optional(),
rateCode: z.string().optional(),
lang: z.nativeEnum(Lang),
})
export const selectedRoomAvailabilityInputSchema = z.object({
@@ -49,10 +58,6 @@ export type GetSelectedRoomAvailabilityInput = z.input<
typeof selectedRoomAvailabilityInputSchema
>
export type GetRoomsAvailabilityInput = z.input<
typeof roomsAvailabilityInputSchema
>
export const ratesInputSchema = z.object({
hotelId: z.string(),
})
@@ -109,6 +114,7 @@ export const roomPackagesInputSchema = z.object({
adults: z.number(),
children: z.number().optional().default(0),
packageCodes: z.array(z.string()).optional().default([]),
lang: z.nativeEnum(Lang),
})
export const cityCoordinatesInputSchema = z.object({
city: z.string(),

View File

@@ -70,10 +70,14 @@ export const metrics = {
fail: meter.createCounter("trpc.hotel.packages.get-fail"),
success: meter.createCounter("trpc.hotel.packages.get-success"),
},
roomAvailability: {
counter: meter.createCounter("trpc.hotel.availability.rooms"),
fail: meter.createCounter("trpc.hotel.availability.rooms-fail"),
success: meter.createCounter("trpc.hotel.availability.rooms-success"),
roomsCombinedAvailability: {
counter: meter.createCounter("trpc.hotel.roomsCombinedAvailability.rooms"),
fail: meter.createCounter(
"trpc.hotel.roomsCombinedAvailability.rooms-fail"
),
success: meter.createCounter(
"trpc.hotel.roomsCombinedAvailability.rooms-success"
),
},
selectedRoomAvailability: {
counter: meter.createCounter("trpc.hotel.availability.room"),

View File

@@ -35,7 +35,7 @@ import {
nearbyHotelIdsInput,
ratesInputSchema,
roomPackagesInputSchema,
roomsAvailabilityInputSchema,
roomsCombinedAvailabilityInputSchema,
selectedRoomAvailabilityInputSchema,
} from "./input"
import { metrics } from "./metrics"
@@ -470,128 +470,98 @@ export const hotelQueryRouter = router({
const apiLang = toApiLang(lang)
return getHotelsAvailabilityByHotelIds(input, apiLang, ctx.serviceToken)
}),
rooms: serviceProcedure
.input(roomsAvailabilityInputSchema)
roomsCombinedAvailability: serviceProcedure
.input(roomsCombinedAvailabilityInputSchema)
.query(async ({ input, ctx }) => {
const { lang } = ctx
const { lang } = input
const apiLang = toApiLang(lang)
const {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
uniqueAdultsCount,
childArray,
bookingCode,
rateCode,
} = input
const params: Record<string, string | number | undefined> = {
roomStayStartDate,
roomStayEndDate,
adults,
...(children && { children }),
...(bookingCode && { bookingCode }),
language: apiLang,
}
metrics.roomAvailability.counter.add(1, {
const metricsData = {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
uniqueAdultsCount,
childArray: childArray ? JSON.stringify(childArray) : undefined,
bookingCode,
})
}
metrics.roomsCombinedAvailability.counter.add(1, metricsData)
console.info(
"api.hotels.roomsAvailability start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotel(hotelId.toString()),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
"api.hotels.roomsCombinedAvailability start",
JSON.stringify({ query: { hotelId, params: metricsData } })
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.roomAvailability.fail.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.roomsAvailability error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
const availabilityResponses = await Promise.allSettled(
uniqueAdultsCount.map(async (adultCount: number) => {
const params: Record<string, string | number | undefined> = {
roomStayStartDate,
roomStayEndDate,
adults: adultCount,
...(childArray &&
childArray.length > 0 && {
children: childArray.join(","),
}),
...(bookingCode && { bookingCode }),
language: apiLang,
}
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotel(hotelId.toString()),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
})
)
return null
}
const apiJson = await apiResponse.json()
params
)
const validateAvailabilityData =
roomsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
metrics.roomAvailability.fail.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
error_type: "validation_error",
error: JSON.stringify(validateAvailabilityData.error),
})
console.error(
"api.hotels.roomsAvailability validation error",
JSON.stringify({
query: { hotelId, params },
error: validateAvailabilityData.error,
})
)
return null
}
metrics.roomAvailability.success.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
})
console.info(
"api.hotels.roomsAvailability success",
JSON.stringify({
query: { hotelId, params: 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) {
validateAvailabilityData.data.mustBeGuaranteed =
validateAvailabilityData.data.rateDefinitions.find(
(rate) => rate.rateCode === rateCode
)?.mustBeGuaranteed
}
return validateAvailabilityData.data
})
)
if (rateCode) {
validateAvailabilityData.data.mustBeGuaranteed =
validateAvailabilityData.data.rateDefinitions.filter(
(rate) => rate.rateCode === rateCode
)[0].mustBeGuaranteed
}
return validateAvailabilityData.data
metrics.roomsCombinedAvailability.success.add(1, metricsData)
return availabilityResponses
}),
room: serviceProcedure
.input(selectedRoomAvailabilityInputSchema)
@@ -885,40 +855,35 @@ export const hotelQueryRouter = router({
}),
}),
rates: router({
get: publicProcedure
.input(ratesInputSchema)
.query(async ({ input, ctx }) => {
// TODO: Do a real API call when the endpoint is ready
// const { hotelId } = input
get: publicProcedure.input(ratesInputSchema).query(async ({}) => {
// TODO: Do a real API call when the endpoint is ready
// const { hotelId } = input
// const params = new URLSearchParams()
// const apiLang = toApiLang(language)
// params.set("hotelId", hotelId.toString())
// params.set("language", apiLang)
// const params = new URLSearchParams()
// const apiLang = toApiLang(language)
// params.set("hotelId", hotelId.toString())
// params.set("language", apiLang)
console.info("api.hotels.rates start", JSON.stringify({}))
const validatedHotelData = ratesSchema.safeParse(tempRatesData)
console.info("api.hotels.rates start", JSON.stringify({}))
const validatedHotelData = ratesSchema.safeParse(tempRatesData)
if (!tempRatesData) {
console.error(
"api.hotels.rates error",
JSON.stringify({ error: null })
)
//Can't return null here since consuming component does not handle null yet
// return null
}
if (!validatedHotelData.success) {
console.error(
"api.hotels.rates validation error",
JSON.stringify({
error: validatedHotelData.error,
})
)
throw badRequestError()
}
console.info("api.hotels.rates success", JSON.stringify({}))
return validatedHotelData.data
}),
if (!tempRatesData) {
console.error("api.hotels.rates error", JSON.stringify({ error: null }))
//Can't return null here since consuming component does not handle null yet
// return null
}
if (!validatedHotelData.success) {
console.error(
"api.hotels.rates validation error",
JSON.stringify({
error: validatedHotelData.error,
})
)
throw badRequestError()
}
console.info("api.hotels.rates success", JSON.stringify({}))
return validatedHotelData.data
}),
}),
get: serviceProcedure
.input(hotelInputSchema)
@@ -1544,7 +1509,7 @@ export const hotelQueryRouter = router({
const { hotelId, startDate, endDate, adults, children, packageCodes } =
input
const { lang } = ctx
const { lang } = input
const apiLang = toApiLang(lang)

View File

@@ -35,16 +35,6 @@ export const hotelsByHotelIdAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.hotels-by-hotel-id-fail"
)
export const roomsAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.rooms"
)
export const roomsAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.rooms-success"
)
export const roomsAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.rooms-fail"
)
export const selectedRoomAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.room"
)