Merged in feat/SW-1066-wellness-subpage (pull request #1239)
Feat/SW-1066 wellness subpage * feat(SW-1066): added wellness subpage Approved-by: Fredrik Thorsson Approved-by: Matilda Landström
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { wellnessAndExercise } from "@/constants/routes/hotelPageParams"
|
||||
import { wellnessSubPage } from "@/constants/routes/hotelSubpages"
|
||||
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
@@ -14,7 +15,7 @@ import type { WellnessAndExerciseSidePeekProps } from "@/types/components/hotelP
|
||||
|
||||
export default async function WellnessAndExerciseSidePeek({
|
||||
healthFacilities,
|
||||
wellnessExerciseButton,
|
||||
wellnessExerciseButton = false,
|
||||
spaPage,
|
||||
}: WellnessAndExerciseSidePeekProps) {
|
||||
const intl = await getIntl()
|
||||
@@ -42,9 +43,10 @@ export default async function WellnessAndExerciseSidePeek({
|
||||
{wellnessExerciseButton && (
|
||||
<Button fullWidth theme="base" intent="secondary" asChild>
|
||||
<Link
|
||||
href={wellnessExerciseButton}
|
||||
href={`/${wellnessSubPage[lang]}`}
|
||||
weight="bold"
|
||||
color="burgundy"
|
||||
appendToCurrentPath
|
||||
>
|
||||
{intl.formatMessage({ id: "Show wellness & exercise" })}
|
||||
</Link>
|
||||
|
||||
@@ -231,6 +231,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
||||
<WellnessAndExerciseSidePeek
|
||||
healthFacilities={healthFacilities}
|
||||
spaPage={spaPage?.spa_page}
|
||||
wellnessExerciseButton={displayWebPage.healthGym}
|
||||
/>
|
||||
<RestaurantBarSidePeek restaurants={restaurants} />
|
||||
{activitiesCards.map((card) => (
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { wellnessSubPage } from "@/constants/routes/hotelSubpages"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import type { Hotel } from "@/types/hotel"
|
||||
|
||||
interface HotelSubpageAdditionalContentProps {
|
||||
subpage: string
|
||||
hotel: Hotel
|
||||
}
|
||||
|
||||
export default function HotelSubpageAdditionalContent({
|
||||
subpage,
|
||||
hotel,
|
||||
}: HotelSubpageAdditionalContentProps) {
|
||||
const lang = getLang()
|
||||
|
||||
switch (subpage) {
|
||||
case wellnessSubPage[lang]:
|
||||
return null
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
.htmlContent {
|
||||
}
|
||||
16
components/ContentType/HotelSubpage/HtmlContent/index.tsx
Normal file
16
components/ContentType/HotelSubpage/HtmlContent/index.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import styles from "./htmlContent.module.css"
|
||||
|
||||
interface HtmlContentProps {
|
||||
html: string
|
||||
}
|
||||
|
||||
export default function HtmlContent({ html }: HtmlContentProps) {
|
||||
return (
|
||||
<div
|
||||
className={styles.htmlContent}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: html,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./sidebar.module.css"
|
||||
|
||||
import type { Hotel } from "@/types/hotel"
|
||||
|
||||
interface WellnessSidebarProps {
|
||||
hotel: Hotel
|
||||
}
|
||||
|
||||
export default async function WellnessSidebar({ hotel }: WellnessSidebarProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<aside className={styles.sidebar}>
|
||||
<Title level="h3" as="h4">
|
||||
{intl.formatMessage({ id: "Opening hours" })}
|
||||
</Title>
|
||||
{hotel.healthFacilities.map((facility) => (
|
||||
<div key={facility.type}>
|
||||
<Subtitle type="two" color="uiTextHighContrast" asChild>
|
||||
<h4>{intl.formatMessage({ id: facility.type })}</h4>
|
||||
</Subtitle>
|
||||
<Body color="uiTextHighContrast">
|
||||
{facility.openingDetails.openingHours.ordinary.alwaysOpen
|
||||
? intl.formatMessage({ id: "Mon-Fri Always open" })
|
||||
: intl.formatMessage(
|
||||
{ id: "Mon-Fri {openingTime}-{closingTime}" },
|
||||
{
|
||||
openingTime:
|
||||
facility.openingDetails.openingHours.ordinary.openingTime,
|
||||
closingTime:
|
||||
facility.openingDetails.openingHours.ordinary.closingTime,
|
||||
}
|
||||
)}
|
||||
</Body>
|
||||
<Body color="uiTextHighContrast">
|
||||
{facility.openingDetails.openingHours.weekends.alwaysOpen
|
||||
? intl.formatMessage({ id: "Sat-Sun Always open" })
|
||||
: intl.formatMessage(
|
||||
{ id: "Sat-Sun {openingTime}-{closingTime}" },
|
||||
{
|
||||
openingTime:
|
||||
facility.openingDetails.openingHours.weekends.openingTime,
|
||||
closingTime:
|
||||
facility.openingDetails.openingHours.weekends.closingTime,
|
||||
}
|
||||
)}
|
||||
</Body>
|
||||
</div>
|
||||
))}
|
||||
<Title level="h3" as="h4">
|
||||
{intl.formatMessage({ id: "Address" })}
|
||||
</Title>
|
||||
<div>
|
||||
<Body color="uiTextHighContrast">{hotel.address.streetAddress}</Body>
|
||||
<Body color="uiTextHighContrast">
|
||||
{hotel.address.zipCode} {hotel.address.city}
|
||||
</Body>
|
||||
<Body color="uiTextHighContrast">{hotel.address.country}</Body>
|
||||
</div>
|
||||
|
||||
<Title level="h3" as="h4">
|
||||
{intl.formatMessage({ id: "Contact us" })}
|
||||
</Title>
|
||||
<Link
|
||||
href={`tel:${hotel.contactInformation.phoneNumber}`}
|
||||
color="peach80"
|
||||
textDecoration="underline"
|
||||
>
|
||||
{hotel.contactInformation.phoneNumber}
|
||||
</Link>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
25
components/ContentType/HotelSubpage/Sidebar/index.tsx
Normal file
25
components/ContentType/HotelSubpage/Sidebar/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { wellnessSubPage } from "@/constants/routes/hotelSubpages"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import WellnessSidebar from "./WellnessSidebar"
|
||||
|
||||
import type { Hotel } from "@/types/hotel"
|
||||
|
||||
interface HotelSubpageSidebarProps {
|
||||
subpage: string
|
||||
hotel: Hotel
|
||||
}
|
||||
|
||||
export default function HotelSubpageSidebar({
|
||||
subpage,
|
||||
hotel,
|
||||
}: HotelSubpageSidebarProps) {
|
||||
const lang = getLang()
|
||||
switch (subpage) {
|
||||
case wellnessSubPage[lang]:
|
||||
return <WellnessSidebar hotel={hotel} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.sidebar {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
@@ -3,71 +3,46 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x4);
|
||||
background-color: var(--Base-Surface-Subtle-Normal);
|
||||
padding: var(--Spacing-x4) 0;
|
||||
padding-bottom: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.heroContainer {
|
||||
.heroWrapper {
|
||||
width: 100%;
|
||||
padding: var(--Spacing-x4) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.heroContainer img {
|
||||
max-width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"main"
|
||||
"sidebar";
|
||||
gap: var(--Spacing-x4);
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
padding: var(--Spacing-x4) var(--Spacing-x2) 0;
|
||||
max-width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
padding: var(--Spacing-x4) 0;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
grid-area: main;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
gap: var(--Spacing-x6);
|
||||
margin: 0 auto;
|
||||
max-width: var(--max-width-content);
|
||||
gap: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.contentContainer {
|
||||
padding: var(--Spacing-x4) 0;
|
||||
}
|
||||
|
||||
.heroContainer {
|
||||
padding: var(--Spacing-x4) 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: var(--Spacing-x4) 0;
|
||||
}
|
||||
.intro {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
@media (min-width: 1367px) {
|
||||
.heroContainer {
|
||||
padding: var(--Spacing-x4) 0;
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
grid-template-areas: "main sidebar";
|
||||
grid-template-columns: var(--max-width-text-block) 1fr;
|
||||
gap: var(--Spacing-x9);
|
||||
padding: var(--Spacing-x4) 0 0;
|
||||
max-width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
gap: var(--Spacing-x9);
|
||||
padding: 0;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||
import Hero from "@/components/Hero"
|
||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import Preamble from "@/components/TempDesignSystem/Text/Preamble"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import HotelSubpageAdditionalContent from "./AdditionalContent"
|
||||
import HtmlContent from "./HtmlContent"
|
||||
import HotelSubpageSidebar from "./Sidebar"
|
||||
import { getSubpageData } from "./utils"
|
||||
|
||||
import styles from "./hotelSubpage.module.css"
|
||||
@@ -20,39 +28,51 @@ export default async function HotelSubpage({
|
||||
subpage,
|
||||
}: HotelSubpageProps) {
|
||||
const lang = getLang()
|
||||
const [hotelPageData, hotel] = await Promise.all([
|
||||
const [intl, hotelPageData, hotelData] = await Promise.all([
|
||||
getIntl(),
|
||||
getHotelPage(),
|
||||
getHotel({ hotelId, language: lang }),
|
||||
])
|
||||
|
||||
if (!hotel?.hotel || !hotelPageData) {
|
||||
if (!hotelData?.hotel || !hotelPageData) {
|
||||
notFound()
|
||||
}
|
||||
const pageData = getSubpageData(subpage, lang, hotel.additionalData)
|
||||
|
||||
const pageData = getSubpageData(intl, subpage, hotelData)
|
||||
if (!pageData) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const hotelData = hotel.hotel
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={styles.hotelSubpage}>
|
||||
<header className={styles.header}>
|
||||
{/* breadcrumbs */}
|
||||
<div className={styles.heroContainer}>{/* hero image */}</div>
|
||||
</header>
|
||||
<div className={styles.header}>
|
||||
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||
<Breadcrumbs variant="hotelSubpage" />
|
||||
</Suspense>
|
||||
{pageData.heroImage && (
|
||||
<div className={styles.heroWrapper}>
|
||||
<Hero src={pageData.heroImage.src} alt={pageData.heroImage.alt} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.contentContainer}>
|
||||
<main className={styles.mainContent}>
|
||||
{/* Main content */}
|
||||
<Title level="h1">
|
||||
{subpage} for {hotelData.name}
|
||||
</Title>
|
||||
<Body>{pageData.elevatorPitch}</Body>
|
||||
<div className={styles.intro}>
|
||||
<Title level="h1">{pageData.heading}</Title>
|
||||
<Preamble>{pageData.elevatorPitch}</Preamble>
|
||||
</div>
|
||||
|
||||
{pageData.mainBody && <HtmlContent html={pageData.mainBody} />}
|
||||
|
||||
<HotelSubpageAdditionalContent
|
||||
subpage={subpage}
|
||||
hotel={hotelData.hotel}
|
||||
/>
|
||||
</main>
|
||||
|
||||
{/* Sidebar */}
|
||||
<HotelSubpageSidebar subpage={subpage} hotel={hotelData.hotel} />
|
||||
</div>
|
||||
</section>
|
||||
{/* Tracking */}
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
import { parkingSubPage } from "@/constants/routes/hotelSubpages"
|
||||
import { wellnessSubPage } from "@/constants/routes/hotelSubpages"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import type { IntlShape } from "react-intl"
|
||||
|
||||
import type { HotelData } from "@/types/hotel"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export function getSubpageData(
|
||||
intl: IntlShape,
|
||||
subpage: string,
|
||||
lang: Lang,
|
||||
additionalData: HotelData["additionalData"]
|
||||
hotelData: HotelData
|
||||
) {
|
||||
const lang = getLang()
|
||||
const additionalData = hotelData.additionalData
|
||||
const hotel = hotelData.hotel
|
||||
switch (subpage) {
|
||||
case parkingSubPage[lang]:
|
||||
return additionalData.hotelParking
|
||||
case wellnessSubPage[lang]:
|
||||
const heroImage = hotel.healthFacilities.find(
|
||||
(fac) => fac.content.images.length
|
||||
)?.content.images[0]
|
||||
return {
|
||||
...additionalData.healthAndFitness,
|
||||
heading: intl.formatMessage({ id: "Wellness & Exercise" }),
|
||||
heroImage: heroImage
|
||||
? {
|
||||
src: heroImage.imageSizes.medium,
|
||||
alt: heroImage.metaData.altText || "",
|
||||
}
|
||||
: null,
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user