Merged in feat/SW-2113-allow-feature-combinations (pull request #1719)

Feat/SW-2113 allow feature combinations

* feat(SW-2113): Refactor features data to be fetched on filter room filter change

* feat(SW-2113): added loading state

* fix: now clear room selection when applying filter and room doesnt exists. And added room features to mobile summary

* fix

* fix: add package to price details

* feat(SW-2113): added buttons to room filter

* fix: active room

* fix: remove console log

* fix: added form and close handler to room package filter

* fix: add restriction so you cannot select pet room with allergy room and vice versa

* fix: fixes from review feedback

* fix

* fix: hide modify button if on nextcoming rooms if no selection is made, and adjust filter logic in togglePackage

* fix: forgot to use roomFeatureCodes from input..

* fix: naming


Approved-by: Simon.Emanuelsson
This commit is contained in:
Tobias Johansson
2025-04-07 11:36:34 +00:00
parent 8d34e1c8bb
commit e6ae6ff650
31 changed files with 725 additions and 359 deletions

View File

@@ -2,8 +2,6 @@ import { z } from "zod"
import { Lang } from "@/constants/languages"
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { Country } from "@/types/enums/country"
@@ -48,6 +46,9 @@ export const roomsCombinedAvailabilityInputSchema = z.object({
roomStayEndDate: z.string(),
roomStayStartDate: z.string(),
redemption: z.boolean().optional().default(false),
roomFeatureCodesArray: z
.array(z.array(z.nativeEnum(RoomPackageCodeEnum)).nullable())
.optional(),
})
export const selectedRoomAvailabilityInputSchema = z.object({
@@ -167,16 +168,17 @@ export const roomFeaturesInputSchema = z.object({
hotelId: z.string(),
startDate: z.string(),
endDate: z.string(),
adultsCount: z.array(z.number()),
childArray: z
adults: z.number(),
childrenInRoom: z
.array(
nullableArrayObjectValidator(
z.object({
age: z.number(),
bed: z.nativeEnum(ChildBedMapEnum),
})
)
z.object({
age: z.number(),
bed: z.nativeEnum(ChildBedMapEnum),
})
)
.nullable(),
roomFeatureCode: z.array(z.nativeEnum(RoomPackageCodeEnum)).optional(),
.optional(),
roomFeatureCodes: z.array(z.nativeEnum(RoomPackageCodeEnum)).optional(),
roomIndex: z.number().optional(),
})
export type RoomFeaturesInput = z.input<typeof roomFeaturesInputSchema>

View File

@@ -37,6 +37,7 @@ import {
hotelsAvailabilityInputSchema,
nearbyHotelIdsInput,
ratesInputSchema,
type RoomFeaturesInput,
roomFeaturesInputSchema,
roomPackagesInputSchema,
roomsCombinedAvailabilityInputSchema,
@@ -471,6 +472,75 @@ export const getHotelsAvailabilityByHotelIds = async (
)
}
async function getRoomFeatures(
{
hotelId,
startDate,
endDate,
adults,
childrenInRoom,
roomFeatureCodes,
}: RoomFeaturesInput,
token: string
) {
const params = {
hotelId,
roomStayStartDate: startDate,
roomStayEndDate: endDate,
adults,
...(childrenInRoom?.length && {
children: generateChildrenString(childrenInRoom),
}),
roomFeatureCode: roomFeatureCodes,
}
metrics.roomFeatures.counter.add(1, params)
const apiResponse = await api.get(
api.endpoints.v1.Availability.roomFeatures(hotelId),
{
headers: {
Authorization: `Bearer ${token}`,
},
},
params
)
if (!apiResponse.ok) {
const text = apiResponse.text()
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
metrics.roomFeatures.fail.add(1, params)
return null
}
const data = await apiResponse.json()
const validatedRoomFeaturesData = roomFeaturesSchema.safeParse(data)
if (!validatedRoomFeaturesData.success) {
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: validatedRoomFeaturesData.error,
})
)
return null
}
metrics.roomFeatures.success.add(1, params)
return validatedRoomFeaturesData.data
}
export const hotelQueryRouter = router({
availability: router({
hotelsByCity: safeProtectedServiceProcedure
@@ -576,6 +646,7 @@ export const hotelQueryRouter = router({
redemption,
roomStayEndDate,
roomStayStartDate,
roomFeatureCodesArray,
},
}) => {
const apiLang = toApiLang(lang)
@@ -643,6 +714,35 @@ export const hotelQueryRouter = router({
}
}
const roomFeatureCodes = roomFeatureCodesArray?.[idx]
if (roomFeatureCodes?.length) {
const roomFeaturesResponse = await getRoomFeatures(
{
hotelId,
startDate: roomStayStartDate,
endDate: roomStayEndDate,
adults: adultCount,
childrenInRoom: kids ?? undefined,
roomFeatureCodes,
},
ctx.serviceToken
)
if (roomFeaturesResponse) {
validateAvailabilityData.data.roomConfigurations.forEach(
(room) => {
const features = roomFeaturesResponse.find(
(feat) => feat.roomTypeCode === room.roomTypeCode
)?.features
if (features) {
room.features = features
}
}
)
}
}
if (rateCode) {
validateAvailabilityData.data.mustBeGuaranteed =
validateAvailabilityData.data.rateDefinitions.find(
@@ -982,73 +1082,7 @@ export const hotelQueryRouter = router({
roomFeatures: serviceProcedure
.input(roomFeaturesInputSchema)
.query(async ({ input, ctx }) => {
const { hotelId, startDate, endDate, adultsCount, childArray } = input
const responses = await Promise.allSettled(
adultsCount.map(async (adultCount, index) => {
const kids = childArray?.[index]
const params = {
hotelId,
roomStayStartDate: startDate,
roomStayEndDate: endDate,
adults: adultCount,
...(kids?.length && { children: generateChildrenString(kids) }),
}
metrics.roomFeatures.counter.add(1, params)
const apiResponse = await api.get(
api.endpoints.v1.Availability.roomFeatures(hotelId),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = apiResponse.text()
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
metrics.roomFeatures.fail.add(1, params)
return null
}
const data = await apiResponse.json()
const validatedRoomFeaturesData = roomFeaturesSchema.safeParse(data)
if (!validatedRoomFeaturesData.success) {
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: validatedRoomFeaturesData.error,
})
)
return null
}
metrics.roomFeatures.success.add(1, params)
return validatedRoomFeaturesData.data
})
)
return responses.map((features) => {
if (features.status === "fulfilled") {
return features.value
}
return null
})
return await getRoomFeatures(input, ctx.serviceToken)
}),
}),
rates: router({