Merge branch 'develop' into feature/sw-561-design-fixes
This commit is contained in:
@@ -10,13 +10,11 @@ import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import styles from "./amenitiesList.module.css"
|
||||
|
||||
import { HotelData } from "@/types/hotel"
|
||||
import type { AmenitiesListProps } from "@/types/components/hotelPage/amenities"
|
||||
|
||||
export default async function AmenitiesList({
|
||||
detailedFacilities,
|
||||
}: {
|
||||
detailedFacilities: HotelData["data"]["attributes"]["detailedFacilities"]
|
||||
}) {
|
||||
}: AmenitiesListProps) {
|
||||
const intl = await getIntl()
|
||||
const sortedAmenities = detailedFacilities
|
||||
.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { activities } from "@/constants/routes/hotelPageParams"
|
||||
|
||||
import Card from "@/components/TempDesignSystem/Card"
|
||||
import CardImage from "@/components/TempDesignSystem/Card/CardImage"
|
||||
import Grids from "@/components/TempDesignSystem/Grids"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import styles from "./cardGrid.module.css"
|
||||
|
||||
import type { ActivityCard } from "@/types/trpc/routers/contentstack/hotelPage"
|
||||
import type { CardProps } from "@/components/TempDesignSystem/Card/card"
|
||||
|
||||
export default function ActivitiesCardGrid(activitiesCard: ActivityCard) {
|
||||
const lang = getLang()
|
||||
const hasImage = activitiesCard.backgroundImage
|
||||
|
||||
const updatedCard: CardProps = {
|
||||
...activitiesCard,
|
||||
id: activities[lang],
|
||||
theme: hasImage ? "image" : "primaryDark",
|
||||
primaryButton: hasImage
|
||||
? {
|
||||
href: activitiesCard.contentPage.href,
|
||||
title: activitiesCard.ctaText,
|
||||
isExternal: false,
|
||||
}
|
||||
: undefined,
|
||||
secondaryButton: hasImage
|
||||
? undefined
|
||||
: {
|
||||
href: activitiesCard.contentPage.href,
|
||||
title: activitiesCard.ctaText,
|
||||
isExternal: false,
|
||||
},
|
||||
}
|
||||
return (
|
||||
<section id={updatedCard.id}>
|
||||
<Grids.Stackable className={styles.desktopGrid}>
|
||||
<Card {...updatedCard} className={styles.spanThree} />
|
||||
</Grids.Stackable>
|
||||
<Grids.Stackable className={styles.mobileGrid}>
|
||||
<CardImage card={updatedCard} />
|
||||
</Grids.Stackable>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +1,32 @@
|
||||
.one {
|
||||
.spanOne {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.two {
|
||||
.spanTwo {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.three {
|
||||
grid-column: 1/-1;
|
||||
.spanThree {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.desktopGrid {
|
||||
section .desktopGrid {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobileGrid {
|
||||
section .mobileGrid {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x-quarter);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.desktopGrid {
|
||||
section .desktopGrid {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.mobileGrid {
|
||||
section .mobileGrid {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
import Card from "@/components/TempDesignSystem/Card"
|
||||
import CardImage from "@/components/TempDesignSystem/Card/CardImage"
|
||||
import Grids from "@/components/TempDesignSystem/Grids"
|
||||
import { sortCards } from "@/utils/imageCard"
|
||||
import { filterFacilityCards, isFacilityCard } from "@/utils/facilityCards"
|
||||
|
||||
import styles from "./cardGrid.module.css"
|
||||
|
||||
import type { CardGridProps } from "@/types/components/hotelPage/facilities"
|
||||
import type {
|
||||
CardGridProps,
|
||||
FacilityCardType,
|
||||
} from "@/types/components/hotelPage/facilities"
|
||||
|
||||
export default function FacilitiesCardGrid({
|
||||
facilitiesCardGrid,
|
||||
}: CardGridProps) {
|
||||
const imageCard = filterFacilityCards(facilitiesCardGrid)
|
||||
const nrCards = facilitiesCardGrid.length
|
||||
|
||||
function getCardClassName(card: FacilityCardType): string {
|
||||
if (nrCards === 1) {
|
||||
return styles.spanThree
|
||||
} else if (nrCards === 2 && !isFacilityCard(card)) {
|
||||
return styles.spanTwo
|
||||
}
|
||||
return styles.spanOne
|
||||
}
|
||||
|
||||
export default async function CardGrid({ facility }: CardGridProps) {
|
||||
const imageCard = sortCards(facility)
|
||||
return (
|
||||
<section id={imageCard.card?.id}>
|
||||
<section id={imageCard.card.id}>
|
||||
<Grids.Stackable className={styles.desktopGrid}>
|
||||
{facility.map((card: any, idx: number) => (
|
||||
<Card
|
||||
theme={card.theme || "primaryDark"}
|
||||
key={idx}
|
||||
scriptedTopTitle={card.scriptedTopTitle}
|
||||
heading={card.heading}
|
||||
bodyText={card.bodyText}
|
||||
secondaryButton={card.secondaryButton}
|
||||
primaryButton={card.primaryButton}
|
||||
backgroundImage={card.backgroundImage}
|
||||
className={styles[card.columnSpan]}
|
||||
/>
|
||||
{facilitiesCardGrid.map((card: FacilityCardType) => (
|
||||
<Card {...card} key={card.id} className={getCardClassName(card)} />
|
||||
))}
|
||||
</Grids.Stackable>
|
||||
<Grids.Stackable className={styles.mobileGrid}>
|
||||
|
||||
@@ -1,17 +1,56 @@
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { isFacilityCard, setFacilityCardGrids } from "@/utils/facilityCards"
|
||||
|
||||
import CardGrid from "./CardGrid"
|
||||
import ActivitiesCardGrid from "./CardGrid/ActivitiesCardGrid"
|
||||
import FacilitiesCardGrid from "./CardGrid"
|
||||
|
||||
import styles from "./facilities.module.css"
|
||||
|
||||
import type { FacilityProps } from "@/types/components/hotelPage/facilities"
|
||||
import type {
|
||||
Facilities,
|
||||
FacilitiesProps,
|
||||
FacilityCardType,
|
||||
FacilityGrid,
|
||||
} from "@/types/components/hotelPage/facilities"
|
||||
|
||||
export default async function Facilities({
|
||||
facilities,
|
||||
activitiesCard,
|
||||
}: FacilitiesProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const facilityCardGrids = setFacilityCardGrids(facilities)
|
||||
|
||||
const translatedFacilityGrids: Facilities = facilityCardGrids.map(
|
||||
(cardGrid: FacilityGrid) => {
|
||||
return cardGrid.map((card: FacilityCardType) => {
|
||||
if (isFacilityCard(card)) {
|
||||
return {
|
||||
...card,
|
||||
heading: intl.formatMessage({ id: card.heading }),
|
||||
secondaryButton: {
|
||||
...card.secondaryButton,
|
||||
title: intl.formatMessage({
|
||||
id: card.secondaryButton.title,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
return card
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export default async function Facilities({ facilities }: FacilityProps) {
|
||||
return (
|
||||
<SectionContainer className={styles.grid}>
|
||||
{facilities.map((facility: any, idx: number) => (
|
||||
<CardGrid key={`grid_${idx}`} facility={facility} />
|
||||
{translatedFacilityGrids.map((cardGrid: FacilityGrid) => (
|
||||
<FacilitiesCardGrid
|
||||
key={cardGrid[0].id}
|
||||
facilitiesCardGrid={cardGrid}
|
||||
/>
|
||||
))}
|
||||
{activitiesCard && <ActivitiesCardGrid {...activitiesCard} />}
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
import {
|
||||
activities,
|
||||
meetingsAndConferences,
|
||||
restaurantAndBar,
|
||||
wellnessAndExercise,
|
||||
} from "@/constants/routes/hotelPageParams"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import type { Facilities } from "@/types/components/hotelPage/facilities"
|
||||
|
||||
const lang = getLang()
|
||||
/*
|
||||
Most of this will be available from the api. Some will need to come from Contentstack. "Activities" will most likely come from Contentstack, which is prepped for.
|
||||
*/
|
||||
export const MOCK_FACILITIES: Facilities = [
|
||||
[
|
||||
{
|
||||
id: "restaurant-and-bar",
|
||||
theme: "primaryDark",
|
||||
scriptedTopTitle: "Restaurant & Bar",
|
||||
heading: "Enjoy relaxed restaurant experience",
|
||||
secondaryButton: {
|
||||
href: `?s=${restaurantAndBar[lang]}`,
|
||||
title: "Read more & book a table",
|
||||
isExternal: false,
|
||||
},
|
||||
columnSpan: "one",
|
||||
},
|
||||
{
|
||||
backgroundImage: {
|
||||
url: "https://imagevault.scandichotels.com/publishedmedia/79xttlmnum0kjbwhyh18/scandic-helsinki-hub-restaurant-food-tuna.jpg",
|
||||
title: "scandic-helsinki-hub-restaurant-food-tuna.jpg",
|
||||
meta: {
|
||||
alt: "food in restaurant at scandic helsinki hub",
|
||||
caption: "food in restaurant at scandic helsinki hub",
|
||||
},
|
||||
id: 81751,
|
||||
dimensions: {
|
||||
width: 5935,
|
||||
height: 3957,
|
||||
aspectRatio: 1.499873641647713,
|
||||
},
|
||||
},
|
||||
columnSpan: "one",
|
||||
},
|
||||
{
|
||||
backgroundImage: {
|
||||
url: "https://imagevault.scandichotels.com/publishedmedia/48sb3eyhhzj727l2j1af/Scandic-helsinki-hub-II-centro-41.jpg",
|
||||
meta: {
|
||||
alt: "restaurant il centro at scandic helsinki hu",
|
||||
caption: "restaurant il centro at scandic helsinki hub",
|
||||
},
|
||||
id: 82457,
|
||||
title: "Scandic-helsinki-hub-II-centro-41.jpg",
|
||||
dimensions: {
|
||||
width: 4200,
|
||||
height: 2800,
|
||||
aspectRatio: 1.5,
|
||||
},
|
||||
},
|
||||
columnSpan: "one",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
backgroundImage: {
|
||||
url: "https://imagevault.scandichotels.com/publishedmedia/csef06n329hjfiet1avj/Scandic-spectrum-8.jpg",
|
||||
meta: {
|
||||
alt: "man with a laptop",
|
||||
caption: "man with a laptop",
|
||||
},
|
||||
id: 82713,
|
||||
title: "Scandic-spectrum-8.jpg",
|
||||
dimensions: {
|
||||
width: 7499,
|
||||
height: 4999,
|
||||
aspectRatio: 1.500100020004001,
|
||||
},
|
||||
},
|
||||
columnSpan: "two",
|
||||
},
|
||||
{
|
||||
id: "meetings-and-conferences",
|
||||
theme: "primaryDim",
|
||||
scriptedTopTitle: "Meetings & Conferences",
|
||||
heading: "Events that make an impression",
|
||||
secondaryButton: {
|
||||
href: `?s=${meetingsAndConferences[lang]}`,
|
||||
title: "About meetings & conferences",
|
||||
isExternal: false,
|
||||
},
|
||||
columnSpan: "one",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
id: "wellness-and-exercise",
|
||||
theme: "one",
|
||||
scriptedTopTitle: "Wellness & Exercise",
|
||||
heading: "Sauna and gym",
|
||||
secondaryButton: {
|
||||
href: `?s=${wellnessAndExercise[lang]}`,
|
||||
title: "Read more about wellness & exercise",
|
||||
isExternal: false,
|
||||
},
|
||||
columnSpan: "one",
|
||||
},
|
||||
{
|
||||
backgroundImage: {
|
||||
url: "https://imagevault.scandichotels.com/publishedmedia/69acct5i3pk5be7d6ub0/scandic-helsinki-hub-sauna.jpg",
|
||||
meta: {
|
||||
alt: "sauna at scandic helsinki hub",
|
||||
caption: "sauna at scandic helsinki hub",
|
||||
},
|
||||
id: 81814,
|
||||
title: "scandic-helsinki-hub-sauna.jpg",
|
||||
dimensions: {
|
||||
width: 4000,
|
||||
height: 2667,
|
||||
aspectRatio: 1.4998125234345707,
|
||||
},
|
||||
},
|
||||
columnSpan: "one",
|
||||
},
|
||||
{
|
||||
backgroundImage: {
|
||||
url: "https://imagevault.scandichotels.com/publishedmedia/eu70o6z85idy24r92ysf/Scandic-Helsinki-Hub-gym-22.jpg",
|
||||
meta: {
|
||||
alt: "Gym at hotel Scandic Helsinki Hub",
|
||||
caption: "Gym at hotel Scandic Helsinki Hub",
|
||||
},
|
||||
id: 81867,
|
||||
title: "Scandic-Helsinki-Hub-gym-22.jpg",
|
||||
dimensions: {
|
||||
width: 4000,
|
||||
height: 2667,
|
||||
aspectRatio: 1.4998125234345707,
|
||||
},
|
||||
},
|
||||
columnSpan: "one",
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { Facility } from "@/types/components/hotelPage/facilities"
|
||||
import type { ActivityCard } from "@/types/trpc/routers/contentstack/hotelPage"
|
||||
|
||||
export function setActivityCard(activitiesCard: ActivityCard): Facility {
|
||||
const hasImage = !!activitiesCard.background_image
|
||||
return [
|
||||
{
|
||||
id: "activities",
|
||||
theme: hasImage ? "image" : "primaryDark",
|
||||
scriptedTopTitle: activitiesCard.scripted_title,
|
||||
heading: activitiesCard.heading,
|
||||
bodyText: activitiesCard.body_text,
|
||||
backgroundImage: hasImage ? activitiesCard.background_image : undefined,
|
||||
primaryButton: hasImage
|
||||
? {
|
||||
href: activitiesCard.contentPage.href,
|
||||
title: activitiesCard.cta_text,
|
||||
isExternal: false,
|
||||
}
|
||||
: undefined,
|
||||
secondaryButton: hasImage
|
||||
? undefined
|
||||
: {
|
||||
href: activitiesCard.contentPage.href,
|
||||
title: activitiesCard.cta_text,
|
||||
isExternal: false,
|
||||
},
|
||||
columnSpan: "three",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function getCardTheme() {
|
||||
// TODO
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
|
||||
import styles from "./roomCard.module.css"
|
||||
|
||||
import { RoomCardProps } from "@/types/components/hotelPage/roomCard"
|
||||
import type { RoomCardProps } from "@/types/components/hotelPage/roomCard"
|
||||
|
||||
export function RoomCard({
|
||||
badgeTextTransKey,
|
||||
|
||||
@@ -10,10 +10,11 @@ import Button from "@/components/TempDesignSystem/Button"
|
||||
import Grids from "@/components/TempDesignSystem/Grids"
|
||||
|
||||
import { RoomCard } from "./RoomCard"
|
||||
import { RoomsProps } from "./types"
|
||||
|
||||
import styles from "./rooms.module.css"
|
||||
|
||||
import type { RoomsProps } from "./types"
|
||||
|
||||
export function Rooms({ rooms }: RoomsProps) {
|
||||
const intl = useIntl()
|
||||
const [allRoomsVisible, setAllRoomsVisible] = useState(false)
|
||||
|
||||
@@ -6,21 +6,38 @@ import useHash from "@/hooks/useHash"
|
||||
|
||||
import styles from "./tabNavigation.module.css"
|
||||
|
||||
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation"
|
||||
import {
|
||||
HotelHashValues,
|
||||
type TabNavigationProps,
|
||||
} from "@/types/components/hotelPage/tabNavigation"
|
||||
|
||||
export default function TabNavigation() {
|
||||
export default function TabNavigation({ restaurantTitle }: TabNavigationProps) {
|
||||
const hash = useHash()
|
||||
const intl = useIntl()
|
||||
|
||||
const hotelTabLinks: { href: HotelHashValues; text: string }[] = [
|
||||
// TODO these titles will need to reflect the facility card titles, which will vary between hotels
|
||||
{ href: HotelHashValues.overview, text: "Overview" },
|
||||
{ href: HotelHashValues.rooms, text: "Rooms" },
|
||||
{ href: HotelHashValues.restaurant, text: "Restaurant & Bar" },
|
||||
{ href: HotelHashValues.meetings, text: "Meetings & Conferences" },
|
||||
{ href: HotelHashValues.wellness, text: "Wellness & Exercise" },
|
||||
{ href: HotelHashValues.activities, text: "Activities" },
|
||||
{ href: HotelHashValues.faq, text: "FAQ" },
|
||||
const hotelTabLinks: { href: HotelHashValues | string; text: string }[] = [
|
||||
{
|
||||
href: HotelHashValues.overview,
|
||||
text: intl.formatMessage({ id: "Overview" }),
|
||||
},
|
||||
{ href: HotelHashValues.rooms, text: intl.formatMessage({ id: "Rooms" }) },
|
||||
{
|
||||
href: HotelHashValues.restaurant,
|
||||
text: intl.formatMessage({ id: restaurantTitle }, { count: 1 }),
|
||||
},
|
||||
{
|
||||
href: HotelHashValues.meetings,
|
||||
text: intl.formatMessage({ id: "Meetings & Conferences" }),
|
||||
},
|
||||
{
|
||||
href: HotelHashValues.wellness,
|
||||
text: intl.formatMessage({ id: "Wellness & Exercise" }),
|
||||
},
|
||||
{
|
||||
href: HotelHashValues.activities,
|
||||
text: intl.formatMessage({ id: "Activities" }),
|
||||
},
|
||||
{ href: HotelHashValues.faq, text: intl.formatMessage({ id: "FAQ" }) },
|
||||
]
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,9 +6,8 @@ import SidePeekProvider from "@/components/SidePeekProvider"
|
||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
import { getRestaurantHeading } from "@/utils/facilityCards"
|
||||
|
||||
import { MOCK_FACILITIES } from "./Facilities/mockData"
|
||||
import { setActivityCard } from "./Facilities/utils"
|
||||
import DynamicMap from "./Map/DynamicMap"
|
||||
import MapCard from "./Map/MapCard"
|
||||
import MobileMapToggle from "./Map/MobileMapToggle"
|
||||
@@ -45,10 +44,9 @@ export default async function HotelPage() {
|
||||
roomCategories,
|
||||
activitiesCard,
|
||||
pointsOfInterest,
|
||||
facilities,
|
||||
} = hotelData
|
||||
|
||||
const facilities = [...MOCK_FACILITIES]
|
||||
activitiesCard && facilities.push(setActivityCard(activitiesCard))
|
||||
const topThreePois = pointsOfInterest.slice(0, 3)
|
||||
|
||||
const coordinates = {
|
||||
@@ -61,7 +59,9 @@ export default async function HotelPage() {
|
||||
<div className={styles.hotelImages}>
|
||||
<PreviewImages images={hotelImages} hotelName={hotelName} />
|
||||
</div>
|
||||
<TabNavigation />
|
||||
<TabNavigation
|
||||
restaurantTitle={getRestaurantHeading(hotelDetailedFacilities)}
|
||||
/>
|
||||
<main className={styles.mainSection}>
|
||||
<div className={styles.introContainer}>
|
||||
<IntroSection
|
||||
@@ -119,7 +119,7 @@ export default async function HotelPage() {
|
||||
<AmenitiesList detailedFacilities={hotelDetailedFacilities} />
|
||||
</div>
|
||||
<Rooms rooms={roomCategories} />
|
||||
<Facilities facilities={facilities} />
|
||||
<Facilities facilities={facilities} activitiesCard={activitiesCard} />
|
||||
</main>
|
||||
{googleMapsApiKey ? (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user