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:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface RoomCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type RoomsProps = {
|
export type RoomsProps = {
|
||||||
|
heading: string
|
||||||
preamble?: string
|
preamble?: string
|
||||||
rooms: Room[]
|
rooms: Room[]
|
||||||
}
|
}
|
||||||
|
|||||||
27
apps/scandic-web/types/components/hotelPage/sections.ts
Normal file
27
apps/scandic-web/types/components/hotelPage/sections.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ export type WellnessAndExerciseSidePeekProps = {
|
|||||||
buttonCTA: string
|
buttonCTA: string
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
heading: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user