feat(SW-1409): Synced tabnavigation headings and section headings on hotel pages

* feat(SW-2409): Added same headings to relevant sidepeeks


Approved-by: Christian Andolf
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-06-03 09:22:28 +00:00
parent a47b2f46d3
commit be98c2faf6
19 changed files with 251 additions and 219 deletions

View File

@@ -12,7 +12,7 @@ import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
import styles from "./accordion.module.css" import styles from "./accordion.module.css"
import type { AccordionProps } from "@/types/components/blocks/Accordion" import type { AccordionProps } from "@/types/components/blocks/Accordion"
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation" import { HotelHashValues } from "@/types/enums/hotelPage"
export default function AccordionSection({ accordion, title }: AccordionProps) { export default function AccordionSection({ accordion, title }: AccordionProps) {
const showToggleButton = accordion.length > 5 const showToggleButton = accordion.length > 5

View File

@@ -13,7 +13,7 @@ import {
type FacilityCardType, type FacilityCardType,
type FacilityGrid, type FacilityGrid,
} from "@/types/components/hotelPage/facilities" } from "@/types/components/hotelPage/facilities"
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation" import { HotelHashValues } from "@/types/enums/hotelPage"
export default async function Facilities({ export default async function Facilities({
facilities, facilities,

View File

@@ -2,7 +2,6 @@
import { cx } from "class-variance-authority" import { cx } from "class-variance-authority"
import { useMemo, useRef, useState } from "react" import { useMemo, useRef, useState } from "react"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -14,10 +13,9 @@ import { RoomCard } from "./RoomCard"
import styles from "./rooms.module.css" import styles from "./rooms.module.css"
import type { RoomsProps } from "@/types/components/hotelPage/room" import type { RoomsProps } from "@/types/components/hotelPage/room"
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation" import { HotelHashValues } from "@/types/enums/hotelPage"
export function Rooms({ rooms, preamble }: RoomsProps) { export function Rooms({ heading, rooms, preamble }: RoomsProps) {
const intl = useIntl()
const showToggleButton = rooms.length > 3 const showToggleButton = rooms.length > 3
const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton) const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton)
const sortedRooms = useMemo(() => { const sortedRooms = useMemo(() => {
@@ -40,12 +38,8 @@ export function Rooms({ rooms, preamble }: RoomsProps) {
> >
<div ref={scrollRef} className={styles.scrollRef}></div> <div ref={scrollRef} className={styles.scrollRef}></div>
<header className={styles.sectionHeader}> <header className={styles.sectionHeader}>
<Typography variant="Title/md" className={styles.heading}> <Typography variant="Title/md">
<h2> <h2 className={styles.heading}>{heading}</h2>
{intl.formatMessage({
defaultMessage: "Rooms",
})}
</h2>
</Typography> </Typography>
{preamble && ( {preamble && (
<Typography variant="Body/Paragraph/mdRegular"> <Typography variant="Body/Paragraph/mdRegular">

View File

@@ -19,6 +19,7 @@ export default async function MeetingsAndConferencesSidePeek({
descriptions, descriptions,
meetingRooms, meetingRooms,
meetingPageUrl, meetingPageUrl,
heading,
}: MeetingsAndConferencesSidePeekProps) { }: MeetingsAndConferencesSidePeekProps) {
const intl = await getIntl() const intl = await getIntl()
const { seatingText, roomText } = await getConferenceRoomTexts(meetingRooms) const { seatingText, roomText } = await getConferenceRoomTexts(meetingRooms)
@@ -26,12 +27,7 @@ export default async function MeetingsAndConferencesSidePeek({
const meetingPageHref = await appendSlugToPathname(meetingPageUrl) const meetingPageHref = await appendSlugToPathname(meetingPageUrl)
return ( return (
<SidePeek <SidePeek contentKey={SidepeekSlugs.meetings} title={heading}>
contentKey={SidepeekSlugs.meetings}
title={intl.formatMessage({
defaultMessage: "Meetings & Conferences",
})}
>
<div className={styles.wrapper}> <div className={styles.wrapper}>
<Subtitle color="burgundy" asChild> <Subtitle color="burgundy" asChild>
<Title level="h3"> <Title level="h3">

View File

@@ -1,5 +1,4 @@
import SidePeek from "@/components/TempDesignSystem/SidePeek" import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import RestaurantBarItem from "./RestaurantBarItem" import RestaurantBarItem from "./RestaurantBarItem"
@@ -8,18 +7,12 @@ import styles from "./restaurantBar.module.css"
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage" import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage"
import type { RestaurantBarSidePeekProps } from "@/types/components/hotelPage/sidepeek/restaurantBar" import type { RestaurantBarSidePeekProps } from "@/types/components/hotelPage/sidepeek/restaurantBar"
export default async function RestaurantBarSidePeek({ export default function RestaurantBarSidePeek({
restaurants, restaurants,
heading,
}: RestaurantBarSidePeekProps) { }: RestaurantBarSidePeekProps) {
const intl = await getIntl()
return ( return (
<SidePeek <SidePeek contentKey={SidepeekSlugs.restaurant} title={heading}>
contentKey={SidepeekSlugs.restaurant}
title={intl.formatMessage({
defaultMessage: "Restaurant & Bar",
})}
>
<div className={styles.content}> <div className={styles.content}>
{restaurants.map((restaurant) => ( {restaurants.map((restaurant) => (
<div key={restaurant.id} className={styles.item}> <div key={restaurant.id} className={styles.item}>

View File

@@ -14,6 +14,7 @@ export default async function WellnessAndExerciseSidePeek({
healthFacilities, healthFacilities,
wellnessExercisePageUrl, wellnessExercisePageUrl,
spaPage, spaPage,
heading,
}: WellnessAndExerciseSidePeekProps) { }: WellnessAndExerciseSidePeekProps) {
const intl = await getIntl() const intl = await getIntl()
const wellnessExercisePageHref = await appendSlugToPathname( const wellnessExercisePageHref = await appendSlugToPathname(
@@ -21,12 +22,7 @@ export default async function WellnessAndExerciseSidePeek({
) )
return ( return (
<SidePeek <SidePeek contentKey={SidepeekSlugs.wellness} title={heading}>
contentKey={SidepeekSlugs.wellness}
title={intl.formatMessage({
defaultMessage: "Gym & Wellness",
})}
>
<div className={styles.wrapper}> <div className={styles.wrapper}>
{healthFacilities.map((facility) => ( {healthFacilities.map((facility) => (
<Facility key={facility.type} data={facility} /> <Facility key={facility.type} data={facility} />

View File

@@ -4,7 +4,6 @@ import { cx } from "class-variance-authority"
import NextLink from "next/link" import NextLink from "next/link"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react" import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -18,24 +17,19 @@ import { trackHotelTabClick } from "@/utils/tracking"
import styles from "./tabNavigation.module.css" import styles from "./tabNavigation.module.css"
import { import type { HotelPageSections } from "@/types/components/hotelPage/sections"
HotelHashValues, import { HotelHashValues } from "@/types/enums/hotelPage"
type TabNavigationProps,
} from "@/types/components/hotelPage/tabNavigation"
export default function TabNavigation({ interface TabNavigationProps {
hasActivities, pageSections: HotelPageSections
hasFAQ, }
hasMeetingRooms,
hasRestaurants, export default function TabNavigation({ pageSections }: TabNavigationProps) {
hasWellness, const activeHash = useHash()
tabValues,
}: TabNavigationProps) {
const hash = useHash()
const intl = useIntl()
const router = useRouter() const router = useRouter()
const tabNavigationRef = useRef<HTMLDivElement>(null) const tabNavigationRef = useRef<HTMLDivElement>(null)
const tabRefs = useMemo(() => new Map<string, HTMLAnchorElement>(), []) const tabRefs = useMemo(() => new Map<string, HTMLAnchorElement>(), [])
const tabLinks = Object.values(pageSections).map(({ hash }) => hash)
useStickyPosition({ useStickyPosition({
ref: tabNavigationRef, ref: tabNavigationRef,
@@ -46,88 +40,7 @@ export default function TabNavigation({
const { containerRef, showLeftShadow, showRightShadow } = const { containerRef, showLeftShadow, showRightShadow } =
useScrollShadows<HTMLDivElement>() useScrollShadows<HTMLDivElement>()
const tabLinks: { hash: HotelHashValues; text: string }[] = [ const { activeSectionId, pauseScrollSpy } = useScrollSpy(tabLinks)
{
hash: HotelHashValues.overview,
text:
tabValues?.overview ||
intl.formatMessage({
defaultMessage: "Overview",
}),
},
{
hash: HotelHashValues.rooms,
text:
tabValues?.rooms ||
intl.formatMessage({
defaultMessage: "Rooms",
}),
},
...(hasRestaurants
? [
{
hash: HotelHashValues.restaurant,
text:
tabValues?.restaurant_bar ||
intl.formatMessage({
defaultMessage: "Restaurant & Bar",
}),
},
]
: []),
...(hasMeetingRooms
? [
{
hash: HotelHashValues.meetings,
text:
tabValues?.conferences_meetings ||
intl.formatMessage({
defaultMessage: "Meetings & Conferences",
}),
},
]
: []),
...(hasWellness
? [
{
hash: HotelHashValues.wellness,
text:
tabValues?.health_wellness ||
intl.formatMessage({
defaultMessage: "Gym & Wellness",
}),
},
]
: []),
...(hasActivities
? [
{
hash: HotelHashValues.activities,
text:
tabValues?.activities ||
intl.formatMessage({
defaultMessage: "Activities",
}),
},
]
: []),
...(hasFAQ
? [
{
hash: HotelHashValues.faq,
text:
tabValues?.faq ||
intl.formatMessage({
defaultMessage: "FAQ",
}),
},
]
: []),
]
const { activeSectionId, pauseScrollSpy } = useScrollSpy(
tabLinks.map(({ hash }) => hash)
)
const scrollLinkToCenter = useCallback( const scrollLinkToCenter = useCallback(
(hash: string) => { (hash: string) => {
@@ -160,9 +73,9 @@ export default function TabNavigation({
) )
useLayoutEffect(() => { useLayoutEffect(() => {
const activeHash = hash || HotelHashValues.overview const hash = activeHash || HotelHashValues.overview
scrollLinkToCenter(activeHash) scrollLinkToCenter(hash)
}, [hash, scrollLinkToCenter]) }, [activeHash, scrollLinkToCenter])
useEffect(() => { useEffect(() => {
if (activeSectionId) { if (activeSectionId) {
@@ -179,19 +92,19 @@ export default function TabNavigation({
})} })}
> >
<nav className={styles.tabsContainer} ref={containerRef}> <nav className={styles.tabsContainer} ref={containerRef}>
{tabLinks.map((link) => { {Object.values(pageSections).map(({ hash, heading }) => {
const isActive = const isActive =
hash === link.hash || activeHash === hash ||
(!hash && link.hash === HotelHashValues.overview) (!activeHash && hash === HotelHashValues.overview)
return ( return (
<Typography <Typography
key={link.hash} key={hash}
variant={ variant={
isActive ? "Body/Paragraph/mdBold" : "Body/Paragraph/mdRegular" isActive ? "Body/Paragraph/mdBold" : "Body/Paragraph/mdRegular"
} }
> >
<NextLink <NextLink
href={`#${link.hash}`} href={`#${hash}`}
className={cx(styles.link, { className={cx(styles.link, {
[styles.active]: isActive, [styles.active]: isActive,
})} })}
@@ -199,15 +112,15 @@ export default function TabNavigation({
scroll={true} scroll={true}
ref={(element) => { ref={(element) => {
if (element) { if (element) {
tabRefs.set(link.hash, element) tabRefs.set(hash, element)
} }
}} }}
onClick={() => { onClick={() => {
pauseScrollSpy() pauseScrollSpy()
trackHotelTabClick(link.text) trackHotelTabClick(heading)
}} }}
> >
{link.text} {heading}
</NextLink> </NextLink>
</Typography> </Typography>
) )

View File

@@ -12,6 +12,7 @@ import Breadcrumbs from "@/components/Breadcrumbs"
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 TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { setFacilityCards } from "@/utils/facilityCards" import { setFacilityCards } from "@/utils/facilityCards"
import { generateHotelSchema } from "@/utils/jsonSchemas" import { generateHotelSchema } from "@/utils/jsonSchemas"
@@ -35,15 +36,20 @@ import PreviewImages from "./PreviewImages"
import { Rooms } from "./Rooms" import { Rooms } from "./Rooms"
import SidePeeks from "./SidePeeks" import SidePeeks from "./SidePeeks"
import TabNavigation from "./TabNavigation" import TabNavigation from "./TabNavigation"
import { getTrackingHotelData, getTrackingPageData } from "./utils" import {
getPageSectionsData,
getTrackingHotelData,
getTrackingPageData,
} from "./utils"
import styles from "./hotelPage.module.css" import styles from "./hotelPage.module.css"
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/enums/hotelPage"
export default async function HotelPage({ hotelId }: HotelPageProps) { export default async function HotelPage({ hotelId }: HotelPageProps) {
const lang = await getLang() const lang = await getLang()
const intl = await getIntl()
void getHotelPage() void getHotelPage()
void getHotel({ void getHotel({
@@ -68,7 +74,12 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
} }
const jsonSchema = generateHotelSchema(hotelData) const jsonSchema = generateHotelSchema(hotelData)
const { faq, content, tabValues } = hotelPageData const {
faq,
content: { spaPage, activitiesCards },
sectionHeadings,
} = hotelPageData
const { hotel, restaurants, roomCategories, additionalData } = hotelData
const { const {
name, name,
address, address,
@@ -84,9 +95,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
ratings, ratings,
parking, parking,
hotelType, hotelType,
} = hotelData.hotel } = hotel
const restaurants = hotelData.restaurants
const roomCategories = hotelData.roomCategories
const { const {
healthAndWellness, healthAndWellness,
healthAndFitness, healthAndFitness,
@@ -98,28 +107,29 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
meetingRooms, meetingRooms,
displayWebPage, displayWebPage,
hotelSpecialNeeds, hotelSpecialNeeds,
} = hotelData.additionalData } = additionalData
const images = gallery?.smallerImages const images = gallery?.smallerImages
const description = hotelContent.texts.descriptions?.medium const description = hotelContent.texts.descriptions?.medium
const pageSections = getPageSectionsData(
const { spaPage, activitiesCards } = content intl,
{
const hasRestaurants = restaurants.length > 0 hasWellness: healthFacilities.length > 0,
const hasMeetingRooms = !!meetingRoomsData?.length hasRestaurants: restaurants.length > 0,
const hasWellness = healthFacilities.length > 0 hasMeetingRooms: !!(meetingRoomsData && meetingRoomsData.length > 0),
hasActivities: activitiesCards.length > 0,
hasFAQ: !!(faq && faq.accordions.length > 0),
},
sectionHeadings
)
const facilities = setFacilityCards( const facilities = setFacilityCards(
restaurantImages ?? undefined, restaurantImages ?? undefined,
conferencesAndMeetings ?? undefined, conferencesAndMeetings ?? undefined,
healthAndWellness ?? undefined, healthAndWellness ?? undefined,
hasRestaurants, pageSections
hasMeetingRooms,
hasWellness
) )
const topThreePois = pointsOfInterest.slice(0, 3)
const coordinates = { const coordinates = {
lat: location.latitude, lat: location.latitude,
lng: location.longitude, lng: location.longitude,
@@ -148,14 +158,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
<PreviewImages images={images} hotelName={name} /> <PreviewImages images={images} hotelName={name} />
) : null} ) : null}
</header> </header>
<TabNavigation <TabNavigation pageSections={pageSections} />
hasActivities={activitiesCards.length > 0}
hasFAQ={!!faq?.accordions.length}
hasWellness={hasWellness}
hasRestaurants={hasRestaurants}
hasMeetingRooms={hasMeetingRooms}
tabValues={tabValues}
/>
<main className={styles.mainSection}> <main className={styles.mainSection}>
<div id={HotelHashValues.overview} className={styles.overview}> <div id={HotelHashValues.overview} className={styles.overview}>
@@ -185,7 +188,11 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
</div> </div>
) : null} ) : null}
</div> </div>
<Rooms rooms={roomCategories} preamble={hotelRoomElevatorPitchText} /> <Rooms
heading={pageSections.rooms.heading}
rooms={roomCategories}
preamble={hotelRoomElevatorPitchText}
/>
{facilities && ( {facilities && (
<Facilities <Facilities
facilities={facilities} facilities={facilities}
@@ -205,7 +212,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
hotelName={name} hotelName={name}
markerInfo={{ hotelType, hotelId }} markerInfo={{ hotelType, hotelId }}
/> />
<MapCard hotelName={name} pois={topThreePois} /> <MapCard hotelName={name} pois={pointsOfInterest.slice(0, 3)} />
</MapWithCardWrapper> </MapWithCardWrapper>
</aside> </aside>
<MobileMapToggle /> <MobileMapToggle />
@@ -237,8 +244,9 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
ecoLabels={hotelFacts.ecoLabels} ecoLabels={hotelFacts.ecoLabels}
descriptions={hotelContent.texts} descriptions={hotelContent.texts}
/> />
{hasWellness ? ( {pageSections.wellness ? (
<WellnessAndExerciseSidePeek <WellnessAndExerciseSidePeek
heading={pageSections.wellness.heading}
healthFacilities={healthFacilities} healthFacilities={healthFacilities}
spaPage={spaPage?.spa_page} spaPage={spaPage?.spa_page}
wellnessExercisePageUrl={ wellnessExercisePageUrl={
@@ -246,8 +254,11 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
} }
/> />
) : null} ) : null}
{hasRestaurants ? ( {pageSections.restaurant ? (
<RestaurantBarSidePeek restaurants={restaurants} /> <RestaurantBarSidePeek
heading={pageSections.restaurant.heading}
restaurants={restaurants}
/>
) : null} ) : null}
{activitiesCards.map((card) => ( {activitiesCards.map((card) => (
<ActivitiesSidePeek <ActivitiesSidePeek
@@ -257,8 +268,9 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
sidepeekSlug={card.upcoming_activities_card.sidepeekSlug} sidepeekSlug={card.upcoming_activities_card.sidepeekSlug}
/> />
))} ))}
{hasMeetingRooms && ( {pageSections.meetings ? (
<MeetingsAndConferencesSidePeek <MeetingsAndConferencesSidePeek
heading={pageSections.meetings.heading}
meetingFacilities={conferencesAndMeetings} meetingFacilities={conferencesAndMeetings}
descriptions={hotelContent.texts.meetingDescription} descriptions={hotelContent.texts.meetingDescription}
meetingRooms={meetingRoomsData ?? []} meetingRooms={meetingRoomsData ?? []}
@@ -266,7 +278,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
displayWebPage.meetingRoom ? meetingRooms.nameInUrl : undefined displayWebPage.meetingRoom ? meetingRooms.nameInUrl : undefined
} }
/> />
)} ) : null}
{roomCategories.map((room) => ( {roomCategories.map((room) => (
<RoomSidePeek key={room.name} hotelId={hotelId} room={room} /> <RoomSidePeek key={room.name} hotelId={hotelId} room={room} />
))} ))}

View File

@@ -1,11 +1,16 @@
import type { IntlShape } from "react-intl" import type { IntlShape } from "react-intl"
import { HealthFacilitiesEnum } from "@/types/components/hotelPage/facilities" import { HealthFacilitiesEnum } from "@/types/components/hotelPage/facilities"
import type {
HotelPageSectionHeadings,
HotelPageSections,
} from "@/types/components/hotelPage/sections"
import { import {
TrackingChannelEnum, TrackingChannelEnum,
type TrackingSDKHotelInfo, type TrackingSDKHotelInfo,
type TrackingSDKPageData, type TrackingSDKPageData,
} from "@/types/components/tracking" } from "@/types/components/tracking"
import { HotelHashValues } from "@/types/enums/hotelPage"
import type { Hotel, HotelData } from "@/types/hotel" import type { Hotel, HotelData } from "@/types/hotel"
import type { HotelPage } from "@/types/trpc/routers/contentstack/hotelPage" import type { HotelPage } from "@/types/trpc/routers/contentstack/hotelPage"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
@@ -84,3 +89,94 @@ export function translateWellnessType(type: string, intl: IntlShape) {
}) })
} }
} }
export function getPageSectionsData(
intl: IntlShape,
dynamicSections: {
hasWellness: boolean
hasRestaurants: boolean
hasMeetingRooms: boolean
hasActivities: boolean
hasFAQ: boolean
},
sectionHeadings?: HotelPageSectionHeadings | null
) {
const {
hasWellness,
hasRestaurants,
hasMeetingRooms,
hasActivities,
hasFAQ,
} = dynamicSections
const sections: HotelPageSections = {
overview: {
hash: HotelHashValues.overview,
heading:
sectionHeadings?.overview ||
intl.formatMessage({
defaultMessage: "Overview",
}),
},
rooms: {
hash: HotelHashValues.rooms,
heading:
sectionHeadings?.rooms ||
intl.formatMessage({
defaultMessage: "Rooms",
}),
},
}
if (hasRestaurants) {
sections.restaurant = {
hash: HotelHashValues.restaurant,
heading:
sectionHeadings?.restaurant_bar ||
intl.formatMessage({
defaultMessage: "Restaurant & Bar",
}),
}
}
if (hasMeetingRooms) {
sections.meetings = {
hash: HotelHashValues.meetings,
heading:
sectionHeadings?.conferences_meetings ||
intl.formatMessage({
defaultMessage: "Meetings & Conferences",
}),
}
}
if (hasWellness) {
sections.wellness = {
hash: HotelHashValues.wellness,
heading:
sectionHeadings?.health_wellness ||
intl.formatMessage({
defaultMessage: "Gym & Wellness",
}),
}
}
if (hasActivities) {
sections.activities = {
hash: HotelHashValues.activities,
heading:
sectionHeadings?.activities ||
intl.formatMessage({
defaultMessage: "Activities",
}),
}
}
if (hasFAQ) {
sections.faq = {
hash: HotelHashValues.faq,
heading:
sectionHeadings?.faq ||
intl.formatMessage({
defaultMessage: "FAQ",
}),
}
}
return sections
}

View File

@@ -82,7 +82,7 @@ export const hotelPageSchema = z.object({
), ),
}) })
.transform(({ hotel_navigation, ...rest }) => ({ .transform(({ hotel_navigation, ...rest }) => ({
tabValues: hotel_navigation, sectionHeadings: hotel_navigation,
...rest, ...rest,
})), })),
}) })

View File

@@ -1,3 +1,5 @@
import type { HotelHashValues } from "@/types/enums/hotelPage"
export interface HotelPageProps { export interface HotelPageProps {
hotelId: string hotelId: string
} }
@@ -10,3 +12,6 @@ export enum SidepeekSlugs {
meetings = "meetings", meetings = "meetings",
wellness = "wellness", wellness = "wellness",
} }
export type HotelHashValue =
(typeof HotelHashValues)[keyof typeof HotelHashValues]

View File

@@ -5,6 +5,7 @@ export interface RoomCardProps {
} }
export type RoomsProps = { export type RoomsProps = {
heading: string
preamble?: string preamble?: string
rooms: Room[] rooms: Room[]
} }

View File

@@ -0,0 +1,27 @@
import type { HotelHashValue } from "./hotelPage"
export interface HotelPageSectionHeadings {
overview?: string | null
rooms?: string | null
restaurant_bar?: string | null
conferences_meetings?: string | null
health_wellness?: string | null
activities?: string | null
offers?: string | null
faq?: string | null
}
interface HotelPageSection {
hash: HotelHashValue
heading: string
}
export interface HotelPageSections {
overview: HotelPageSection
rooms: HotelPageSection
restaurant?: HotelPageSection
meetings?: HotelPageSection
wellness?: HotelPageSection
activities?: HotelPageSection
faq?: HotelPageSection
}

View File

@@ -6,4 +6,5 @@ export type MeetingsAndConferencesSidePeekProps = {
descriptions: Hotel["hotelContent"]["texts"]["meetingDescription"] descriptions: Hotel["hotelContent"]["texts"]["meetingDescription"]
meetingRooms: MeetingRooms meetingRooms: MeetingRooms
meetingPageUrl: string | undefined meetingPageUrl: string | undefined
heading: string
} }

View File

@@ -2,6 +2,7 @@ import type { Restaurant } from "@/types/hotel"
export interface RestaurantBarSidePeekProps { export interface RestaurantBarSidePeekProps {
restaurants: Restaurant[] restaurants: Restaurant[]
heading: string
} }
export interface RestaurantBarItemProps { export interface RestaurantBarItemProps {

View File

@@ -7,4 +7,5 @@ export type WellnessAndExerciseSidePeekProps = {
buttonCTA: string buttonCTA: string
url: string url: string
} }
heading: string
} }

View File

@@ -1,29 +0,0 @@
export enum HotelHashValues {
overview = "overview",
rooms = "rooms",
restaurant = "restaurants",
meetings = "meetings",
wellness = "wellness",
activities = "activities",
faq = "faq",
}
type Tabs = {
overview?: string | null
rooms?: string | null
restaurant_bar?: string | null
conferences_meetings?: string | null
health_wellness?: string | null
activities?: string | null
offers?: string | null
faq?: string | null
}
export type TabNavigationProps = {
hasActivities: boolean
hasFAQ: boolean
hasWellness: boolean
hasRestaurants: boolean
hasMeetingRooms: boolean
tabValues?: Tabs | null
}

View File

@@ -7,3 +7,13 @@ export namespace HotelPageEnum {
} }
} }
} }
export const HotelHashValues = {
overview: "overview",
rooms: "rooms",
restaurant: "restaurants",
meetings: "meetings",
wellness: "wellness",
activities: "activities",
faq: "faq",
} as const

View File

@@ -11,9 +11,13 @@ import {
RestaurantHeadings, RestaurantHeadings,
WellnessHeadings, WellnessHeadings,
} from "@/types/components/hotelPage/facilities" } from "@/types/components/hotelPage/facilities"
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage" import {
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation" type HotelHashValue,
SidepeekSlugs,
} from "@/types/components/hotelPage/hotelPage";
import type { HotelPageSections } from "@/types/components/hotelPage/sections"
import { FacilityEnum } from "@/types/enums/facilities" import { FacilityEnum } from "@/types/enums/facilities"
import { HotelHashValues } from "@/types/enums/hotelPage"
import type { import type {
Amenities, Amenities,
Facility, Facility,
@@ -26,24 +30,34 @@ export function setFacilityCards(
restaurantImages: FacilityData | undefined, restaurantImages: FacilityData | undefined,
conferencesAndMeetings: FacilityData | undefined, conferencesAndMeetings: FacilityData | undefined,
healthAndWellness: FacilityData | undefined, healthAndWellness: FacilityData | undefined,
hasRestaurants: boolean, pageSections: HotelPageSections
hasMeetingRooms: boolean,
hasWellness: boolean
): Facility[] { ): Facility[] {
const facilities = [] const facilities = []
if (hasRestaurants) { if (pageSections.restaurant) {
facilities.push( facilities.push(
setFacilityCard(restaurantImages, FacilityCardTypeEnum.restaurant) setFacilityCard(
restaurantImages,
FacilityCardTypeEnum.restaurant,
pageSections.restaurant.heading
)
) )
} }
if (hasMeetingRooms) { if (pageSections.meetings) {
facilities.push( facilities.push(
setFacilityCard(conferencesAndMeetings, FacilityCardTypeEnum.conference) setFacilityCard(
conferencesAndMeetings,
FacilityCardTypeEnum.conference,
pageSections.meetings.heading
)
) )
} }
if (hasWellness) { if (pageSections.wellness) {
facilities.push( facilities.push(
setFacilityCard(healthAndWellness, FacilityCardTypeEnum.wellness) setFacilityCard(
healthAndWellness,
FacilityCardTypeEnum.wellness,
pageSections.wellness.heading
)
) )
} }
return facilities return facilities
@@ -51,12 +65,13 @@ export function setFacilityCards(
function setFacilityCard( function setFacilityCard(
facility: FacilityData | undefined, facility: FacilityData | undefined,
type: FacilityCardTypeEnum type: FacilityCardTypeEnum,
heading: string
): Facility { ): Facility {
return { return {
...facility, ...facility,
id: type, id: type,
headingText: facility?.headingText ?? "", headingText: heading,
heroImages: facility?.heroImages ?? [], heroImages: facility?.heroImages ?? [],
} }
} }
@@ -72,7 +87,7 @@ export function isFacilityImage(card: FacilityCardType): card is FacilityImage {
function setCardProps( function setCardProps(
theme: CardProps["theme"], theme: CardProps["theme"],
buttonText: (typeof FacilityCardButtonText)[keyof typeof FacilityCardButtonText], buttonText: (typeof FacilityCardButtonText)[keyof typeof FacilityCardButtonText],
href: HotelHashValues, href: HotelHashValue,
heading: string, heading: string,
slug: SidepeekSlugs, slug: SidepeekSlugs,
scriptedTopTitle?: string scriptedTopTitle?: string