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:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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ä"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
})
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
||||||
|
)
|
||||||
|
|||||||
6
types/components/hotelPage/meetingRooms.ts
Normal file
6
types/components/hotelPage/meetingRooms.ts
Normal 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"]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
6
types/components/hotelPage/sidepeek/openingHours.ts
Normal file
6
types/components/hotelPage/sidepeek/openingHours.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type { BreakfastAmenityProps } from "./amenities"
|
||||||
|
|
||||||
|
export interface OpeningHoursProps extends BreakfastAmenityProps {
|
||||||
|
heading?: string
|
||||||
|
type?: "amenities" | "default"
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user