feat/SW-1546-list-ancillaries-my-stay (pull request #1259)
Feat/SW-1546 list ancillaries my stay * feat(SW-1546): foundation for listing ancillaries * feat(SW-1546): foundation for listing ancillaries * feat(SW-1546): refactor type * feat(SW-1546): fix date format * feat(SW-1546): refactor usestate * feat(SW-1546): refactor typing * feat(SW-1546): refactor types * feat(SW-1546): responsive width on modal * feat(SW-1546): update design * feat(SW-1546): rebase master * feat(SW-1546): show points only if logged in * feat(SW-1546): always show points * feat(SW-1546): small fix * feat(SW-1546): remove spread object * feat(SW-1546): fix import order Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -167,9 +167,20 @@ export const bookingConfirmationSchema = z
|
||||
}),
|
||||
id: z.string(),
|
||||
type: z.literal("booking"),
|
||||
links: z.object({
|
||||
addAncillary: z
|
||||
.object({
|
||||
href: z.string(),
|
||||
meta: z.object({
|
||||
method: z.string(),
|
||||
}),
|
||||
})
|
||||
.nullable(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.transform(({ data }) => ({
|
||||
...data.attributes,
|
||||
extraBedTypes: data.attributes.childBedPreferences,
|
||||
showAncillaries: !!data.links.addAncillary,
|
||||
}))
|
||||
|
||||
@@ -93,6 +93,15 @@ export const breakfastPackageInputSchema = z.object({
|
||||
.pipe(z.coerce.date()),
|
||||
})
|
||||
|
||||
export const ancillaryPackageInputSchema = z.object({
|
||||
fromDate: z
|
||||
.string()
|
||||
.min(1, { message: "fromDate is required" })
|
||||
.pipe(z.coerce.date()),
|
||||
hotelId: z.string().min(1, { message: "hotelId is required" }),
|
||||
toDate: z.string().pipe(z.coerce.date()).optional(),
|
||||
})
|
||||
|
||||
export const roomPackagesInputSchema = z.object({
|
||||
hotelId: z.string(),
|
||||
startDate: z.string(),
|
||||
|
||||
@@ -12,6 +12,11 @@ export const metrics = {
|
||||
fail: meter.createCounter("trpc.package.breakfast-fail"),
|
||||
success: meter.createCounter("trpc.package.breakfast-success"),
|
||||
},
|
||||
ancillaryPackage: {
|
||||
counter: meter.createCounter("trpc.package.ancillary"),
|
||||
fail: meter.createCounter("trpc.package.ancillary-fail"),
|
||||
success: meter.createCounter("trpc.package.ancillary-success"),
|
||||
},
|
||||
hotel: {
|
||||
counter: meter.createCounter("trpc.hotel.get"),
|
||||
fail: meter.createCounter("trpc.hotel.get-fail"),
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
} from "./schemas/hotel"
|
||||
import { locationCitySchema } from "./schemas/location/city"
|
||||
import { locationHotelSchema } from "./schemas/location/hotel"
|
||||
import { breakfastPackageSchema, packageSchema } from "./schemas/packages"
|
||||
import {
|
||||
ancillaryPackageSchema,
|
||||
breakfastPackageSchema,
|
||||
packageSchema,
|
||||
} from "./schemas/packages"
|
||||
import { rateSchema } from "./schemas/rate"
|
||||
import { relationshipsSchema } from "./schemas/relationships"
|
||||
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
|
||||
@@ -343,6 +347,35 @@ export const breakfastPackagesSchema = z
|
||||
data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm))
|
||||
)
|
||||
|
||||
export const ancillaryPackagesSchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
attributes: z.object({
|
||||
ancillaries: z.array(ancillaryPackageSchema),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.transform(({ data }) =>
|
||||
data.attributes.ancillaries
|
||||
.map((ancillary) => ({
|
||||
categoryName: ancillary.categoryName,
|
||||
ancillaryContent: ancillary.ancillaryContent
|
||||
.filter((item) => item.status === "Available")
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
description: item.descriptions.html,
|
||||
imageUrl: item.images[0].imageSizes.small,
|
||||
price: {
|
||||
total: parseInt(item.variants.ancillary.price.totalPrice),
|
||||
currency: item.variants.ancillary.price.currency,
|
||||
},
|
||||
points: item.variants.ancillaryLoyalty?.points,
|
||||
})),
|
||||
}))
|
||||
.filter((ancillary) => ancillary.ancillaryContent.length > 0)
|
||||
)
|
||||
|
||||
export const packagesSchema = z
|
||||
.object({
|
||||
data: z
|
||||
|
||||
@@ -21,6 +21,7 @@ import { getVerifiedUser, parsedUser } from "../user/query"
|
||||
import { additionalDataSchema } from "./schemas/additionalData"
|
||||
import { meetingRoomsSchema } from "./schemas/meetingRoom"
|
||||
import {
|
||||
ancillaryPackageInputSchema,
|
||||
breakfastPackageInputSchema,
|
||||
cityCoordinatesInputSchema,
|
||||
getAdditionalDataInputSchema,
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
} from "./input"
|
||||
import { metrics } from "./metrics"
|
||||
import {
|
||||
ancillaryPackagesSchema,
|
||||
breakfastPackagesSchema,
|
||||
getNearbyHotelIdsSchema,
|
||||
hotelsAvailabilitySchema,
|
||||
@@ -1622,7 +1624,7 @@ export const hotelQueryRouter = router({
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotelsAvailability error",
|
||||
"api.package.breakfast error",
|
||||
JSON.stringify({
|
||||
query: metricsData,
|
||||
error: {
|
||||
@@ -1683,5 +1685,90 @@ export const hotelQueryRouter = router({
|
||||
(pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||
)
|
||||
}),
|
||||
ancillary: safeProtectedServiceProcedure
|
||||
.input(ancillaryPackageInputSchema)
|
||||
.query(async function ({ ctx, input }) {
|
||||
const { lang } = ctx
|
||||
|
||||
const apiLang = toApiLang(lang)
|
||||
const params = {
|
||||
EndDate: dt(input.toDate).format("YYYY-MM-DD"),
|
||||
StartDate: dt(input.fromDate).format("YYYY-MM-DD"),
|
||||
language: apiLang,
|
||||
}
|
||||
|
||||
const metricsData = { ...params, hotelId: input.hotelId }
|
||||
metrics.ancillaryPackage.counter.add(1, metricsData)
|
||||
console.info(
|
||||
"api.package.ancillary start",
|
||||
JSON.stringify({ query: metricsData })
|
||||
)
|
||||
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Package.Ancillary.hotel(input.hotelId),
|
||||
{
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: 60,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
metrics.ancillaryPackage.fail.add(1, {
|
||||
...metricsData,
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.package.ancillary start error",
|
||||
JSON.stringify({
|
||||
query: metricsData,
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const ancillaryPackages = ancillaryPackagesSchema.safeParse(apiJson)
|
||||
if (!ancillaryPackages.success) {
|
||||
metrics.ancillaryPackage.fail.add(1, {
|
||||
...metricsData,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(ancillaryPackages.error),
|
||||
})
|
||||
console.error(
|
||||
"api.package.ancillary validation error",
|
||||
JSON.stringify({
|
||||
query: metricsData,
|
||||
error: ancillaryPackages.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
metrics.ancillaryPackage.success.add(1, metricsData)
|
||||
console.info(
|
||||
"api.package.ancillary success",
|
||||
JSON.stringify({
|
||||
query: metricsData,
|
||||
})
|
||||
)
|
||||
return ancillaryPackages.data
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { imageSizesSchema } from "./image"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { PackageTypeEnum } from "@/types/enums/packages"
|
||||
|
||||
@@ -23,6 +25,18 @@ const inventorySchema = z.object({
|
||||
available: z.number(),
|
||||
})
|
||||
|
||||
export const ancillaryContentSchema = z.object({
|
||||
status: z.string(),
|
||||
id: z.string(),
|
||||
variants: z.object({
|
||||
ancillary: z.object({ price: packagePriceSchema }),
|
||||
ancillaryLoyalty: z.object({ points: z.number() }).optional(),
|
||||
}),
|
||||
title: z.string(),
|
||||
descriptions: z.object({ html: z.string() }),
|
||||
images: z.array(z.object({ imageSizes: imageSizesSchema })),
|
||||
})
|
||||
|
||||
export const packageSchema = z.object({
|
||||
code: z.nativeEnum(RoomPackageCodeEnum),
|
||||
description: z.string(),
|
||||
@@ -39,3 +53,8 @@ export const breakfastPackageSchema = z.object({
|
||||
requestedPrice: packagePriceSchema,
|
||||
packageType: z.literal(PackageTypeEnum.BreakfastAdult),
|
||||
})
|
||||
|
||||
export const ancillaryPackageSchema = z.object({
|
||||
categoryName: z.string(),
|
||||
ancillaryContent: z.array(ancillaryContentSchema),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user