Merge branch 'develop' into feature/sw-561-design-fixes

This commit is contained in:
Linus Flood
2024-10-08 13:20:40 +02:00
128 changed files with 2289 additions and 913 deletions

View File

@@ -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)

View File

@@ -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>
)
}

View File

@@ -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;
}
}

View File

@@ -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}>

View File

@@ -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>
)
}

View File

@@ -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",
},
],
]

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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)

View File

@@ -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 (

View File

@@ -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 ? (
<>