feat: add multiroom tracking to booking flow
This commit is contained in:
@@ -3,52 +3,67 @@ import { z } from "zod"
|
||||
import { ChildBedTypeEnum } from "@/constants/booking"
|
||||
import { Lang, langToApiLang } from "@/constants/languages"
|
||||
|
||||
const signupSchema = z.discriminatedUnion("becomeMember", [
|
||||
z.object({
|
||||
dateOfBirth: z.string(),
|
||||
postalCode: z.string(),
|
||||
becomeMember: z.literal<boolean>(true),
|
||||
}),
|
||||
z.object({ becomeMember: z.literal<boolean>(false) }),
|
||||
])
|
||||
|
||||
const roomsSchema = z.array(
|
||||
z.object({
|
||||
adults: z.number().int().nonnegative(),
|
||||
childrenAges: z
|
||||
.array(
|
||||
z.object({
|
||||
age: z.number().int().nonnegative(),
|
||||
bedType: z.nativeEnum(ChildBedTypeEnum),
|
||||
})
|
||||
)
|
||||
.default([]),
|
||||
rateCode: z.string(),
|
||||
roomTypeCode: z.coerce.string(),
|
||||
guest: z.intersection(
|
||||
z.object({
|
||||
const roomsSchema = z
|
||||
.array(
|
||||
z.object({
|
||||
adults: z.number().int().nonnegative(),
|
||||
childrenAges: z
|
||||
.array(
|
||||
z.object({
|
||||
age: z.number().int().nonnegative(),
|
||||
bedType: z.nativeEnum(ChildBedTypeEnum),
|
||||
})
|
||||
)
|
||||
.default([]),
|
||||
rateCode: z.string(),
|
||||
roomTypeCode: z.coerce.string(),
|
||||
guest: z.object({
|
||||
becomeMember: z.boolean(),
|
||||
countryCode: z.string(),
|
||||
dateOfBirth: z.string().nullish(),
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
lastName: z.string(),
|
||||
email: z.string().email(),
|
||||
membershipNumber: z.string().nullish(),
|
||||
postalCode: z.string().nullish(),
|
||||
phoneNumber: z.string(),
|
||||
countryCode: z.string(),
|
||||
membershipNumber: z.string().optional(),
|
||||
}),
|
||||
signupSchema
|
||||
),
|
||||
smsConfirmationRequested: z.boolean(),
|
||||
packages: z.object({
|
||||
breakfast: z.boolean(),
|
||||
allergyFriendly: z.boolean(),
|
||||
petFriendly: z.boolean(),
|
||||
accessibility: z.boolean(),
|
||||
}),
|
||||
roomPrice: z.object({
|
||||
memberPrice: z.number().nullish(),
|
||||
publicPrice: z.number().nullish(),
|
||||
}),
|
||||
smsConfirmationRequested: z.boolean(),
|
||||
packages: z.object({
|
||||
breakfast: z.boolean(),
|
||||
allergyFriendly: z.boolean(),
|
||||
petFriendly: z.boolean(),
|
||||
accessibility: z.boolean(),
|
||||
}),
|
||||
roomPrice: z.object({
|
||||
memberPrice: z.number().nullish(),
|
||||
publicPrice: z.number().nullish(),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.superRefine((data, ctx) => {
|
||||
data.forEach((room, idx) => {
|
||||
if (idx === 0 && room.guest.becomeMember) {
|
||||
if (!room.guest.dateOfBirth) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.invalid_type,
|
||||
expected: "string",
|
||||
received: typeof room.guest.dateOfBirth,
|
||||
path: ["guest", "dateOfBirth"],
|
||||
})
|
||||
}
|
||||
|
||||
if (!room.guest.postalCode) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.invalid_type,
|
||||
expected: "string",
|
||||
received: typeof room.guest.postalCode,
|
||||
path: ["guest", "postalCode"],
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const paymentSchema = z.object({
|
||||
paymentMethod: z.string(),
|
||||
|
||||
@@ -26,21 +26,25 @@ export const getHotelsByHotelIdsAvailabilityInputSchema = z.object({
|
||||
})
|
||||
|
||||
export const roomsCombinedAvailabilityInputSchema = z.object({
|
||||
hotelId: z.number(),
|
||||
roomStayStartDate: z.string(),
|
||||
roomStayEndDate: z.string(),
|
||||
uniqueAdultsCount: z.array(z.number()),
|
||||
adultsCount: z.array(z.number()),
|
||||
bookingCode: z.string().optional(),
|
||||
childArray: z
|
||||
.array(
|
||||
z.object({
|
||||
bed: z.nativeEnum(ChildBedMapEnum),
|
||||
age: z.number(),
|
||||
})
|
||||
z
|
||||
.array(
|
||||
z.object({
|
||||
age: z.number(),
|
||||
bed: z.nativeEnum(ChildBedMapEnum),
|
||||
})
|
||||
)
|
||||
.nullable()
|
||||
)
|
||||
.optional(),
|
||||
bookingCode: z.string().optional(),
|
||||
rateCode: z.string().optional(),
|
||||
.nullish(),
|
||||
hotelId: z.number(),
|
||||
lang: z.nativeEnum(Lang),
|
||||
rateCode: z.string().optional(),
|
||||
roomStayEndDate: z.string(),
|
||||
roomStayStartDate: z.string(),
|
||||
})
|
||||
|
||||
export const selectedRoomAvailabilityInputSchema = z.object({
|
||||
|
||||
@@ -22,6 +22,7 @@ import { relationshipsSchema } from "./schemas/relationships"
|
||||
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
|
||||
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
|
||||
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import type {
|
||||
AdditionalData,
|
||||
City,
|
||||
@@ -137,6 +138,13 @@ const cancellationRules = {
|
||||
NotCancellable: 0,
|
||||
} as const
|
||||
|
||||
// Used to ensure `Available` rooms
|
||||
// are shown before all `NotAvailable`
|
||||
const statusLookup = {
|
||||
[AvailabilityEnum.Available]: 1,
|
||||
[AvailabilityEnum.NotAvailable]: 2,
|
||||
}
|
||||
|
||||
export const roomsAvailabilitySchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
@@ -161,8 +169,8 @@ export const roomsAvailabilitySchema = z
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
attributes.roomConfigurations = attributes.roomConfigurations.map(
|
||||
(room) => {
|
||||
const roomConfigurations = attributes.roomConfigurations
|
||||
.map((room) => {
|
||||
if (room.products.length) {
|
||||
room.breakfastIncludedInAllRatesMember = room.products.every(
|
||||
(product) =>
|
||||
@@ -222,10 +230,16 @@ export const roomsAvailabilitySchema = z
|
||||
}
|
||||
|
||||
return room
|
||||
}
|
||||
)
|
||||
})
|
||||
.sort(
|
||||
// @ts-expect-error - array indexing
|
||||
(a, b) => statusLookup[a.status] - statusLookup[b.status]
|
||||
)
|
||||
|
||||
return attributes
|
||||
return {
|
||||
...attributes,
|
||||
roomConfigurations,
|
||||
}
|
||||
})
|
||||
|
||||
export const ratesSchema = z.array(rateSchema)
|
||||
|
||||
@@ -499,20 +499,20 @@ export const hotelQueryRouter = router({
|
||||
const { lang } = input
|
||||
const apiLang = toApiLang(lang)
|
||||
const {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
uniqueAdultsCount,
|
||||
childArray,
|
||||
adultsCount,
|
||||
bookingCode,
|
||||
childArray,
|
||||
hotelId,
|
||||
rateCode,
|
||||
roomStayEndDate,
|
||||
roomStayStartDate,
|
||||
} = input
|
||||
|
||||
const metricsData = {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
uniqueAdultsCount,
|
||||
adultsCount,
|
||||
childArray: childArray ? JSON.stringify(childArray) : undefined,
|
||||
bookingCode,
|
||||
}
|
||||
@@ -525,15 +525,15 @@ export const hotelQueryRouter = router({
|
||||
)
|
||||
|
||||
const availabilityResponses = await Promise.allSettled(
|
||||
uniqueAdultsCount.map(async (adultCount: number) => {
|
||||
adultsCount.map(async (adultCount: number, idx: number) => {
|
||||
const kids = childArray?.[idx]
|
||||
const params: Record<string, string | number | undefined> = {
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults: adultCount,
|
||||
...(childArray &&
|
||||
childArray.length > 0 && {
|
||||
children: generateChildrenString(childArray),
|
||||
}),
|
||||
...(kids?.length && {
|
||||
children: generateChildrenString(kids),
|
||||
}),
|
||||
...(bookingCode && { bookingCode }),
|
||||
language: apiLang,
|
||||
}
|
||||
@@ -769,9 +769,9 @@ export const hotelQueryRouter = router({
|
||||
type: matchingRoom.mainBed.type,
|
||||
extraBed: matchingRoom.fixedExtraBed
|
||||
? {
|
||||
type: matchingRoom.fixedExtraBed.type,
|
||||
description: matchingRoom.fixedExtraBed.description,
|
||||
}
|
||||
type: matchingRoom.fixedExtraBed.type,
|
||||
description: matchingRoom.fixedExtraBed.description,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
@@ -794,23 +794,27 @@ export const hotelQueryRouter = router({
|
||||
)
|
||||
|
||||
return {
|
||||
selectedRoom,
|
||||
rateDetails: rateDefinition?.generalTerms,
|
||||
bedTypes,
|
||||
breakfastIncluded: !!rateDefinition?.breakfastIncluded,
|
||||
cancellationRule: rateDefinition?.cancellationRule,
|
||||
cancellationText: rateDefinition?.cancellationText ?? "",
|
||||
isFlexRate:
|
||||
rateDefinition?.cancellationRule ===
|
||||
CancellationRuleEnum.CancellableBefore6PM,
|
||||
mustBeGuaranteed: !!rateDefinition?.mustBeGuaranteed,
|
||||
memberMustBeGuaranteed: !!memberRateDefinition?.mustBeGuaranteed,
|
||||
breakfastIncluded: !!rateDefinition?.breakfastIncluded,
|
||||
memberRate: rates?.member,
|
||||
mustBeGuaranteed: !!rateDefinition?.mustBeGuaranteed,
|
||||
publicRate: rates?.public,
|
||||
rate: selectedRoom.products[0].rate,
|
||||
rateDefinitionTitle: rateDefinition?.title ?? "",
|
||||
rateDetails: rateDefinition?.generalTerms,
|
||||
// Send rate Title when it is a booking code rate
|
||||
rateTitle:
|
||||
rateDefinition?.rateType !== RateTypeEnum.Regular
|
||||
? rateDefinition?.title
|
||||
: undefined,
|
||||
memberRate: rates?.member,
|
||||
publicRate: rates?.public,
|
||||
bedTypes,
|
||||
rateType: rateDefinition?.rateType ?? "",
|
||||
selectedRoom,
|
||||
}
|
||||
}),
|
||||
hotelsByCityWithBookingCode: serviceProcedure
|
||||
@@ -1096,9 +1100,9 @@ export const hotelQueryRouter = router({
|
||||
|
||||
return hotelData
|
||||
? {
|
||||
...hotelData,
|
||||
url: hotelPage?.url ?? null,
|
||||
}
|
||||
...hotelData,
|
||||
url: hotelPage?.url ?? null,
|
||||
}
|
||||
: null
|
||||
})
|
||||
)
|
||||
|
||||
@@ -38,9 +38,15 @@ export const roomConfigurationSchema = z
|
||||
(product) => !product.public?.rateCode && !product.member?.rateCode
|
||||
)
|
||||
if (allProductsMissBothRateCodes) {
|
||||
data.status = AvailabilityEnum.NotAvailable
|
||||
return {
|
||||
...data,
|
||||
status: AvailabilityEnum.NotAvailable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
// Creating a new objekt since data is frozen (readony)
|
||||
// and can cause errors to be thrown if trying to manipulate
|
||||
// object elsewhere
|
||||
return { ...data }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user