394 lines
9.5 KiB
TypeScript
394 lines
9.5 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { fromUppercaseToLangEnum } from "@/utils/languages"
|
|
|
|
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(),
|
|
}),
|
|
}),
|
|
})
|
|
|
|
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(),
|
|
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 HotelFacilityDetailSchema = z.object({
|
|
heading: z.string(),
|
|
description: z.string(),
|
|
})
|
|
|
|
const HotelFacilitySchema = z.object({
|
|
breakfast: HotelFacilityDetailSchema,
|
|
checkout: HotelFacilityDetailSchema,
|
|
gym: HotelFacilityDetailSchema,
|
|
internet: HotelFacilityDetailSchema,
|
|
laundry: HotelFacilityDetailSchema,
|
|
luggage: HotelFacilityDetailSchema,
|
|
shop: HotelFacilityDetailSchema,
|
|
telephone: HotelFacilityDetailSchema,
|
|
})
|
|
|
|
const HotelInformationDetailSchema = z.object({
|
|
heading: z.string(),
|
|
description: z.string(),
|
|
link: z.string().optional(),
|
|
})
|
|
|
|
const HotelInformationSchema = z.object({
|
|
accessibility: HotelInformationDetailSchema,
|
|
safety: HotelInformationDetailSchema,
|
|
sustainability: HotelInformationDetailSchema,
|
|
})
|
|
|
|
const InteriorSchema = z.object({
|
|
numberOfBeds: z.number(),
|
|
numberOfCribs: z.number(),
|
|
numberOfFloors: z.number(),
|
|
numberOfRooms: z.object({
|
|
connected: z.number(),
|
|
forEllergics: z.number(),
|
|
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 ImageMetaDataSchema = z.object({
|
|
title: z.string(),
|
|
altText: z.string(),
|
|
altText_En: z.string(),
|
|
copyRight: z.string(),
|
|
})
|
|
|
|
const ImageSizesSchema = z.object({
|
|
tiny: z.string(),
|
|
small: z.string(),
|
|
medium: z.string(),
|
|
large: z.string(),
|
|
})
|
|
|
|
const HotelContentSchema = z.object({
|
|
images: z.object({
|
|
metaData: ImageMetaDataSchema,
|
|
imageSizes: ImageSizesSchema,
|
|
}),
|
|
texts: z.object({
|
|
facilityInformation: z.string(),
|
|
surroundingInformation: z.string(),
|
|
descriptions: z.object({
|
|
short: z.string(),
|
|
medium: z.string(),
|
|
}),
|
|
}),
|
|
restaurantsOverviewPage: z.object({
|
|
restaurantsOverviewPageLinkText: z.string(),
|
|
restaurantsOverviewPageLink: z.string(),
|
|
restaurantsContentDescriptionShort: z.string(),
|
|
restaurantsContentDescriptionMedium: z.string(),
|
|
}),
|
|
})
|
|
|
|
const DetailedFacilitySchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
code: z.string().optional(),
|
|
applyToAllHotels: z.boolean(),
|
|
public: z.boolean(),
|
|
icon: z.number(),
|
|
iconName: z.string().optional(),
|
|
sortOrder: z.number(),
|
|
})
|
|
|
|
const HealthFacilitySchema = z.object({
|
|
type: z.string(),
|
|
content: z.object({
|
|
images: z.array(
|
|
z.object({
|
|
metaData: ImageMetaDataSchema,
|
|
imageSizes: ImageSizesSchema,
|
|
})
|
|
),
|
|
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(),
|
|
closingTime: z.string(),
|
|
sortOrder: z.number().optional(),
|
|
}),
|
|
weekends: z.object({
|
|
alwaysOpen: z.boolean(),
|
|
isClosed: z.boolean(),
|
|
openingTime: z.string(),
|
|
closingTime: z.string(),
|
|
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(),
|
|
}),
|
|
})
|
|
|
|
const PointsOfInterestSchema = z.object({
|
|
name: z.string(),
|
|
distance: z.number(),
|
|
category: z.object({
|
|
name: z.string(),
|
|
group: z.string(),
|
|
}),
|
|
location: LocationSchema,
|
|
isHighlighted: z.boolean(),
|
|
})
|
|
|
|
const ParkingPricingSchema = z.object({
|
|
freeParking: z.boolean(),
|
|
paymentType: z.string(),
|
|
localCurrency: z.object({
|
|
currency: z.string(),
|
|
range: z.object({
|
|
min: z.number(),
|
|
max: z.number(),
|
|
}),
|
|
ordinary: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
weekend: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
}),
|
|
requestedCurrency: z.object({
|
|
currency: z.string(),
|
|
range: z.object({
|
|
min: z.number(),
|
|
max: z.number(),
|
|
}),
|
|
ordinary: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
weekend: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
|
|
const ParkingSchema = z.object({
|
|
type: z.string(),
|
|
name: z.string(),
|
|
address: z.string(),
|
|
numberOfParkingSpots: z.number(),
|
|
numberOfChargingSpaces: z.number(),
|
|
distanceToHotel: z.number(),
|
|
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 MetaSpecialAlertSchema = z.object({
|
|
type: z.string(),
|
|
description: z.string(),
|
|
displayInBookingFlow: z.boolean(),
|
|
startDate: z.string(),
|
|
endDate: z.string(),
|
|
})
|
|
|
|
const MetaSchema = z.object({
|
|
specialAlerts: z.array(MetaSpecialAlertSchema),
|
|
})
|
|
|
|
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(),
|
|
}),
|
|
}),
|
|
})
|
|
|
|
// 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.string(), // No enum here but the standard return appears to be "hotels".
|
|
language: z
|
|
.string()
|
|
.refine((val) => fromUppercaseToLangEnum(val) !== undefined, {
|
|
message: "Invalid language",
|
|
})
|
|
.transform((val) => {
|
|
const lang = fromUppercaseToLangEnum(val)
|
|
if (!lang) {
|
|
throw new Error("Invalid language")
|
|
}
|
|
return lang
|
|
}),
|
|
attributes: z.object({
|
|
name: z.string(),
|
|
operaId: z.string(),
|
|
keywords: z.array(z.string()),
|
|
isPublished: z.boolean(),
|
|
cityId: z.string(),
|
|
cityName: z.string(),
|
|
ratings: RatingsSchema,
|
|
address: AddressSchema,
|
|
contactInformation: ContactInformationSchema,
|
|
hotelFacts: z.object({
|
|
checkin: CheckinSchema,
|
|
ecoLabels: EcoLabelsSchema,
|
|
hotelFacilityDetail: HotelFacilitySchema,
|
|
hotelInformation: HotelInformationSchema,
|
|
interior: InteriorSchema,
|
|
receptionHours: ReceptionHoursSchema,
|
|
yearBuilt: z.string(),
|
|
}),
|
|
location: LocationSchema,
|
|
hotelContent: HotelContentSchema,
|
|
detailedFacilities: z.array(DetailedFacilitySchema),
|
|
healthFacilities: z.array(HealthFacilitySchema),
|
|
rewardNight: RewardNightSchema,
|
|
pointsOfInterest: z.array(PointsOfInterestSchema),
|
|
parking: z.array(ParkingSchema),
|
|
specialNeedGroups: z.array(SpecialNeedGroupSchema),
|
|
socialMedia: SocialMediaSchema,
|
|
meta: MetaSchema,
|
|
isActive: z.boolean(),
|
|
}),
|
|
relationships: RelationshipsSchema,
|
|
}),
|
|
//TODO: We can pass an "included" param to the hotel API to retrieve additional data for an individual hotel.
|
|
// - This is out of scope for current work (and I'm unsure if we need it for hotel pages specifically),
|
|
// - but if/when we do we can extend this schema to add necessary requirements.
|
|
// - Example "included" data available in our tempHotelData file.
|
|
// included: z.any(),
|
|
})
|