Merged in feat/SW-1090-amenities-meetings-sidepeeks (pull request #1114)

Feat/SW-1090: Sidepeek amenities and meetings

Approved-by: Erik Tiekstra
Approved-by: Fredrik Thorsson
This commit is contained in:
Matilda Landström
2025-01-10 15:18:21 +00:00
parent 03bd8ac94b
commit 57bc08aae2
26 changed files with 350 additions and 37 deletions

View File

@@ -1,9 +1,15 @@
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import OpeningHours from "../../../OpeningHours"
import type { BreakfastAmenityProps } from "@/types/components/hotelPage/sidepeek/amenities"
import { IconName } from "@/types/components/icon" import { IconName } from "@/types/components/icon"
export default async function BreakfastAmenity() { export default async function BreakfastAmenity({
openingHours,
alternateOpeningHours,
}: BreakfastAmenityProps) {
const intl = await getIntl() const intl = await getIntl()
return ( return (
<AccordionItem <AccordionItem
@@ -11,7 +17,12 @@ export default async function BreakfastAmenity() {
icon={IconName.CoffeeAlt} icon={IconName.CoffeeAlt}
variant="sidepeek" variant="sidepeek"
> >
{/* TODO: breakfast to be implemented */} <OpeningHours
openingHours={openingHours}
alternateOpeningHours={alternateOpeningHours}
heading={intl.formatMessage({ id: "Opening hours" })}
type="amenities"
/>
</AccordionItem> </AccordionItem>
) )
} }

View File

@@ -21,6 +21,7 @@ export default async function AmenitiesSidePeek({
parking, parking,
checkInInformation, checkInInformation,
accessibility, accessibility,
restaurants,
}: AmenitiesSidePeekProps) { }: AmenitiesSidePeekProps) {
const lang = getLang() const lang = getLang()
const intl = await getIntl() const intl = await getIntl()
@@ -40,6 +41,17 @@ export default async function AmenitiesSidePeek({
(amenity) => !amenitiesToRemove.includes(amenity.id) (amenity) => !amenitiesToRemove.includes(amenity.id)
) )
const breakfastOpeningHours = restaurants
?.map((restaurant) => {
const breakfastDetail = restaurant.openingDetails.find(
(details) =>
details.openingHours.name === "Breakfast" ||
details.openingHours.name === intl.formatMessage({ id: "Breakfast" })
)
return breakfastDetail
})
.filter(Boolean)[0]
return ( return (
<SidePeek <SidePeek
contentKey={amenities[lang]} contentKey={amenities[lang]}
@@ -47,7 +59,13 @@ export default async function AmenitiesSidePeek({
> >
<Accordion> <Accordion>
{parking.length ? <ParkingAmenity parking={parking} /> : null} {parking.length ? <ParkingAmenity parking={parking} /> : null}
<BreakfastAmenity />
{breakfastOpeningHours && (
<BreakfastAmenity
openingHours={breakfastOpeningHours.openingHours}
alternateOpeningHours={breakfastOpeningHours.alternateOpeningHours}
/>
)}
<CheckInAmenity checkInInformation={checkInInformation} /> <CheckInAmenity checkInInformation={checkInInformation} />
{accessibility && ( {accessibility && (
<AccessibilityAmenity accessibility={accessibility} /> <AccessibilityAmenity accessibility={accessibility} />

View File

@@ -1,4 +1,5 @@
import { meetingsAndConferences } from "@/constants/routes/hotelPageParams" import { meetingsAndConferences } from "@/constants/routes/hotelPageParams"
import { getMeetingRooms } from "@/lib/trpc/memoizedRequests"
import Image from "@/components/Image" import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
@@ -10,6 +11,8 @@ import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { getConferenceRoomTexts } from "./util"
import styles from "./meetingsAndConferences.module.css" import styles from "./meetingsAndConferences.module.css"
import type { MeetingsAndConferencesSidePeekProps } from "@/types/components/hotelPage/sidepeek/meetingsAndConferences" import type { MeetingsAndConferencesSidePeekProps } from "@/types/components/hotelPage/sidepeek/meetingsAndConferences"
@@ -17,10 +20,17 @@ import type { MeetingsAndConferencesSidePeekProps } from "@/types/components/hot
export default async function MeetingsAndConferencesSidePeek({ export default async function MeetingsAndConferencesSidePeek({
meetingFacilities, meetingFacilities,
descriptions, descriptions,
hotelId,
link, link,
}: MeetingsAndConferencesSidePeekProps) { }: MeetingsAndConferencesSidePeekProps) {
const lang = getLang() const lang = getLang()
const intl = await getIntl() const [intl, meetingRooms] = await Promise.all([
getIntl(),
getMeetingRooms({ hotelId, language: lang }),
])
const { seatingText, roomText } = await getConferenceRoomTexts(meetingRooms)
const fallbackAlt = intl.formatMessage({ id: "Creative spaces for meetings" }) const fallbackAlt = intl.formatMessage({ id: "Creative spaces for meetings" })
const primaryImage = meetingFacilities?.heroImages[0]?.imageSizes.medium const primaryImage = meetingFacilities?.heroImages[0]?.imageSizes.medium
@@ -65,6 +75,14 @@ export default async function MeetingsAndConferencesSidePeek({
{descriptions?.medium && ( {descriptions?.medium && (
<Body color="uiTextHighContrast">{descriptions.medium}</Body> <Body color="uiTextHighContrast">{descriptions.medium}</Body>
)} )}
{roomText || seatingText ? (
<Body color="uiTextMediumContrast">
{roomText}
{roomText && seatingText && <br />}
{seatingText}
</Body>
) : null}
{link && ( {link && (
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
<Button fullWidth theme="base" intent="secondary" asChild> <Button fullWidth theme="base" intent="secondary" asChild>

View File

@@ -0,0 +1,70 @@
import { getIntl } from "@/i18n"
import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms"
export async function getConferenceRoomTexts(
meetingRooms: MeetingRooms | null
) {
if (!meetingRooms) {
return { seatingText: null, roomText: null }
}
const roomSizes = meetingRooms.map((room) => room.attributes.size)
const roomSeating = meetingRooms
.map((room) => {
return room.attributes.seatings.map((seating) => seating.capacity)
})
.flat()
const seatingText = await getSeatingText(roomSeating)
const roomText = await getRoomText(roomSizes)
return { seatingText, roomText }
}
export async function getRoomText(roomSizes: number[]) {
const largestRoom = Math.max(...roomSizes)
const smallestRoom = Math.min(...roomSizes)
const intl = await getIntl()
let roomText
if (largestRoom === smallestRoom) {
roomText = intl.formatMessage(
{ id: "{number} square meters" },
{ number: largestRoom }
)
} else if (smallestRoom != null && largestRoom) {
{
roomText = intl.formatMessage(
{
id: "{number} to {number} square meters",
},
{ largest: largestRoom, smallest: smallestRoom }
)
}
}
return roomText
}
export async function getSeatingText(roomSeating: number[]) {
const biggestSeating = Math.max(...roomSeating)
const smallestSeating = Math.min(...roomSeating)
const intl = await getIntl()
let seatingText
if (biggestSeating === smallestSeating) {
seatingText = intl.formatMessage(
{ id: "{number} persons" },
{ number: biggestSeating }
)
} else if (smallestSeating != null && biggestSeating) {
{
seatingText = intl.formatMessage(
{
id: "{number} to {number} persons",
},
{ highest: biggestSeating, lowest: smallestSeating }
)
}
}
return seatingText
}

View File

@@ -4,16 +4,16 @@ import { getIntl } from "@/i18n"
import styles from "./openingHours.module.css" import styles from "./openingHours.module.css"
import { import type { OpeningHoursProps } from "@/types/components/hotelPage/sidepeek/openingHours"
DaysEnum, import { DaysEnum } from "@/types/components/hotelPage/sidepeek/restaurantBar"
type RestaurantBarOpeningHoursProps,
} from "@/types/components/hotelPage/sidepeek/restaurantBar"
import type { RestaurantOpeningHoursDay } from "@/types/hotel" import type { RestaurantOpeningHoursDay } from "@/types/hotel"
export default async function OpeningHours({ export default async function OpeningHours({
openingHours, openingHours,
alternateOpeningHours, alternateOpeningHours,
}: RestaurantBarOpeningHoursProps) { heading,
type = "default",
}: OpeningHoursProps) {
const intl = await getIntl() const intl = await getIntl()
const closed = intl.formatMessage({ id: "Closed" }) const closed = intl.formatMessage({ id: "Closed" })
@@ -58,9 +58,9 @@ export default async function OpeningHours({
} }
return ( return (
<div className={styles.wrapper}> <div className={type === "default" ? styles.wrapper : ""}>
<Body textTransform="bold" asChild> <Body textTransform="bold" asChild>
<h5>{openingHours.name}</h5> <h5>{heading ?? openingHours.name}</h5>
</Body> </Body>
{Object.entries(groupedOpeningHours).map(([time, days]) => { {Object.entries(groupedOpeningHours).map(([time, days]) => {
return ( return (

View File

@@ -7,7 +7,7 @@ import Link from "@/components/TempDesignSystem/Link"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import OpeningHours from "../OpeningHours" import OpeningHours from "../../OpeningHours"
import styles from "./restaurantBarItem.module.css" import styles from "./restaurantBarItem.module.css"
@@ -49,7 +49,7 @@ export default async function RestaurantBarItem({
{openingDetails.length ? ( {openingDetails.length ? (
<div className={styles.content}> <div className={styles.content}>
<Subtitle type="two" asChild> <Subtitle type="two" asChild>
<h4>{intl.formatMessage({ id: "Opening Hours" })}</h4> <h4>{intl.formatMessage({ id: "Opening hours" })}</h4>
</Subtitle> </Subtitle>
{openingDetails.map((details) => ( {openingDetails.map((details) => (
<OpeningHours <OpeningHours

View File

@@ -31,7 +31,7 @@ export default async function Facility({ data }: FacilityProps) {
</Subtitle> </Subtitle>
<div> <div>
<Subtitle type="two" color="uiTextHighContrast"> <Subtitle type="two" color="uiTextHighContrast">
{intl.formatMessage({ id: "Opening Hours" })} {intl.formatMessage({ id: "Opening hours" })}
</Subtitle> </Subtitle>
<div className={styles.openingHours}> <div className={styles.openingHours}>
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">

View File

@@ -9,7 +9,6 @@ import Breadcrumbs from "@/components/Breadcrumbs"
import SidePeekProvider from "@/components/SidePeeks/SidePeekProvider" import SidePeekProvider from "@/components/SidePeeks/SidePeekProvider"
import Alert from "@/components/TempDesignSystem/Alert" import Alert from "@/components/TempDesignSystem/Alert"
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { getRestaurantHeading } from "@/utils/facilityCards" import { getRestaurantHeading } from "@/utils/facilityCards"
import { generateHotelSchema } from "@/utils/jsonSchemas" import { generateHotelSchema } from "@/utils/jsonSchemas"
@@ -49,8 +48,7 @@ import type {
export default async function HotelPage({ hotelId }: HotelPageProps) { export default async function HotelPage({ hotelId }: HotelPageProps) {
const lang = getLang() const lang = getLang()
const [intl, hotelPageData, hotelData] = await Promise.all([ const [hotelPageData, hotelData] = await Promise.all([
getIntl(),
getHotelPage(), getHotelPage(),
getHotelData({ hotelId, language: lang }), getHotelData({ hotelId, language: lang }),
]) ])
@@ -84,6 +82,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
} = hotelData.data.attributes } = hotelData.data.attributes
const roomCategories = hotelData.included?.rooms || [] const roomCategories = hotelData.included?.rooms || []
const restaurants = hotelData.included?.restaurants || [] const restaurants = hotelData.included?.restaurants || []
const images = gallery?.smallerImages const images = gallery?.smallerImages
const description = hotelContent.texts.descriptions.medium const description = hotelContent.texts.descriptions.medium
@@ -200,6 +199,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
parking={parking} parking={parking}
checkInInformation={hotelFacts.checkin} checkInInformation={hotelFacts.checkin}
accessibility={hotelFacts.hotelInformation.accessibility} accessibility={hotelFacts.hotelInformation.accessibility}
restaurants={restaurants}
/> />
<AboutTheHotelSidePeek <AboutTheHotelSidePeek
hotelAddress={address} hotelAddress={address}
@@ -222,6 +222,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
<MeetingsAndConferencesSidePeek <MeetingsAndConferencesSidePeek
meetingFacilities={conferencesAndMeetings} meetingFacilities={conferencesAndMeetings}
descriptions={hotelContent.texts.meetingDescription} descriptions={hotelContent.texts.meetingDescription}
hotelId={hotelId}
/> />
{roomCategories.map((room) => ( {roomCategories.map((room) => (
<RoomSidePeek key={room.name} room={room} /> <RoomSidePeek key={room.name} room={room} />

View File

@@ -295,7 +295,7 @@
"Open language menu": "Åbn sprogmenuen", "Open language menu": "Åbn sprogmenuen",
"Open menu": "Åbn menuen", "Open menu": "Åbn menuen",
"Open my pages menu": "Åbn mine sider menuen", "Open my pages menu": "Åbn mine sider menuen",
"Opening Hours": "Åbningstider", "Opening hours": "Åbningstider",
"Outdoor": "Udendørs", "Outdoor": "Udendørs",
"OutdoorPool": "Udendørs pool", "OutdoorPool": "Udendørs pool",
"Overview": "Oversigt", "Overview": "Oversigt",
@@ -549,5 +549,9 @@
"uppercase letter": "stort bogstav", "uppercase letter": "stort bogstav",
"{amount} out of {total}": "{amount} ud af {total}", "{amount} out of {total}": "{amount} ud af {total}",
"{card} ending with {cardno}": "{card} slutter med {cardno}", "{card} ending with {cardno}": "{card} slutter med {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}" "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{number} persons": "{number} Personen",
"{number} square meters": "{number} Quadratmeter",
"{number} to {number} persons": "{lowest} bis {highest} Personen",
"{number} to {number} square meters": "{smallest} bis {largest} Quadratmeter"
} }

View File

@@ -294,7 +294,7 @@
"Open language menu": "Sprachmenü öffnen", "Open language menu": "Sprachmenü öffnen",
"Open menu": "Menü öffnen", "Open menu": "Menü öffnen",
"Open my pages menu": "Meine Seiten Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen",
"Opening Hours": "Öffnungszeiten", "Opening hours": "Öffnungszeiten",
"Outdoor": "Im Freien", "Outdoor": "Im Freien",
"OutdoorPool": "Außenpool", "OutdoorPool": "Außenpool",
"Overview": "Übersicht", "Overview": "Übersicht",
@@ -548,5 +548,9 @@
"uppercase letter": "großbuchstabe", "uppercase letter": "großbuchstabe",
"{amount} out of {total}": "{amount} von {total}", "{amount} out of {total}": "{amount} von {total}",
"{card} ending with {cardno}": "{card} endet mit {cardno}", "{card} ending with {cardno}": "{card} endet mit {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}" "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{number} persons": "{number} personer",
"{number} square meters": "{number} kvadratmeter",
"{number} to {number} persons": "{lowest} til {highest} personer",
"{number} to {number} square meters": "{smallest} til {largest} kvadratmeter"
} }

View File

@@ -318,7 +318,7 @@
"Open language menu": "Open language menu", "Open language menu": "Open language menu",
"Open menu": "Open menu", "Open menu": "Open menu",
"Open my pages menu": "Open my pages menu", "Open my pages menu": "Open my pages menu",
"Opening Hours": "Opening Hours", "Opening hours": "Opening hours",
"Outdoor": "Outdoor", "Outdoor": "Outdoor",
"OutdoorPool": "Outdoor pool", "OutdoorPool": "Outdoor pool",
"Overview": "Overview", "Overview": "Overview",
@@ -598,5 +598,9 @@
"uppercase letter": "uppercase letter", "uppercase letter": "uppercase letter",
"{amount} out of {total}": "{amount} out of {total}", "{amount} out of {total}": "{amount} out of {total}",
"{card} ending with {cardno}": "{card} ending with {cardno}", "{card} ending with {cardno}": "{card} ending with {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}" "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{number} persons": "{number} persons",
"{number} square meters": "{number} square meters",
"{number} to {number} persons": "{lowest} to {highest} persons",
"{number} to {number} square meters": "{smallest} to {largest} square meters"
} }

View File

@@ -295,7 +295,7 @@
"Open language menu": "Avaa kielivalikko", "Open language menu": "Avaa kielivalikko",
"Open menu": "Avaa valikko", "Open menu": "Avaa valikko",
"Open my pages menu": "Avaa omat sivut -valikko", "Open my pages menu": "Avaa omat sivut -valikko",
"Opening Hours": "Aukioloajat", "Opening hours": "Aukioloajat",
"Outdoor": "Ulkona", "Outdoor": "Ulkona",
"OutdoorPool": "Ulkouima-allas", "OutdoorPool": "Ulkouima-allas",
"Overview": "Yleiskatsaus", "Overview": "Yleiskatsaus",
@@ -547,5 +547,9 @@
"uppercase letter": "iso kirjain", "uppercase letter": "iso kirjain",
"{amount} out of {total}": "{amount}/{total}", "{amount} out of {total}": "{amount}/{total}",
"{card} ending with {cardno}": "{card} päättyen {cardno}", "{card} ending with {cardno}": "{card} päättyen {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}" "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{number} persons": "{number} henkilöä",
"{number} square meters": "{number} neliömetriä",
"{number} to {number} persons": "{lowest} - {highest} henkilöä",
"{number} to {number} square meters": "{smallest} - {largest} neliömetriä"
} }

View File

@@ -294,7 +294,7 @@
"Open language menu": "Åpne språkmenyen", "Open language menu": "Åpne språkmenyen",
"Open menu": "Åpne menyen", "Open menu": "Åpne menyen",
"Open my pages menu": "Åpne mine sider menyen", "Open my pages menu": "Åpne mine sider menyen",
"Opening Hours": "Åpningstider", "Opening hours": "Åpningstider",
"Outdoor": "Utendørs", "Outdoor": "Utendørs",
"OutdoorPool": "Utendørs basseng", "OutdoorPool": "Utendørs basseng",
"Overview": "Oversikt", "Overview": "Oversikt",
@@ -547,5 +547,9 @@
"uppercase letter": "stor bokstav", "uppercase letter": "stor bokstav",
"{amount} out of {total}": "{amount} av {total}", "{amount} out of {total}": "{amount} av {total}",
"{card} ending with {cardno}": "{card} slutter med {cardno}", "{card} ending with {cardno}": "{card} slutter med {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}" "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{number} persons": "{number} personer",
"{number} square meters": "{number} kvadratmeter",
"{number} to {number} persons": "{lowest} til {highest} personer",
"{number} to {number} square meters": "{smallest} til {largest} kvadratmeter"
} }

View File

@@ -294,7 +294,7 @@
"Open language menu": "Öppna språkmenyn", "Open language menu": "Öppna språkmenyn",
"Open menu": "Öppna menyn", "Open menu": "Öppna menyn",
"Open my pages menu": "Öppna mina sidor menyn", "Open my pages menu": "Öppna mina sidor menyn",
"Opening Hours": "Öppettider", "Opening hours": "Öppettider",
"Outdoor": "Utomhus", "Outdoor": "Utomhus",
"OutdoorPool": "Utomhuspool", "OutdoorPool": "Utomhuspool",
"Overview": "Översikt", "Overview": "Översikt",
@@ -549,5 +549,9 @@
"uppercase letter": "stor bokstav", "uppercase letter": "stor bokstav",
"{amount} out of {total}": "{amount} av {total}", "{amount} out of {total}": "{amount} av {total}",
"{card} ending with {cardno}": "{card} som slutar på {cardno}", "{card} ending with {cardno}": "{card} som slutar på {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}" "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{number} persons": "{number} personer",
"{number} square meters": "{number} kvadratmeter",
"{number} to {number} persons": "{lowest} till {highest} personer",
"{number} to {number} square meters": "{smallest} till {largest} kvadratmeter"
} }

View File

@@ -161,3 +161,12 @@ export const getCurrentRewards = cache(
return serverClient().contentstack.rewards.current() return serverClient().contentstack.rewards.current()
} }
) )
export const getMeetingRooms = cache(
async function getMemoizedMeetingRooms(input: {
hotelId: string
language: Lang
}) {
return serverClient().hotel.meetingRooms(input)
}
)

View File

@@ -93,3 +93,8 @@ export const getCityCoordinatesInputSchema = z.object({
address: z.string().optional(), address: z.string().optional(),
}), }),
}) })
export const getMeetingRoomsInputSchema = z.object({
hotelId: z.string(),
language: z.string(),
})

View File

@@ -889,3 +889,49 @@ export const getHotelIdsByCityIdSchema = 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(),
})
),
})

View File

@@ -21,6 +21,7 @@ import {
getHotelDataInputSchema, getHotelDataInputSchema,
getHotelsAvailabilityInputSchema, getHotelsAvailabilityInputSchema,
getHotelsInput, getHotelsInput,
getMeetingRoomsInputSchema,
getRatesInputSchema, getRatesInputSchema,
getRoomPackagesInputSchema, getRoomPackagesInputSchema,
getRoomsAvailabilityInputSchema, getRoomsAvailabilityInputSchema,
@@ -31,6 +32,7 @@ import {
breakfastPackagesSchema, breakfastPackagesSchema,
getHotelDataSchema, getHotelDataSchema,
getHotelsAvailabilitySchema, getHotelsAvailabilitySchema,
getMeetingRoomsSchema,
getRatesSchema, getRatesSchema,
getRoomPackagesSchema, getRoomPackagesSchema,
getRoomsAvailabilitySchema, getRoomsAvailabilitySchema,
@@ -51,6 +53,9 @@ import {
hotelsAvailabilityCounter, hotelsAvailabilityCounter,
hotelsAvailabilityFailCounter, hotelsAvailabilityFailCounter,
hotelsAvailabilitySuccessCounter, hotelsAvailabilitySuccessCounter,
meetingRoomsCounter,
meetingRoomsFailCounter,
meetingRoomsSuccessCounter,
roomsAvailabilityCounter, roomsAvailabilityCounter,
roomsAvailabilityFailCounter, roomsAvailabilityFailCounter,
roomsAvailabilitySuccessCounter, roomsAvailabilitySuccessCounter,
@@ -1168,4 +1173,82 @@ export const hotelQueryRouter = router({
return location return location
}), }),
}), }),
meetingRooms: safeProtectedServiceProcedure
.input(getMeetingRoomsInputSchema)
.query(async function ({ ctx, input }) {
const { hotelId, language } = input
const params: Record<string, string | string[]> = {
hotelId,
language,
}
const metricsData = { ...params, hotelId: input.hotelId }
meetingRoomsCounter.add(1, metricsData)
console.info(
"api.hotels.meetingRooms start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.Hotels.meetingRooms(input.hotelId),
{
cache: undefined,
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
next: {
revalidate: TWENTYFOUR_HOURS,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
meetingRoomsFailCounter.add(1, {
...metricsData,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.meetingRooms error",
JSON.stringify({
query: { params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const validatedMeetingRooms = getMeetingRoomsSchema.safeParse(apiJson)
if (!validatedMeetingRooms.success) {
console.error(
"api.hotels.meetingRooms validation error",
JSON.stringify({
query: { params },
error: validatedMeetingRooms.error,
})
)
throw badRequestError()
}
meetingRoomsSuccessCounter.add(1, {
hotelId,
})
console.info(
"api.hotels.meetingRooms success",
JSON.stringify({ query: { params } })
)
return validatedMeetingRooms.data.data
}),
}) })

View File

@@ -72,3 +72,13 @@ export const getHotelIdsSuccessCounter = meter.createCounter(
export const getHotelIdsFailCounter = meter.createCounter( export const getHotelIdsFailCounter = meter.createCounter(
"trpc.hotel.hotel-ids.get-fail" "trpc.hotel.hotel-ids.get-fail"
) )
export const meetingRoomsCounter = meter.createCounter(
"trpc.hotels.meetingRooms"
)
export const meetingRoomsSuccessCounter = meter.createCounter(
"trpc.hotels.meetingRooms-success"
)
export const meetingRoomsFailCounter = meter.createCounter(
"trpc.hotels.meetingRooms-fail"
)

View File

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

View File

@@ -1,12 +1,22 @@
import type { Hotel } from "@/types/hotel" import type {
Hotel,
RestaurantData,
RestaurantOpeningHours,
} from "@/types/hotel"
export type AmenitiesSidePeekProps = { export type AmenitiesSidePeekProps = {
amenitiesList: Hotel["detailedFacilities"] amenitiesList: Hotel["detailedFacilities"]
parking: Hotel["parking"] parking: Hotel["parking"]
checkInInformation: Hotel["hotelFacts"]["checkin"] checkInInformation: Hotel["hotelFacts"]["checkin"]
accessibility: Hotel["hotelFacts"]["hotelInformation"]["accessibility"] accessibility: Hotel["hotelFacts"]["hotelInformation"]["accessibility"]
restaurants: RestaurantData[]
} }
export type FilteredAmenitiesProps = { export type FilteredAmenitiesProps = {
filteredAmenities: Hotel["detailedFacilities"] filteredAmenities: Hotel["detailedFacilities"]
} }
export interface BreakfastAmenityProps {
openingHours: RestaurantOpeningHours
alternateOpeningHours?: RestaurantOpeningHours
}

View File

@@ -3,5 +3,6 @@ import type { Hotel } from "@/types/hotel"
export type MeetingsAndConferencesSidePeekProps = { export type MeetingsAndConferencesSidePeekProps = {
meetingFacilities: Hotel["conferencesAndMeetings"] meetingFacilities: Hotel["conferencesAndMeetings"]
descriptions: Hotel["hotelContent"]["texts"]["meetingDescription"] descriptions: Hotel["hotelContent"]["texts"]["meetingDescription"]
hotelId: string
link?: string link?: string
} }

View File

@@ -0,0 +1,6 @@
import type { BreakfastAmenityProps } from "./amenities"
export interface OpeningHoursProps extends BreakfastAmenityProps {
heading?: string
type?: "amenities" | "default"
}

View File

@@ -1,4 +1,4 @@
import type { RestaurantData, RestaurantOpeningHours } from "@/types/hotel" import type { RestaurantData } from "@/types/hotel"
export enum DaysEnum { export enum DaysEnum {
Monday = "monday", Monday = "monday",
@@ -17,8 +17,3 @@ export interface RestaurantBarSidePeekProps {
export interface RestaurantBarItemProps { export interface RestaurantBarItemProps {
restaurant: RestaurantData restaurant: RestaurantData
} }
export interface RestaurantBarOpeningHoursProps {
openingHours: RestaurantOpeningHours
alternateOpeningHours?: RestaurantOpeningHours
}

View File

@@ -31,9 +31,9 @@ export function isFacilityImage(card: FacilityCardType): card is FacilityImage {
function setCardProps( function setCardProps(
theme: CardProps["theme"], theme: CardProps["theme"],
heading: string,
buttonText: string, buttonText: string,
href: string, href: string,
heading: string,
scriptedTopTitle?: string scriptedTopTitle?: string
): FacilityCard { ): FacilityCard {
return { return {