1544 lines
45 KiB
TypeScript
1544 lines
45 KiB
TypeScript
import { unstable_cache } from "next/cache"
|
|
|
|
import { ApiLang } from "@/constants/languages"
|
|
import { env } from "@/env/server"
|
|
import * as api from "@/lib/api"
|
|
import { dt } from "@/lib/dt"
|
|
import { badRequestError } from "@/server/errors/trpc"
|
|
import {
|
|
contentStackBaseWithServiceProcedure,
|
|
publicProcedure,
|
|
router,
|
|
safeProtectedServiceProcedure,
|
|
serviceProcedure,
|
|
} from "@/server/trpc"
|
|
import { toApiLang } from "@/server/utils"
|
|
|
|
import { cache } from "@/utils/cache"
|
|
|
|
import { getHotelPageUrl } from "../contentstack/hotelPage/utils"
|
|
import { getVerifiedUser, parsedUser } from "../user/query"
|
|
import { additionalDataSchema } from "./schemas/additionalData"
|
|
import { meetingRoomsSchema } from "./schemas/meetingRoom"
|
|
import {
|
|
breakfastPackageInputSchema,
|
|
cityCoordinatesInputSchema,
|
|
getAdditionalDataInputSchema,
|
|
getHotelsByHotelIdsAvailabilityInputSchema,
|
|
getHotelsInput,
|
|
getMeetingRoomsInputSchema,
|
|
hotelInputSchema,
|
|
hotelsAvailabilityInputSchema,
|
|
nearbyHotelIdsInput,
|
|
ratesInputSchema,
|
|
roomPackagesInputSchema,
|
|
roomsAvailabilityInputSchema,
|
|
selectedRoomAvailabilityInputSchema,
|
|
} from "./input"
|
|
import { metrics } from "./metrics"
|
|
import {
|
|
breakfastPackagesSchema,
|
|
getNearbyHotelIdsSchema,
|
|
hotelsAvailabilitySchema,
|
|
hotelSchema,
|
|
packagesSchema,
|
|
ratesSchema,
|
|
roomsAvailabilitySchema,
|
|
} from "./output"
|
|
import tempRatesData from "./tempRatesData.json"
|
|
import {
|
|
getCitiesByCountry,
|
|
getCountries,
|
|
getHotelIdsByCityId,
|
|
getHotelIdsByCountry,
|
|
getLocations,
|
|
TWENTYFOUR_HOURS,
|
|
} from "./utils"
|
|
|
|
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
|
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
|
import { HotelTypeEnum } from "@/types/enums/hotelType"
|
|
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
|
import type { HotelData } from "@/types/hotel"
|
|
import type { HotelPageUrl } from "@/types/trpc/routers/contentstack/hotelPage"
|
|
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
|
|
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
|
|
|
export const getHotel = cache(
|
|
async (input: HotelInput, serviceToken: string) => {
|
|
const callable = unstable_cache(
|
|
async function (
|
|
hotelId: HotelInput["hotelId"],
|
|
language: HotelInput["language"],
|
|
isCardOnlyPayment?: HotelInput["isCardOnlyPayment"]
|
|
) {
|
|
/**
|
|
* Since API expects the params appended and not just
|
|
* a comma separated string we need to initialize the
|
|
* SearchParams with a sequence of pairs
|
|
* (include=City&include=NearbyHotels&include=Restaurants etc.)
|
|
**/
|
|
const params = new URLSearchParams([
|
|
["include", "AdditionalData"],
|
|
["include", "City"],
|
|
["include", "NearbyHotels"],
|
|
["include", "Restaurants"],
|
|
["include", "RoomCategories"],
|
|
["language", toApiLang(language)],
|
|
])
|
|
metrics.hotel.counter.add(1, {
|
|
hotelId,
|
|
language,
|
|
})
|
|
console.info(
|
|
"api.hotels.hotelData start",
|
|
JSON.stringify({ query: { hotelId, params } })
|
|
)
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${serviceToken}`,
|
|
},
|
|
// needs to clear default option as only
|
|
// cache or next.revalidate is permitted
|
|
cache: undefined,
|
|
next: {
|
|
revalidate: env.CACHE_TIME_HOTELS,
|
|
tags: [`${language}:hotel:${hotelId}`],
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
const text = await apiResponse.text()
|
|
metrics.hotel.fail.add(1, {
|
|
hotelId,
|
|
language,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.hotelData error",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
error: {
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
const validateHotelData = hotelSchema.safeParse(apiJson)
|
|
|
|
if (!validateHotelData.success) {
|
|
metrics.hotel.fail.add(1, {
|
|
hotelId,
|
|
language,
|
|
error_type: "validation_error",
|
|
error: JSON.stringify(validateHotelData.error),
|
|
})
|
|
|
|
console.error(
|
|
"api.hotels.hotelData validation error",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
error: validateHotelData.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
|
|
metrics.hotel.success.add(1, {
|
|
hotelId,
|
|
language,
|
|
})
|
|
console.info(
|
|
"api.hotels.hotelData success",
|
|
JSON.stringify({
|
|
query: { hotelId, params: params },
|
|
})
|
|
)
|
|
const hotelData = validateHotelData.data
|
|
|
|
if (isCardOnlyPayment) {
|
|
hotelData.hotel.merchantInformationData.alternatePaymentOptions = []
|
|
}
|
|
|
|
const gallery = hotelData.additionalData?.gallery
|
|
if (gallery) {
|
|
const smallerImages = gallery.smallerImages
|
|
const hotelGalleryImages =
|
|
hotelData.hotel.hotelType === HotelTypeEnum.Signature
|
|
? smallerImages.slice(0, 10)
|
|
: smallerImages.slice(0, 6)
|
|
hotelData.hotel.galleryImages = hotelGalleryImages
|
|
}
|
|
|
|
return hotelData
|
|
},
|
|
[`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`],
|
|
{
|
|
revalidate: env.CACHE_TIME_HOTELS,
|
|
tags: [
|
|
`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`,
|
|
],
|
|
}
|
|
)
|
|
|
|
return callable(input.hotelId, input.language, input.isCardOnlyPayment)
|
|
}
|
|
)
|
|
|
|
export const hotelQueryRouter = router({
|
|
availability: router({
|
|
hotelsByCity: serviceProcedure
|
|
.input(hotelsAvailabilityInputSchema)
|
|
.query(async ({ input, ctx }) => {
|
|
const { lang } = ctx
|
|
const apiLang = toApiLang(lang)
|
|
const {
|
|
cityId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
} = input
|
|
|
|
const params: Record<string, string | number> = {
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
...(children && { children }),
|
|
...(bookingCode && { bookingCode }),
|
|
language: apiLang,
|
|
}
|
|
metrics.hotelsAvailability.counter.add(1, {
|
|
cityId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
console.info(
|
|
"api.hotels.hotelsAvailability start",
|
|
JSON.stringify({ query: { cityId, params } })
|
|
)
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Availability.city(cityId),
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
if (!apiResponse.ok) {
|
|
const text = await apiResponse.text()
|
|
metrics.hotelsAvailability.fail.add(1, {
|
|
cityId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.hotelsAvailability error",
|
|
JSON.stringify({
|
|
query: { cityId, params },
|
|
error: {
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
const apiJson = await apiResponse.json()
|
|
const validateAvailabilityData =
|
|
hotelsAvailabilitySchema.safeParse(apiJson)
|
|
if (!validateAvailabilityData.success) {
|
|
metrics.hotelsAvailability.fail.add(1, {
|
|
cityId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "validation_error",
|
|
error: JSON.stringify(validateAvailabilityData.error),
|
|
})
|
|
console.error(
|
|
"api.hotels.hotelsAvailability validation error",
|
|
JSON.stringify({
|
|
query: { cityId, params },
|
|
error: validateAvailabilityData.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
metrics.hotelsAvailability.success.add(1, {
|
|
cityId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
console.info(
|
|
"api.hotels.hotelsAvailability success",
|
|
JSON.stringify({
|
|
query: { cityId, params: params },
|
|
})
|
|
)
|
|
return {
|
|
availability: validateAvailabilityData.data.data.flatMap(
|
|
(hotels) => hotels.attributes
|
|
),
|
|
}
|
|
}),
|
|
hotelsByHotelIds: serviceProcedure
|
|
.input(getHotelsByHotelIdsAvailabilityInputSchema)
|
|
.query(async ({ input, ctx }) => {
|
|
const { lang } = ctx
|
|
const apiLang = toApiLang(lang)
|
|
const {
|
|
hotelIds,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
} = input
|
|
|
|
const params: Record<string, string | number | number[]> = {
|
|
hotelIds,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
...(children && { children }),
|
|
...(bookingCode && { bookingCode }),
|
|
language: apiLang,
|
|
}
|
|
metrics.hotelsByHotelIdAvailability.counter.add(1, {
|
|
hotelIds,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
console.info(
|
|
"api.hotels.hotelsByHotelIdAvailability start",
|
|
JSON.stringify({ query: { params } })
|
|
)
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Availability.hotels(),
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
if (!apiResponse.ok) {
|
|
const text = await apiResponse.text()
|
|
metrics.hotelsByHotelIdAvailability.fail.add(1, {
|
|
hotelIds,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.hotelsByHotelIdAvailability error",
|
|
JSON.stringify({
|
|
query: { params },
|
|
error: {
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
const apiJson = await apiResponse.json()
|
|
const validateAvailabilityData =
|
|
hotelsAvailabilitySchema.safeParse(apiJson)
|
|
if (!validateAvailabilityData.success) {
|
|
metrics.hotelsByHotelIdAvailability.fail.add(1, {
|
|
hotelIds,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "validation_error",
|
|
error: JSON.stringify(validateAvailabilityData.error),
|
|
})
|
|
console.error(
|
|
"api.hotels.hotelsByHotelIdAvailability validation error",
|
|
JSON.stringify({
|
|
query: { params },
|
|
error: validateAvailabilityData.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
metrics.hotelsByHotelIdAvailability.success.add(1, {
|
|
hotelIds,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
console.info(
|
|
"api.hotels.hotelsByHotelIdAvailability success",
|
|
JSON.stringify({
|
|
query: { params },
|
|
})
|
|
)
|
|
return {
|
|
availability: validateAvailabilityData.data.data.flatMap(
|
|
(hotels) => hotels.attributes
|
|
),
|
|
}
|
|
}),
|
|
rooms: serviceProcedure
|
|
.input(roomsAvailabilityInputSchema)
|
|
.query(async ({ input, ctx }) => {
|
|
const { lang } = ctx
|
|
const apiLang = toApiLang(lang)
|
|
const {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
rateCode,
|
|
} = input
|
|
|
|
const params: Record<string, string | number | undefined> = {
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
...(children && { children }),
|
|
...(bookingCode && { bookingCode }),
|
|
language: apiLang,
|
|
}
|
|
|
|
metrics.roomAvailability.counter.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
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
|
|
)
|
|
|
|
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,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
const apiJson = await apiResponse.json()
|
|
|
|
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,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
metrics.roomAvailability.success.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
console.info(
|
|
"api.hotels.roomsAvailability success",
|
|
JSON.stringify({
|
|
query: { hotelId, params: params },
|
|
})
|
|
)
|
|
|
|
if (rateCode) {
|
|
validateAvailabilityData.data.mustBeGuaranteed =
|
|
validateAvailabilityData.data.rateDefinitions.filter(
|
|
(rate) => rate.rateCode === rateCode
|
|
)[0].mustBeGuaranteed
|
|
}
|
|
|
|
return validateAvailabilityData.data
|
|
}),
|
|
room: serviceProcedure
|
|
.input(selectedRoomAvailabilityInputSchema)
|
|
.query(async ({ input, ctx }) => {
|
|
const {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
rateCode,
|
|
roomTypeCode,
|
|
packageCodes,
|
|
} = input
|
|
|
|
const params: Record<string, string | number | undefined> = {
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
...(children && { children }),
|
|
...(bookingCode && { bookingCode }),
|
|
language: toApiLang(ctx.lang),
|
|
}
|
|
|
|
metrics.selectedRoomAvailability.counter.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
console.info(
|
|
"api.hotels.selectedRoomAvailability start",
|
|
JSON.stringify({ query: { hotelId, params } })
|
|
)
|
|
const apiResponseAvailability = await api.get(
|
|
api.endpoints.v1.Availability.hotel(hotelId.toString()),
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponseAvailability.ok) {
|
|
const text = await apiResponseAvailability.text()
|
|
metrics.selectedRoomAvailability.fail.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponseAvailability.status,
|
|
statusText: apiResponseAvailability.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.selectedRoomAvailability error",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
error: {
|
|
status: apiResponseAvailability.status,
|
|
statusText: apiResponseAvailability.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
const apiJsonAvailability = await apiResponseAvailability.json()
|
|
const validateAvailabilityData =
|
|
roomsAvailabilitySchema.safeParse(apiJsonAvailability)
|
|
if (!validateAvailabilityData.success) {
|
|
metrics.selectedRoomAvailability.fail.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "validation_error",
|
|
error: JSON.stringify(validateAvailabilityData.error),
|
|
})
|
|
console.error(
|
|
"api.hotels.selectedRoomAvailability validation error",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
error: validateAvailabilityData.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
|
|
const hotelData = await getHotel(
|
|
{
|
|
hotelId,
|
|
isCardOnlyPayment: false,
|
|
language: ctx.lang,
|
|
},
|
|
ctx.serviceToken
|
|
)
|
|
|
|
const availableRooms =
|
|
validateAvailabilityData.data.roomConfigurations.filter((room) => {
|
|
if (packageCodes) {
|
|
return (
|
|
room.status === "Available" &&
|
|
room.features.some(
|
|
(feature) =>
|
|
packageCodes.includes(feature.code) && feature.inventory > 0
|
|
)
|
|
)
|
|
}
|
|
return room.status === "Available"
|
|
})
|
|
|
|
const selectedRoom = availableRooms.find(
|
|
(room) => room.roomTypeCode === roomTypeCode
|
|
)
|
|
|
|
const availableRoomsInCategory = availableRooms.filter(
|
|
(room) => room.roomType === selectedRoom?.roomType
|
|
)
|
|
if (!selectedRoom) {
|
|
metrics.selectedRoomAvailability.fail.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "not_found",
|
|
error: `Couldn't find selected room with input: ${roomTypeCode}`,
|
|
})
|
|
console.error("No matching room found")
|
|
return null
|
|
}
|
|
|
|
const rateTypes = selectedRoom.products.find(
|
|
(rate) =>
|
|
rate.productType.public?.rateCode === rateCode ||
|
|
rate.productType.member?.rateCode === rateCode
|
|
)
|
|
|
|
if (!rateTypes) {
|
|
metrics.selectedRoomAvailability.fail.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
error_type: "not_found",
|
|
error: `Couldn't find rateTypes for selected room: ${JSON.stringify(selectedRoom)}`,
|
|
})
|
|
console.error("No matching rate found")
|
|
return null
|
|
}
|
|
const rates = rateTypes.productType
|
|
|
|
const rateDefinition =
|
|
validateAvailabilityData.data.rateDefinitions.find(
|
|
(rate) => rate.rateCode === rateCode
|
|
)
|
|
|
|
const bedTypes = availableRoomsInCategory
|
|
.map((availRoom) => {
|
|
const matchingRoom = hotelData?.roomCategories
|
|
?.find((room) =>
|
|
room.roomTypes
|
|
.map((roomType) => roomType.code)
|
|
.includes(availRoom.roomTypeCode)
|
|
)
|
|
?.roomTypes.find(
|
|
(roomType) => roomType.code === availRoom.roomTypeCode
|
|
)
|
|
|
|
if (matchingRoom) {
|
|
return {
|
|
description: matchingRoom.description,
|
|
size: matchingRoom.mainBed.widthRange,
|
|
value: matchingRoom.code,
|
|
type: matchingRoom.mainBed.type,
|
|
extraBed: matchingRoom.fixedExtraBed
|
|
? {
|
|
type: matchingRoom.fixedExtraBed.type,
|
|
description: matchingRoom.fixedExtraBed.description,
|
|
}
|
|
: undefined,
|
|
}
|
|
}
|
|
})
|
|
.filter((bed): bed is BedTypeSelection => Boolean(bed))
|
|
|
|
metrics.selectedRoomAvailability.success.add(1, {
|
|
hotelId,
|
|
roomStayStartDate,
|
|
roomStayEndDate,
|
|
adults,
|
|
children,
|
|
bookingCode,
|
|
})
|
|
console.info(
|
|
"api.hotels.selectedRoomAvailability success",
|
|
JSON.stringify({
|
|
query: { hotelId, params: params },
|
|
})
|
|
)
|
|
|
|
return {
|
|
selectedRoom,
|
|
rateDetails: rateDefinition?.generalTerms,
|
|
cancellationText: rateDefinition?.cancellationText ?? "",
|
|
mustBeGuaranteed: !!rateDefinition?.mustBeGuaranteed,
|
|
breakfastIncluded: !!rateDefinition?.breakfastIncluded,
|
|
memberRate: rates?.member,
|
|
publicRate: rates.public,
|
|
bedTypes,
|
|
}
|
|
}),
|
|
}),
|
|
rates: router({
|
|
get: publicProcedure
|
|
.input(ratesInputSchema)
|
|
.query(async ({ input, ctx }) => {
|
|
// 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)
|
|
|
|
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
|
|
}),
|
|
}),
|
|
get: serviceProcedure
|
|
.input(hotelInputSchema)
|
|
.query(async ({ ctx, input }) => {
|
|
return getHotel(input, ctx.serviceToken)
|
|
}),
|
|
hotels: router({
|
|
get: contentStackBaseWithServiceProcedure
|
|
.input(getHotelsInput)
|
|
.query(async function ({ ctx, input }) {
|
|
const { locationFilter, hotelsToInclude } = input
|
|
|
|
const language = ctx.lang
|
|
const apiLang = toApiLang(language)
|
|
const options: RequestOptionsWithOutBody = {
|
|
// needs to clear default option as only
|
|
// cache or next.revalidate is permitted
|
|
cache: undefined,
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
next: {
|
|
revalidate: env.CACHE_TIME_HOTELS,
|
|
},
|
|
}
|
|
|
|
let hotelsToFetch: string[] = []
|
|
|
|
metrics.hotels.counter.add(1, {
|
|
input: JSON.stringify(input),
|
|
language,
|
|
})
|
|
console.info(
|
|
"api.hotel.hotels start",
|
|
JSON.stringify({
|
|
query: {
|
|
...input,
|
|
language,
|
|
},
|
|
})
|
|
)
|
|
|
|
if (hotelsToInclude.length) {
|
|
hotelsToFetch = hotelsToInclude
|
|
} else if (locationFilter?.city) {
|
|
const locationsParams = new URLSearchParams({
|
|
language: apiLang,
|
|
})
|
|
const locations = await getLocations(
|
|
language,
|
|
options,
|
|
locationsParams,
|
|
null
|
|
)
|
|
if (!locations || "error" in locations) {
|
|
return []
|
|
}
|
|
|
|
const cityId = locations
|
|
.filter(
|
|
(loc): loc is CityLocation =>
|
|
"type" in loc && loc.type === "cities"
|
|
)
|
|
.find((loc) => loc.cityIdentifier === locationFilter.city)?.id
|
|
|
|
if (!cityId) {
|
|
metrics.hotels.fail.add(1, {
|
|
input: JSON.stringify(input),
|
|
language,
|
|
error_type: "not_found",
|
|
error: `CityId not found for cityIdentifier: ${locationFilter.city}`,
|
|
})
|
|
|
|
console.error(
|
|
"api.hotel.hotels not found error",
|
|
JSON.stringify({
|
|
query: { ...input, language },
|
|
error: `CityId not found for cityIdentifier: ${locationFilter.city}`,
|
|
})
|
|
)
|
|
return []
|
|
}
|
|
const hotelIdsParams = new URLSearchParams({
|
|
language: apiLang,
|
|
city: cityId,
|
|
onlyBasicInfo: "true",
|
|
})
|
|
const hotelIds = await getHotelIdsByCityId(
|
|
cityId,
|
|
options,
|
|
hotelIdsParams
|
|
)
|
|
|
|
if (!hotelIds?.length) {
|
|
metrics.hotels.fail.add(1, {
|
|
cityId,
|
|
language,
|
|
error_type: "not_found",
|
|
error: `No hotelIds found for cityId: ${cityId}`,
|
|
})
|
|
|
|
console.error(
|
|
"api.hotel.hotels not found error",
|
|
JSON.stringify({
|
|
query: { cityId, language },
|
|
error: `No hotelIds found for cityId: ${cityId}`,
|
|
})
|
|
)
|
|
return []
|
|
}
|
|
|
|
const filteredHotelIds = hotelIds.filter(
|
|
(id) => !locationFilter.excluded.includes(id)
|
|
)
|
|
|
|
hotelsToFetch = filteredHotelIds
|
|
} else if (locationFilter?.country) {
|
|
const hotelIdsParams = new URLSearchParams({
|
|
language: ApiLang.En,
|
|
country: locationFilter.country,
|
|
onlyBasicInfo: "true",
|
|
})
|
|
const hotelIds = await getHotelIdsByCountry(
|
|
locationFilter.country,
|
|
options,
|
|
hotelIdsParams
|
|
)
|
|
|
|
if (!hotelIds?.length) {
|
|
metrics.hotels.fail.add(1, {
|
|
country: locationFilter.country,
|
|
language,
|
|
error_type: "not_found",
|
|
error: `No hotelIds found for country: ${locationFilter.country}`,
|
|
})
|
|
|
|
console.error(
|
|
"api.hotel.hotels not found error",
|
|
JSON.stringify({
|
|
query: { country: locationFilter.country, language },
|
|
error: `No hotelIds found for cityId: ${locationFilter.country}`,
|
|
})
|
|
)
|
|
return []
|
|
}
|
|
|
|
const filteredHotelIds = hotelIds.filter(
|
|
(id) => !locationFilter.excluded.includes(id)
|
|
)
|
|
|
|
hotelsToFetch = filteredHotelIds
|
|
}
|
|
|
|
if (!hotelsToFetch.length) {
|
|
metrics.hotels.fail.add(1, {
|
|
input: JSON.stringify(input),
|
|
language,
|
|
error_type: "not_found",
|
|
error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`,
|
|
})
|
|
|
|
console.error(
|
|
"api.hotel.hotels not found error",
|
|
JSON.stringify({
|
|
query: JSON.stringify(input),
|
|
error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`,
|
|
})
|
|
)
|
|
return []
|
|
}
|
|
|
|
const hotels = await Promise.all(
|
|
hotelsToFetch.map(async (hotelId) => {
|
|
const [hotelData, url] = await Promise.all([
|
|
getHotel(
|
|
{ hotelId, isCardOnlyPayment: false, language },
|
|
ctx.serviceToken
|
|
),
|
|
getHotelPageUrl(language, hotelId),
|
|
])
|
|
|
|
return {
|
|
data: hotelData,
|
|
url,
|
|
}
|
|
})
|
|
)
|
|
|
|
metrics.hotels.success.add(1, {
|
|
input: JSON.stringify(input),
|
|
language,
|
|
})
|
|
|
|
console.info(
|
|
"api.hotels success",
|
|
JSON.stringify({
|
|
query: {
|
|
input: JSON.stringify(input),
|
|
language,
|
|
},
|
|
})
|
|
)
|
|
|
|
return hotels.filter(
|
|
(hotel): hotel is { data: HotelData; url: HotelPageUrl } =>
|
|
!!hotel.data
|
|
)
|
|
}),
|
|
}),
|
|
nearbyHotelIds: serviceProcedure
|
|
.input(nearbyHotelIdsInput)
|
|
.query(async function ({ ctx, input }) {
|
|
const { lang } = ctx
|
|
const apiLang = toApiLang(lang)
|
|
|
|
const { hotelId } = input
|
|
const params: Record<string, string | number> = {
|
|
language: apiLang,
|
|
}
|
|
metrics.nearbyHotelIds.counter.add(1, {
|
|
hotelId,
|
|
})
|
|
console.info(
|
|
"api.hotels.nearbyHotelIds start",
|
|
JSON.stringify({ query: { hotelId, params } })
|
|
)
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Hotel.Hotels.nearbyHotels(hotelId),
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
if (!apiResponse.ok) {
|
|
const text = await apiResponse.text()
|
|
metrics.nearbyHotelIds.fail.add(1, {
|
|
hotelId,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.nearbyHotelIds error",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
error: {
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
const apiJson = await apiResponse.json()
|
|
const validateHotelData = getNearbyHotelIdsSchema.safeParse(apiJson)
|
|
if (!validateHotelData.success) {
|
|
metrics.nearbyHotelIds.fail.add(1, {
|
|
hotelId,
|
|
error_type: "validation_error",
|
|
error: JSON.stringify(validateHotelData.error),
|
|
})
|
|
console.error(
|
|
"api.hotels.nearbyHotelIds validation error",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
error: validateHotelData.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
metrics.nearbyHotelIds.success.add(1, {
|
|
hotelId,
|
|
})
|
|
console.info(
|
|
"api.hotels.nearbyHotelIds success",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
})
|
|
)
|
|
|
|
return validateHotelData.data.map((id: string) => parseInt(id, 10))
|
|
}),
|
|
locations: router({
|
|
get: serviceProcedure.query(async function ({ ctx }) {
|
|
const searchParams = new URLSearchParams()
|
|
searchParams.set("language", toApiLang(ctx.lang))
|
|
|
|
const options: RequestOptionsWithOutBody = {
|
|
// needs to clear default option as only
|
|
// cache or next.revalidate is permitted
|
|
cache: undefined,
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
next: {
|
|
revalidate: env.CACHE_TIME_HOTELS,
|
|
},
|
|
}
|
|
|
|
const countries = await getCountries(options, searchParams, ctx.lang)
|
|
if (!countries) {
|
|
return null
|
|
}
|
|
const countryNames = countries.data.map((country) => country.name)
|
|
const citiesByCountry = await getCitiesByCountry(
|
|
countryNames,
|
|
options,
|
|
searchParams,
|
|
ctx.lang
|
|
)
|
|
|
|
const locations = await getLocations(
|
|
ctx.lang,
|
|
options,
|
|
searchParams,
|
|
citiesByCountry
|
|
)
|
|
|
|
if (Array.isArray(locations)) {
|
|
return {
|
|
data: locations,
|
|
}
|
|
}
|
|
|
|
return locations
|
|
}),
|
|
}),
|
|
map: router({
|
|
city: serviceProcedure
|
|
.input(cityCoordinatesInputSchema)
|
|
.query(async function ({ input }) {
|
|
const apiKey = process.env.GOOGLE_STATIC_MAP_KEY
|
|
const { city, hotel } = input
|
|
|
|
async function fetchCoordinates(address: string) {
|
|
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${apiKey}`
|
|
const response = await fetch(url)
|
|
const data = await response.json()
|
|
|
|
if (data.status !== "OK") {
|
|
console.error(`Geocode error: ${data.status}`)
|
|
return null
|
|
}
|
|
|
|
const location = data.results[0]?.geometry?.location
|
|
if (!location) {
|
|
console.error("No location found in geocode response")
|
|
return null
|
|
}
|
|
|
|
return location
|
|
}
|
|
|
|
let location = await fetchCoordinates(city)
|
|
if (!location) {
|
|
location = await fetchCoordinates(`${city}, ${hotel.address}`)
|
|
}
|
|
|
|
if (!location) {
|
|
throw new Error("Unable to fetch coordinates")
|
|
}
|
|
|
|
return location
|
|
}),
|
|
}),
|
|
meetingRooms: safeProtectedServiceProcedure
|
|
.input(getMeetingRoomsInputSchema)
|
|
.query(async function ({ ctx, input }) {
|
|
const { hotelId, language } = input
|
|
|
|
const params: Record<string, string | string[]> = {
|
|
hotelId,
|
|
language,
|
|
}
|
|
const metricsData = { ...params, hotelId: input.hotelId }
|
|
metrics.meetingRooms.counter.add(1, metricsData)
|
|
console.info(
|
|
"api.hotels.meetingRooms start",
|
|
JSON.stringify({ query: { hotelId, params } })
|
|
)
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Hotel.Hotels.meetingRooms(input.hotelId),
|
|
{
|
|
cache: undefined,
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
next: {
|
|
revalidate: env.CACHE_TIME_HOTELS,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
const text = await apiResponse.text()
|
|
metrics.meetingRooms.fail.add(1, {
|
|
...metricsData,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.meetingRooms error",
|
|
JSON.stringify({
|
|
query: { params },
|
|
error: {
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
const validatedMeetingRooms = meetingRoomsSchema.safeParse(apiJson)
|
|
|
|
if (!validatedMeetingRooms.success) {
|
|
console.error(
|
|
"api.hotels.meetingRooms validation error",
|
|
JSON.stringify({
|
|
query: { params },
|
|
error: validatedMeetingRooms.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
metrics.meetingRooms.success.add(1, {
|
|
hotelId,
|
|
})
|
|
console.info(
|
|
"api.hotels.meetingRooms success",
|
|
JSON.stringify({ query: { params } })
|
|
)
|
|
|
|
return validatedMeetingRooms.data.data
|
|
}),
|
|
additionalData: safeProtectedServiceProcedure
|
|
.input(getAdditionalDataInputSchema)
|
|
.query(async function ({ ctx, input }) {
|
|
const { hotelId, language } = input
|
|
|
|
const params: Record<string, string | string[]> = {
|
|
hotelId,
|
|
language,
|
|
}
|
|
const metricsData = { ...params, hotelId: input.hotelId }
|
|
metrics.additionalData.counter.add(1, metricsData)
|
|
console.info(
|
|
"api.hotels.additionalData start",
|
|
JSON.stringify({ query: { hotelId, params } })
|
|
)
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Hotel.Hotels.additionalData(input.hotelId),
|
|
{
|
|
cache: undefined,
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
next: {
|
|
revalidate: env.CACHE_TIME_HOTELS,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
const text = await apiResponse.text()
|
|
metrics.additionalData.fail.add(1, {
|
|
...metricsData,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.additionalData error",
|
|
JSON.stringify({
|
|
query: { params },
|
|
error: {
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
const validatedAdditionalData = additionalDataSchema.safeParse(apiJson)
|
|
|
|
if (!validatedAdditionalData.success) {
|
|
console.error(
|
|
"api.hotels.additionalData validation error",
|
|
JSON.stringify({
|
|
query: { params },
|
|
error: validatedAdditionalData.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
metrics.additionalData.success.add(1, {
|
|
hotelId,
|
|
})
|
|
console.info(
|
|
"api.hotels.additionalData success",
|
|
JSON.stringify({ query: { params } })
|
|
)
|
|
|
|
return validatedAdditionalData.data
|
|
}),
|
|
packages: router({
|
|
get: serviceProcedure
|
|
.input(roomPackagesInputSchema)
|
|
.query(async ({ input, ctx }) => {
|
|
const { hotelId, startDate, endDate, adults, children, packageCodes } =
|
|
input
|
|
|
|
const { lang } = ctx
|
|
|
|
const apiLang = toApiLang(lang)
|
|
|
|
const searchParams = new URLSearchParams({
|
|
startDate,
|
|
endDate,
|
|
adults: adults.toString(),
|
|
children: children.toString(),
|
|
language: apiLang,
|
|
})
|
|
|
|
packageCodes.forEach((code) => {
|
|
searchParams.append("packageCodes", code)
|
|
})
|
|
|
|
const params = searchParams.toString()
|
|
|
|
metrics.packages.counter.add(1, {
|
|
hotelId,
|
|
})
|
|
console.info(
|
|
"api.hotels.packages start",
|
|
JSON.stringify({ query: { hotelId, params } })
|
|
)
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Package.Packages.hotel(hotelId),
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
},
|
|
searchParams
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
metrics.packages.fail.add(1, {
|
|
hotelId,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.packages error",
|
|
JSON.stringify({ query: { hotelId, params } })
|
|
)
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
const validatedPackagesData = packagesSchema.safeParse(apiJson)
|
|
if (!validatedPackagesData.success) {
|
|
metrics.packages.fail.add(1, {
|
|
hotelId,
|
|
error_type: "validation_error",
|
|
error: JSON.stringify(validatedPackagesData.error),
|
|
})
|
|
|
|
console.error(
|
|
"api.hotels.packages validation error",
|
|
JSON.stringify({
|
|
query: { hotelId, params },
|
|
error: validatedPackagesData.error,
|
|
})
|
|
)
|
|
throw badRequestError()
|
|
}
|
|
|
|
metrics.packages.success.add(1, {
|
|
hotelId,
|
|
})
|
|
console.info(
|
|
"api.hotels.packages success",
|
|
JSON.stringify({ query: { hotelId, params: params } })
|
|
)
|
|
|
|
return validatedPackagesData.data
|
|
}),
|
|
breakfast: safeProtectedServiceProcedure
|
|
.input(breakfastPackageInputSchema)
|
|
.query(async function ({ ctx, input }) {
|
|
const { lang } = ctx
|
|
|
|
const apiLang = toApiLang(lang)
|
|
const params = {
|
|
Adults: input.adults,
|
|
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.breakfastPackage.counter.add(1, metricsData)
|
|
console.info(
|
|
"api.package.breakfast start",
|
|
JSON.stringify({ query: metricsData })
|
|
)
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Package.Breakfast.hotel(input.hotelId),
|
|
{
|
|
cache: undefined,
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
|
},
|
|
next: {
|
|
revalidate: 60,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
const text = await apiResponse.text()
|
|
metrics.breakfastPackage.fail.add(1, {
|
|
...metricsData,
|
|
error_type: "http_error",
|
|
error: JSON.stringify({
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
}),
|
|
})
|
|
console.error(
|
|
"api.hotels.hotelsAvailability error",
|
|
JSON.stringify({
|
|
query: metricsData,
|
|
error: {
|
|
status: apiResponse.status,
|
|
statusText: apiResponse.statusText,
|
|
text,
|
|
},
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
const breakfastPackages = breakfastPackagesSchema.safeParse(apiJson)
|
|
if (!breakfastPackages.success) {
|
|
metrics.breakfastPackage.fail.add(1, {
|
|
...metricsData,
|
|
error_type: "validation_error",
|
|
error: JSON.stringify(breakfastPackages.error),
|
|
})
|
|
console.error(
|
|
"api.package.breakfast validation error",
|
|
JSON.stringify({
|
|
query: metricsData,
|
|
error: breakfastPackages.error,
|
|
})
|
|
)
|
|
return null
|
|
}
|
|
|
|
metrics.breakfastPackage.success.add(1, metricsData)
|
|
console.info(
|
|
"api.package.breakfast success",
|
|
JSON.stringify({
|
|
query: metricsData,
|
|
})
|
|
)
|
|
|
|
if (ctx.session?.token) {
|
|
const apiUser = await getVerifiedUser({ session: ctx.session })
|
|
if (apiUser && !("error" in apiUser)) {
|
|
const user = parsedUser(apiUser.data, false)
|
|
if (
|
|
user.membership &&
|
|
["L6", "L7"].includes(user.membership.membershipLevel)
|
|
) {
|
|
const freeBreakfastPackage = breakfastPackages.data.find(
|
|
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
|
)
|
|
if (freeBreakfastPackage?.localPrice) {
|
|
return [freeBreakfastPackage]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return breakfastPackages.data.filter(
|
|
(pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
|
)
|
|
}),
|
|
}),
|
|
})
|