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(), }), }), }) .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(), 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().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 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.string(), //Check output. 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().optional(), 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(), }), }), }) const RoomContentSchema = z.object({ images: z.array( z.object({ metaData: ImageMetaDataSchema, imageSizes: ImageSizesSchema, }) ), texts: z.object({ descriptions: z.object({ short: z.string(), medium: z.string(), }), }), }) const RoomTypesSchema = z.object({ name: z.string(), description: z.string(), code: z.string(), roomCount: z.number(), mainBed: z.object({ type: z.string(), description: z.string(), widthRange: z.object({ min: z.number(), max: z.number(), }), }), fixedExtraBed: z.object({ type: z.string(), description: z.string().optional(), widthRange: z.object({ min: z.number(), max: z.number(), }), }), roomSize: z.object({ min: z.number(), max: z.number(), }), occupancy: z.object({ total: z.number(), adults: z.number(), children: z.number(), }), isLackingCribs: z.boolean(), isLackingExtraBeds: z.boolean(), }) const RoomFacilitiesSchema = z.object({ availableInAllRooms: z.boolean(), name: z.string(), isUniqueSellingPoint: z.boolean(), sortOrder: z.number(), }) export const RoomSchema = z.object({ attributes: z.object({ name: z.string(), sortOrder: z.number(), content: RoomContentSchema, roomTypes: z.array(RoomTypesSchema), roomFacilities: z.array(RoomFacilitiesSchema), occupancy: z.object({ total: z.number(), adults: z.number(), children: z.number(), }), roomSize: z.object({ min: z.number(), max: z.number(), }), }), id: z.string(), type: z.enum(["roomcategories"]), }) // 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, }), // NOTE: We can pass an "include" param to the hotel API to retrieve // additional data for an individual hotel. // Example "included" data can be found in our tempHotelData file. included: z.array(RoomSchema).optional(), }) const rate = z.object({ id: z.number(), name: z.string(), description: z.string(), size: z.string(), pricePerNight: z.number(), currency: z.string(), imageSrc: z.string(), }) export const getRatesSchema = z.array(rate) export type Rate = z.infer 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