Merge branch 'develop' into feat/sw-703-image-loader

This commit is contained in:
Linus Flood
2024-11-04 15:21:21 +01:00
80 changed files with 1422 additions and 367 deletions

View File

@@ -42,13 +42,17 @@ export default function CardsGrid({
case CardsGridEnum.cards.Card:
return (
<Card
theme={cards_grid.theme ?? "one"}
theme={
cards_grid.theme ?? card.backgroundImage ? "image" : "one"
}
key={card.system.uid}
scriptedTopTitle={card.scripted_top_title}
heading={card.heading}
bodyText={card.body_text}
secondaryButton={card.secondaryButton}
primaryButton={card.primaryButton}
backgroundImage={card.backgroundImage}
imageGradient
/>
)
case CardsGridEnum.cards.TeaserCard:

View File

@@ -70,7 +70,7 @@ function RewardTableHeader({ name, description }: RewardTableHeaderProps) {
<details className={styles.details}>
<summary className={styles.summary}>
<hgroup className={styles.rewardHeader}>
<Title as="h5" level="h2" textTransform={"regular"}>
<Title as="h4" level="h2" textTransform={"regular"}>
{name}
</Title>
<span className={styles.chevron}>

View File

@@ -19,7 +19,7 @@ export default function RewardCard({
<details className={styles.details}>
<summary className={styles.summary}>
<hgroup className={styles.rewardCardHeader}>
<Title as="h5" level="h2" textTransform={"regular"}>
<Title as="h4" level="h2" textTransform={"regular"}>
{title}
</Title>
<span className={styles.chevron}>

View File

@@ -56,7 +56,7 @@ export default async function NextLevelRewardsBlock({
{ level: nextLevelRewards.level?.name }
)}
</Body>
<Title level="h4" as="h5" color="pale" textAlign="center">
<Title level="h4" as="h4" color="pale" textAlign="center">
{reward.label}
</Title>
</div>

View File

@@ -7,7 +7,7 @@ export default async function EmptyPreviousStaysBlock() {
const { formatMessage } = await getIntl()
return (
<section className={styles.container}>
<Title as="h5" level="h3" color="red" textAlign="center">
<Title as="h4" level="h3" color="red" textAlign="center">
{formatMessage({
id: "You have no previous stays.",
})}

View File

@@ -14,7 +14,7 @@ export default async function EmptyUpcomingStaysBlock() {
return (
<section className={styles.container}>
<div className={styles.titleContainer}>
<Title as="h5" level="h3" color="red" className={styles.title}>
<Title as="h4" level="h3" color="red" className={styles.title}>
{formatMessage({ id: "You have no upcoming stays." })}
<span className={styles.burgundyTitle}>
{formatMessage({ id: "Where should you go next?" })}

View File

@@ -47,7 +47,7 @@ export default function StayCard({ stay }: StayCardProps) {
height={240}
/>
<footer className={styles.footer}>
<Title as="h5" className={styles.hotel} level="h3">
<Title as="h4" className={styles.hotel} level="h3">
{hotelInformation.hotelName}
</Title>
<div className={styles.date}>

View File

@@ -14,7 +14,7 @@ export default async function EmptyUpcomingStaysBlock() {
return (
<section className={styles.container}>
<div className={styles.titleContainer}>
<Title as="h5" level="h3" color="red" className={styles.title}>
<Title as="h4" level="h3" color="red" className={styles.title}>
{formatMessage({ id: "You have no upcoming stays." })}
<span className={styles.burgundyTitle}>
{formatMessage({ id: "Where should you go next?" })}

View File

@@ -47,10 +47,10 @@ export default function BookingWidgetClient({
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined =
searchParams
? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
adults: "number",
age: "number",
bed: "number",
}) as BookingWidgetSearchParams)
adults: "number",
age: "number",
bed: "number",
}) as BookingWidgetSearchParams)
: undefined
const getLocationObj = (destination: string): Location | undefined => {
@@ -67,18 +67,20 @@ export default function BookingWidgetClient({
return undefined
}
const reqFromDate = bookingWidgetSearchData?.fromDate?.toString()
const reqToDate = bookingWidgetSearchData?.toDate?.toString()
const isDateParamValid =
bookingWidgetSearchData?.fromDate &&
bookingWidgetSearchData?.toDate &&
dt(bookingWidgetSearchData?.toDate.toString()).isAfter(
dt(bookingWidgetSearchData?.fromDate.toString())
)
reqFromDate &&
reqToDate &&
dt(reqFromDate).isAfter(dt().subtract(1, "day")) &&
dt(reqToDate).isAfter(dt(reqFromDate))
const selectedLocation = bookingWidgetSearchData
? getLocationObj(
(bookingWidgetSearchData.city ??
bookingWidgetSearchData.hotel) as string
)
(bookingWidgetSearchData.city ??
bookingWidgetSearchData.hotel) as string
)
: undefined
const methods = useForm<BookingWidgetSchema>({

View File

@@ -0,0 +1,22 @@
import { serverClient } from "@/lib/trpc/server"
import StaticPage from ".."
export default async function CollectionPage() {
const collectionPageRes =
await serverClient().contentstack.collectionPage.get()
if (!collectionPageRes) {
return null
}
const { tracking, collectionPage } = collectionPageRes
return (
<StaticPage
content={collectionPage}
tracking={tracking}
pageType="collection"
/>
)
}

View File

@@ -0,0 +1,17 @@
import { serverClient } from "@/lib/trpc/server"
import StaticPage from ".."
export default async function ContentPage() {
const contentPageRes = await serverClient().contentstack.contentPage.get()
if (!contentPageRes) {
return null
}
const { tracking, contentPage } = contentPageRes
return (
<StaticPage content={contentPage} tracking={tracking} pageType="content" />
)
}

View File

@@ -1,5 +1,3 @@
import { serverClient } from "@/lib/trpc/server"
import Blocks from "@/components/Blocks"
import Hero from "@/components/Hero"
import Sidebar from "@/components/Sidebar"
@@ -8,21 +6,22 @@ import Preamble from "@/components/TempDesignSystem/Text/Preamble"
import Title from "@/components/TempDesignSystem/Text/Title"
import TrackingSDK from "@/components/TrackingSDK"
import styles from "./contentPage.module.css"
import { staticPageVariants } from "./variants"
export default async function ContentPage() {
const contentPageRes = await serverClient().contentstack.contentPage.get()
import styles from "./staticPage.module.css"
if (!contentPageRes) {
return null
}
import type { StaticPageProps } from "./staticPage"
const { tracking, contentPage } = contentPageRes
const { blocks, hero_image, header, sidebar } = contentPage
export default function StaticPage({
content,
tracking,
pageType,
}: StaticPageProps) {
const { blocks, hero_image, header } = content
return (
<>
<section className={styles.contentPage}>
<section className={staticPageVariants({ pageType })}>
<header className={styles.header}>
<div className={styles.headerContent}>
{header ? (
@@ -54,7 +53,9 @@ export default async function ContentPage() {
{blocks ? <Blocks blocks={blocks} /> : null}
</main>
{sidebar?.length ? <Sidebar blocks={sidebar} /> : null}
{"sidebar" in content && content.sidebar?.length ? (
<Sidebar blocks={content.sidebar} />
) : null}
</div>
</section>

View File

@@ -1,4 +1,4 @@
.contentPage {
.page {
padding-bottom: var(--Spacing-x9);
}
@@ -32,20 +32,27 @@
}
.contentContainer {
padding: var(--Spacing-x4) var(--Spacing-x2) 0;
}
.content .contentContainer {
display: grid;
grid-template-areas:
"main"
"sidebar";
gap: var(--Spacing-x4);
align-items: start;
padding: var(--Spacing-x4) var(--Spacing-x2) 0;
}
.mainContent {
grid-area: main;
display: grid;
gap: var(--Spacing-x4);
width: 100%;
gap: var(--Spacing-x6);
}
.content .mainContent {
grid-area: main;
}
@media (min-width: 768px) {
@@ -58,12 +65,20 @@
.heroContainer {
padding: var(--Spacing-x4) 0;
}
.contentContainer {
max-width: var(--max-width-content);
padding: var(--Spacing-x4) 0 0;
margin: 0 auto;
}
.content .contentContainer {
grid-template-areas: "main sidebar";
grid-template-columns: var(--max-width-text-block) 1fr;
gap: var(--Spacing-x9);
max-width: var(--max-width-content);
margin: 0 auto;
padding: var(--Spacing-x4) 0 0;
}
.mainContent {
gap: var(--Spacing-x9);
}
}

View File

@@ -0,0 +1,15 @@
import { staticPageVariants } from "./variants"
import type { VariantProps } from "class-variance-authority"
import type { TrackingSDKPageData } from "@/types/components/tracking"
import type { CollectionPage } from "@/types/trpc/routers/contentstack/collectionPage"
import type { ContentPage } from "@/types/trpc/routers/contentstack/contentPage"
export interface StaticPageProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "content">,
VariantProps<typeof staticPageVariants> {
pageType?: "collection" | "content"
content: CollectionPage["collection_page"] | ContentPage["content_page"]
tracking: TrackingSDKPageData
}

View File

@@ -0,0 +1,15 @@
import { cva } from "class-variance-authority"
import styles from "./staticPage.module.css"
export const staticPageVariants = cva(styles.page, {
variants: {
pageType: {
collection: styles.collection,
content: styles.content,
},
},
defaultVariants: {
pageType: "content",
},
})

View File

@@ -14,6 +14,7 @@
outline: none;
padding: 0;
width: 100%;
text-align: left;
}
.body {

View File

@@ -27,6 +27,7 @@
outline: none;
padding: 0;
width: 100%;
text-align: left;
}
.body {
opacity: 0.8;

View File

@@ -11,7 +11,12 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./sectionAccordion.module.css"
import {
StepEnum,
StepStoreKeys,
} from "@/types/components/hotelReservation/enterDetails/step"
import { SectionAccordionProps } from "@/types/components/hotelReservation/selectRate/sectionAccordion"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export default function SectionAccordion({
header,
@@ -23,9 +28,27 @@ export default function SectionAccordion({
const currentStep = useEnterDetailsStore((state) => state.currentStep)
const [isComplete, setIsComplete] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const isValid = useEnterDetailsStore((state) => state.isValid[step])
const navigate = useEnterDetailsStore((state) => state.navigate)
const stepData = useEnterDetailsStore((state) => state.userData)
const stepStoreKey = StepStoreKeys[step]
const [title, setTitle] = useState(label)
useEffect(() => {
if (step === StepEnum.selectBed) {
const value = stepData.bedType
value && setTitle(value)
}
// If breakfast step, check if an option has been selected
if (step === StepEnum.breakfast && stepData.breakfast) {
const value = stepData.breakfast
if (value === BreakfastPackageEnum.NO_BREAKFAST) {
setTitle(intl.formatMessage({ id: "No breakfast" }))
} else {
setTitle(intl.formatMessage({ id: "Breakfast buffet" }))
}
}
}, [stepData, stepStoreKey, step, intl])
useEffect(() => {
// We need to set the state on mount because of hydration errors
@@ -39,7 +62,6 @@ export default function SectionAccordion({
function onModify() {
navigate(step)
}
return (
<section className={styles.wrapper} data-open={isOpen} data-step={step}>
<div className={styles.iconWrapper}>
@@ -65,7 +87,7 @@ export default function SectionAccordion({
className={styles.selection}
color="uiTextHighContrast"
>
{label}
{title}
</Subtitle>
</div>
{isComplete && !isOpen && (

View File

@@ -20,7 +20,7 @@ export default function SelectionCard({
return (
<div className={styles.card}>
<div>
<Title className={styles.name} as="h5" level="h3">
<Title className={styles.name} as="h4" level="h3">
{title}
</Title>
<div className={styles.nameInfo}>i</div>

View File

@@ -1,6 +1,6 @@
"use client"
import ContentstackLivePreview from "@contentstack/live-preview-utils"
import { ContentstackLivePreview } from "@contentstack/live-preview-utils"
import { useEffect } from "react"
export default function InitLivePreview() {

View File

@@ -60,7 +60,7 @@ export default function Sidebar({ blocks }: SidebarProps) {
<TeaserCard
title={block.teaser_card.heading}
description={block.teaser_card.body_text}
style={block.teaser_card.theme}
intent={block.teaser_card.theme}
key={block.teaser_card.system.uid}
primaryButton={block.teaser_card.primaryButton}
secondaryButton={block.teaser_card.secondaryButton}

View File

@@ -41,6 +41,7 @@
.content {
margin: var(--Spacing-x0) var(--Spacing-x4);
position: absolute;
display: grid;
gap: var(--Spacing-x2);
}

View File

@@ -35,7 +35,7 @@ export default function LoyaltyCard({
focalPoint={image.focalPoint}
/>
) : null}
<Title as="h5" level="h3" textAlign="center">
<Title as="h4" level="h3" textAlign="center">
{heading}
</Title>
{bodyText ? <Body textAlign="center">{bodyText}</Body> : null}

View File

@@ -41,7 +41,7 @@ function SidePeek({
if (isSSR) {
return (
<div>
<div className={styles.visuallyHidden}>
<h2>{title}</h2>
{children}
</div>

View File

@@ -21,6 +21,12 @@
}
}
.visuallyHidden {
position: absolute;
opacity: 0;
visibility: hidden;
}
.overlay {
position: fixed;
top: 0;
@@ -40,7 +46,7 @@
height: 100vh;
background-color: var(--Base-Background-Primary-Normal);
z-index: var(--sidepeek-z-index);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.85);
outline: none;
}
.modal[data-entering] {

View File

@@ -21,7 +21,7 @@ export default function TeaserCardSidepeek({
const { heading, content, primary_button, secondary_button } = sidePeekContent
return (
<>
<div>
<Button
onPress={() => setSidePeekIsOpen(true)}
theme="base"
@@ -77,6 +77,6 @@ export default function TeaserCardSidepeek({
)}
</div>
</SidePeek>
</>
</div>
)
}

View File

@@ -19,25 +19,23 @@ export default function TeaserCard({
sidePeekButton,
sidePeekContent,
image,
style = "default",
intent,
alwaysStack = false,
className,
}: TeaserCardProps) {
const cardClasses = teaserCardVariants({ style, alwaysStack, className })
const classNames = teaserCardVariants({ intent, alwaysStack, className })
return (
<article className={cardClasses}>
<article className={classNames}>
{image && (
<div className={styles.imageContainer}>
<Image
src={image.url}
alt={image.meta?.alt || ""}
className={styles.backgroundImage}
width={399}
height={201}
focalPoint={image.focalPoint}
/>
</div>
<Image
src={image.url}
alt={image.meta?.alt || ""}
className={styles.image}
width={Math.ceil(image.dimensions.aspectRatio * 200)}
height={200}
focalPoint={image.focalPoint}
/>
)}
<div className={styles.content}>
<Subtitle textAlign="left" type="two" color="black">

View File

@@ -18,24 +18,17 @@
border: 1px solid var(--Base-Border-Subtle);
}
.imageContainer {
.image {
width: 100%;
height: 12.58625rem; /* 201.38px / 16 = 12.58625rem */
overflow: hidden;
}
.backgroundImage {
width: 100%;
height: 100%;
object-fit: cover;
height: 12.5rem; /* 200px */
}
.content {
display: flex;
flex-direction: column;
display: grid;
gap: var(--Spacing-x-one-and-half);
align-items: flex-start;
padding: var(--Spacing-x2) var(--Spacing-x3);
grid-template-rows: auto 1fr auto;
flex-grow: 1;
}
.description {
@@ -53,17 +46,6 @@
width: 100%;
}
.body {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
/* line-height variables are in %, so using the value in rem instead */
max-height: calc(3 * 1.5rem);
}
@media (min-width: 1367px) {
.card:not(.alwaysStack) .ctaContainer {
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));

View File

@@ -4,7 +4,7 @@ import styles from "./teaserCard.module.css"
export const teaserCardVariants = cva(styles.card, {
variants: {
style: {
intent: {
default: styles.default,
featured: styles.featured,
},
@@ -14,7 +14,7 @@ export const teaserCardVariants = cva(styles.card, {
},
},
defaultVariants: {
style: "default",
intent: "default",
alwaysStack: false,
},
})

View File

@@ -4,7 +4,9 @@
font-weight: 500;
}
.h1 {
/* Temporarily remove h1 styling until design tokens är updated */
/* .h1 {
font-family: var(--typography-Title-1-fontFamily);
font-size: clamp(
var(--typography-Title-1-Mobile-fontSize),
@@ -14,9 +16,9 @@
letter-spacing: var(--typography-Title-1-letterSpacing);
line-height: var(--typography-Title-1-lineHeight);
text-decoration: var(--typography-Title-1-textDecoration);
}
} */
.h2 {
.h1 {
font-family: var(--typography-Title-2-fontFamily);
font-size: clamp(
var(--typography-Title-2-Mobile-fontSize),
@@ -26,9 +28,10 @@
letter-spacing: var(--typography-Title-2-letterSpacing);
line-height: var(--typography-Title-2-lineHeight);
text-decoration: var(--typography-Title-2-textDecoration);
font-weight: var(--typography-Title-2-fontWeight);
}
.h3 {
.h2 {
font-family: var(--typography-Title-3-fontFamily);
font-size: clamp(
var(--typography-Title-3-Mobile-fontSize),
@@ -38,9 +41,10 @@
letter-spacing: var(--typography-Title-3-letterSpacing);
line-height: var(--typography-Title-3-lineHeight);
text-decoration: var(--typography-Title-3-textDecoration);
font-weight: var(--typography-Title-3-fontWeight);
}
.h4 {
.h3 {
font-family: var(--typography-Title-4-fontFamily);
font-size: clamp(
var(--typography-Title-4-Mobile-fontSize),
@@ -50,9 +54,10 @@
letter-spacing: var(--typography-Title-4-letterSpacing);
line-height: var(--typography-Title-4-lineHeight);
text-decoration: var(--typography-Title-4-textDecoration);
font-weight: var(--typography-Title-4-fontWeight);
}
.h5 {
.h4 {
font-family: var(--typography-Title-5-fontFamily);
font-size: clamp(
var(--typography-Title-5-Mobile-fontSize),
@@ -62,6 +67,7 @@
letter-spacing: var(--typography-Title-5-letterSpacing);
line-height: var(--typography-Title-5-lineHeight);
text-decoration: var(--typography-Title-5-textDecoration);
font-weight: var(--typography-Title-5-fontWeight);
}
.capitalize {

View File

@@ -26,7 +26,7 @@ const config = {
h2: styles.h2,
h3: styles.h3,
h4: styles.h4,
h5: styles.h5,
h5: styles.h4,
},
},
defaultVariants: {