fix: clean up hotel and its typings

This commit is contained in:
Simon Emanuelsson
2024-12-17 16:17:25 +01:00
parent ec74af8814
commit 13a164242f
110 changed files with 1931 additions and 1559 deletions

View File

@@ -12,6 +12,8 @@ export default async function BookingConfirmationPage({
setLang(params.lang) setLang(params.lang)
void getBookingConfirmation(searchParams.confirmationNumber) void getBookingConfirmation(searchParams.confirmationNumber)
return ( return (
<BookingConfirmation confirmationNumber={searchParams.confirmationNumber} /> <BookingConfirmation
confirmationNumber={searchParams.confirmationNumber}
/>
) )
} }

View File

@@ -1,4 +1,4 @@
import { getHotelData } from "@/lib/trpc/memoizedRequests" import { getHotel } from "@/lib/trpc/memoizedRequests"
import SidePeek from "@/components/HotelReservation/SidePeek" import SidePeek from "@/components/HotelReservation/SidePeek"
@@ -12,9 +12,10 @@ export default async function HotelSidePeek({
return <SidePeek hotel={null} /> return <SidePeek hotel={null} />
} }
const hotel = await getHotelData({ const hotel = await getHotel({
hotelId: searchParams.hotel, hotelId: searchParams.hotel,
language: params.lang, language: params.lang,
isCardOnlyPayment: false,
}) })
return <SidePeek hotel={hotel} /> return <SidePeek hotel={hotel} />

View File

