Merged in feat/SW-1065-meetings-page (pull request #1287)

Feat(SW-1065): Meetings hotel subpage

Approved-by: Erik Tiekstra
This commit is contained in:
Matilda Landström
2025-02-12 15:13:17 +00:00
parent cac090df34
commit c0e4553d9f
31 changed files with 669 additions and 15 deletions

View File

@@ -21,7 +21,7 @@ export default async function MeetingsAndConferencesSidePeek({
meetingFacilities,
descriptions,
hotelId,
link,
meetingPageUrl,
}: MeetingsAndConferencesSidePeekProps) {
const lang = getLang()
const [intl, meetingRooms] = await Promise.all([
@@ -83,10 +83,15 @@ export default async function MeetingsAndConferencesSidePeek({
</Body>
) : null}
{link && (
{meetingPageUrl && (
<div className={styles.buttonContainer}>
<Button fullWidth theme="base" intent="secondary" asChild>
<Link href={link} weight="bold" color="burgundy">
<Link
href={`/${meetingPageUrl}`}
weight="bold"
color="burgundy"
appendToCurrentPath
>
{intl.formatMessage({ id: "About meetings & conferences" })}
</Link>
</Button>

View File

@@ -88,6 +88,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
hotelRoomElevatorPitchText,
gallery,
hotelParking,
meetingRooms,
displayWebPage,
hotelSpecialNeeds,
} = hotelData.additionalData
@@ -259,6 +260,9 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
meetingFacilities={conferencesAndMeetings}
descriptions={hotelContent.texts.meetingDescription}
hotelId={hotelId}
meetingPageUrl={
displayWebPage.meetingRoom ? meetingRooms.nameInUrl : undefined
}
/>
{roomCategories.map((room) => (
<RoomSidePeek key={room.name} room={room} />

View File

@@ -0,0 +1,56 @@
"use client"
import { useRef, useState } from "react"
import { useIntl } from "react-intl"
import Grids from "@/components/TempDesignSystem/Grids"
import MeetingRoomCard from "@/components/TempDesignSystem/MeetingRoomCard"
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
import styles from "./meetingsAdditionalContent.module.css"
import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms"
interface MeetingsAdditionalContentProps {
rooms: MeetingRooms
}
export default function MeetingsAdditionalContent({
rooms,
}: MeetingsAdditionalContentProps) {
const intl = useIntl()
const showToggleButton = rooms.length > 3
const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton)
const scrollRef = useRef<HTMLDivElement>(null)
function handleShowMore() {
if (scrollRef.current && allRoomsVisible) {
scrollRef.current.scrollIntoView({ behavior: "smooth" })
}
setAllRoomsVisible((state) => !state)
}
return (
<div className={styles.section} ref={scrollRef}>
<Grids.Stackable
className={`${styles.grid} ${allRoomsVisible ? styles.allVisible : ""}`}
>
{rooms.map((room) => (
<MeetingRoomCard key={room.id} room={room.attributes} />
))}
</Grids.Stackable>
{showToggleButton ? (
<ShowMoreButton
loadMoreData={handleShowMore}
showLess={allRoomsVisible}
textShowMore={intl.formatMessage({
id: "Show more rooms",
})}
textShowLess={intl.formatMessage({
id: "Show less rooms",
})}
/>
) : null}
</div>
)
}

View File

@@ -0,0 +1,9 @@
.grid:not(.allVisible) > :nth-child(n + 4) {
display: none;
}
.section {
display: grid;
gap: var(--Spacing-x4);
z-index: 0;
}

View File

@@ -1,5 +1,3 @@
import { getLang } from "@/i18n/serverContext"
import AccessibilityAdditionalContent from "./Accessibility"
import ParkingAdditionalContent from "./Parking"

View File

@@ -0,0 +1,47 @@
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import styles from "./sidebar.module.css"
import { Country } from "@/types/enums/country"
interface MeetingsSidebarProps {
phoneNumber: string
email?: string
country: string
}
export default async function MeetingsSidebar({
phoneNumber,
email,
country,
}: MeetingsSidebarProps) {
const intl = await getIntl()
return (
<aside className={styles.sidebar}>
<Title level="h3" as="h4">
{intl.formatMessage({ id: "Contact us" })}
</Title>
<div>
<Link
href={`tel:${phoneNumber}`}
color="peach80"
textDecoration="underline"
>
{phoneNumber}
</Link>
{country === Country.Finland ? (
<Body>
{intl.formatMessage({
id: "Price 0,16 €/min + local call charges",
})}
</Body>
) : null}
<Body>{email} </Body>
</div>
</aside>
)
}

View File

@@ -1,7 +1,9 @@
import RestaurantSidebar from "./RestaurantSidebar/RestaurantSidebar"
import MeetingsSidebar from "./MeetingsSidebar"
import ParkingSidebar from "./ParkingSidebar"
import WellnessSidebar from "./WellnessSidebar"
import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms"
import type { AdditionalData, Hotel, Restaurant } from "@/types/hotel"
interface HotelSubpageSidebarProps {
@@ -9,6 +11,7 @@ interface HotelSubpageSidebarProps {
hotel: Hotel
additionalData: AdditionalData
restaurants: Restaurant[]
meetingRooms: MeetingRooms | undefined
}
export default function HotelSubpageSidebar({
@@ -16,6 +19,7 @@ export default function HotelSubpageSidebar({
hotel,
additionalData,
restaurants,
meetingRooms,
}: HotelSubpageSidebarProps) {
const restaurantSubPage = restaurants.find(
(restaurant) => restaurant.nameInUrl === subpage
@@ -32,6 +36,17 @@ export default function HotelSubpageSidebar({
return <WellnessSidebar hotel={hotel} />
case additionalData.hotelSpecialNeeds.nameInUrl:
return null
case additionalData.meetingRooms.nameInUrl:
if (!meetingRooms) {
return null
}
return (
<MeetingsSidebar
phoneNumber={meetingRooms[0].attributes.phoneNumber}
email={meetingRooms[0].attributes.email}
country={hotel.address.country}
/>
)
default:
return null
}

View File

@@ -1,4 +1,13 @@
.sidebar {
display: grid;
gap: var(--Spacing-x2);
grid-column: 1;
}
@media (min-width: 1367px) {
.sidebar {
grid-column: 2;
grid-row: 1;
align-items: start;
}
}

View File

@@ -29,6 +29,7 @@
display: grid;
width: 100%;
gap: var(--Spacing-x4);
grid-column: 1;
}
.intro {
@@ -36,6 +37,10 @@
gap: var(--Spacing-x2);
}
.meetingsContent {
grid-column: 1;
}
@media (min-width: 1367px) {
.contentContainer {
grid-template-columns: var(--max-width-text-block) 1fr;
@@ -47,4 +52,8 @@
max-width: none;
margin: 0;
}
.meetingsContent {
grid-column: 1 / span 2;
}
}

View File

@@ -1,7 +1,11 @@
import { notFound } from "next/navigation"
import { Suspense } from "react"
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
import {
getHotel,
getHotelPage,
getMeetingRooms,
} from "@/lib/trpc/memoizedRequests"
import Breadcrumbs from "@/components/Breadcrumbs"
import Hero from "@/components/Hero"
@@ -11,6 +15,7 @@ import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import MeetingsAdditionalContent from "./AdditionalContent/Meetings"
import HotelSubpageAdditionalContent from "./AdditionalContent"
import HtmlContent from "./HtmlContent"
import HotelSubpageSidebar from "./Sidebar"
@@ -18,10 +23,7 @@ import { getSubpageData } from "./utils"
import styles from "./hotelSubpage.module.css"
interface HotelSubpageProps {
hotelId: string
subpage: string
}
import type { HotelSubpageProps } from "@/types/components/hotelPage/subpage"
export default async function HotelSubpage({
hotelId,
@@ -45,6 +47,11 @@ export default async function HotelSubpage({
const { hotel, restaurants, additionalData } = hotelData
let meetingRooms
if (hotelData.additionalData.meetingRooms.nameInUrl === subpage) {
meetingRooms = await getMeetingRooms({ hotelId: hotelId, language: lang })
}
return (
<>
<section className={styles.hotelSubpage}>
@@ -80,11 +87,18 @@ export default async function HotelSubpage({
/>
</main>
{meetingRooms && (
<div className={styles.meetingsContent}>
<MeetingsAdditionalContent rooms={meetingRooms} />
</div>
)}
<HotelSubpageSidebar
subpage={subpage}
hotel={hotel}
additionalData={additionalData}
restaurants={restaurants}
meetingRooms={meetingRooms}
/>
</div>
</section>

View File

@@ -74,6 +74,19 @@ export function getSubpageData(
}
: null,
}
case additionalData.meetingRooms.nameInUrl:
const meetingImage = hotel.conferencesAndMeetings?.heroImages[0]
return {
elevatorPitch: hotel.hotelContent.texts.meetingDescription?.medium,
mainBody: additionalData.meetingRooms.mainBody,
heading: intl.formatMessage({ id: "Meetings, Conferences & Events" }),
heroImage: meetingImage
? {
src: meetingImage.imageSizes.medium,
alt: meetingImage.metaData.altText || "",
}
: null,
}
default:
return null
}