fix(SW-955): restaurantsOverviewPage atttributes optional * fix(SW-955): restaurantsOverviewPage atttributes optional Approved-by: Pontus Dreij
886 lines
23 KiB
TypeScript
886 lines
23 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { ChildBedTypeEnum } from "@/constants/booking"
|
|
import { dt } from "@/lib/dt"
|
|
import { toLang } from "@/server/utils"
|
|
|
|
import { imageMetaDataSchema, imageSizesSchema } from "./schemas/image"
|
|
import { roomSchema } from "./schemas/room"
|
|
import { getPoiGroupByCategoryName } from "./utils"
|
|
|
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
|
import { CurrencyEnum } from "@/types/enums/currency"
|
|
import { FacilityEnum } from "@/types/enums/facilities"
|
|
import { PackageTypeEnum } from "@/types/enums/packages"
|
|
|
|
const ratingsSchema = z
|
|
.object({
|
|
tripAdvisor: z.object({
|
|
numberOfReviews: z.number(),
|
|
rating: z.number(),
|
|
ratingImageUrl: z.string(),
|
|
webUrl: z.string(),
|
|
awards: z.array(
|
|
z.object({
|
|
displayName: z.string(),
|
|
images: z.object({
|
|
small: z.string(),
|
|
medium: z.string(),
|
|
large: z.string(),
|
|
}),
|
|
})
|
|
),
|
|
reviews: z
|
|
.object({
|
|
widgetHtmlTagId: z.string(),
|
|
widgetScriptEmbedUrlIframe: z.string(),
|
|
widgetScriptEmbedUrlJavaScript: z.string(),
|
|
})
|
|
.optional(),
|
|
}),
|
|
})
|
|
.optional()
|
|
|
|
const addressSchema = z.object({
|
|
streetAddress: z.string(),
|
|
city: z.string(),
|
|
zipCode: z.string(),
|
|
country: z.string(),
|
|
})
|
|
|
|
const contactInformationSchema = z.object({
|
|
phoneNumber: z.string(),
|
|
faxNumber: z.string().optional(),
|
|
email: z.string(),
|
|
websiteUrl: z.string(),
|
|
})
|
|
|
|
const checkinSchema = z.object({
|
|
checkInTime: z.string(),
|
|
checkOutTime: z.string(),
|
|
onlineCheckOutAvailableFrom: z.string().nullable().optional(),
|
|
onlineCheckout: z.boolean(),
|
|
})
|
|
|
|
const ecoLabelsSchema = z.object({
|
|
euEcoLabel: z.boolean(),
|
|
greenGlobeLabel: z.boolean(),
|
|
nordicEcoLabel: z.boolean(),
|
|
svanenEcoLabelCertificateNumber: z.string().optional(),
|
|
})
|
|
|
|
const interiorSchema = z.object({
|
|
numberOfBeds: z.number(),
|
|
numberOfCribs: z.number(),
|
|
numberOfFloors: z.number(),
|
|
numberOfRooms: z.object({
|
|
connected: z.number(),
|
|
forAllergics: z.number().optional(),
|
|
forDisabled: z.number(),
|
|
nonSmoking: z.number(),
|
|
pet: z.number(),
|
|
withExtraBeds: z.number(),
|
|
total: z.number(),
|
|
}),
|
|
})
|
|
|
|
const receptionHoursSchema = z.object({
|
|
alwaysOpen: z.boolean(),
|
|
isClosed: z.boolean(),
|
|
openingTime: z.string().optional(),
|
|
closingTime: z.string().optional(),
|
|
})
|
|
|
|
const locationSchema = z.object({
|
|
distanceToCentre: z.number(),
|
|
latitude: z.number(),
|
|
longitude: z.number(),
|
|
})
|
|
|
|
const hotelContentSchema = z.object({
|
|
images: z.object({
|
|
metaData: imageMetaDataSchema,
|
|
imageSizes: imageSizesSchema,
|
|
}),
|
|
texts: z.object({
|
|
facilityInformation: z.string().optional(),
|
|
surroundingInformation: z.string(),
|
|
descriptions: z.object({
|
|
short: z.string(),
|
|
medium: z.string(),
|
|
}),
|
|
}),
|
|
restaurantsOverviewPage: z.object({
|
|
restaurantsOverviewPageLinkText: z.string().optional(),
|
|
restaurantsOverviewPageLink: z.string().optional(),
|
|
restaurantsContentDescriptionShort: z.string().optional(),
|
|
restaurantsContentDescriptionMedium: z.string().optional(),
|
|
}),
|
|
})
|
|
|
|
const detailedFacilitySchema = z.object({
|
|
id: z.nativeEnum(FacilityEnum),
|
|
name: z.string(),
|
|
public: z.boolean(),
|
|
sortOrder: z.number(),
|
|
filter: z.string().optional(),
|
|
})
|
|
|
|
export const facilitySchema = z.object({
|
|
headingText: z.string(),
|
|
heroImages: z.array(
|
|
z.object({
|
|
metaData: imageMetaDataSchema,
|
|
imageSizes: imageSizesSchema,
|
|
})
|
|
),
|
|
})
|
|
|
|
export const imageSchema = z.object({
|
|
metaData: imageMetaDataSchema,
|
|
imageSizes: imageSizesSchema,
|
|
})
|
|
|
|
export const gallerySchema = z.object({
|
|
heroImages: z.array(imageSchema),
|
|
smallerImages: z.array(imageSchema),
|
|
})
|
|
|
|
const healthFacilitySchema = z.object({
|
|
type: z.string(),
|
|
content: z.object({
|
|
images: z.array(imageSchema),
|
|
texts: z.object({
|
|
facilityInformation: z.string().optional(),
|
|
surroundingInformation: z.string().optional(),
|
|
descriptions: z.object({
|
|
short: z.string(),
|
|
medium: z.string(),
|
|
}),
|
|
}),
|
|
}),
|
|
openingDetails: z.object({
|
|
useManualOpeningHours: z.boolean(),
|
|
manualOpeningHours: z.string().optional(),
|
|
openingHours: z.object({
|
|
ordinary: z.object({
|
|
alwaysOpen: z.boolean(),
|
|
isClosed: z.boolean(),
|
|
openingTime: z.string().optional(),
|
|
closingTime: z.string().optional(),
|
|
sortOrder: z.number().optional(),
|
|
}),
|
|
weekends: z.object({
|
|
alwaysOpen: z.boolean(),
|
|
isClosed: z.boolean(),
|
|
openingTime: z.string().optional(),
|
|
closingTime: z.string().optional(),
|
|
sortOrder: z.number().optional(),
|
|
}),
|
|
}),
|
|
}),
|
|
details: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
type: z.string(),
|
|
value: z.string().optional(),
|
|
})
|
|
),
|
|
})
|
|
|
|
const rewardNightSchema = z.object({
|
|
points: z.number(),
|
|
campaign: z.object({
|
|
start: z.string(),
|
|
end: z.string(),
|
|
points: z.number(),
|
|
}),
|
|
})
|
|
|
|
export const pointOfInterestSchema = z
|
|
.object({
|
|
name: z.string(),
|
|
distance: z.number(),
|
|
category: z.object({
|
|
name: z.string(),
|
|
group: z.string(),
|
|
}),
|
|
location: locationSchema,
|
|
isHighlighted: z.boolean(),
|
|
})
|
|
.transform((poi) => ({
|
|
name: poi.name,
|
|
distance: poi.distance,
|
|
categoryName: poi.category.name,
|
|
group: getPoiGroupByCategoryName(poi.category.name),
|
|
coordinates: {
|
|
lat: poi.location.latitude,
|
|
lng: poi.location.longitude,
|
|
},
|
|
}))
|
|
|
|
const parkingPricingSchema = z.object({
|
|
freeParking: z.boolean(),
|
|
paymentType: z.string().optional(),
|
|
localCurrency: z.object({
|
|
currency: z.string().optional(),
|
|
range: z.object({
|
|
min: z.number().optional(),
|
|
max: z.number().optional(),
|
|
}),
|
|
ordinary: z
|
|
.array(
|
|
z.object({
|
|
period: z.string().optional(),
|
|
amount: z.number().optional(),
|
|
startTime: z.string().optional(),
|
|
endTime: z.string().optional(),
|
|
})
|
|
)
|
|
.optional(),
|
|
weekend: z
|
|
.array(
|
|
z.object({
|
|
period: z.string().optional(),
|
|
amount: z.number().optional(),
|
|
startTime: z.string().optional(),
|
|
endTime: z.string().optional(),
|
|
})
|
|
)
|
|
.optional(),
|
|
}),
|
|
requestedCurrency: z
|
|
.object({
|
|
currency: z.string().optional(),
|
|
range: z
|
|
.object({
|
|
min: z.number().optional(),
|
|
max: z.number().optional(),
|
|
})
|
|
.optional(),
|
|
ordinary: z
|
|
.array(
|
|
z.object({
|
|
period: z.string().optional(),
|
|
amount: z.number().optional(),
|
|
startTime: z.string().optional(),
|
|
endTime: z.string().optional(),
|
|
})
|
|
)
|
|
.optional(),
|
|
weekend: z
|
|
.array(
|
|
z.object({
|
|
period: z.string().optional(),
|
|
amount: z.number().optional(),
|
|
startTime: z.string().optional(),
|
|
endTime: z.string().optional(),
|
|
})
|
|
)
|
|
.optional(),
|
|
})
|
|
.optional(),
|
|
})
|
|
|
|
export const parkingSchema = z.object({
|
|
type: z.string().optional(),
|
|
name: z.string().optional(),
|
|
address: z.string().optional(),
|
|
numberOfParkingSpots: z.number().optional(),
|
|
numberOfChargingSpaces: z.number().optional(),
|
|
distanceToHotel: z.number().optional(),
|
|
canMakeReservation: z.boolean(),
|
|
pricing: parkingPricingSchema,
|
|
})
|
|
|
|
const specialNeedSchema = z.object({
|
|
name: z.string(),
|
|
details: z.string(),
|
|
})
|
|
|
|
const specialNeedGroupSchema = z.object({
|
|
name: z.string(),
|
|
specialNeeds: z.array(specialNeedSchema),
|
|
})
|
|
|
|
const socialMediaSchema = z.object({
|
|
instagram: z.string().optional(),
|
|
facebook: z.string().optional(),
|
|
})
|
|
|
|
const specialAlertSchema = z.object({
|
|
type: z.string(),
|
|
title: z.string().optional(),
|
|
description: z.string().optional(),
|
|
displayInBookingFlow: z.boolean(),
|
|
startDate: z.string().optional(),
|
|
endDate: z.string().optional(),
|
|
})
|
|
|
|
const specialAlertsSchema = z
|
|
.array(specialAlertSchema)
|
|
.transform((data) => {
|
|
const now = dt().utc().format("YYYY-MM-DD")
|
|
const filteredAlerts = data.filter((alert) => {
|
|
const shouldShowNow =
|
|
alert.startDate && alert.endDate
|
|
? alert.startDate <= now && alert.endDate >= now
|
|
: true
|
|
const hasText = alert.description || alert.title
|
|
return shouldShowNow && hasText
|
|
})
|
|
return filteredAlerts.map((alert, idx) => ({
|
|
id: `alert-${alert.type}-${idx}`,
|
|
type: AlertTypeEnum.Info,
|
|
heading: alert.title || null,
|
|
text: alert.description || null,
|
|
}))
|
|
})
|
|
.default([])
|
|
|
|
const relationshipsSchema = z.object({
|
|
restaurants: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
nearbyHotels: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
roomCategories: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
meetingRooms: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
})
|
|
|
|
const merchantInformationSchema = z.object({
|
|
webMerchantId: z.string(),
|
|
cards: z.record(z.string(), z.boolean()).transform((val) => {
|
|
return Object.entries(val)
|
|
.filter(([_, enabled]) => enabled)
|
|
.map(([key]) => key)
|
|
}),
|
|
alternatePaymentOptions: z
|
|
.record(z.string(), z.boolean())
|
|
.transform((val) => {
|
|
return Object.entries(val)
|
|
.filter(([_, enabled]) => enabled)
|
|
.map(([key]) => key)
|
|
}),
|
|
})
|
|
|
|
const hotelFacilityDetailSchema = z
|
|
.object({
|
|
description: z.string(),
|
|
heading: z.string(),
|
|
})
|
|
.optional()
|
|
|
|
/** Possibly more values */
|
|
const hotelFacilityDetailsSchema = z.object({
|
|
breakfast: hotelFacilityDetailSchema,
|
|
checkout: hotelFacilityDetailSchema,
|
|
gym: hotelFacilityDetailSchema,
|
|
internet: hotelFacilityDetailSchema,
|
|
laundry: hotelFacilityDetailSchema,
|
|
luggage: hotelFacilityDetailSchema,
|
|
shop: hotelFacilityDetailSchema,
|
|
telephone: hotelFacilityDetailSchema,
|
|
})
|
|
|
|
const hotelInformationSchema = z
|
|
.object({
|
|
description: z.string(),
|
|
heading: z.string(),
|
|
link: z.string().optional(),
|
|
})
|
|
.optional()
|
|
|
|
const hotelInformationsSchema = z.object({
|
|
accessibility: hotelInformationSchema,
|
|
safety: hotelInformationSchema,
|
|
sustainability: hotelInformationSchema,
|
|
})
|
|
|
|
const hotelFactsSchema = z.object({
|
|
checkin: checkinSchema,
|
|
ecoLabels: ecoLabelsSchema,
|
|
hotelFacilityDetail: hotelFacilityDetailsSchema.default({}),
|
|
hotelInformation: hotelInformationsSchema.default({}),
|
|
interior: interiorSchema,
|
|
receptionHours: receptionHoursSchema,
|
|
yearBuilt: z.string(),
|
|
})
|
|
|
|
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
|
|
export const getHotelDataSchema = z.object({
|
|
data: z.object({
|
|
id: z.string(),
|
|
type: z.literal("hotels"), // No enum here but the standard return appears to be "hotels".
|
|
language: z.string().transform((val) => {
|
|
const lang = toLang(val)
|
|
if (!lang) {
|
|
throw new Error("Invalid language")
|
|
}
|
|
return lang
|
|
}),
|
|
attributes: z.object({
|
|
accessibilityElevatorPitchText: z.string().optional(),
|
|
address: addressSchema,
|
|
cityId: z.string(),
|
|
cityName: z.string(),
|
|
conferencesAndMeetings: facilitySchema.optional(),
|
|
contactInformation: contactInformationSchema,
|
|
detailedFacilities: z
|
|
.array(detailedFacilitySchema)
|
|
.transform((facilities) =>
|
|
facilities.sort((a, b) => b.sortOrder - a.sortOrder)
|
|
),
|
|
gallery: gallerySchema.optional(),
|
|
galleryImages: z.array(imageSchema).optional(),
|
|
healthAndWellness: facilitySchema.optional(),
|
|
healthFacilities: z.array(healthFacilitySchema),
|
|
hotelContent: hotelContentSchema,
|
|
hotelFacts: hotelFactsSchema,
|
|
hotelRoomElevatorPitchText: z.string().optional(),
|
|
hotelType: z.string().optional(),
|
|
isActive: z.boolean(),
|
|
isPublished: z.boolean(),
|
|
keywords: z.array(z.string()),
|
|
location: locationSchema,
|
|
merchantInformationData: merchantInformationSchema,
|
|
name: z.string(),
|
|
operaId: z.string(),
|
|
parking: z.array(parkingSchema),
|
|
pointsOfInterest: z
|
|
.array(pointOfInterestSchema)
|
|
.transform((pois) => pois.sort((a, b) => a.distance - b.distance)),
|
|
ratings: ratingsSchema,
|
|
rewardNight: rewardNightSchema,
|
|
restaurantImages: facilitySchema.optional(),
|
|
socialMedia: socialMediaSchema,
|
|
specialAlerts: specialAlertsSchema,
|
|
specialNeedGroups: z.array(specialNeedGroupSchema),
|
|
}),
|
|
relationships: relationshipsSchema,
|
|
}),
|
|
// NOTE: We can pass an "include" param to the hotel API to retrieve
|
|
// additional data for an individual hotel.
|
|
included: z.array(roomSchema).optional(),
|
|
})
|
|
|
|
export const childrenSchema = z.object({
|
|
age: z.number(),
|
|
bedType: z.nativeEnum(ChildBedTypeEnum),
|
|
})
|
|
|
|
const occupancySchema = z.object({
|
|
adults: z.number(),
|
|
children: z.array(childrenSchema),
|
|
})
|
|
|
|
const linksSchema = z.object({
|
|
links: z.array(
|
|
z.object({
|
|
url: z.string().url(),
|
|
type: z.string(),
|
|
})
|
|
),
|
|
})
|
|
|
|
export const priceSchema = z.object({
|
|
pricePerNight: z.coerce.number(),
|
|
pricePerStay: z.coerce.number(),
|
|
currency: z.string(),
|
|
})
|
|
|
|
export const productTypePriceSchema = z.object({
|
|
rateCode: z.string(),
|
|
rateType: z.string().optional(),
|
|
localPrice: priceSchema,
|
|
requestedPrice: priceSchema.optional(),
|
|
})
|
|
|
|
const productSchema = z.object({
|
|
productType: z.object({
|
|
public: productTypePriceSchema.default({
|
|
rateCode: "",
|
|
rateType: "",
|
|
localPrice: {
|
|
currency: "SEK",
|
|
pricePerNight: 0,
|
|
pricePerStay: 0,
|
|
},
|
|
requestedPrice: undefined,
|
|
}),
|
|
member: productTypePriceSchema.optional(),
|
|
}),
|
|
})
|
|
|
|
const hotelsAvailabilitySchema = z.object({
|
|
data: z.array(
|
|
z.object({
|
|
attributes: z.object({
|
|
checkInDate: z.string(),
|
|
checkOutDate: z.string(),
|
|
occupancy: occupancySchema,
|
|
status: z.string(),
|
|
hotelId: z.number(),
|
|
productType: z
|
|
.object({
|
|
public: productTypePriceSchema.optional(),
|
|
member: productTypePriceSchema.optional(),
|
|
})
|
|
.optional(),
|
|
}),
|
|
relationships: linksSchema.optional(),
|
|
type: z.string().optional(),
|
|
})
|
|
),
|
|
})
|
|
|
|
export const getHotelsAvailabilitySchema = hotelsAvailabilitySchema
|
|
export type HotelsAvailability = z.infer<typeof hotelsAvailabilitySchema>
|
|
export type ProductType =
|
|
HotelsAvailability["data"][number]["attributes"]["productType"]
|
|
export type ProductTypePrices = z.infer<typeof productTypePriceSchema>
|
|
|
|
const roomConfigurationSchema = z.object({
|
|
status: z.string(),
|
|
roomTypeCode: z.string(),
|
|
roomType: z.string(),
|
|
roomsLeft: z.number(),
|
|
features: z.array(
|
|
z.object({
|
|
inventory: z.number(),
|
|
code: z.enum([
|
|
RoomPackageCodeEnum.PET_ROOM,
|
|
RoomPackageCodeEnum.ALLERGY_ROOM,
|
|
RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
|
]),
|
|
})
|
|
),
|
|
products: z.array(productSchema),
|
|
})
|
|
|
|
const rateDefinitionSchema = z.object({
|
|
title: z.string(),
|
|
breakfastIncluded: z.boolean(),
|
|
rateType: z.string().optional(),
|
|
rateCode: z.string(),
|
|
generalTerms: z.array(z.string()),
|
|
cancellationRule: z.string(),
|
|
cancellationText: z.string(),
|
|
mustBeGuaranteed: z.boolean(),
|
|
})
|
|
|
|
const roomsAvailabilitySchema = z
|
|
.object({
|
|
data: z.object({
|
|
attributes: z.object({
|
|
checkInDate: z.string(),
|
|
checkOutDate: z.string(),
|
|
occupancy: occupancySchema.optional(),
|
|
hotelId: z.number(),
|
|
roomConfigurations: z.array(roomConfigurationSchema),
|
|
rateDefinitions: z.array(rateDefinitionSchema),
|
|
mustBeGuaranteed: z.boolean().optional(),
|
|
}),
|
|
relationships: linksSchema.optional(),
|
|
type: z.string().optional(),
|
|
}),
|
|
})
|
|
.transform((o) => o.data.attributes)
|
|
|
|
export const getRoomsAvailabilitySchema = roomsAvailabilitySchema
|
|
export type RoomsAvailability = z.infer<typeof roomsAvailabilitySchema>
|
|
export type RoomConfiguration = z.infer<typeof roomConfigurationSchema>
|
|
export type Product = z.infer<typeof productSchema>
|
|
export type RateDefinition = z.infer<typeof rateDefinitionSchema>
|
|
|
|
const flexibilityPrice = z.object({
|
|
standard: z.number(),
|
|
member: z.number(),
|
|
})
|
|
|
|
const rate = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
description: z.string(),
|
|
size: z.string(),
|
|
imageSrc: z.string(),
|
|
breakfastIncluded: z.boolean(),
|
|
prices: z.object({
|
|
currency: z.string(),
|
|
nonRefundable: flexibilityPrice,
|
|
freeRebooking: flexibilityPrice,
|
|
freeCancellation: flexibilityPrice,
|
|
}),
|
|
})
|
|
|
|
export const getRatesSchema = z.array(rate)
|
|
export type Rate = z.infer<typeof rate>
|
|
|
|
const hotelFilter = z.object({
|
|
roomFacilities: z.array(z.string()),
|
|
hotelFacilities: z.array(z.string()),
|
|
hotelSurroundings: z.array(z.string()),
|
|
})
|
|
|
|
export const getFiltersSchema = hotelFilter
|
|
export type HotelFilter = z.infer<typeof hotelFilter>
|
|
|
|
export const apiCitiesByCountrySchema = z.object({
|
|
data: z.array(
|
|
z
|
|
.object({
|
|
attributes: z.object({
|
|
cityIdentifier: z.string().optional(),
|
|
name: z.string(),
|
|
keywords: z.array(z.string()).optional(),
|
|
timeZoneId: z.string().optional(),
|
|
ianaTimeZoneId: z.string().optional(),
|
|
isPublished: z.boolean().optional().default(false),
|
|
}),
|
|
id: z.string(),
|
|
type: z.literal("cities"),
|
|
})
|
|
.transform((data) => {
|
|
return {
|
|
...data.attributes,
|
|
id: data.id,
|
|
type: data.type,
|
|
}
|
|
})
|
|
),
|
|
})
|
|
|
|
export interface CitiesByCountry
|
|
extends z.output<typeof apiCitiesByCountrySchema> {}
|
|
export type CitiesGroupedByCountry = Record<string, CitiesByCountry["data"]>
|
|
|
|
export const apiCountriesSchema = z.object({
|
|
data: z
|
|
.array(
|
|
z.object({
|
|
attributes: z.object({
|
|
currency: z.string().optional(),
|
|
name: z.string(),
|
|
}),
|
|
hotelInformationSystemId: z.number().optional(),
|
|
id: z.string().optional().default(""),
|
|
language: z.string().optional(),
|
|
type: z.literal("countries"),
|
|
})
|
|
)
|
|
.transform((data) => {
|
|
return data.map((country) => {
|
|
return {
|
|
...country.attributes,
|
|
hotelInformationSystemId: country.hotelInformationSystemId,
|
|
id: country.id,
|
|
language: country.language,
|
|
type: country.type,
|
|
}
|
|
})
|
|
}),
|
|
})
|
|
|
|
export interface Countries extends z.output<typeof apiCountriesSchema> {}
|
|
|
|
export const apiLocationCitySchema = z.object({
|
|
attributes: z.object({
|
|
cityIdentifier: z.string().optional(),
|
|
keyWords: z.array(z.string()).optional(),
|
|
name: z.string().optional().default(""),
|
|
}),
|
|
country: z.string().optional().default(""),
|
|
id: z.string().optional().default(""),
|
|
type: z.literal("cities"),
|
|
})
|
|
|
|
export const apiCitySchema = z
|
|
.object({
|
|
data: z.array(
|
|
z.object({
|
|
attributes: z.object({
|
|
cityIdentifier: z.string().optional(),
|
|
name: z.string(),
|
|
keywords: z.array(z.string()),
|
|
timeZoneId: z.string().optional(),
|
|
ianaTimeZoneId: z.string().optional(),
|
|
isPublished: z.boolean().optional().default(false),
|
|
}),
|
|
id: z.string().optional(),
|
|
type: z.literal("cities"),
|
|
})
|
|
),
|
|
})
|
|
.transform(({ data }) => {
|
|
if (data.length) {
|
|
const city = data[0]
|
|
return {
|
|
...city.attributes,
|
|
id: city.id,
|
|
type: city.type,
|
|
}
|
|
}
|
|
return null
|
|
})
|
|
|
|
export const apiLocationHotelSchema = z.object({
|
|
attributes: z.object({
|
|
distanceToCentre: z.number().optional(),
|
|
images: z
|
|
.object({
|
|
large: z.string().optional(),
|
|
medium: z.string().optional(),
|
|
small: z.string().optional(),
|
|
tiny: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
keyWords: z.array(z.string()).optional(),
|
|
name: z.string().optional().default(""),
|
|
operaId: z.string().optional(),
|
|
}),
|
|
id: z.string().optional().default(""),
|
|
relationships: z
|
|
.object({
|
|
city: z
|
|
.object({
|
|
links: z
|
|
.object({
|
|
related: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
})
|
|
.optional(),
|
|
})
|
|
.optional(),
|
|
type: z.literal("hotels"),
|
|
})
|
|
|
|
export const apiLocationsSchema = z.object({
|
|
data: z
|
|
.array(
|
|
z
|
|
.discriminatedUnion("type", [
|
|
apiLocationCitySchema,
|
|
apiLocationHotelSchema,
|
|
])
|
|
.transform((location) => {
|
|
if (location.type === "cities") {
|
|
return {
|
|
...location.attributes,
|
|
country: location?.country ?? "",
|
|
id: location.id,
|
|
type: location.type,
|
|
}
|
|
}
|
|
return {
|
|
...location.attributes,
|
|
id: location.id,
|
|
relationships: {
|
|
city: {
|
|
cityIdentifier: "",
|
|
ianaTimeZoneId: "",
|
|
id: "",
|
|
isPublished: false,
|
|
keywords: [],
|
|
name: "",
|
|
timeZoneId: "",
|
|
type: "cities",
|
|
url: location?.relationships?.city?.links?.related ?? "",
|
|
},
|
|
},
|
|
type: location.type,
|
|
}
|
|
})
|
|
)
|
|
.transform((data) =>
|
|
data
|
|
.filter((node) => !!node)
|
|
.sort((a, b) => {
|
|
if (a.type === b.type) {
|
|
return a.name.localeCompare(b.name)
|
|
} else {
|
|
return a.type === "cities" ? -1 : 1
|
|
}
|
|
})
|
|
),
|
|
})
|
|
|
|
export const packagePriceSchema = z.object({
|
|
currency: z.nativeEnum(CurrencyEnum),
|
|
price: z.string(),
|
|
totalPrice: z.string(),
|
|
})
|
|
|
|
export const breakfastPackageSchema = z.object({
|
|
code: z.string(),
|
|
description: z.string(),
|
|
localPrice: packagePriceSchema,
|
|
requestedPrice: packagePriceSchema,
|
|
packageType: z.literal(PackageTypeEnum.BreakfastAdult),
|
|
})
|
|
|
|
export const breakfastPackagesSchema = z
|
|
.object({
|
|
data: z.object({
|
|
attributes: z.object({
|
|
hotelId: z.number(),
|
|
packages: z.array(breakfastPackageSchema),
|
|
}),
|
|
type: z.literal("breakfastpackage"),
|
|
}),
|
|
})
|
|
.transform(({ data }) =>
|
|
data.attributes.packages.filter((pkg) => pkg.code.match(/^(BRF\d+)$/gm))
|
|
)
|
|
|
|
export const packagesSchema = z.object({
|
|
code: z.nativeEnum(RoomPackageCodeEnum),
|
|
itemCode: z.string().optional(),
|
|
description: z.string(),
|
|
localPrice: packagePriceSchema,
|
|
requestedPrice: packagePriceSchema,
|
|
inventories: z.array(
|
|
z.object({
|
|
date: z.string(),
|
|
total: z.number(),
|
|
available: z.number(),
|
|
})
|
|
),
|
|
})
|
|
|
|
export const getRoomPackagesSchema = z
|
|
.object({
|
|
data: z.object({
|
|
attributes: z.object({
|
|
hotelId: z.number(),
|
|
packages: z.array(packagesSchema).optional().default([]),
|
|
}),
|
|
relationships: z
|
|
.object({
|
|
links: z.array(
|
|
z.object({
|
|
url: z.string(),
|
|
type: z.string(),
|
|
})
|
|
),
|
|
})
|
|
.optional(),
|
|
type: z.string(),
|
|
}),
|
|
})
|
|
.transform((data) => data.data.attributes.packages)
|