@@ -1,4 +1,4 @@
import { getHotelData } from "@/lib/trpc/memoizedRequests" import { getHotel } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
@@ -73,15 +73,16 @@ async function enhanceHotels(hotels: {
const language = getLang() const language = getLang()
const hotelFetchers = hotels.availability.map(async (hotel) => { const hotelFetchers = hotels.availability.map(async (hotel) => {
const hotelData = await getHotelData({ const hotelData = await getHotel({
hotelId: hotel.hotelId.toString(), hotelId: hotel.hotelId.toString(),
isCardOnlyPayment: false,
language, language,
}) })
if (!hotelData) return { hotelData: null, price: hotel.productType } if (!hotelData) return { hotelData: null, price: hotel.productType }
return { return {
hotelData: hotelData.data.attributes, hotelData: hotelData.hotel,
price: hotel.productType, price: hotel.productType,
} }
}) })

View File

@@ -2,7 +2,7 @@ import { differenceInCalendarDays, format, isWeekend } from "date-fns"
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { Suspense } from "react" import { Suspense } from "react"
import { getHotelData } from "@/lib/trpc/memoizedRequests" import { getHotel } from "@/lib/trpc/memoizedRequests"
import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard"
import { RoomsContainer } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainer" import { RoomsContainer } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainer"
@@ -28,13 +28,16 @@ export default async function SelectRatePage({
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) { }: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
setLang(params.lang) setLang(params.lang)
const searchDetails = await getHotelSearchDetails({ searchParams }) const searchDetails = await getHotelSearchDetails({ searchParams })
if (!searchDetails) return notFound() if (!searchDetails) {
const { hotel, adultsInRoom, childrenInRoom, selectHotelParams } = return notFound()
searchDetails }
const { hotel, adultsInRoom, childrenInRoom, selectHotelParams } = searchDetails
if (!hotel) return notFound() if (!hotel) {
return notFound()
}
const hotelData = await getHotelData({ const hotelData = await getHotel({
hotelId: hotel.id, hotelId: hotel.id,
language: params.lang, language: params.lang,
}) })
@@ -72,9 +75,9 @@ export default async function SelectRatePage({
leadTime: differenceInCalendarDays(arrivalDate, new Date()), leadTime: differenceInCalendarDays(arrivalDate, new Date()),
searchType: "hotel", searchType: "hotel",
bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday", bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
country: hotelData?.data?.attributes.address.country, country: hotelData?.hotel.address.country,
hotelID: hotel?.id, hotelID: hotel?.id,
region: hotelData?.data?.attributes.address.city, region: hotelData?.hotel.address.city,
} }
const hotelId = +hotel.id const hotelId = +hotel.id

View File

@@ -16,8 +16,8 @@ import {
type TrackingSDKUserData, type TrackingSDKUserData,
} from "@/types/components/tracking" } from "@/types/components/tracking"
import type { Packages } from "@/types/requests/packages" import type { Packages } from "@/types/requests/packages"
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
import type { RoomConfiguration } from "@/server/routers/hotels/output"
type Props = { type Props = {
initialHotelsTrackingData: TrackingSDKHotelInfo initialHotelsTrackingData: TrackingSDKHotelInfo
@@ -135,7 +135,7 @@ export default function EnterDetailsTracking(props: Props) {
roomPrice: roomPrice.perStay.local.price, roomPrice: roomPrice.perStay.local.price,
discount: roomRate.memberRate discount: roomRate.memberRate
? roomRate.publicRate.localPrice.pricePerStay - ? roomRate.publicRate.localPrice.pricePerStay -
roomRate.memberRate.localPrice.pricePerStay roomRate.memberRate.localPrice.pricePerStay
: 0, : 0,
analyticsrateCode: getAnalyticsRateCode(roomRate.publicRate.rateCode), analyticsrateCode: getAnalyticsRateCode(roomRate.publicRate.rateCode),
ancillaries: breakfastAncillary ? [breakfastAncillary] : [], ancillaries: breakfastAncillary ? [breakfastAncillary] : [],

View File

@@ -4,7 +4,7 @@ import { Suspense } from "react"
import { import {
getBreakfastPackages, getBreakfastPackages,
getHotelData, getHotel,
getPackages, getPackages,
getProfileSafely, getProfileSafely,
getSelectedRoomAvailability, getSelectedRoomAvailability,
@@ -106,19 +106,19 @@ export default async function StepPage({
const packages = packageCodes const packages = packageCodes
? await getPackages({ ? await getPackages({
adults, adults,
children: childrenInRoom?.length, children: childrenInRoom?.length,
endDate: toDate, endDate: toDate,
hotelId, hotelId,
packageCodes, packageCodes,
startDate: fromDate, startDate: fromDate,
}) })
: null : null
const roomAvailability = await getSelectedRoomAvailability( const roomAvailability = await getSelectedRoomAvailability(
selectedRoomAvailabilityInput selectedRoomAvailabilityInput
) )
const hotelData = await getHotelData({ const hotelData = await getHotel({
hotelId, hotelId,
isCardOnlyPayment: roomAvailability?.mustBeGuaranteed, isCardOnlyPayment: roomAvailability?.mustBeGuaranteed,
language: lang, language: lang,
@@ -153,14 +153,14 @@ export default async function StepPage({
const memberPrice = roomAvailability.memberRate const memberPrice = roomAvailability.memberRate
? { ? {
price: roomAvailability.memberRate.localPrice.pricePerStay, price: roomAvailability.memberRate.localPrice.pricePerStay,
currency: roomAvailability.memberRate.localPrice.currency, currency: roomAvailability.memberRate.localPrice.currency,
} }
: undefined : undefined
const arrivalDate = new Date(fromDate) const arrivalDate = new Date(searchParams.fromDate)
const departureDate = new Date(toDate) const departureDate = new Date(searchParams.toDate)
const hotelAttributes = hotelData?.data.attributes const hotelAttributes = hotelData?.hotel
const initialHotelsTrackingData: TrackingSDKHotelInfo = { const initialHotelsTrackingData: TrackingSDKHotelInfo = {
searchTerm: searchParams.city, searchTerm: searchParams.city,
@@ -207,7 +207,7 @@ export default async function StepPage({
searchParamsStr={selectRoomParams.toString()} searchParamsStr={selectRoomParams.toString()}
step={searchParams.step} step={searchParams.step}
user={user} user={user}
vat={hotelData.data.attributes.vat} vat={hotelAttributes.vat}
> >
<main> <main>
<HotelHeader hotelData={hotelData} /> <HotelHeader hotelData={hotelData} />
@@ -262,11 +262,11 @@ export default async function StepPage({
user={user} user={user}
roomPrice={roomPrice} roomPrice={roomPrice}
otherPaymentOptions={ otherPaymentOptions={
hotelData.data.attributes.merchantInformationData hotelData.hotel.merchantInformationData
.alternatePaymentOptions .alternatePaymentOptions
} }
supportedCards={ supportedCards={
hotelData.data.attributes.merchantInformationData.cards hotelData.hotel.merchantInformationData.cards
} }
mustBeGuaranteed={mustBeGuaranteed} mustBeGuaranteed={mustBeGuaranteed}
/> />

View File

@@ -1,5 +1,5 @@
import { env } from "@/env/server" import { env } from "@/env/server"
import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests" import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
import BookingWidget, { preload } from "@/components/BookingWidget" import BookingWidget, { preload } from "@/components/BookingWidget"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
@@ -21,7 +21,7 @@ export default async function BookingWidgetPage({
if (params.contentType === PageContentTypeEnum.hotelPage) { if (params.contentType === PageContentTypeEnum.hotelPage) {
const hotelPageData = await getHotelPage() const hotelPageData = await getHotelPage()
const hotelData = await getHotelData({ const hotelData = await getHotel({
hotelId: hotelPageData?.hotel_page_id || "", hotelId: hotelPageData?.hotel_page_id || "",
language: getLang(), language: getLang(),
}) })

View File

@@ -29,8 +29,8 @@ export default async function HotelListing({
</Title> </Title>
{hotels.map(({ data, url }) => ( {hotels.map(({ data, url }) => (
<HotelListingItem <HotelListingItem
key={data.name} key={data.hotel.name}
hotel={data} hotel={data.hotel}
contentType={contentType} contentType={contentType}
url={url} url={url}
/> />

View File

@@ -1,14 +1,15 @@
import type { import type {
Hotel,
HotelAddress, HotelAddress,
HotelData, HotelContent,
HotelLocation, HotelLocation,
HotelTripAdvisor, HotelTripAdvisor,
} from "@/types/hotel" } from "@/types/hotel"
export type IntroSectionProps = { export type IntroSectionProps = {
hotelName: HotelData["data"]["attributes"]["name"]
hotelDescription: HotelData["data"]["attributes"]["hotelContent"]["texts"]["descriptions"]["short"]
location: HotelLocation
address: HotelAddress address: HotelAddress
hotelDescription: HotelContent["texts"]["descriptions"]["short"]
hotelName: Hotel["name"]
location: HotelLocation
tripAdvisor: HotelTripAdvisor tripAdvisor: HotelTripAdvisor
} }

View File

@@ -10,9 +10,9 @@ import {
} from "@/types/components/hotelPage/sidepeek/parking" } from "@/types/components/hotelPage/sidepeek/parking"
export default async function ParkingPrices({ export default async function ParkingPrices({
pricing,
currency, currency,
freeParking, freeParking,
pricing,
}: ParkingPricesProps) { }: ParkingPricesProps) {
const intl = await getIntl() const intl = await getIntl()
const day = intl.formatMessage({ id: "Price per day" }) const day = intl.formatMessage({ id: "Price per day" })

View File

@@ -53,8 +53,8 @@ export default async function ParkingAmenity({
</Caption> </Caption>
<Divider color="baseSurfaceSubtleHover" /> <Divider color="baseSurfaceSubtleHover" />
<ParkingPrices <ParkingPrices
pricing={data.pricing.localCurrency.ordinary} pricing={data.pricing.localCurrency?.ordinary}
currency={data.pricing.localCurrency.currency} currency={data.pricing.localCurrency?.currency}
freeParking={data.pricing.freeParking} freeParking={data.pricing.freeParking}
/> />
</div> </div>
@@ -64,8 +64,8 @@ export default async function ParkingAmenity({
</Caption> </Caption>
<Divider color="baseSurfaceSubtleHover" /> <Divider color="baseSurfaceSubtleHover" />
<ParkingPrices <ParkingPrices
pricing={data.pricing.localCurrency.weekend} pricing={data.pricing.localCurrency?.weekend}
currency={data.pricing.localCurrency.currency} currency={data.pricing.localCurrency?.currency}
freeParking={data.pricing.freeParking} freeParking={data.pricing.freeParking}
/> />
</div> </div>

View File

@@ -16,8 +16,8 @@ export default async function RestaurantBarItem({
restaurant, restaurant,
}: RestaurantBarItemProps) { }: RestaurantBarItemProps) {
const intl = await getIntl() const intl = await getIntl()
const { name, openingDetails, menus, content } = restaurant const { bookTableUrl, name, openingDetails, content, menus } = restaurant
const { bookTableUrl, images } = restaurant.content const { images } = restaurant.content
const visibleImages = restaurant.content.images.slice(0, 2) const visibleImages = restaurant.content.images.slice(0, 2)
const imageWidth = images.length === 2 ? 240 : 496 const imageWidth = images.length === 2 ? 240 : 496

View File

@@ -2,7 +2,7 @@ import { notFound } from "next/navigation"
import { Suspense } from "react" import { Suspense } from "react"
import { env } from "@/env/server" import { env } from "@/env/server"
import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests" import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
import AccordionSection from "@/components/Blocks/Accordion" import AccordionSection from "@/components/Blocks/Accordion"
import Breadcrumbs from "@/components/Breadcrumbs" import Breadcrumbs from "@/components/Breadcrumbs"
@@ -41,23 +41,27 @@ import styles from "./hotelPage.module.css"
import { FacilityCardTypeEnum } from "@/types/components/hotelPage/facilities" import { FacilityCardTypeEnum } from "@/types/components/hotelPage/facilities"
import type { HotelPageProps } from "@/types/components/hotelPage/hotelPage" import type { HotelPageProps } from "@/types/components/hotelPage/hotelPage"
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation" import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation"
import type { AdditionalData, Facility } from "@/types/hotel" import type { Facility } from "@/types/hotel"
import { PageContentTypeEnum } from "@/types/requests/contentType" import { PageContentTypeEnum } from "@/types/requests/contentType"
export default async function HotelPage({ hotelId }: HotelPageProps) { export default async function HotelPage({ hotelId }: HotelPageProps) {
const lang = getLang() const lang = getLang()
const [hotelPageData, hotelData] = await Promise.all([ const [hotelPageData, hotelData] = await Promise.all([
getHotelPage(), getHotelPage(),
getHotelData({ hotelId, language: lang }), getHotel({
hotelId,
isCardOnlyPayment: false,
language: lang,
}),
]) ])
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
if (!hotelData?.data || !hotelPageData) { if (!hotelData?.hotel || !hotelPageData) {
return notFound() return notFound()
} }
const jsonSchema = generateHotelSchema(hotelData.data.attributes) const jsonSchema = generateHotelSchema(hotelData.hotel)
const { faq, content, tabValues } = hotelPageData const { faq, content, tabValues } = hotelPageData
const { const {
name, name,
@@ -73,12 +77,9 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
location, location,
ratings, ratings,
parking, parking,
} = hotelData.data.attributes } = hotelData.hotel
const roomCategories = hotelData.included.rooms || [] const restaurants = hotelData.restaurants
const restaurants = hotelData.included.restaurants || [] const roomCategories = hotelData.roomCategories
const additionalData =
hotelData.included.additionalData || ({} as AdditionalData)
const { const {
healthAndWellness, healthAndWellness,
restaurantImages, restaurantImages,
@@ -87,7 +88,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
gallery, gallery,
hotelParking, hotelParking,
displayWebPage, displayWebPage,
} = additionalData } = hotelData.additionalData
const images = gallery?.smallerImages const images = gallery?.smallerImages
const description = hotelContent.texts.descriptions.medium const description = hotelContent.texts.descriptions.medium

View File

@@ -22,8 +22,9 @@ export default async function BookingConfirmation({
confirmationNumber, confirmationNumber,
}: BookingConfirmationProps) { }: BookingConfirmationProps) {
const lang = getLang() const lang = getLang()
const { booking, hotel, room } = const { booking, hotel, room } = await getBookingConfirmation(
await getBookingConfirmation(confirmationNumber) confirmationNumber
)
const arrivalDate = new Date(booking.checkInDate) const arrivalDate = new Date(booking.checkInDate)
const departureDate = new Date(booking.checkOutDate) const departureDate = new Date(booking.checkOutDate)

View File

@@ -1,6 +1,6 @@
import { z } from "zod" import { z } from "zod"
import { breakfastPackageSchema } from "@/server/routers/hotels/output" import { breakfastPackageSchema } from "@/server/routers/hotels/schemas/packages"
export const breakfastStoreSchema = z.object({ export const breakfastStoreSchema = z.object({
breakfast: breakfastPackageSchema.or(z.literal(false)), breakfast: breakfastPackageSchema.or(z.literal(false)),

View File

@@ -10,10 +10,10 @@ import styles from "./header.module.css"
import type { HotelHeaderProps } from "@/types/components/hotelReservation/enterDetails/hotelHeader" import type { HotelHeaderProps } from "@/types/components/hotelReservation/enterDetails/hotelHeader"
export default async function HotelHeader({ hotelData }: HotelHeaderProps) { export default async function HotelHeader({
hotelData: { hotel },
}: HotelHeaderProps) {
const intl = await getIntl() const intl = await getIntl()
const hotel = hotelData.data.attributes
const image = hotel.hotelContent?.images const image = hotel.hotelContent?.images
const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}` const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}`

View File

@@ -32,7 +32,7 @@ import {
} from "@/types/components/tracking" } from "@/types/components/tracking"
function isValidHotelData(hotel: NullableHotelData): hotel is HotelData { function isValidHotelData(hotel: NullableHotelData): hotel is HotelData {
return hotel !== null && hotel !== undefined return hotel != null
} }
export async function SelectHotelMapContainer({ export async function SelectHotelMapContainer({

View File

@@ -1,6 +1,6 @@
import { Suspense } from "react" import { Suspense } from "react"
import { getHotelData } from "@/lib/trpc/memoizedRequests" import { getHotel } from "@/lib/trpc/memoizedRequests"
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
import ImageGallery from "@/components/ImageGallery" import ImageGallery from "@/components/ImageGallery"
@@ -18,55 +18,44 @@ import { NoRoomsAlert } from "./NoRoomsAlert"
import styles from "./hotelInfoCard.module.css" import styles from "./hotelInfoCard.module.css"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate" import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCard"
import type { Lang } from "@/constants/languages"
type Props = {
hotelId: number
lang: Lang
fromDate: Date
toDate: Date
adultCount: number
childArray?: Child[]
}
export default async function HotelInfoCard({ export default async function HotelInfoCard({
hotelId, hotelId,
lang, lang,
...props ...props
}: Props) { }: HotelInfoCardProps) {
const hotelData = await getHotelData({ const hotelData = await getHotel({
hotelId: hotelId.toString(), hotelId: hotelId.toString(),
isCardOnlyPayment: false,
language: lang, language: lang,
}) })
const hotelAttributes = hotelData?.data.attributes const hotel = hotelData?.hotel
const intl = await getIntl() const intl = await getIntl()
const sortedFacilities = hotelAttributes?.detailedFacilities const sortedFacilities = hotel?.detailedFacilities
.sort((a, b) => b.sortOrder - a.sortOrder) .sort((a, b) => b.sortOrder - a.sortOrder)
.slice(0, 5) .slice(0, 5)
return ( return (
<article className={styles.container}> <article className={styles.container}>
{hotelAttributes && ( {hotel && (
<section className={styles.wrapper}> <section className={styles.wrapper}>
<div className={styles.imageWrapper}> <div className={styles.imageWrapper}>
<ImageGallery <ImageGallery
title={hotelAttributes.name} title={hotel.name}
images={hotelAttributes.galleryImages} images={hotel.galleryImages}
fill fill
/> />
{hotelAttributes.ratings?.tripAdvisor && ( {hotel.ratings?.tripAdvisor && (
<TripAdvisorChip <TripAdvisorChip rating={hotel.ratings.tripAdvisor.rating} />
rating={hotelAttributes.ratings.tripAdvisor.rating}
/>
)} )}
</div> </div>
<div className={styles.hotelContent}> <div className={styles.hotelContent}>
<div className={styles.hotelInformation}> <div className={styles.hotelInformation}>
<Title as="h2" textTransform="uppercase"> <Title as="h2" textTransform="uppercase">
{hotelAttributes.name} {hotel.name}
</Title> </Title>
<div className={styles.hotelAddressDescription}> <div className={styles.hotelAddressDescription}>
<Caption color="uiTextMediumContrast"> <Caption color="uiTextMediumContrast">
@@ -75,16 +64,16 @@ export default async function HotelInfoCard({
id: "{address}, {city} ∙ {distanceToCityCenterInKm} km to city center", id: "{address}, {city} ∙ {distanceToCityCenterInKm} km to city center",
}, },
{ {
address: hotelAttributes.address.streetAddress, address: hotel.address.streetAddress,
city: hotelAttributes.address.city, city: hotel.address.city,
distanceToCityCenterInKm: getSingleDecimal( distanceToCityCentreInKm: getSingleDecimal(
hotelAttributes.location.distanceToCentre / 1000 hotel.location.distanceToCentre / 1000
), ),
} }
)} )}
</Caption> </Caption>
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">
{hotelAttributes.hotelContent.texts.descriptions.medium} {hotel.hotelContent.texts.descriptions.medium}
</Body> </Body>
</div> </div>
</div> </div>
@@ -111,15 +100,15 @@ export default async function HotelInfoCard({
</div> </div>
<ReadMore <ReadMore
label={intl.formatMessage({ id: "Show all amenities" })} label={intl.formatMessage({ id: "Show all amenities" })}
hotelId={hotelAttributes.operaId} hotelId={hotel.operaId}
hotel={hotelAttributes} hotel={hotel}
showCTA={false} showCTA={false}
/> />
</div> </div>
</div> </div>
</section> </section>
)} )}
{hotelAttributes?.specialAlerts.map((alert) => { {hotel?.specialAlerts.map((alert) => {
return ( return (
<div className={styles.hotelAlert} key={`wrapper_${alert.id}`}> <div className={styles.hotelAlert} key={`wrapper_${alert.id}`}>
<Alert <Alert

View File

@@ -22,7 +22,7 @@ import styles from "./roomCard.module.css"
import type { RoomCardProps } from "@/types/components/hotelReservation/selectRate/roomCard" import type { RoomCardProps } from "@/types/components/hotelReservation/selectRate/roomCard"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { HotelTypeEnum } from "@/types/enums/hotelType" import { HotelTypeEnum } from "@/types/enums/hotelType"
import type { RateDefinition } from "@/server/routers/hotels/output" import type { RateDefinition } from "@/types/trpc/routers/hotel/roomAvailability"
export default function RoomCard({ export default function RoomCard({
hotelId, hotelId,

View File

@@ -1,6 +1,6 @@
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import { import {
getHotelData, getHotel,
getPackages, getPackages,
getRoomsAvailability, getRoomsAvailability,
} from "@/lib/trpc/memoizedRequests" } from "@/lib/trpc/memoizedRequests"
@@ -13,33 +13,28 @@ import { generateChildrenString } from "../../utils"
import Rooms from "." import Rooms from "."
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate" import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer"
import type { Lang } from "@/constants/languages"
export type Props = {
hotelId: number
fromDate: Date
toDate: Date
adultCount: number
childArray?: Child[]
lang: Lang
}
export async function RoomsContainer({ export async function RoomsContainer({
hotelId,
fromDate,
toDate,
adultCount, adultCount,
childArray, childArray,
fromDate,
hotelId,
lang, lang,
}: Props) { toDate,
}: RoomsContainerProps) {
const session = await auth() const session = await auth()
const isUserLoggedIn = isValidSession(session) const isUserLoggedIn = isValidSession(session)
const fromDateString = dt(fromDate).format("YYYY-MM-DD") const fromDateString = dt(fromDate).format("YYYY-MM-DD")
const toDateString = dt(toDate).format("YYYY-MM-DD") const toDateString = dt(toDate).format("YYYY-MM-DD")
const hotelDataPromise = safeTry( const hotelDataPromise = safeTry(
getHotelData({ hotelId: hotelId.toString(), language: lang }) getHotel({
hotelId: hotelId.toString(),
isCardOnlyPayment: false,
language: lang,
})
) )
const packagesPromise = safeTry( const packagesPromise = safeTry(
@@ -94,10 +89,10 @@ export async function RoomsContainer({
return ( return (
<Rooms <Rooms
availablePackages={packages ?? []} availablePackages={packages ?? []}
roomsAvailability={roomsAvailability} hotelType={hotelData?.hotel.hotelType}
roomCategories={hotelData?.included.rooms ?? []}
hotelType={hotelData?.data.attributes?.hotelType}
isUserLoggedIn={isUserLoggedIn} isUserLoggedIn={isUserLoggedIn}
roomsAvailability={roomsAvailability}
roomCategories={hotelData?.roomCategories ?? []}
/> />
) )
} }

View File

@@ -25,14 +25,14 @@ import {
} from "@/types/components/hotelReservation/selectRate/roomFilter" } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { SelectRateProps } from "@/types/components/hotelReservation/selectRate/roomSelection" import type { SelectRateProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { RoomConfiguration } from "@/server/routers/hotels/output" import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
export default function Rooms({ export default function Rooms({
roomsAvailability,
roomCategories = [],
availablePackages, availablePackages,
hotelType, hotelType,
isUserLoggedIn, isUserLoggedIn,
roomsAvailability,
roomCategories = [],
}: SelectRateProps) { }: SelectRateProps) {
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()

View File

@@ -1,5 +1,4 @@
import type { RoomParam } from "@/types/components/hotelReservation/selectRate/section" import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
import type { RoomConfiguration } from "@/server/routers/hotels/output"
/** /**
* Get the lowest priced room for each room type that appears more than once. * Get the lowest priced room for each room type that appears more than once.
@@ -64,16 +63,16 @@ export function filterDuplicateRoomTypesByLowestPrice(
if ( if (
!previousLowest || !previousLowest ||
currentRequestedPrice < currentRequestedPrice <
Math.min( Math.min(
Number( Number(
previousLowest.products[0].productType.public.requestedPrice previousLowest.products[0].productType.public.requestedPrice
?.pricePerNight ?.pricePerNight
) ?? Infinity, ) ?? Infinity,
Number( Number(
previousLowest.products[0].productType.member?.requestedPrice previousLowest.products[0].productType.member?.requestedPrice
?.pricePerNight ?.pricePerNight
) ?? Infinity ) ?? Infinity
) || ) ||
(currentRequestedPrice === (currentRequestedPrice ===
Math.min( Math.min(
Number( Number(
@@ -86,16 +85,16 @@ export function filterDuplicateRoomTypesByLowestPrice(
) ?? Infinity ) ?? Infinity
) && ) &&
currentLocalPrice < currentLocalPrice <
Math.min( Math.min(
Number( Number(
previousLowest.products[0].productType.public.localPrice previousLowest.products[0].productType.public.localPrice
?.pricePerNight ?.pricePerNight
) ?? Infinity, ) ?? Infinity,
Number( Number(
previousLowest.products[0].productType.member?.localPrice previousLowest.products[0].productType.member?.localPrice
?.pricePerNight ?.pricePerNight
) ?? Infinity ) ?? Infinity
)) ))
) { ) {
roomMap.set(roomType, room) roomMap.set(roomType, room)
} }

View File

@@ -7,13 +7,11 @@ import HotelSidePeek from "@/components/SidePeeks/HotelSidePeek"
import RoomSidePeek from "@/components/SidePeeks/RoomSidePeek" import RoomSidePeek from "@/components/SidePeeks/RoomSidePeek"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import type { HotelData } from "@/types/hotel" import type { HotelReservationSidePeekProps } from "@/types/components/hotelReservation/sidePeek"
export default function HotelReservationSidePeek({ export default function HotelReservationSidePeek({
hotel, hotel,
}: { }: HotelReservationSidePeekProps) {
hotel: HotelData | null
}) {
const activeSidePeek = useSidePeekStore((state) => state.activeSidePeek) const activeSidePeek = useSidePeekStore((state) => state.activeSidePeek)
const hotelId = useSidePeekStore((state) => state.hotelId) const hotelId = useSidePeekStore((state) => state.hotelId)
const roomTypeCode = useSidePeekStore((state) => state.roomTypeCode) const roomTypeCode = useSidePeekStore((state) => state.roomTypeCode)
@@ -21,7 +19,7 @@ export default function HotelReservationSidePeek({
const close = useSidePeekStore((state) => state.closeSidePeek) const close = useSidePeekStore((state) => state.closeSidePeek)
const lang = useLang() const lang = useLang()
const { data: hotelData } = trpc.hotel.hotelData.get.useQuery( const { data: hotelData } = trpc.hotel.get.useQuery(
{ {
hotelId: hotelId ?? "", hotelId: hotelId ?? "",
language: lang, language: lang,
@@ -32,7 +30,7 @@ export default function HotelReservationSidePeek({
} }
) )
const selectedRoom = hotelData?.included.rooms?.find((room) => const selectedRoom = hotelData?.roomCategories.find((room) =>
room.roomTypes.some((type) => type.code === roomTypeCode) room.roomTypes.some((type) => type.code === roomTypeCode)
) )
@@ -41,8 +39,8 @@ export default function HotelReservationSidePeek({
<> <>
{hotelData && ( {hotelData && (
<HotelSidePeek <HotelSidePeek
hotel={hotelData.data?.attributes} additionalHotelData={hotelData.additionalData}
additionalHotelData={hotelData.included.additionalData} hotel={hotelData.hotel}
activeSidePeek={activeSidePeek} activeSidePeek={activeSidePeek}
close={close} close={close}
showCTA={showCTA} showCTA={showCTA}

View File

@@ -2,7 +2,7 @@ import { cva } from "class-variance-authority"
import styles from "./poi.module.css" import styles from "./poi.module.css"
import { PointOfInterestGroupEnum } from "@/types/hotel" import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
export const poiVariants = cva(styles.icon, { export const poiVariants = cva(styles.icon, {
variants: { variants: {

View File

@@ -1,5 +1,5 @@
import { IconName } from "@/types/components/icon" import { IconName } from "@/types/components/icon"
import { PointOfInterestGroupEnum } from "@/types/hotel" import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
export function getIconByPoiGroupAndCategory( export function getIconByPoiGroupAndCategory(
group: PointOfInterestGroupEnum, group: PointOfInterestGroupEnum,

View File

@@ -6,12 +6,15 @@ import type {
BreackfastPackagesInput, BreackfastPackagesInput,
PackagesInput, PackagesInput,
} from "@/types/requests/packages" } from "@/types/requests/packages"
import type {
CityCoordinatesInput,
HotelInput,
} from "@/types/trpc/routers/hotel/hotel"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
import type { import type {
GetHotelsInput, GetHotelsInput,
GetRoomsAvailabilityInput, GetRoomsAvailabilityInput,
GetSelectedRoomAvailabilityInput, GetSelectedRoomAvailabilityInput,
HotelDataInput,
} from "@/server/routers/hotels/input" } from "@/server/routers/hotels/input"
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input" import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
@@ -35,9 +38,9 @@ export const getProfileSafely = cache(
export const getSavedPaymentCardsSafely = cache( export const getSavedPaymentCardsSafely = cache(
async function getMemoizedSavedPaymentCardsSafely( async function getMemoizedSavedPaymentCardsSafely(
args: GetSavedPaymentCardsInput input: GetSavedPaymentCardsInput
) { ) {
return serverClient().user.safePaymentCards(args) return serverClient().user.safePaymentCards(input)
} }
) )
@@ -69,10 +72,13 @@ export const getHotels = cache(async function getMemoizedHotels(
return serverClient().hotel.hotels.get(input) return serverClient().hotel.hotels.get(input)
}) })
export const getHotelData = cache(async function getMemoizedHotelData( export const getHotel = cache(async function getMemoizedHotelData(
input: HotelDataInput input: HotelInput
) { ) {
return serverClient().hotel.hotelData.get(input) if (!input.isCardOnlyPayment) {
input.isCardOnlyPayment = false
}
return serverClient().hotel.get(input)
}) })
export const getHotelPage = cache(async function getMemoizedHotelPage() { export const getHotelPage = cache(async function getMemoizedHotelPage() {
@@ -87,9 +93,9 @@ export const getRoomsAvailability = cache(
export const getSelectedRoomAvailability = cache( export const getSelectedRoomAvailability = cache(
function getMemoizedSelectedRoomAvailability( function getMemoizedSelectedRoomAvailability(
args: GetSelectedRoomAvailabilityInput input: GetSelectedRoomAvailabilityInput
) { ) {
return serverClient().hotel.availability.room(args) return serverClient().hotel.availability.room(input)
} }
) )
@@ -129,29 +135,26 @@ export const getSiteConfig = cache(async function getMemoizedSiteConfig() {
return serverClient().contentstack.base.siteConfig() return serverClient().contentstack.base.siteConfig()
}) })
export const getBreakfastPackages = cache(function getMemoizedBreakfastPackages( export const getBreakfastPackages = cache(
input: BreackfastPackagesInput async function getMemoizedBreakfastPackages(input: BreackfastPackagesInput) {
) { return serverClient().hotel.packages.breakfast(input)
return serverClient().hotel.packages.breakfast(input) }
}) )
export const getPackages = cache(function getMemoizedPackages( export const getPackages = cache(async function getMemoizedPackages(
input: PackagesInput input: PackagesInput
) { ) {
return serverClient().hotel.packages.get(input) return serverClient().hotel.packages.get(input)
}) })
export const getBookingConfirmation = cache( export const getBookingConfirmation = cache(
function getMemoizedBookingConfirmation(confirmationNumber: string) { async function getMemoizedBookingConfirmation(confirmationNumber: string) {
return serverClient().booking.confirmation({ confirmationNumber }) return serverClient().booking.confirmation({ confirmationNumber })
} }
) )
export const getCityCoordinates = cache( export const getCityCoordinates = cache(
async function getMemoizedCityCoordinates(input: { async function getMemoizedCityCoordinates(input: CityCoordinatesInput) {
city: string
hotel: { address: string | undefined }
}) {
return serverClient().hotel.map.city(input) return serverClient().hotel.map.city(input)
} }
) )

View File

@@ -5,7 +5,7 @@ import { dt } from "@/lib/dt"
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc" import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
import { router, serviceProcedure } from "@/server/trpc" import { router, serviceProcedure } from "@/server/trpc"
import { getHotelData } from "../hotels/query" import { getHotel } from "../hotels/query"
import { bookingConfirmationInput, getBookingStatusInput } from "./input" import { bookingConfirmationInput, getBookingStatusInput } from "./input"
import { bookingConfirmationSchema, createBookingSchema } from "./output" import { bookingConfirmationSchema, createBookingSchema } from "./output"
import { getBookedHotelRoom } from "./utils" import { getBookedHotelRoom } from "./utils"
@@ -84,8 +84,12 @@ export const bookingQueryRouter = router({
throw badRequestError() throw badRequestError()
} }
const hotelData = await getHotelData( const hotelData = await getHotel(
{ hotelId: booking.data.hotelId, language: ctx.lang }, {
hotelId: booking.data.hotelId,
isCardOnlyPayment: false,
language: ctx.lang,
},
ctx.serviceToken ctx.serviceToken
) )
@@ -123,14 +127,12 @@ export const bookingQueryRouter = router({
* Add hotels check in and out times to booking check in and out date * Add hotels check in and out times to booking check in and out date
* as that is date only (YYYY-MM-DD) * as that is date only (YYYY-MM-DD)
*/ */
const checkInTime = const checkInTime = hotelData.hotel.hotelFacts.checkin.checkInTime
hotelData.data.attributes.hotelFacts.checkin.checkInTime
const [checkInHour, checkInMinute] = checkInTime.split(":") const [checkInHour, checkInMinute] = checkInTime.split(":")
const checkIn = dt(booking.data.checkInDate) const checkIn = dt(booking.data.checkInDate)
.set("hour", Number(checkInHour)) .set("hour", Number(checkInHour))
.set("minute", Number(checkInMinute)) .set("minute", Number(checkInMinute))
const checkOutTime = const checkOutTime = hotelData.hotel.hotelFacts.checkin.checkOutTime
hotelData.data.attributes.hotelFacts.checkin.checkOutTime
const [checkOutHour, checkOutMinute] = checkOutTime.split(":") const [checkOutHour, checkOutMinute] = checkOutTime.split(":")
const checkOut = dt(booking.data.checkOutDate) const checkOut = dt(booking.data.checkOutDate)
.set("hour", Number(checkOutHour)) .set("hour", Number(checkOutHour))
@@ -140,13 +142,10 @@ export const bookingQueryRouter = router({
booking.data.checkOutDate = checkOut.toDate() booking.data.checkOutDate = checkOut.toDate()
return { return {
...hotelData,
booking: booking.data, booking: booking.data,
hotel: {
...hotelData.data.attributes,
included: hotelData.included,
},
room: getBookedHotelRoom( room: getBookedHotelRoom(
hotelData.included.rooms, hotelData.roomCategories,
booking.data.roomTypeCode booking.data.roomTypeCode
), ),
} }

View File

@@ -1,8 +1,8 @@
import type { RoomData } from "@/types/hotel" import type { Room } from "@/types/hotel"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
export function getBookedHotelRoom( export function getBookedHotelRoom(
rooms: RoomData[] | undefined, rooms: Room[] | undefined,
roomTypeCode: BookingConfirmation["booking"]["roomTypeCode"] roomTypeCode: BookingConfirmation["booking"]["roomTypeCode"]
) { ) {
if (!rooms?.length || !roomTypeCode) { if (!rooms?.length || !roomTypeCode) {

View File

@@ -1,6 +1,6 @@
import { z } from "zod" import { z } from "zod"
import { hotelAttributesSchema } from "../../hotels/output" import { attributesSchema as hotelAttributesSchema } from "../../hotels/schemas/hotel"
import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { getDescription, getImage, getTitle } from "./utils" import { getDescription, getImage, getTitle } from "./utils"

View File

@@ -15,7 +15,7 @@ import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag" import { generateTag } from "@/utils/generateTag"
import { getHotelData } from "../../hotels/query" import { getHotel } from "../../hotels/query"
import { metadataSchema } from "./output" import { metadataSchema } from "./output"
import { affix } from "./utils" import { affix } from "./utils"
@@ -171,15 +171,19 @@ export const metadataQueryRouter = router({
}>(GetHotelPageMetadata, variables) }>(GetHotelPageMetadata, variables)
const hotelPageData = hotelPageResponse.hotel_page const hotelPageData = hotelPageResponse.hotel_page
const hotelData = hotelPageData.hotel_page_id const hotelData = hotelPageData.hotel_page_id
? await getHotelData( ? await getHotel(
{ hotelId: hotelPageData.hotel_page_id, language: ctx.lang }, {
hotelId: hotelPageData.hotel_page_id,
isCardOnlyPayment: false,
language: ctx.lang,
},
ctx.serviceToken ctx.serviceToken
) )
: null : null
return getTransformedMetadata({ return getTransformedMetadata({
...hotelPageData, ...hotelPageData,
hotelData: hotelData?.data.attributes, hotelData: hotelData?.hotel,
}) })
default: default:
return null return null

View File

@@ -1,9 +1,11 @@
import { z } from "zod" import { z } from "zod"
import { Lang } from "@/constants/languages"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { Country } from "@/types/enums/country" import { Country } from "@/types/enums/country"
export const getHotelsAvailabilityInputSchema = z.object({ export const hotelsAvailabilityInputSchema = z.object({
cityId: z.string(), cityId: z.string(),
roomStayStartDate: z.string(), roomStayStartDate: z.string(),
roomStayEndDate: z.string(), roomStayEndDate: z.string(),
@@ -21,7 +23,7 @@ export const getHotelsByHotelIdsAvailabilityInputSchema = z.object({
bookingCode: z.string().optional().default(""), bookingCode: z.string().optional().default(""),
}) })
export const getRoomsAvailabilityInputSchema = z.object({ export const roomsAvailabilityInputSchema = z.object({
hotelId: z.number(), hotelId: z.number(),
roomStayStartDate: z.string(), roomStayStartDate: z.string(),
roomStayEndDate: z.string(), roomStayEndDate: z.string(),
@@ -31,7 +33,7 @@ export const getRoomsAvailabilityInputSchema = z.object({
rateCode: z.string().optional(), rateCode: z.string().optional(),
}) })
export const getSelectedRoomAvailabilityInputSchema = z.object({ export const selectedRoomAvailabilityInputSchema = z.object({
hotelId: z.string(), hotelId: z.string(),
roomStayStartDate: z.string(), roomStayStartDate: z.string(),
roomStayEndDate: z.string(), roomStayEndDate: z.string(),
@@ -44,25 +46,23 @@ export const getSelectedRoomAvailabilityInputSchema = z.object({
}) })
export type GetSelectedRoomAvailabilityInput = z.input< export type GetSelectedRoomAvailabilityInput = z.input<
typeof getSelectedRoomAvailabilityInputSchema typeof selectedRoomAvailabilityInputSchema
> >
export type GetRoomsAvailabilityInput = z.input< export type GetRoomsAvailabilityInput = z.input<
typeof getRoomsAvailabilityInputSchema typeof roomsAvailabilityInputSchema
> >
export const getRatesInputSchema = z.object({ export const ratesInputSchema = z.object({
hotelId: z.string(), hotelId: z.string(),
}) })
export const getHotelDataInputSchema = z.object({ export const hotelInputSchema = z.object({
hotelId: z.string(), hotelId: z.string(),
language: z.string(), isCardOnlyPayment: z.boolean().default(false),
isCardOnlyPayment: z.boolean().optional(), language: z.nativeEnum(Lang),
}) })
export type HotelDataInput = z.input<typeof getHotelDataInputSchema>
export const getHotelsInput = z.object({ export const getHotelsInput = z.object({
locationFilter: z locationFilter: z
.object({ .object({
@@ -73,13 +73,13 @@ export const getHotelsInput = z.object({
.nullable(), .nullable(),
hotelsToInclude: z.array(z.string()), hotelsToInclude: z.array(z.string()),
}) })
export interface GetHotelsInput extends z.infer<typeof getHotelsInput> {} export interface GetHotelsInput extends z.infer<typeof getHotelsInput> { }
export const nearbyHotelIdsInput = z.object({ export const nearbyHotelIdsInput = z.object({
hotelId: z.string(), hotelId: z.string(),
}) })
export const getBreakfastPackageInputSchema = z.object({ export const breakfastPackageInputSchema = z.object({
adults: z.number().min(1, { message: "at least one adult is required" }), adults: z.number().min(1, { message: "at least one adult is required" }),
fromDate: z fromDate: z
.string() .string()
@@ -92,7 +92,7 @@ export const getBreakfastPackageInputSchema = z.object({
.pipe(z.coerce.date()), .pipe(z.coerce.date()),
}) })
export const getRoomPackagesInputSchema = z.object({ export const roomPackagesInputSchema = z.object({
hotelId: z.string(), hotelId: z.string(),
startDate: z.string(), startDate: z.string(),
endDate: z.string(), endDate: z.string(),
@@ -100,7 +100,7 @@ export const getRoomPackagesInputSchema = z.object({
children: z.number().optional().default(0), children: z.number().optional().default(0),
packageCodes: z.array(z.string()).optional().default([]), packageCodes: z.array(z.string()).optional().default([]),
}) })
export const getCityCoordinatesInputSchema = z.object({ export const cityCoordinatesInputSchema = z.object({
city: z.string(), city: z.string(),
hotel: z.object({ hotel: z.object({
address: z.string().optional(), address: z.string().optional(),

View File

@@ -0,0 +1,65 @@
import { metrics as opentelemetryMetrics } from "@opentelemetry/api"
const meter = opentelemetryMetrics.getMeter("trpc.hotels")
export const metrics = {
additionalData: {
counter: meter.createCounter("trpc.hotels.additionalData"),
fail: meter.createCounter("trpc.hotels.additionalData-fail"),
success: meter.createCounter("trpc.hotels.additionalData-success"),
},
breakfastPackage: {
counter: meter.createCounter("trpc.package.breakfast"),
fail: meter.createCounter("trpc.package.breakfast-fail"),
success: meter.createCounter("trpc.package.breakfast-success"),
},
hotel: {
counter: meter.createCounter("trpc.hotel.get"),
fail: meter.createCounter("trpc.hotel.get-fail"),
success: meter.createCounter("trpc.hotel.get-success"),
},
hotels: {
counter: meter.createCounter("trpc.hotel.hotels.get"),
fail: meter.createCounter("trpc.hotel.hotels.get-fail"),
success: meter.createCounter("trpc.hotel.hotels.get-success"),
},
hotelIds: {
counter: meter.createCounter("trpc.hotel.hotel-ids.get"),
fail: meter.createCounter("trpc.hotel.hotel-ids.get-fail"),
success: meter.createCounter("trpc.hotel.hotel-ids.get-success"),
},
hotelsAvailability: {
counter: meter.createCounter("trpc.hotel.availability.hotels"),
fail: meter.createCounter("trpc.hotel.availability.hotels-fail"),
success: meter.createCounter("trpc.hotel.availability.hotels-success"),
},
hotelsByHotelIdAvailability: {
counter: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id"),
fail: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id-fail"),
success: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id-success"),
},
meetingRooms: {
counter: meter.createCounter("trpc.hotels.meetingRooms"),
fail: meter.createCounter("trpc.hotels.meetingRooms-fail"),
success: meter.createCounter("trpc.hotels.meetingRooms-success"),
},
nearbyHotelIds: {
counter: meter.createCounter("trpc.hotel.nearby-hotel-ids.get"),
fail: meter.createCounter("trpc.hotel.nearby-hotel-ids.get-fail"),
success: meter.createCounter("trpc.hotel.nearby-hotel-ids.get-success"),
},
packages: {
counter: meter.createCounter("trpc.hotel.packages.get"),
fail: meter.createCounter("trpc.hotel.packages.get-fail"),
success: meter.createCounter("trpc.hotel.packages.get-success"),
},
roomAvailability: {
counter: meter.createCounter("trpc.hotel.availability.rooms"),
fail: meter.createCounter("trpc.hotel.availability.rooms-fail"),
success: meter.createCounter("trpc.hotel.availability.rooms-success"),
},
selectedRoomAvailability: {
counter: meter.createCounter("trpc.hotel.availability.room"),
fail: meter.createCounter("trpc.hotel.availability.room-fail"),
success: meter.createCounter("trpc.hotel.availability.room-success"),
},
}

View File

@@ -1,668 +1,120 @@
import { z } from "zod" import { z } from "zod"
import { ChildBedTypeEnum, type PaymentMethodEnum } from "@/constants/booking"
import { toLang } from "@/server/utils" import { toLang } from "@/server/utils"
import { additionalDataSchema } from "./schemas/additionalData" import { occupancySchema } from "./schemas/availability/occupancy"
import { imageSchema } from "./schemas/image" import { productTypeSchema } from "./schemas/availability/productType"
import { restaurantSchema } from "./schemas/restaurants" import { citySchema } from "./schemas/city"
import { roomSchema } from "./schemas/room" import {
import { specialAlertsSchema } from "./schemas/specialAlerts" attributesSchema,
import { getPoiGroupByCategoryName } from "./utils" includesSchema,
relationshipsSchema as hotelRelationshipsSchema,
} from "./schemas/hotel"
import { locationCitySchema } from "./schemas/location/city"
import { locationHotelSchema } from "./schemas/location/hotel"
import { breakfastPackageSchema, packageSchema } from "./schemas/packages"
import { rateSchema } from "./schemas/rate"
import { relationshipsSchema } from "./schemas/relationships"
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { AdditionalData, City, NearbyHotel, Restaurant, Room } from "@/types/hotel"
import { FacilityEnum } from "@/types/enums/facilities"
import { PackageTypeEnum } from "@/types/enums/packages"
import type { AdditionalData, RestaurantData, RoomData } from "@/types/hotel"
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(),
})
export 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: imageSchema,
texts: z.object({
facilityInformation: z.string().optional(),
surroundingInformation: z.string(),
descriptions: z.object({
short: z.string(),
medium: z.string(),
}),
meetingDescription: z
.object({
short: z.string().optional(),
medium: z.string().optional(),
})
.optional(),
}),
restaurantsOverviewPage: z.object({
restaurantsOverviewPageLinkText: z.string().optional(),
restaurantsOverviewPageLink: z.string().optional(),
restaurantsContentDescriptionShort: z.string().optional(),
restaurantsContentDescriptionMedium: z.string().optional(),
}), // TODO remove, use new /additionalData endpoint
})
const detailedFacilitySchema = z.object({
id: z.number(),
name: z.string(),
public: z.boolean(),
sortOrder: z.number(),
filter: z.string().optional(),
icon: z.string().optional(),
})
export const facilitySchema = z.object({
headingText: z.string().default(""),
heroImages: z.array(imageSchema),
}) // TODO remove, use new /additionalData endpoint
export const gallerySchema = z.object({
heroImages: z.array(imageSchema),
smallerImages: z.array(imageSchema),
}) // TODO remove, use new /additionalData endpoint
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().optional(),
distance: z.number().optional(),
category: z.object({
name: z.string().optional(),
group: z.string().optional(),
}),
location: locationSchema.optional(),
isHighlighted: z.boolean().optional(),
})
.transform((poi) => ({
name: poi.name,
distance: poi.distance,
categoryName: poi.category.name,
group: getPoiGroupByCategoryName(poi.category.name),
coordinates: {
lat: poi.location?.latitude ?? 0,
lng: poi.location?.longitude ?? 0,
},
}))
const parkingPricingSchema = z.object({
freeParking: z.boolean(),
paymentType: z.string().optional(),
localCurrency: z.object({
currency: z.string().default("N/A"),
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().default("N/A"),
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(),
externalParkingUrl: z.string().optional(),
pricing: parkingPricingSchema,
})
const socialMediaSchema = z.object({
instagram: z.string().optional(),
facebook: z.string().optional(),
})
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)
.filter((key): key is PaymentMethodEnum => !!key)
}),
alternatePaymentOptions: z
.record(z.string(), z.boolean())
.transform((val) => {
return Object.entries(val)
.filter(([_, enabled]) => enabled)
.map(([key]) => key)
.filter((key): key is PaymentMethodEnum => !!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(),
})
type DetailedFacility = { id: FacilityEnum } & z.infer<
typeof detailedFacilitySchema
>
export const hotelAttributesSchema = z.object({
address: addressSchema,
cityId: z.string(),
cityName: z.string(),
conferencesAndMeetings: facilitySchema.optional(), // TODO remove, use new /additionalData endpoint
contactInformation: contactInformationSchema,
detailedFacilities: z.array(detailedFacilitySchema).transform(
(facilities) =>
facilities
// Filter away facilities with ID:s that we don't recognize
.filter(
(f) => f.id !== undefined && f.id !== null && f.id in FacilityEnum
)
.sort((a, b) => b.sortOrder - a.sortOrder) as DetailedFacility[]
),
gallery: gallerySchema.optional(), // TODO remove, use new /additionalData endpoint
galleryImages: z.array(imageSchema).optional(),
healthFacilities: z.array(healthFacilitySchema),
hotelContent: hotelContentSchema,
hotelFacts: hotelFactsSchema,
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 ?? 0) - (b.distance ?? 0))
),
ratings: ratingsSchema,
rewardNight: rewardNightSchema,
restaurantImages: facilitySchema.optional(), // TODO remove, use new /additionalData endpoint
socialMedia: socialMediaSchema,
specialAlerts: specialAlertsSchema,
vat: z.number(),
})
const includedSchema = z
.array(z.union([roomSchema, restaurantSchema, additionalDataSchema]))
.transform((data) => {
const rooms = data.filter((d) => d.type === "roomcategories") as RoomData[]
const restaurants = data.filter(
(d) => d.type === "restaurants"
) as RestaurantData[]
const additionalData = data.filter(
(d) => d.type === "additionalData"
) as AdditionalData[]
return {
rooms,
restaurants,
additionalData,
}
})
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html // NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
export const getHotelDataSchema = z.object({ export const hotelSchema = z
data: z.object({ .object({
id: z.string(), data: z.object({
type: z.literal("hotels"), // No enum here but the standard return appears to be "hotels". attributes: attributesSchema,
language: z.string().transform((val) => { id: z.string(),
const lang = toLang(val) language: z.string().transform((val) => {
if (!lang) { const lang = toLang(val)
throw new Error("Invalid language") if (!lang) {
} throw new Error("Invalid language")
return lang }
return lang
}),
relationships: hotelRelationshipsSchema,
type: z.literal("hotels"), // No enum here but the standard return appears to be "hotels".
}), }),
attributes: hotelAttributesSchema, // NOTE: We can pass an "include" param to the hotel API to retrieve
relationships: relationshipsSchema, // additional data for an individual hotel.
}), included: includesSchema,
// NOTE: We can pass an "include" param to the hotel API to retrieve })
// additional data for an individual hotel. .transform(({ data: { attributes, ...data }, included }) => {
included: includedSchema.optional().transform((incl) => { const additionalData = included.find((inc): inc is AdditionalData => inc!.type === "additionalData")
const cities = included.filter((inc): inc is City => inc!.type === "cities")
const nearbyHotels = included.filter(
(inc): inc is NearbyHotel => inc!.type === "hotels"
)
const restaurants = included.filter(
(inc): inc is Restaurant => inc!.type === "restaurants"
)
const roomCategories = included.filter(
(inc): inc is Room => inc!.type === "roomcategories"
)
return { return {
restaurants: incl?.restaurants, additionalData,
rooms: incl?.rooms, cities,
additionalData: incl?.additionalData[0], hotel: {
} ...data,
}), ...attributes,
})
export const childrenSchema = z.object({
age: z.number(),
bedType: z.nativeEnum(ChildBedTypeEnum),
})
const occupancySchema = z.object({
adults: z.number(),
children: z.array(childrenSchema).default([]),
})
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().default("N/A"),
})
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: "N/A",
pricePerNight: 0,
pricePerStay: 0,
}, },
requestedPrice: undefined, nearbyHotels,
}), restaurants,
member: productTypePriceSchema.optional(), roomCategories,
}), }
}) })
const hotelsAvailabilitySchema = z.object({ export const hotelsAvailabilitySchema = z.object({
data: z.array( data: z.array(
z.object({ z.object({
attributes: z.object({ attributes: z.object({
checkInDate: z.string(), checkInDate: z.string(),
checkOutDate: z.string(), checkOutDate: z.string(),
occupancy: occupancySchema,
status: z.string(),
hotelId: z.number(), hotelId: z.number(),
productType: z occupancy: occupancySchema,
.object({ productType: productTypeSchema,
public: productTypePriceSchema.optional(), status: z.string(),
member: productTypePriceSchema.optional(),
})
.optional(),
}), }),
relationships: linksSchema.optional(), relationships: relationshipsSchema.optional(),
type: z.string().optional(), type: z.string().optional(),
}) })
), ),
}) })
export const getHotelsAvailabilitySchema = hotelsAvailabilitySchema export const roomsAvailabilitySchema = z
export type HotelsAvailability = z.infer<typeof hotelsAvailabilitySchema>
export type ProductType =
HotelsAvailability["data"][number]["attributes"]["productType"]
export type ProductTypePrices = z.infer<typeof productTypePriceSchema>
export type HotelsAvailabilityItem =
HotelsAvailability["data"][number]["attributes"]
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,
]),
})
)
.default([]),
products: z.array(productSchema).default([]),
})
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({ .object({
data: z.object({ data: z.object({
attributes: z.object({ attributes: z.object({
checkInDate: z.string(), checkInDate: z.string(),
checkOutDate: z.string(), checkOutDate: z.string(),
occupancy: occupancySchema.optional(),
hotelId: z.number(), hotelId: z.number(),
bookingCode: z.string().optional(),
roomConfigurations: z.array(roomConfigurationSchema),
rateDefinitions: z.array(rateDefinitionSchema),
mustBeGuaranteed: z.boolean().optional(), mustBeGuaranteed: z.boolean().optional(),
occupancy: occupancySchema.optional(),
rateDefinitions: z.array(rateDefinitionSchema),
roomConfigurations: z.array(roomConfigurationSchema),
}), }),
relationships: linksSchema.optional(), relationships: relationshipsSchema.optional(),
type: z.string().optional(), type: z.string().optional(),
}), }),
}) })
.transform((o) => o.data.attributes) .transform((o) => o.data.attributes)
export const getRoomsAvailabilitySchema = roomsAvailabilitySchema export const ratesSchema = z.array(rateSchema)
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({ export const citiesByCountrySchema = 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( data: z.array(
z citySchema.transform((data) => {
.object({ return {
attributes: z.object({ ...data.attributes,
cityIdentifier: z.string().optional(), id: data.id,
name: z.string(), type: data.type,
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 export const countriesSchema = z.object({
extends z.output<typeof apiCitiesByCountrySchema> {}
export type CitiesGroupedByCountry = Record<string, CitiesByCountry["data"]>
export const apiCountriesSchema = z.object({
data: z data: z
.array( .array(
z.object({ z.object({
@@ -689,35 +141,9 @@ export const apiCountriesSchema = z.object({
}), }),
}) })
export interface Countries extends z.output<typeof apiCountriesSchema> {} export const citiesSchema = z
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({ .object({
data: z.array( data: z.array(citySchema),
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 }) => { .transform(({ data }) => {
if (data.length) { if (data.length) {
@@ -731,46 +157,11 @@ export const apiCitySchema = z
return null return null
}) })
export const apiLocationHotelSchema = z.object({ export const locationsSchema = 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 data: z
.array( .array(
z z
.discriminatedUnion("type", [ .discriminatedUnion("type", [locationCitySchema, locationHotelSchema])
apiLocationCitySchema,
apiLocationHotelSchema,
])
.transform((location) => { .transform((location) => {
if (location.type === "cities") { if (location.type === "cities") {
return { return {
@@ -813,20 +204,6 @@ export const apiLocationsSchema = z.object({
), ),
}) })
export const packagePriceSchema = z.object({
currency: z.string().default("N/A"),
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 export const breakfastPackagesSchema = z
.object({ .object({
data: z.object({ data: z.object({
@@ -838,38 +215,23 @@ export const breakfastPackagesSchema = z
}), }),
}) })
.transform(({ data }) => .transform(({ data }) =>
data.attributes.packages.filter((pkg) => pkg.code.match(/^(BRF\d+)$/gm)) data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm))
) )
export const packagesSchema = z.object({ export const packagesSchema = z
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({ .object({
data: z data: z
.object({ .object({
attributes: z.object({ attributes: z.object({
hotelId: z.number(), hotelId: z.number(),
packages: z.array(packagesSchema).optional().default([]), packages: z.array(packageSchema).default([]),
}), }),
relationships: z relationships: z
.object({ .object({
links: z.array( links: z.array(
z.object({ z.object({
url: z.string(),
type: z.string(), type: z.string(),
url: z.string(),
}) })
), ),
}) })
@@ -878,7 +240,7 @@ export const getRoomPackagesSchema = z
}) })
.optional(), .optional(),
}) })
.transform((data) => data.data?.attributes?.packages ?? []) .transform(({ data }) => data?.attributes.packages)
export const getHotelIdsByCityIdSchema = z export const getHotelIdsByCityIdSchema = z
.object({ .object({
@@ -900,50 +262,4 @@ export const getNearbyHotelIdsSchema = z
}) })
), ),
}) })
.transform((data) => data.data.map((hotel) => hotel.id)) .transform((data) => data.data.map((hotel) => hotel.id))
export const getMeetingRoomsSchema = z.object({
data: z.array(
z.object({
attributes: z.object({
name: z.string(),
email: z.string().optional(),
phoneNumber: z.string(),
size: z.number(),
doorWidth: z.number(),
doorHeight: z.number(),
length: z.number(),
width: z.number(),
height: z.number(),
floorNumber: z.number(),
content: z.object({
images: z.array(imageSchema),
texts: z.object({
facilityInformation: z.string().optional(),
surroundingInformation: z.string().optional(),
descriptions: z.object({
short: z.string().optional(),
medium: z.string().optional(),
}),
meetingDescription: z
.object({
short: z.string().optional(),
medium: z.string().optional(),
})
.optional(),
}),
}),
seatings: z.array(
z.object({
type: z.string(),
capacity: z.number(),
})
),
lighting: z.string(),
sortOrder: z.number().optional(),
}),
id: z.string(),
type: z.string(),
})
),
})

File diff suppressed because it is too large Load Diff

View File

@@ -60,8 +60,13 @@ export const additionalDataSchema = z
}), }),
type: z.literal("additionalData"), type: z.literal("additionalData"),
}) })
.transform(({ attributes, type }) => ({
...attributes, export function transformAdditionalData(
type, data: z.output<typeof additionalDataSchema>
id: attributes.id, ) {
})) return {
...data.attributes,
id: data.attributes.id,
type: data.type
}
}

View File

@@ -0,0 +1,13 @@
import { z } from "zod"
import { ChildBedTypeEnum } from "@/constants/booking"
export const childrenSchema = z.object({
age: z.number(),
bedType: z.nativeEnum(ChildBedTypeEnum),
})
export const occupancySchema = z.object({
adults: z.number(),
children: z.array(childrenSchema),
})

View File

@@ -0,0 +1,10 @@
import { z } from "zod"
import { productTypePriceSchema } from "../productTypePrice"
export const productTypeSchema = z
.object({
public: productTypePriceSchema.optional(),
member: productTypePriceSchema.optional(),
})
.optional()

View File

@@ -0,0 +1,14 @@
import { z } from "zod"
export const citySchema = z.object({
attributes: z.object({
cityIdentifier: z.string().default(""),
ianaTimeZoneId: z.string().default(""),
isPublished: z.boolean().default(false),
keywords: z.array(z.string()).default([]),
name: z.string(),
timeZoneId: z.string().default(""),
}),
id: z.string(),
type: z.literal("cities"),
})

View File

@@ -0,0 +1,73 @@
import { z } from "zod"
import { addressSchema } from "./hotel/address"
import { contactInformationSchema } from "./hotel/contactInformation"
import { hotelContentSchema } from "./hotel/content"
import { detailedFacilitiesSchema } from "./hotel/detailedFacility"
import { hotelFactsSchema } from "./hotel/facts"
import { gallerySchema } from "./hotel/gallery"
import { healthFacilitySchema } from "./hotel/healthFacilities"
import { includeSchema } from "./hotel/include/include"
import { locationSchema } from "./hotel/location"
import { merchantInformationSchema } from "./hotel/merchantInformation"
import { parkingSchema } from "./hotel/parking"
import { pointOfInterestsSchema } from "./hotel/poi"
import { ratingsSchema } from "./hotel/rating"
import { rewardNightSchema } from "./hotel/rewardNight"
import { socialMediaSchema } from "./hotel/socialMedia"
import { specialAlertsSchema } from "./hotel/specialAlerts"
import { specialNeedGroupSchema } from "./hotel/specialNeedGroups"
import { imageSchema } from "./image"
import { facilitySchema } from "./additionalData"
export const attributesSchema = z.object({
accessibilityElevatorPitchText: z.string().optional(),
address: addressSchema,
cityId: z.string(),
cityName: z.string(),
conferencesAndMeetings: facilitySchema.optional(),
contactInformation: contactInformationSchema,
detailedFacilities: detailedFacilitiesSchema,
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: pointOfInterestsSchema,
ratings: ratingsSchema,
rewardNight: rewardNightSchema,
restaurantImages: facilitySchema.optional(),
socialMedia: socialMediaSchema,
specialAlerts: specialAlertsSchema,
specialNeedGroups: z.array(specialNeedGroupSchema),
vat: z.number(),
})
export const includesSchema = z
.array(includeSchema)
.default([])
.transform((data) => data.filter((item) => !!item))
const relationshipSchema = z.object({
links: z.object({
related: z.string(),
}),
})
export const relationshipsSchema = z.object({
meetingRooms: relationshipSchema,
nearbyHotels: relationshipSchema,
restaurants: relationshipSchema,
roomCategories: relationshipSchema,
})

View File

@@ -0,0 +1,8 @@
import { z } from "zod"
export const addressSchema = z.object({
city: z.string(),
country: z.string(),
streetAddress: z.string(),
zipCode: z.string(),
})

View File

@@ -0,0 +1,8 @@
import { z } from "zod"
export const contactInformationSchema = z.object({
email: z.string(),
faxNumber: z.string().optional(),
phoneNumber: z.string(),
websiteUrl: z.string(),
})

View File

@@ -0,0 +1,40 @@
import { z } from "zod"
import { imageSchema } from "../image"
export const hotelContentSchema = z.object({
images: imageSchema.default({
metaData: {
altText: "default image",
altText_En: "default image",
copyRight: "default image",
title: "default image",
},
imageSizes: {
large: "https://placehold.co/1280x720",
medium: "https://placehold.co/1280x720",
small: "https://placehold.co/1280x720",
tiny: "https://placehold.co/1280x720",
},
}),
restaurantsOverviewPage: z.object({
restaurantsContentDescriptionMedium: z.string().optional(),
restaurantsContentDescriptionShort: z.string().optional(),
restaurantsOverviewPageLink: z.string().optional(),
restaurantsOverviewPageLinkText: z.string().optional(),
}),
texts: z.object({
descriptions: z.object({
medium: z.string(),
short: z.string(),
}),
facilityInformation: z.string().optional(),
meetingDescription: z
.object({
medium: z.string().optional(),
short: z.string().optional(),
})
.optional(),
surroundingInformation: z.string(),
}),
})

View File

@@ -0,0 +1,18 @@
import { z } from "zod"
import { FacilityEnum } from "@/types/enums/facilities"
const detailedFacilitySchema = z.object({
filter: z.string().optional(),
icon: z.string().optional(),
id: z.nativeEnum(FacilityEnum),
name: z.string(),
public: z.boolean(),
sortOrder: z.number(),
})
export const detailedFacilitiesSchema = z
.array(detailedFacilitySchema)
.transform((facilities) =>
facilities.sort((a, b) => b.sortOrder - a.sortOrder)
)

View File

@@ -0,0 +1,80 @@
import { z } from "zod"
const ecoLabelsSchema = z.object({
euEcoLabel: z.boolean(),
greenGlobeLabel: z.boolean(),
nordicEcoLabel: z.boolean(),
svanenEcoLabelCertificateNumber: z.string().optional(),
})
export const checkinSchema = z.object({
checkInTime: z.string(),
checkOutTime: z.string(),
onlineCheckout: z.boolean(),
onlineCheckOutAvailableFrom: z.string().nullable().optional(),
})
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 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(),
})
export const hotelFactsSchema = z.object({
checkin: checkinSchema,
ecoLabels: ecoLabelsSchema,
hotelFacilityDetail: hotelFacilityDetailsSchema.default({}),
hotelInformation: hotelInformationsSchema.default({}),
interior: interiorSchema,
receptionHours: receptionHoursSchema,
yearBuilt: z.string(),
})

View File

@@ -0,0 +1,8 @@
import { z } from "zod"
import { imageSchema } from "../image"
export const gallerySchema = z.object({
heroImages: z.array(imageSchema),
smallerImages: z.array(imageSchema),
})

View File

@@ -0,0 +1,41 @@
import { z } from "zod"
import { imageSchema } from "../image"
const healthFacilitiesOpenHoursSchema = z.object({
alwaysOpen: z.boolean(),
closingTime: z.string().optional(),
isClosed: z.boolean(),
openingTime: z.string().optional(),
sortOrder: z.number().optional(),
})
export const healthFacilitySchema = z.object({
content: z.object({
images: z.array(imageSchema),
texts: z.object({
descriptions: z.object({
short: z.string(),
medium: z.string(),
}),
facilityInformation: z.string().optional(),
surroundingInformation: z.string().optional(),
}),
}),
details: z.array(
z.object({
name: z.string(),
type: z.string(),
value: z.string().optional(),
})
),
openingDetails: z.object({
manualOpeningHours: z.string().optional(),
openingHours: z.object({
ordinary: healthFacilitiesOpenHoursSchema,
weekends: healthFacilitiesOpenHoursSchema,
}),
useManualOpeningHours: z.boolean(),
}),
type: z.string(),
})

View File

@@ -0,0 +1,37 @@
import { z } from "zod"
import { citySchema } from "@/server/routers/hotels/schemas/city"
import { nearbyHotelsSchema } from "@/server/routers/hotels/schemas/hotel/include/nearbyHotels"
import { restaurantsSchema } from "@/server/routers/hotels/schemas/hotel/include/restaurants"
import {
roomCategoriesSchema,
transformRoomCategories,
} from "@/server/routers/hotels/schemas/hotel/include/roomCategories"
import { additionalDataSchema, transformAdditionalData } from "../../additionalData"
export const includeSchema = z
.discriminatedUnion("type", [
additionalDataSchema,
citySchema,
nearbyHotelsSchema,
restaurantsSchema,
roomCategoriesSchema,
])
.transform((data) => {
switch (data.type) {
case "additionalData":
return transformAdditionalData(data)
case "cities":
case "hotels":
case "restaurants":
return {
...data.attributes,
id: data.id,
type: data.type,
}
case "roomcategories":
return transformRoomCategories(data)
default:
return null
}
})

View File

@@ -0,0 +1,41 @@
import { z } from "zod"
import { attributesSchema } from "@/server/routers/hotels/schemas/hotel"
export const nearbyHotelsSchema = z.object({
attributes: z.lazy(() =>
z
.object({
displayWebPage: z
.object({
healthGym: z.boolean().default(false),
meetingRoom: z.boolean().default(false),
parking: z.boolean().default(false),
specialNeeds: z.boolean().default(false),
})
.default({
healthGym: false,
meetingRoom: false,
parking: false,
specialNeeds: false,
}),
})
.merge(
attributesSchema.pick({
address: true,
cityId: true,
cityName: true,
detailedFacilities: true,
hotelContent: true,
isActive: true,
isPublished: true,
location: true,
name: true,
operaId: true,
ratings: true,
})
)
),
id: z.string(),
type: z.literal("hotels"),
})

View File

@@ -0,0 +1,72 @@
import { z } from "zod"
import { imageSchema } from "@/server/routers/hotels/schemas/image"
import { CurrencyEnum } from "@/types/enums/currency"
const contentSchema = z.object({
images: z.array(imageSchema).default([]),
texts: z.object({
descriptions: z.object({
medium: z.string().default(""),
short: z.string().default(""),
}),
}),
})
const externalBreakfastSchema = z.object({
isAvailable: z.boolean().default(false),
localPriceForExternalGuests: z.object({
amount: z.number().default(0),
currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.SEK),
}),
})
const menuItemSchema = z.object({
name: z.string(),
url: z.string().url(),
})
const daySchema = z.object({
alwaysOpen: z.boolean().default(false),
closingTime: z.string().default(""),
isClosed: z.boolean().default(false),
openingTime: z.string().default(""),
sortOrder: z.number().int().default(0),
})
const openingDetailsSchema = z.object({
alternateOpeningHours: z.object({
isActive: z.boolean().default(false),
}),
openingHours: z.object({
friday: daySchema,
isActive: z.boolean().default(false),
monday: daySchema,
name: z.string().default(""),
saturday: daySchema,
sunday: daySchema,
thursday: daySchema,
tuesday: daySchema,
wednesday: daySchema,
}),
})
export const restaurantsSchema = z.object({
attributes: z.object({
bookTableUrl: z.string().default(""),
content: contentSchema,
// When using .email().default("") is not sufficent
// so .optional also needs to be chained
email: z.string().email().optional(),
externalBreakfast: externalBreakfastSchema,
isPublished: z.boolean().default(false),
menus: z.array(menuItemSchema).default([]),
name: z.string().default(""),
openingDetails: z.array(openingDetailsSchema).default([]),
restaurantPage: z.boolean().default(false),
specialAlerts: z.array(z.object({})).default([]),
}),
id: z.string(),
type: z.literal("restaurants"),
})

View File

@@ -0,0 +1,97 @@
import { z } from "zod"
import { imageMetaDataSchema, imageSizesSchema } from "../../image"
const minMaxSchema = z.object({
max: z.number(),
min: z.number(),
})
const bedTypeSchema = z.object({
description: z.string().default(""),
type: z.string(),
widthRange: minMaxSchema,
})
const occupancySchema = z.object({
adults: z.number(),
children: z.number(),
total: z.number(),
})
const roomContentSchema = z.object({
images: z.array(
z.object({
imageSizes: imageSizesSchema,
metaData: imageMetaDataSchema,
})
),
texts: z.object({
descriptions: z.object({
medium: z.string().optional(),
short: z.string().optional(),
}),
}),
})
const roomTypesSchema = z.object({
code: z.string(),
description: z.string(),
fixedExtraBed: bedTypeSchema,
isLackingCribs: z.boolean(),
isLackingExtraBeds: z.boolean(),
mainBed: bedTypeSchema,
name: z.string(),
occupancy: occupancySchema,
roomCount: z.number(),
roomSize: minMaxSchema,
})
const roomFacilitiesSchema = z.object({
availableInAllRooms: z.boolean(),
icon: z.string().optional(),
isUniqueSellingPoint: z.boolean(),
name: z.string(),
sortOrder: z.number(),
})
export const roomCategoriesSchema = z.object({
attributes: z.object({
content: roomContentSchema,
name: z.string(),
occupancy: minMaxSchema,
roomFacilities: z.array(roomFacilitiesSchema),
roomSize: minMaxSchema,
roomTypes: z.array(roomTypesSchema),
sortOrder: z.number(),
}),
id: z.string(),
type: z.literal("roomcategories"),
})
export function transformRoomCategories(
data: z.output<typeof roomCategoriesSchema>
) {
return {
descriptions: data.attributes.content.texts.descriptions,
id: data.id,
images: data.attributes.content.images,
name: data.attributes.name,
occupancy: data.attributes.occupancy,
roomFacilities: data.attributes.roomFacilities,
roomSize: data.attributes.roomSize,
roomTypes: data.attributes.roomTypes,
sortOrder: data.attributes.sortOrder,
type: data.type,
totalOccupancy:
data.attributes.occupancy.min === data.attributes.occupancy.max
? {
max: data.attributes.occupancy.max,
range: `${data.attributes.occupancy.max}`,
}
: {
max: data.attributes.occupancy.max,
range: `${data.attributes.occupancy.min}-${data.attributes.occupancy.max}`,
},
}
}

View File

@@ -0,0 +1,7 @@
import { z } from "zod"
export const locationSchema = z.object({
distanceToCentre: z.number(),
latitude: z.number(),
longitude: z.number(),
})

View File

@@ -0,0 +1,21 @@
import { z } from "zod"
import type { PaymentMethodEnum } from "@/constants/booking"
export const merchantInformationSchema = z.object({
alternatePaymentOptions: z
.record(z.string(), z.boolean())
.transform((val) => {
return Object.entries(val)
.filter(([_, enabled]) => enabled)
.map(([key]) => key)
.filter((key): key is PaymentMethodEnum => !!key)
}),
cards: z.record(z.string(), z.boolean()).transform((val) => {
return Object.entries(val)
.filter(([_, enabled]) => enabled)
.map(([key]) => key)
.filter((key): key is PaymentMethodEnum => !!key)
}),
webMerchantId: z.string(),
})

View File

@@ -0,0 +1,41 @@
import { z } from "zod"
const periodSchema = z.object({
amount: z.number().optional(),
endTime: z.string().optional(),
period: z.string().optional(),
startTime: z.string().optional(),
})
const currencySchema = z
.object({
currency: z.string().optional(),
ordinary: z.array(periodSchema).optional(),
range: z
.object({
min: z.number().optional(),
max: z.number().optional(),
})
.optional(),
weekend: z.array(periodSchema).optional(),
})
.optional()
const pricingSchema = z.object({
freeParking: z.boolean(),
localCurrency: currencySchema,
paymentType: z.string().optional(),
requestedCurrency: currencySchema,
})
export const parkingSchema = z.object({
address: z.string().optional(),
canMakeReservation: z.boolean(),
distanceToHotel: z.number().optional(),
externalParkingUrl: z.string().optional(),
name: z.string().optional(),
numberOfChargingSpaces: z.number().optional(),
numberOfParkingSpots: z.number().optional(),
pricing: pricingSchema,
type: z.string().optional(),
})

View File

@@ -0,0 +1,32 @@
import { z } from "zod"
import { getPoiGroupByCategoryName } from "../../utils"
import { locationSchema } from "./location"
export const pointOfInterestSchema = z
.object({
category: z.object({
name: z.string().optional(),
group: z.string().optional(),
}),
distance: z.number().optional(),
isHighlighted: z.boolean().optional(),
location: locationSchema.optional(),
name: z.string().optional(),
})
.transform((poi) => ({
categoryName: poi.category.name,
coordinates: {
lat: poi.location?.latitude ?? 0,
lng: poi.location?.longitude ?? 0,
},
distance: poi.distance,
group: getPoiGroupByCategoryName(poi.category.name),
name: poi.name,
}))
export const pointOfInterestsSchema = z
.array(pointOfInterestSchema)
.transform((pois) =>
pois.sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0))
)

View File

@@ -0,0 +1,31 @@
import { z } from "zod"
const awardSchema = z.object({
displayName: z.string(),
images: z.object({
large: z.string(),
medium: z.string(),
small: z.string(),
}),
})
const reviewsSchema = z
.object({
widgetHtmlTagId: z.string(),
widgetScriptEmbedUrlIframe: z.string(),
widgetScriptEmbedUrlJavaScript: z.string(),
})
.optional()
export const ratingsSchema = z
.object({
tripAdvisor: z.object({
awards: z.array(awardSchema),
numberOfReviews: z.number(),
rating: z.number(),
ratingImageUrl: z.string(),
reviews: reviewsSchema,
webUrl: z.string(),
}),
})
.optional()

View File

@@ -0,0 +1,10 @@
import { z } from "zod"
export const rewardNightSchema = z.object({
campaign: z.object({
end: z.string(),
points: z.number(),
start: z.string(),
}),
points: z.number(),
})

View File

@@ -0,0 +1,6 @@
import { z } from "zod"
export const socialMediaSchema = z.object({
facebook: z.string().optional(),
instagram: z.string().optional(),
})

View File

@@ -0,0 +1,35 @@
import { z } from "zod"
import { dt } from "@/lib/dt"
import { AlertTypeEnum } from "@/types/enums/alert"
const specialAlertSchema = z.object({
description: z.string().optional(),
displayInBookingFlow: z.boolean(),
endDate: z.string().optional(),
startDate: z.string().optional(),
title: z.string().optional(),
type: z.string(),
})
export 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) => ({
heading: alert.title || null,
id: `alert-${alert.type}-${idx}`,
text: alert.description || null,
type: AlertTypeEnum.Info,
}))
})
.default([])

View File

@@ -0,0 +1,11 @@
import { z } from "zod"
const specialNeedSchema = z.object({
details: z.string(),
name: z.string(),
})
export const specialNeedGroupSchema = z.object({
name: z.string(),
specialNeeds: z.array(specialNeedSchema),
})

View File

@@ -0,0 +1,7 @@
import { z } from "zod"
export const hotelFilterSchema = z.object({
hotelFacilities: z.array(z.string()),
hotelSurroundings: z.array(z.string()),
roomFacilities: z.array(z.string()),
})

View File

@@ -1,17 +1,17 @@
import { z } from "zod" import { z } from "zod"
const imageSizesSchema = z.object({ export const imageSizesSchema = z.object({
tiny: z.string(),
small: z.string(),
medium: z.string(),
large: z.string(), large: z.string(),
medium: z.string(),
small: z.string(),
tiny: z.string(),
}) })
const imageMetaDataSchema = z.object({ export const imageMetaDataSchema = z.object({
title: z.string(),
altText: z.string(), altText: z.string(),
altText_En: z.string(), altText_En: z.string(),
copyRight: z.string(), copyRight: z.string(),
title: z.string(),
}) })
const DEFAULT_IMAGE_OBJ = { const DEFAULT_IMAGE_OBJ = {

View File

@@ -0,0 +1,12 @@
import { z } from "zod"
export const locationCitySchema = 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"),
})

View File

@@ -0,0 +1,33 @@
import { z } from "zod"
export const locationHotelSchema = 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"),
})

View File

@@ -0,0 +1,50 @@
import { z } from "zod"
import { imageSchema } from "./image"
export const meetingRoomsSchema = z
.object({
data: z.array(
z.object({
attributes: z.object({
name: z.string(),
email: z.string().optional(),
phoneNumber: z.string(),
size: z.number(),
doorWidth: z.number(),
doorHeight: z.number(),
length: z.number(),
width: z.number(),
height: z.number(),
floorNumber: z.number(),
content: z.object({
images: z.array(imageSchema),
texts: z.object({
facilityInformation: z.string().optional(),
surroundingInformation: z.string().optional(),
descriptions: z.object({
short: z.string().optional(),
medium: z.string().optional(),
}),
meetingDescription: z
.object({
short: z.string().optional(),
medium: z.string().optional(),
})
.optional(),
}),
}),
seatings: z.array(
z.object({
type: z.string(),
capacity: z.number(),
})
),
lighting: z.string(),
sortOrder: z.number().optional(),
}),
id: z.string(),
type: z.string(),
})
),
})

View File

@@ -1,16 +1,9 @@
import { z } from "zod" import { z } from "zod"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { PackageTypeEnum } from "@/types/enums/packages"
export const getRoomPackagesInputSchema = z.object({ // TODO: Remove optional and default when the API change has been deployed
hotelId: z.string(),
startDate: z.string(),
endDate: z.string(),
adults: z.number(),
children: z.number().optional().default(0),
packageCodes: z.array(z.string()).optional().default([]),
})
export const packagePriceSchema = z export const packagePriceSchema = z
.object({ .object({
currency: z.string().default("N/A"), currency: z.string().default("N/A"),
@@ -22,40 +15,27 @@ export const packagePriceSchema = z
currency: "N/A", currency: "N/A",
price: "0", price: "0",
totalPrice: "0", totalPrice: "0",
}) // TODO: Remove optional and default when the API change has been deployed })
export const packagesSchema = z.object({ const inventorySchema = z.object({
date: z.string(),
total: z.number(),
available: z.number(),
})
export const packageSchema = z.object({
code: z.nativeEnum(RoomPackageCodeEnum), code: z.nativeEnum(RoomPackageCodeEnum),
description: z.string(), description: z.string(),
inventories: z.array(inventorySchema),
itemCode: z.string().default(""),
localPrice: packagePriceSchema,
requestedPrice: packagePriceSchema,
})
export const breakfastPackageSchema = z.object({
code: z.string(),
description: z.string(),
localPrice: packagePriceSchema, localPrice: packagePriceSchema,
requestedPrice: packagePriceSchema, requestedPrice: packagePriceSchema,
inventories: z.array( packageType: z.literal(PackageTypeEnum.BreakfastAdult),
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).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)

View File

@@ -0,0 +1,16 @@
import { z } from "zod"
import { CurrencyEnum } from "@/types/enums/currency"
export const priceSchema = z.object({
currency: z.nativeEnum(CurrencyEnum),
pricePerNight: z.coerce.number(),
pricePerStay: z.coerce.number(),
})
export const productTypePriceSchema = z.object({
localPrice: priceSchema,
rateCode: z.string(),
rateType: z.string().optional(),
requestedPrice: priceSchema.optional(),
})

View File

@@ -0,0 +1,21 @@
import { z } from "zod"
const flexibilityPrice = z.object({
member: z.number(),
standard: z.number(),
})
export const rateSchema = z.object({
breakfastIncluded: z.boolean(),
description: z.string(),
id: z.number(),
imageSrc: z.string(),
name: z.string(),
prices: z.object({
currency: z.string(),
freeCancellation: flexibilityPrice,
freeRebooking: flexibilityPrice,
nonRefundable: flexibilityPrice,
}),
size: z.string(),
})

View File

@@ -0,0 +1,10 @@
import { z } from "zod"
export const relationshipsSchema = z.object({
links: z.array(
z.object({
type: z.string(),
url: z.string().url(),
})
),
})

View File

@@ -0,0 +1,23 @@
import { z } from "zod"
import { productSchema } from "./product"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
export const roomConfigurationSchema = z.object({
features: z.array(
z.object({
inventory: z.number(),
code: z.enum([
RoomPackageCodeEnum.PET_ROOM,
RoomPackageCodeEnum.ALLERGY_ROOM,
RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
]),
})
),
products: z.array(productSchema),
roomsLeft: z.number(),
roomType: z.string(),
roomTypeCode: z.string(),
status: z.string(),
})

View File

@@ -0,0 +1,21 @@
import { z } from "zod"
import { productTypePriceSchema } from "../productTypePrice"
import { CurrencyEnum } from "@/types/enums/currency"
export const productSchema = z.object({
productType: z.object({
member: productTypePriceSchema.optional(),
public: productTypePriceSchema.default({
localPrice: {
currency: CurrencyEnum.SEK,
pricePerNight: 0,
pricePerStay: 0,
},
rateCode: "",
rateType: "",
requestedPrice: undefined,
}),
}),
})

View File

@@ -0,0 +1,12 @@
import { z } from "zod"
export const rateDefinitionSchema = z.object({
breakfastIncluded: z.boolean(),
cancellationRule: z.string(),
cancellationText: z.string(),
generalTerms: z.array(z.string()),
mustBeGuaranteed: z.boolean(),
rateCode: z.string(),
rateType: z.string().optional(),
title: z.string(),
})

View File

@@ -4,25 +4,24 @@ import { unstable_cache } from "next/cache"
import * as api from "@/lib/api" import * as api from "@/lib/api"
import { import {
apiCitiesByCountrySchema, citiesByCountrySchema,
apiCitySchema, citiesSchema,
apiCountriesSchema, countriesSchema,
apiLocationsSchema,
type CitiesGroupedByCountry,
getHotelIdsByCityIdSchema, getHotelIdsByCityIdSchema,
locationsSchema,
} from "./output" } from "./output"
import {
getHotelIdsCounter,
getHotelIdsFailCounter,
getHotelIdsSuccessCounter,
} from "./telemetry"
import type { Country } from "@/types/enums/country" import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
import type { RequestOptionsWithOutBody } from "@/types/fetch" import type { RequestOptionsWithOutBody } from "@/types/fetch"
import { PointOfInterestGroupEnum } from "@/types/hotel" import type {
import type { HotelLocation } from "@/types/trpc/routers/hotel/locations" CitiesGroupedByCountry,
Countries,
HotelLocation,
} from "@/types/trpc/routers/hotel/locations"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
import type { Endpoint } from "@/lib/api/endpoints" import type { Endpoint } from "@/lib/api/endpoints"
import { Country } from "@/types/enums/country"
import { metrics } from "./metrics"
export function getPoiGroupByCategoryName(category: string | undefined) { export function getPoiGroupByCategoryName(category: string | undefined) {
if (!category) return PointOfInterestGroupEnum.LOCATION if (!category) return PointOfInterestGroupEnum.LOCATION
@@ -75,7 +74,7 @@ export async function getCity(
} }
const cityJson = await cityResponse.json() const cityJson = await cityResponse.json()
const city = apiCitySchema.safeParse(cityJson) const city = citiesSchema.safeParse(cityJson)
if (!city.success) { if (!city.success) {
console.info(`Validation of city failed`) console.info(`Validation of city failed`)
console.info(`cityUrl: ${locationCityUrl}`) console.info(`cityUrl: ${locationCityUrl}`)
@@ -108,7 +107,7 @@ export async function getCountries(
} }
const countriesJson = await countryResponse.json() const countriesJson = await countryResponse.json()
const countries = apiCountriesSchema.safeParse(countriesJson) const countries = countriesSchema.safeParse(countriesJson)
if (!countries.success) { if (!countries.success) {
console.info(`Validation for countries failed`) console.info(`Validation for countries failed`)
console.error(countries.error) console.error(countries.error)
@@ -149,8 +148,7 @@ export async function getCitiesByCountry(
} }
const countryJson = await countryResponse.json() const countryJson = await countryResponse.json()
const citiesByCountry = const citiesByCountry = citiesByCountrySchema.safeParse(countryJson)
apiCitiesByCountrySchema.safeParse(countryJson)
if (!citiesByCountry.success) { if (!citiesByCountry.success) {
console.info(`Failed to validate Cities by Country payload`) console.info(`Failed to validate Cities by Country payload`)
console.error(citiesByCountry.error) console.error(citiesByCountry.error)
@@ -200,7 +198,7 @@ export async function getLocations(
} }
const apiJson = await apiResponse.json() const apiJson = await apiResponse.json()
const verifiedLocations = apiLocationsSchema.safeParse(apiJson) const verifiedLocations = locationsSchema.safeParse(apiJson)
if (!verifiedLocations.success) { if (!verifiedLocations.success) {
console.info(`Locations Verification Failed`) console.info(`Locations Verification Failed`)
console.error(verifiedLocations.error) console.error(verifiedLocations.error)
@@ -273,7 +271,7 @@ export async function getHotelIdsByCityId(
) { ) {
return unstable_cache( return unstable_cache(
async function (params: URLSearchParams) { async function (params: URLSearchParams) {
getHotelIdsCounter.add(1, { params: params.toString() }) metrics.hotelIds.counter.add(1, { params: params.toString() })
console.info( console.info(
"api.hotel.hotel-ids start", "api.hotel.hotel-ids start",
JSON.stringify({ params: params.toString() }) JSON.stringify({ params: params.toString() })
@@ -286,7 +284,7 @@ export async function getHotelIdsByCityId(
if (!apiResponse.ok) { if (!apiResponse.ok) {
const responseMessage = await apiResponse.text() const responseMessage = await apiResponse.text()
getHotelIdsFailCounter.add(1, { metrics.hotelIds.fail.add(1, {
params: params.toString(), params: params.toString(),
error_type: "http_error", error_type: "http_error",
error: responseMessage, error: responseMessage,
@@ -309,7 +307,7 @@ export async function getHotelIdsByCityId(
const apiJson = await apiResponse.json() const apiJson = await apiResponse.json()
const validatedHotelIds = getHotelIdsByCityIdSchema.safeParse(apiJson) const validatedHotelIds = getHotelIdsByCityIdSchema.safeParse(apiJson)
if (!validatedHotelIds.success) { if (!validatedHotelIds.success) {
getHotelIdsFailCounter.add(1, { metrics.hotelIds.fail.add(1, {
params: params.toString(), params: params.toString(),
error_type: "validation_error", error_type: "validation_error",
error: JSON.stringify(validatedHotelIds.error), error: JSON.stringify(validatedHotelIds.error),
@@ -324,7 +322,7 @@ export async function getHotelIdsByCityId(
return null return null
} }
getHotelIdsSuccessCounter.add(1, { cityId }) metrics.hotelIds.success.add(1, { cityId })
console.info( console.info(
"api.hotel.hotel-ids success", "api.hotel.hotel-ids success",
JSON.stringify({ params: params.toString() }) JSON.stringify({ params: params.toString() })
@@ -344,7 +342,7 @@ export async function getHotelIdsByCountry(
) { ) {
return unstable_cache( return unstable_cache(
async function (params: URLSearchParams) { async function (params: URLSearchParams) {
getHotelIdsCounter.add(1, { country }) metrics.hotelIds.counter.add(1, { country })
console.info( console.info(
"api.hotel.hotel-ids start", "api.hotel.hotel-ids start",
JSON.stringify({ query: { country } }) JSON.stringify({ query: { country } })
@@ -357,7 +355,7 @@ export async function getHotelIdsByCountry(
if (!apiResponse.ok) { if (!apiResponse.ok) {
const responseMessage = await apiResponse.text() const responseMessage = await apiResponse.text()
getHotelIdsFailCounter.add(1, { metrics.hotelIds.fail.add(1, {
country, country,
error_type: "http_error", error_type: "http_error",
error: responseMessage, error: responseMessage,
@@ -380,7 +378,7 @@ export async function getHotelIdsByCountry(
const apiJson = await apiResponse.json() const apiJson = await apiResponse.json()
const validatedHotelIds = getHotelIdsByCityIdSchema.safeParse(apiJson) const validatedHotelIds = getHotelIdsByCityIdSchema.safeParse(apiJson)
if (!validatedHotelIds.success) { if (!validatedHotelIds.success) {
getHotelIdsFailCounter.add(1, { metrics.hotelIds.fail.add(1, {
country, country,
error_type: "validation_error", error_type: "validation_error",
error: JSON.stringify(validatedHotelIds.error), error: JSON.stringify(validatedHotelIds.error),
@@ -395,7 +393,7 @@ export async function getHotelIdsByCountry(
return null return null
} }
getHotelIdsSuccessCounter.add(1, { country }) metrics.hotelIds.success.add(1, { country })
console.info( console.info(
"api.hotel.hotel-ids success", "api.hotel.hotel-ids success",
JSON.stringify({ query: { country } }) JSON.stringify({ query: { country } })

View File

@@ -1,6 +1,6 @@
import { meetingRoomsSchema } from "@/server/routers/hotels/schemas/meetingRoom"
import type { z } from "zod" import type { z } from "zod"
import type { getMeetingRoomsSchema } from "@/server/routers/hotels/output" export type MeetingRoomData = z.output<typeof meetingRoomsSchema>
export type MeetingRoomData = z.infer<typeof getMeetingRoomsSchema>
export type MeetingRooms = MeetingRoomData["data"] export type MeetingRooms = MeetingRoomData["data"]

View File

@@ -1,10 +1,10 @@
import type { RoomData } from "@/types/hotel" import type { Room } from "@/types/hotel"
export interface RoomCardProps { export interface RoomCardProps {
room: RoomData room: Room
} }
export type RoomsProps = { export type RoomsProps = {
rooms: RoomData[]
preamble?: string preamble?: string
rooms: Room[]
} }

View File

@@ -1,6 +1,6 @@
import type { import type {
Hotel, Hotel,
RestaurantData, Restaurant,
RestaurantOpeningHours, RestaurantOpeningHours,
} from "@/types/hotel" } from "@/types/hotel"
import type { ParkingAmenityProps } from "./parking" import type { ParkingAmenityProps } from "./parking"
@@ -10,7 +10,7 @@ export type AmenitiesSidePeekProps = {
parking: ParkingAmenityProps parking: ParkingAmenityProps
checkInInformation: Hotel["hotelFacts"]["checkin"] checkInInformation: Hotel["hotelFacts"]["checkin"]
accessibility: Hotel["hotelFacts"]["hotelInformation"]["accessibility"] accessibility: Hotel["hotelFacts"]["hotelInformation"]["accessibility"]
restaurants: RestaurantData[] restaurants: Restaurant[]
} }
export type FilteredAmenitiesProps = { export type FilteredAmenitiesProps = {

View File

@@ -1,27 +1,29 @@
import type { Hotel } from "@/types/hotel" import type { Hotel, Parking } from "@/types/hotel"
export enum Periods { export enum Periods {
allDay = "AllDay",
day = "Day", day = "Day",
night = "Night", night = "Night",
allDay = "AllDay",
} }
export type ParkingAmenityProps = { export type ParkingAmenityProps = {
hasExtraParkingPage: boolean
parking: Hotel["parking"] parking: Hotel["parking"]
parkingElevatorPitch: string parkingElevatorPitch: string
hasExtraParkingPage: boolean
} }
export type ParkingListProps = { export interface ParkingListProps
numberOfChargingSpaces: Hotel["parking"][number]["numberOfChargingSpaces"] extends Pick<
canMakeReservation: Hotel["parking"][number]["canMakeReservation"] Parking,
numberOfParkingSpots: Hotel["parking"][number]["numberOfParkingSpots"] | "address"
distanceToHotel: Hotel["parking"][number]["distanceToHotel"] | "canMakeReservation"
address: Hotel["parking"][number]["address"] | "distanceToHotel"
} | "numberOfChargingSpaces"
| "numberOfParkingSpots"
> { }
export type ParkingPricesProps = { export interface ParkingPricesProps
pricing: Hotel["parking"][number]["pricing"]["localCurrency"]["ordinary"] extends Pick<Parking["pricing"], "freeParking">,
currency: Hotel["parking"][number]["pricing"]["localCurrency"]["currency"] Pick<NonNullable<Parking["pricing"]["localCurrency"]>, "currency"> {
freeParking: Hotel["parking"][number]["pricing"]["freeParking"] pricing: NonNullable<Parking["pricing"]["localCurrency"]>["ordinary"]
} }

View File

@@ -1,9 +1,9 @@
import type { RestaurantData } from "@/types/hotel" import type { Restaurant } from "@/types/hotel"
export interface RestaurantBarSidePeekProps { export interface RestaurantBarSidePeekProps {
restaurants: RestaurantData[] restaurants: Restaurant[]
} }
export interface RestaurantBarItemProps { export interface RestaurantBarItemProps {
restaurant: RestaurantData restaurant: Restaurant
} }

View File

@@ -1,5 +1,5 @@
import type { RoomData } from "@/types/hotel" import type { Room } from "@/types/hotel"
export interface RoomSidePeekProps { export interface RoomSidePeekProps {
room: RoomData room: Room
} }

View File

@@ -4,4 +4,4 @@ export interface BookingConfirmationProps {
confirmationNumber: string confirmationNumber: string
} }
export interface ConfirmationProps extends BookingConfirmation {} export interface ConfirmationProps extends BookingConfirmation { }

View File

@@ -1,19 +1,17 @@
import { type z } from "zod" import type { z } from "zod"
import type { breakfastFormSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema" import type { breakfastFormSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema"
import type { import type { breakfastPackagesSchema } from "@/server/routers/hotels/output"
breakfastPackageSchema, import type { breakfastPackageSchema } from "@/server/routers/hotels/schemas/packages"
breakfastPackagesSchema,
} from "@/server/routers/hotels/output"
export interface BreakfastFormSchema export interface BreakfastFormSchema
extends z.output<typeof breakfastFormSchema> {} extends z.output<typeof breakfastFormSchema> { }
export interface BreakfastPackages export interface BreakfastPackages
extends z.output<typeof breakfastPackagesSchema> {} extends z.output<typeof breakfastPackagesSchema> { }
export interface BreakfastPackage export interface BreakfastPackage
extends z.output<typeof breakfastPackageSchema> {} extends z.output<typeof breakfastPackageSchema> { }
export interface BreakfastProps { export interface BreakfastProps {
packages: BreakfastPackages packages: BreakfastPackages

View File

@@ -1,7 +1,5 @@
import type { RouterOutput } from "@/lib/trpc/client" import type { HotelData } from "@/types/hotel"
type HotelDataGet = RouterOutput["hotel"]["hotelData"]["get"]
export interface HotelHeaderProps { export interface HotelHeaderProps {
hotelData: NonNullable<HotelDataGet> hotelData: HotelData
} }

View File

@@ -1,7 +1,7 @@
import { RoomConfiguration } from "@/server/routers/hotels/output" import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
export interface SelectedRoomProps { export interface SelectedRoomProps {
hotelId: string hotelId: string
room: RoomConfiguration
rateDescription: string rateDescription: string
room: RoomConfiguration
} }

View File

@@ -1,5 +1,5 @@
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate" import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability" import type { RoomAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
export interface ClientSummaryProps export interface ClientSummaryProps
extends Pick< extends Pick<

View File

@@ -1,6 +1,6 @@
import type { ProductType } from "@/server/routers/hotels/output" import type { ProductType } from "@/types/trpc/routers/hotel/availability"
export type HotelPriceListProps = { export type HotelPriceListProps = {
price: ProductType
hotelId: string hotelId: string
price: ProductType
} }

View File

@@ -1,21 +1,21 @@
import type { Hotel } from "@/types/hotel" import type { Hotel } from "@/types/hotel"
import type { ProductType } from "@/server/routers/hotels/output" import type { ProductType } from "@/types/trpc/routers/hotel/availability"
export enum HotelCardListingTypeEnum { export enum HotelCardListingTypeEnum {
MapListing = "mapListing", MapListing = "mapListing",
PageListing = "pageListing", PageListing = "pageListing",
} }
export type HotelCardListingProps = {
hotelData: HotelData[]
type?: HotelCardListingTypeEnum
}
export type HotelData = { export type HotelData = {
hotelData: Hotel hotelData: Hotel
price: ProductType price: ProductType
} }
export type HotelCardListingProps = {
hotelData: HotelData[]
type?: HotelCardListingTypeEnum
}
export interface NullableHotelData extends Omit<HotelData, "hotelData"> { export interface NullableHotelData extends Omit<HotelData, "hotelData"> {
hotelData: HotelData["hotelData"] | null hotelData: HotelData["hotelData"] | null
} }

View File

@@ -1,4 +1,4 @@
import { ProductTypePrices } from "@/server/routers/hotels/output" import type { ProductTypePrices } from "@/types/trpc/routers/hotel/availability"
export type PriceCardProps = { export type PriceCardProps = {
productTypePrices: ProductTypePrices productTypePrices: ProductTypePrices

View File

@@ -1,10 +1,9 @@
import type { CheckInData, Hotel, ParkingData } from "@/types/hotel"
import type { HotelLocation } from "@/types/trpc/routers/hotel/locations"
import type { Lang } from "@/constants/languages"
import type { import type {
AlternativeHotelsSearchParams, AlternativeHotelsSearchParams,
SelectHotelSearchParams, SelectHotelSearchParams,
} from "./selectHotelSearchParams" } from "./selectHotelSearchParams"
import type { CheckInData, Hotel, Parking } from "@/types/hotel"
import type { Lang } from "@/constants/languages"
export enum AvailabilityEnum { export enum AvailabilityEnum {
Available = "Available", Available = "Available",
@@ -23,7 +22,7 @@ export interface ContactProps {
} }
export interface ParkingProps { export interface ParkingProps {
parking: ParkingData[] parking: Parking[]
} }
export interface AccessibilityProps { export interface AccessibilityProps {

View File

@@ -1,11 +1,13 @@
import type { z } from "zod" import type { z } from "zod"
import type { import type {
priceSchema,
Product, Product,
productTypePriceSchema,
RoomConfiguration, RoomConfiguration,
} from "@/server/routers/hotels/output" } from "@/types/trpc/routers/hotel/roomAvailability"
import type {
priceSchema,
productTypePriceSchema,
} from "@/server/routers/hotels/schemas/productTypePrice"
import type { RoomPackage } from "./roomFilter" import type { RoomPackage } from "./roomFilter"
type ProductPrice = z.output<typeof productTypePriceSchema> type ProductPrice = z.output<typeof productTypePriceSchema>

View File

@@ -0,0 +1,11 @@
import type { Lang } from "@/constants/languages"
import type { Child } from "./selectRate"
export interface HotelInfoCardProps {
adultCount: number
childArray?: Child[]
fromDate: Date
hotelId: number
lang: Lang
toDate: Date
}

View File

@@ -1,9 +1,9 @@
import type { RoomsAvailability } from "@/server/routers/hotels/output" import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
import type { RoomPackageData } from "./roomFilter" import type { RoomPackages } from "./roomFilter"
import type { Rate } from "./selectRate" import type { Rate } from "./selectRate"
export interface RateSummaryProps { export interface RateSummaryProps {
isUserLoggedIn: boolean isUserLoggedIn: boolean
packages: RoomPackageData | undefined packages: RoomPackages | undefined
roomsAvailability: RoomsAvailability roomsAvailability: RoomsAvailability
} }

View File

@@ -1,23 +1,25 @@
import type { z } from "zod" import type { z } from "zod"
import type { RoomData } from "@/types/hotel" import type { Room } from "@/types/hotel"
import type { import type {
packagePriceSchema,
RateDefinition, RateDefinition,
RoomConfiguration, RoomConfiguration,
} from "@/server/routers/hotels/output" } from "@/types/trpc/routers/hotel/roomAvailability"
import type { packagePriceSchema } from "@/server/routers/hotels/schemas/packages"
import type { RoomPriceSchema } from "./flexibilityOption" import type { RoomPriceSchema } from "./flexibilityOption"
import type { RoomPackageCodes, RoomPackageData } from "./roomFilter" import type { RoomPackageCodes, RoomPackages } from "./roomFilter"
import type { RateCode } from "./selectRate"
export type RoomCardProps = { export type RoomCardProps = {
hotelId: string hotelId: string
hotelType: string | undefined hotelType: string | undefined
roomConfiguration: RoomConfiguration roomConfiguration: RoomConfiguration
rateDefinitions: RateDefinition[] rateDefinitions: RateDefinition[]
roomCategories: RoomData[] roomCategories: Room[]
selectedPackages: RoomPackageCodes[] selectedPackages: RoomPackageCodes[]
packages: RoomPackageData | undefined
roomListIndex: number roomListIndex: number
packages: RoomPackages | undefined
handleSelectRate: React.Dispatch<React.SetStateAction<RateCode | undefined>>
} }
type RoomPackagePriceSchema = z.output<typeof packagePriceSchema> type RoomPackagePriceSchema = z.output<typeof packagePriceSchema>

View File

@@ -1,6 +1,6 @@
import type { z } from "zod" import type { z } from "zod"
import type { packagesSchema } from "@/server/routers/hotels/output" import type { packageSchema } from "@/server/routers/hotels/schemas/packages"
export enum RoomPackageCodeEnum { export enum RoomPackageCodeEnum {
PET_ROOM = "PETR", PET_ROOM = "PETR",
@@ -24,7 +24,6 @@ export interface RoomFilterProps {
roomListIndex: number roomListIndex: number
} }
export type RoomPackage = z.output<typeof packagesSchema> export type RoomPackage = z.output<typeof packageSchema>
export interface RoomPackageData extends Array<RoomPackage> {}
export type RoomPackageCodes = RoomPackage["code"] export type RoomPackageCodes = RoomPackage["code"]
export type RoomPackages = RoomPackage[]

View File

@@ -1,31 +1,32 @@
import type { RoomData } from "@/types/hotel"
import type { RoomsAvailability } from "@/server/routers/hotels/output"
import type { import type {
DefaultFilterOptions, DefaultFilterOptions,
RoomPackage, RoomPackage,
RoomPackageCodes, RoomPackageCodes,
RoomPackageData, RoomPackages,
} from "./roomFilter" } from "./roomFilter"
import type { Room } from "@/types/hotel"
import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
export interface RoomTypeListProps { export interface RoomTypeListProps {
roomsAvailability: RoomsAvailability roomsAvailability: RoomsAvailability
roomCategories: RoomData[] roomCategories: Room[]
availablePackages: RoomPackageData | undefined availablePackages: RoomPackage | undefined
selectedPackages: RoomPackageCodes[] selectedPackages: RoomPackageCodes[]
hotelType: string | undefined hotelType: string | undefined
roomListIndex: number roomListIndex: number
} }
export interface SelectRateProps { export interface SelectRateProps {
roomsAvailability: RoomsAvailability availablePackages: RoomPackages
roomCategories: RoomData[]
availablePackages: RoomPackageData
hotelType: string | undefined hotelType: string | undefined
isUserLoggedIn: boolean isUserLoggedIn: boolean
roomsAvailability: RoomsAvailability
roomCategories: Room[]
} }
export interface RoomSelectionPanelProps { export interface RoomSelectionPanelProps {
roomCategories: RoomData[] roomCategories: Room[]
availablePackages: RoomPackage[] availablePackages: RoomPackage[]
selectedPackages: RoomPackageCodes[] selectedPackages: RoomPackageCodes[]
hotelType: string | undefined hotelType: string | undefined

View File

@@ -0,0 +1,11 @@
import type { Lang } from "@/constants/languages"
import type { Child } from "./selectRate"
export interface RoomsContainerProps {
adultCount: number
childArray?: Child[]
fromDate: Date
hotelId: number
lang: Lang
toDate: Date
}

View File

@@ -1,4 +1,7 @@
import type { Product, RoomConfiguration } from "@/server/routers/hotels/output" import type {
Product,
RoomConfiguration,
} from "@/types/trpc/routers/hotel/roomAvailability"
import type { ChildBedMapEnum } from "../../bookingWidget/enums" import type { ChildBedMapEnum } from "../../bookingWidget/enums"
import type { RoomPackageCodeEnum } from "./roomFilter" import type { RoomPackageCodeEnum } from "./roomFilter"

View File

@@ -1,10 +1,10 @@
import { Hotel } from "@/types/hotel" import { HotelData } from "@/types/hotel"
export enum SidePeekEnum { export enum SidePeekEnum {
hotelDetails = "hotel-detail-side-peek", hotelDetails = "hotel-detail-side-peek",
roomDetails = "room-detail-side-peek", roomDetails = "room-detail-side-peek",
} }
export type SidePeekProps = { export type HotelReservationSidePeekProps = {
hotel: Hotel hotel: HotelData | null
} }

View File

@@ -5,11 +5,11 @@ import type {
Price, Price,
RoomPrice, RoomPrice,
} from "@/types/stores/enter-details" } from "@/types/stores/enter-details"
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
import type { BedTypeSchema } from "./enterDetails/bedType" import type { BedTypeSchema } from "./enterDetails/bedType"
import type { BreakfastPackage } from "./enterDetails/breakfast" import type { BreakfastPackage } from "./enterDetails/breakfast"
import type { DetailsSchema } from "./enterDetails/details" import type { DetailsSchema } from "./enterDetails/details"
import type { Child, SelectRateSearchParams } from "./selectRate/selectRate" import type { Child, SelectRateSearchParams } from "./selectRate/selectRate"
import type { RoomAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
export type RoomsData = Pick<DetailsState, "roomPrice"> & export type RoomsData = Pick<DetailsState, "roomPrice"> &
Pick<RoomAvailability, "cancellationText" | "rateDetails"> & Pick<RoomAvailability, "cancellationText" | "rateDetails"> &
@@ -21,7 +21,7 @@ export type RoomsData = Pick<DetailsState, "roomPrice"> &
export interface SummaryProps export interface SummaryProps
extends Pick<RoomAvailability, "cancellationText" | "rateDetails">, extends Pick<RoomAvailability, "cancellationText" | "rateDetails">,
Pick<RoomAvailability["selectedRoom"], "roomType"> { Pick<RoomAvailability["selectedRoom"], "roomType"> {
isMember: boolean isMember: boolean
breakfastIncluded: boolean breakfastIncluded: boolean
} }

View File

@@ -1,8 +1,7 @@
import { poiVariants } from "@/components/Maps/Markers/Poi/variants"
import type { VariantProps } from "class-variance-authority" import type { VariantProps } from "class-variance-authority"
import type { PointOfInterestGroupEnum } from "@/types/hotel" import type { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
import type { poiVariants } from "@/components/Maps/Markers/Poi/variants"
export interface PoiMarkerProps extends VariantProps<typeof poiVariants> { export interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
group: PointOfInterestGroupEnum group: PointOfInterestGroupEnum

View File

@@ -1,9 +1,8 @@
import { SidePeekEnum } from "../hotelReservation/sidePeek" import type { Room } from "@/types/hotel"
import type { SidePeekEnum } from "../hotelReservation/sidePeek"
import type { RoomData } from "@/types/hotel"
export type RoomSidePeekProps = { export type RoomSidePeekProps = {
room: RoomData room: Room
activeSidePeek: SidePeekEnum | null activeSidePeek: SidePeekEnum | null
close: () => void close: () => void
} }

View File

@@ -0,0 +1,8 @@
export enum PointOfInterestGroupEnum {
PUBLIC_TRANSPORT = "Public transport",
ATTRACTIONS = "Attractions",
BUSINESS = "Business",
LOCATION = "Location",
PARKING = "Parking",
SHOPPING_DINING = "Shopping & Dining",
}

Some files were not shown because too many files have changed in this diff Show More