Merge branch 'develop' into feat/setup-hotel-api-call

This commit is contained in:
Chuma McPhoy
2024-07-01 13:53:50 +02:00
63 changed files with 1612 additions and 284 deletions

View File

@@ -2,6 +2,6 @@
background-color: var(--Scandic-Brand-Warm-White);
display: grid;
font-family: var(--typography-Body-Regular-fontFamily);
gap: var(--Spacing-x5);
gap: var(--Spacing-x3);
grid-template-rows: auto 1fr;
}

View File

@@ -5,7 +5,7 @@ import Script from "next/script"
import TrpcProvider from "@/lib/trpc/Provider"
import AdobeScript from "@/components/Current/AdobeScript"
import AdobeSDKScript from "@/components/Current/AdobeSDKScript"
import Footer from "@/components/Current/Footer"
import VwoScript from "@/components/Current/VwoScript"
import { getIntl } from "@/i18n"
@@ -33,6 +33,7 @@ export default async function RootLayout({
return (
<html lang={params.lang}>
<head>
<AdobeSDKScript />
<Script data-cookieconsent="ignore" src="/_static/js/cookie-bot.js" />
<Script
strategy="beforeInteractive"
@@ -42,10 +43,6 @@ export default async function RootLayout({
id="Cookiebot"
src="https://consent.cookiebot.com/uc.js"
/>
<Script id="ensure-datalayer">{`
window.datalayer = window.datalayer || {}
`}</Script>
<AdobeScript />
<VwoScript />
</head>
<body>
@@ -56,9 +53,6 @@ export default async function RootLayout({
<Footer lang={params.lang} />
</TrpcProvider>
</ServerIntlProvider>
<Script id="page-tracking">{`
typeof _satellite !== "undefined" && _satellite.pageBottom();
`}</Script>
</body>
</html>
)

View File

@@ -8,16 +8,16 @@
.blocks {
display: grid;
gap: var(--Spacing-x5);
gap: var(--Spacing-x7);
padding-left: var(--Spacing-x2);
padding-right: var(--Spacing-x2);
}
@media screen and (min-width: 1367px) {
.content {
gap: var(--Spacing-x3);
padding-left: var(--Spacing-x3);
padding-right: var(--Spacing-x3);
gap: var(--Spacing-x5);
padding-left: var(--Spacing-x5);
padding-right: var(--Spacing-x5);
}
.content:has(> aside) {
@@ -29,6 +29,7 @@
}
.blocks {
gap: var(--Spacing-x9);
padding-left: var(--Spacing-x0);
padding-right: var(--Spacing-x0);
}

View File

@@ -0,0 +1,13 @@
import Script from "next/script"
import { env } from "@/env/server"
export default function AdobeSDKScript() {
return env.ADOBE_SDK_SCRIPT_SRC ? (
<Script
data-cookieconsent="statistics"
src={env.ADOBE_SDK_SCRIPT_SRC}
async
/>
) : null
}

View File

@@ -5,6 +5,8 @@ import type { IconProps } from "@/types/components/icon"
export default function ArrowRightIcon({
className,
color,
height = "25",
width = "24",
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
@@ -12,9 +14,9 @@ export default function ArrowRightIcon({
<svg
className={classNames}
fill="none"
height="25"
height={height}
viewBox="0 0 24 25"
width="24"
width={width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>

View File

@@ -2,15 +2,21 @@ import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function EmailIcon({ className, color, ...props }: IconProps) {
export default function EmailIcon({
className,
color,
width = "20",
height = "20",
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="20"
height={height}
viewBox="0 0 20 20"
width="20"
width={width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function PersonIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="mask0_1572_4293"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="20"
height="20"
>
<rect width="20" height="20" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_1572_4293)">
<path
d="M10 10C9.16667 10 8.45833 9.70833 7.875 9.125C7.29167 8.54167 7 7.83333 7 7C7 6.16667 7.29167 5.45833 7.875 4.875C8.45833 4.29167 9.16667 4 10 4C10.8333 4 11.5417 4.29167 12.125 4.875C12.7083 5.45833 13 6.16667 13 7C13 7.83333 12.7083 8.54167 12.125 9.125C11.5417 9.70833 10.8333 10 10 10ZM4 14.5V14C4 13.6806 4.08681 13.3785 4.26042 13.0938C4.43403 12.809 4.67361 12.5694 4.97917 12.375C5.74306 11.9306 6.55064 11.5903 7.40192 11.3542C8.25321 11.1181 9.11779 11 9.99567 11C10.8736 11 11.7396 11.1181 12.5938 11.3542C13.4479 11.5903 14.2569 11.9306 15.0208 12.375C15.3264 12.5556 15.566 12.7917 15.7396 13.0833C15.9132 13.375 16 13.6806 16 14V14.5C16 14.9125 15.853 15.2656 15.5591 15.5594C15.2652 15.8531 14.9119 16 14.4992 16H5.4941C5.08137 16 4.72917 15.8531 4.4375 15.5594C4.14583 15.2656 4 14.9125 4 14.5ZM5.5 14.5H14.5V14C14.5 13.9281 14.479 13.8628 14.437 13.8039C14.395 13.7451 14.3396 13.6993 14.2708 13.6667C13.6319 13.2778 12.9514 12.9861 12.2292 12.7917C11.5069 12.5972 10.7639 12.5 10 12.5C9.23611 12.5 8.49306 12.5972 7.77083 12.7917C7.04861 12.9861 6.36806 13.2778 5.72917 13.6667C5.65972 13.7222 5.60417 13.7759 5.5625 13.8276C5.52083 13.8793 5.5 13.9368 5.5 14V14.5ZM10.0044 8.5C10.4181 8.5 10.7708 8.35269 11.0625 8.05808C11.3542 7.76346 11.5 7.40929 11.5 6.99558C11.5 6.58186 11.3527 6.22917 11.0581 5.9375C10.7635 5.64583 10.4093 5.5 9.99558 5.5C9.58186 5.5 9.22917 5.64731 8.9375 5.94192C8.64583 6.23654 8.5 6.59071 8.5 7.00442C8.5 7.41814 8.64731 7.77083 8.94192 8.0625C9.23654 8.35417 9.59071 8.5 10.0044 8.5Z"
fill="#1C1B1F"
/>
</g>
</svg>
)
}

View File

@@ -2,15 +2,21 @@ import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function PhoneIcon({ className, color, ...props }: IconProps) {
export default function PhoneIcon({
className,
color,
height = "24",
width = "24",
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="24"
height={height}
viewBox="0 0 24 24"
width="24"
width={width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>

View File

@@ -12,5 +12,6 @@ export { default as HouseIcon } from "./House"
export { default as InfoCircleIcon } from "./InfoCircle"
export { default as LocationIcon } from "./Location"
export { default as LockIcon } from "./Lock"
export { default as PersonIcon } from "./Person"
export { default as PhoneIcon } from "./Phone"
export { default as PlusCircleIcon } from "./PlusCircle"

View File

@@ -2,25 +2,50 @@ import SectionContainer from "@/components/Section/Container"
import Header from "@/components/Section/Header"
import Card from "@/components/TempDesignSystem/Card"
import Grids from "@/components/TempDesignSystem/Grids"
import LoyaltyCard from "@/components/TempDesignSystem/LoyaltyCard"
import { CardsGridProps } from "@/types/components/loyalty/blocks"
import { LoyaltyCardsGridEnum } from "@/types/components/loyalty/enums"
export default function CardsGrid({ cards_grid }: CardsGridProps) {
export default function CardsGrid({
cards_grid,
firstItem = false,
}: CardsGridProps) {
return (
<SectionContainer>
<Header title={cards_grid.title} subtitle={cards_grid.preamble} />
<Header
title={cards_grid.title}
subtitle={cards_grid.preamble}
topTitle={firstItem}
/>
<Grids.Stackable>
{cards_grid.cards.map((card) => (
<Card
theme={cards_grid.theme || "one"}
key={card.system.uid}
scriptedTopTitle={card.scripted_top_title}
heading={card.heading}
bodyText={card.body_text}
secondaryButton={card.secondaryButton}
primaryButton={card.primaryButton}
/>
))}
{cards_grid.cards.map((card) => {
switch (card.__typename) {
case LoyaltyCardsGridEnum.LoyaltyCard:
return (
<LoyaltyCard
key={card.system.uid}
image={card.image}
heading={card.heading}
bodyText={card.body_text}
link={card.link}
/>
)
case LoyaltyCardsGridEnum.Card: {
return (
<Card
theme={cards_grid.theme || "one"}
key={card.system.uid}
scriptedTopTitle={card.scripted_top_title}
heading={card.heading}
bodyText={card.body_text}
secondaryButton={card.secondaryButton}
primaryButton={card.primaryButton}
/>
)
}
}
})}
</Grids.Stackable>
</SectionContainer>
)

View File

@@ -1,6 +1,6 @@
"use client"
import { useParams } from "next/navigation"
import { notFound, useParams } from "next/navigation"
import { useIntl } from "react-intl"
import { Lang } from "@/constants/languages"
@@ -15,9 +15,8 @@ import {
NewFriend,
TrueFriend,
} from "@/components/Levels"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import levelsData from "./data"
@@ -33,28 +32,21 @@ export default function LoyaltyLevels() {
const { levels } = levelsData[lang]
return (
<section className={styles.container}>
<div className={styles.cardContainer}>
{levels.map((level: Level) => (
<LevelCard
key={level.level}
formatMessage={formatMessage}
lang={lang}
level={level}
/>
))}
</div>
<Button asChild intent="primary">
<Link className={styles.link} href={`/${lang}/compare-all-levels`}>
{formatMessage({ id: "Compare all levels" })}
</Link>
</Button>
<section className={styles.cardContainer}>
{levels.map((level: Level) => (
<LevelCard
key={level.level}
formatMessage={formatMessage}
lang={lang}
level={level}
/>
))}
</section>
)
}
function LevelCard({ formatMessage, lang, level }: LevelCardProps) {
const pointsString = `${level.requiredPoints.toLocaleString(lang)}p`
const pointsString = `${level.requiredPoints.toLocaleString(lang)} ${formatMessage({ id: "Points" })} `
const qualifications = level.requiredNights
? `${pointsString} ${formatMessage({ id: "or" })} ${level.requiredNights} ${formatMessage({ id: "nights" })}`
: pointsString
@@ -82,20 +74,41 @@ function LevelCard({ formatMessage, lang, level }: LevelCardProps) {
case 7:
Level = BestFriend
break
default: {
const loyaltyLevel = level.level as never
console.error(`Unsupported loyalty level given: ${loyaltyLevel}`)
notFound()
}
}
return (
<article className={styles.card}>
<Title className={styles.levelHeading} level="h4">
{level.level}
<header>
<BiroScript
type="two"
color="primaryLightOnSurfaceAccent"
tilted="large"
>
{formatMessage({ id: "Level" })} {level.level}
</BiroScript>
<Level color="red" />
</header>
<Title textAlign="center" level="h5">
{qualifications}
</Title>
{Level ? <Level color="primaryLightOnSurfaceAccent" /> : null}
<div className={styles.textContainer}>
<Body textTransform="bold">{qualifications}</Body>
{level.benefits.map((benefit) => (
<Body key={benefit.title} textAlign="center">
<CheckIcon className={styles.checkIcon} />
<Caption
className={styles.levelText}
key={benefit.title}
textAlign="center"
color="textMediumContrast"
>
<CheckIcon
className={styles.checkIcon}
color="primaryLightOnSurfaceAccent"
/>
{benefit.title}
</Body>
</Caption>
))}
</div>
</article>

View File

@@ -1,8 +1,3 @@
.container {
display: grid;
gap: var(--Spacing-x3);
}
.cardContainer {
display: grid;
gap: var(--Spacing-x2);
@@ -20,37 +15,34 @@
}
.card {
align-content: flex-start;
background-color: var(--UI-Grey-10);
background-color: var(--Scandic-Brand-Pale-Peach);
border-radius: var(--Corner-radius-xLarge);
display: grid;
gap: var(--Spacing-x2);
justify-content: center;
min-height: 280px;
justify-items: center;
padding: var(--Spacing-x5) var(--Spacing-x1);
}
.levelHeading {
color: var(--Scandic-Peach-70);
grid-template-rows: auto auto 1fr;
}
.textContainer {
align-content: flex-start;
display: grid;
align-content: flex-end;
display: flex;
gap: var(--Spacing-x-one-and-half);
width: 100%;
flex-wrap: wrap;
justify-content: center;
justify-items: center;
}
.levelText {
margin: 0;
}
.checkIcon {
vertical-align: text-bottom;
vertical-align: middle;
}
@media screen and (min-width: 1367px) {
.container {
gap: var(--Spacing-x4);
}
.cardContainer {
display: grid;
grid-template-columns: repeat(12, 1fr);

View File

@@ -43,6 +43,7 @@ export function Blocks({ blocks }: BlocksProps) {
<CardsGrid
cards_grid={block.cards_grid}
key={`${block.cards_grid.title}-${idx}`}
firstItem={firstItem}
/>
)
default:

View File

@@ -1,5 +1,5 @@
.icon,
.icon * {
fill: var(--Scandic-Brand-Burgundy);
margin-bottom: var(--Spacing-x-half);
.link {
display: flex;
align-items: center;
gap: var(--Spacing-x1);
}

View File

@@ -1,7 +1,9 @@
import { serverClient } from "@/lib/trpc/server"
import { EmailIcon, PhoneIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import { getValueFromContactConfig } from "@/utils/contactConfig"
import styles from "./contactRow.module.css"
@@ -27,15 +29,29 @@ export default async function ContactRow({ contact }: ContactRowProps) {
Icon = PhoneIcon
}
let openableLink = val
if (contact.contact_field.includes("email")) {
openableLink = `mailto:${val}`
} else if (contact.contact_field.includes("phone")) {
openableLink = `tel:${val}`
}
return (
<div>
{Icon ? <Icon className={styles.icon} /> : null}
<Body color="burgundy" textAlign="center" textTransform="bold">
<Body color="burgundy" textTransform="bold">
{contact.display_text}
</Body>
<Body color="burgundy" textAlign="center">
<Link
className={styles.link}
href={openableLink}
variant="myPage"
color="burgundy"
size="small"
>
{Icon ? <Icon width="20" height="20" color="burgundy" /> : null}
{val}
</Body>
</Link>
<Footnote color="burgundy">{contact.footnote}</Footnote>
</div>
)
}

View File

@@ -4,14 +4,12 @@
@media screen and (min-width: 1367px) {
.contactContainer {
align-items: center;
border-top: 1px solid var(--UI-Grey-30);
display: flex;
flex-direction: column;
gap: var(--Spacing-x5);
gap: var(--Spacing-x2);
justify-content: center;
padding: var(--Spacing-x4) var(--Spacing-x2) var(--Spacing-x5);
text-align: center;
padding-top: var(--Spacing-x2);
}
.contact {

View File

@@ -12,9 +12,7 @@ export default async function Contact({ contactBlock }: ContactProps) {
const { formatMessage } = await getIntl()
return (
<article className={styles.contactContainer}>
<Subtitle textAlign="center">
{formatMessage({ id: "Contact us" })}
</Subtitle>
<Subtitle>{formatMessage({ id: "Contact us" })}</Subtitle>
<section className={styles.contact}>
{contactBlock.map(({ contact, __typename }, i) => {
switch (__typename) {

View File

@@ -1,10 +1,11 @@
import { login } from "@/constants/routes/handleAuth"
import { auth } from "@/auth"
import ArrowRight from "@/components/Icons/ArrowRight"
import { ScandicFriends } from "@/components/Levels"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
@@ -20,29 +21,42 @@ export default async function JoinLoyaltyContact({
lang,
}: JoinLoyaltyContactProps & LangParams) {
const { formatMessage } = await getIntl()
const session = await auth()
if (session) {
return null
}
return (
<section className={styles.container}>
<section>
<article className={styles.wrapper}>
<Title as="h4" level="h3">
{block.title}
</Title>
<ScandicFriends color="primaryLightOnSurfaceAccent" />
{block.preamble ? (
<Body textAlign="center">{block.preamble}</Body>
) : null}
<Button asChild intent="primary">
<Body asChild fontOnly textAlign="center" textTransform="bold">
<Link href={login[lang]}>
{formatMessage({ id: "Join Scandic Friends" })}
</Link>
</Body>
</Button>
<Footnote asChild fontOnly textAlign="center" textTransform="bold">
<Link color="burgundy" href={`/${lang}/login`}>
{formatMessage({ id: "Already a friend?" })} <br />
{formatMessage({ id: "Click here to log in" })}
<ScandicFriends color="red" />
{block.preamble ? <Body>{block.preamble}</Body> : null}
<Button asChild intent="primary" theme="base" className={styles.button}>
<Link href={login[lang]} color="white">
{formatMessage({ id: "Join Scandic Friends" })}
</Link>
</Footnote>
</Button>
<section className={styles.loginContainer}>
<Body>{formatMessage({ id: "Already a friend?" })}</Body>
<Link
className={styles.link}
color="burgundy"
href={`/${lang}/login`}
variant="icon"
size="small"
>
<ArrowRight
color="burgundy"
className={styles.icon}
height="20"
width="20"
/>
{formatMessage({ id: "Log in here" })}
</Link>
</section>
</article>
{block.contact ? <Contact contactBlock={block.contact} /> : null}
</section>

View File

@@ -1,12 +1,24 @@
.container {
background-color: var(--Main-Grey-White);
.wrapper {
display: grid;
gap: var(--Spacing-x3);
padding-bottom: var(--Spacing-x5);
padding-top: var(--Spacing-x4);
}
.wrapper {
.loginContainer {
display: grid;
gap: var(--Spacing-x2);
}
.button {
width: fit-content;
}
.link {
display: flex;
align-items: center;
flex-direction: column;
gap: var(--Spacing-x5);
padding: var(--Spacing-x4) var(--Spacing-x2) var(--Spacing-x5);
}
}
.icon {
align-self: center;
}

View File

@@ -0,0 +1,14 @@
import { auth } from "@/auth"
import MyPagesSidebar from "@/components/MyPages/Sidebar"
import { LangParams } from "@/types/params"
export default async function MyPagesNavigation({ lang }: LangParams) {
const session = await auth()
if (!session) {
return null
}
return <MyPagesSidebar lang={lang} />
}

View File

@@ -1,10 +1,14 @@
import JsonToHtml from "@/components/JsonToHtml"
import JoinLoyaltyContact from "./JoinLoyalty"
import MyPagesNavigation from "./MyPagesNavigation"
import styles from "./sidebar.module.css"
import { SidebarTypenameEnum } from "@/types/components/loyalty/enums"
import {
LoyaltySidebarDynamicComponentEnum,
SidebarTypenameEnum,
} from "@/types/components/loyalty/enums"
import { SidebarProps } from "@/types/components/loyalty/sidebar"
import { LangParams } from "@/types/params"
@@ -36,6 +40,19 @@ export default function SidebarLoyalty({
lang={lang}
/>
)
case SidebarTypenameEnum.LoyaltyPageSidebarDynamicContent:
switch (block.dynamic_content.component) {
case LoyaltySidebarDynamicComponentEnum.my_pages_navigation:
return (
<MyPagesNavigation
key={`${block.__typename}-${idx}`}
lang={lang}
/>
)
default:
return null
}
default:
return null
}

View File

@@ -1,11 +1,15 @@
.aside {
align-content: flex-start;
display: grid;
gap: var(--Spacing-x4);
display: none;
}
@media screen and (max-width: 1366px) {
.content {
padding: var(--Spacing-x0) var(--Spacing-x2);
.content {
padding: var(--Spacing-x0) var(--Spacing-x2);
}
@media screen and (min-width: 1366px) {
.aside {
align-content: flex-start;
display: grid;
gap: var(--Spacing-x4);
}
}

View File

@@ -2,6 +2,7 @@
import { useIntl } from "react-intl"
import { ChevronDownIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import styles from "./button.module.css"
@@ -17,11 +18,14 @@ export default function ShowMoreButton({
<div className={styles.container}>
<Button
disabled={disabled}
intent="primary"
onClick={loadMoreData}
theme="secondaryDark"
variant="icon"
type="button"
className={styles.button}
theme="base"
intent="text"
>
<ChevronDownIcon />
{formatMessage({ id: "Show more" })}
</Button>
</div>

View File

@@ -1,18 +1,32 @@
.container {
display: grid;
grid-template-rows: 1fr min(50px);
background-color: var(--Base-Surface-Primary-Normal);
border-radius: var(--Corner-radius-Medium);
min-height: 250px;
margin-bottom: var(--Spacing-x-half);
overflow: hidden;
}
.titleContainer {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--Scandic-Brand-Pale-Peach);
border-radius: var(--Corner-radius-Medium);
}
.title {
display: flex;
gap: var(--Spacing-x3);
flex-direction: column;
justify-content: center;
margin-bottom: var(--Spacing-x-half);
min-height: 250px;
padding: var(--Spacing-x0) var(--Spacing-x3);
align-items: center;
}
.burgundyTitle {
color: var(--Scandic-Brand-Burgundy);
display: block;
text-align: center;
}
.link {
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -1,24 +1,35 @@
import Button from "@/components/TempDesignSystem/Button"
import { homeHrefs } from "@/constants/homeHrefs"
import { env } from "@/env/server"
import { ArrowRightIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import styles from "./emptyUpcomingStays.module.css"
export default async function EmptyUpcomingStaysBlock() {
import { LangParams } from "@/types/params"
export default async function EmptyUpcomingStaysBlock({ lang }: LangParams) {
const { formatMessage } = await getIntl()
return (
<section className={styles.container}>
<Title as="h5" level="h3" color="red">
{formatMessage({ id: "You have no upcoming stays." })}
<span className={styles.burgundyTitle}>
{" "}
{formatMessage({ id: "Where should you go next?" })}
</span>
</Title>
<Button asChild intent="primary" type="button">
<Link href="#">{formatMessage({ id: "Get inspired" })}</Link>
</Button>
<div className={styles.titleContainer}>
<Title as="h5" 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?" })}
</span>
</Title>
</div>
<Link
href={homeHrefs[env.NODE_ENV][lang]}
className={styles.link}
color="peach80"
>
{formatMessage({ id: "Get inspired" })}
<ArrowRightIcon color="peach80" />
</Link>
</section>
)
}

View File

@@ -34,7 +34,7 @@ export default async function SoonestStays({
))}
</Grids.Stackable>
) : (
<EmptyUpcomingStaysBlock />
<EmptyUpcomingStaysBlock lang={lang} />
)}
</SectionContainer>
)

View File

@@ -11,7 +11,6 @@ import type { StayCardProps } from "@/types/components/myPages/stays/stayCard"
export default function StayCard({ stay, lang }: StayCardProps) {
const { checkinDate, checkoutDate, hotelInformation } = stay.attributes
const arrival = dt(checkinDate).locale(lang)
const arrivalDate = arrival.format("DD MMM")
const arrivalDateTime = arrival.format("YYYY-MM-DD")
@@ -33,7 +32,7 @@ export default function StayCard({ stay, lang }: StayCardProps) {
{hotelInformation.hotelName}
</Title>
<div className={styles.date}>
<CalendarIcon color="burgundy" />
<CalendarIcon color="burgundy" height={24} width={24} />
<Caption asChild>
<time dateTime={arrivalDateTime}>{arrivalDate}</time>
</Caption>

View File

@@ -36,4 +36,4 @@
align-items: center;
display: flex;
gap: var(--Spacing-x-half);
}
}

View File

@@ -1,18 +1,32 @@
.container {
display: grid;
grid-template-rows: 1fr min(50px);
background-color: var(--Base-Surface-Primary-Normal);
border-radius: var(--Corner-radius-Medium);
min-height: 250px;
margin-bottom: var(--Spacing-x-half);
overflow: hidden;
}
.titleContainer {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--Scandic-Brand-Pale-Peach);
border-radius: var(--Corner-radius-Medium);
}
.title {
display: flex;
gap: var(--Spacing-x3);
flex-direction: column;
justify-content: center;
margin-bottom: var(--Spacing-x-half);
min-height: 250px;
padding: var(--Spacing-x0) var(--Spacing-x3);
align-items: center;
}
.burgundyTitle {
color: var(--Scandic-Brand-Burgundy);
display: block;
text-align: center;
}
.link {
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -1,7 +1,7 @@
import { homeHrefs } from "@/constants/homeHrefs"
import { env } from "@/env/server"
import Button from "@/components/TempDesignSystem/Button"
import { ArrowRightIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
@@ -14,18 +14,22 @@ export default async function EmptyUpcomingStaysBlock({ lang }: LangParams) {
const { formatMessage } = await getIntl()
return (
<section className={styles.container}>
<Title as="h5" level="h3" color="red">
{formatMessage({ id: "You have no upcoming stays." })}
<span className={styles.burgundyTitle}>
{" "}
{formatMessage({ id: "Where will you go next?" })}
</span>
</Title>
<Button asChild intent="primary" type="button">
<Link href={homeHrefs[env.NODE_ENV][lang]}>
{formatMessage({ id: "Get inspired" })}
</Link>
</Button>
<div className={styles.titleContainer}>
<Title as="h5" level="h3" color="red" className={styles.title}>
{formatMessage({ id: "You have no upcoming stays." })}
<span className={styles.burgundyTitle}>
{formatMessage({ id: "Where will you go next?" })}
</span>
</Title>
</div>
<Link
href={homeHrefs[env.NODE_ENV][lang]}
className={styles.link}
color="peach80"
>
{formatMessage({ id: "Get inspired" })}
<ArrowRightIcon color="peach80" />
</Link>
</section>
)
}

View File

@@ -16,7 +16,7 @@ export default function SectionHeader({
return (
<header className={styles.header}>
<Title
as={topTitle ? "h2" : "h3"}
as={topTitle ? "h3" : "h4"}
className={styles.title}
level={topTitle ? "h1" : "h2"}
>

View File

@@ -41,6 +41,12 @@ a.inverted {
border: none;
}
.text,
a.text {
background: none;
border: none;
}
/* VARIANTS */
.default,
a.default {
@@ -50,7 +56,9 @@ a.default {
}
.icon {
align-items: baseline;
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
}
/* SIZES */
@@ -97,6 +105,25 @@ a.default {
color: var(--Base-Button-Primary-On-Fill-Disabled);
}
.icon.basePrimary svg,
.icon.basePrimary svg * {
fill: var(--Base-Button-Primary-On-Fill-Normal);
}
.icon.basePrimary:active svg,
.icon.basePrimary:focus svg,
.icon.basePrimary:hover svg,
.icon.basePrimary:active svg *,
.icon.basePrimary:focus svg *,
.icon.basePrimary:hover svg * {
fill: var(--Base-Button-Primary-On-Fill-Hover);
}
.icon.basePrimary:disabled *,
.icon.basePrimary:disabled svg * {
fill: var(--Base-Button-Primary-On-Fill-Disabled);
}
.baseSecondary {
background-color: var(--Base-Button-Secondary-Fill-Normal);
border-color: var(--Base-Button-Secondary-Border-Normal);
@@ -117,6 +144,25 @@ a.default {
color: var(--Base-Button-Secondary-On-Fill-Disabled);
}
.icon.baseSecondary svg,
.icon.baseSecondary svg * {
fill: var(--Base-Button-Secondary-On-Fill-Normal);
}
.icon.baseSecondary:active svg,
.icon.baseSecondary:focus svg,
.icon.baseSecondary:hover svg,
.icon.baseSecondary:active svg *,
.icon.baseSecondary:focus svg *,
.icon.baseSecondary:hover svg * {
fill: var(--Base-Button-Secondary-On-Fill-Hover);
}
.icon.baseSecondary:disabled svg,
.icon.baseSecondary:disabled svg * {
fill: var(--Base-Button-Secondary-On-Fill-Disabled);
}
.baseTertiary {
background-color: var(--Base-Button-Tertiary-Fill-Normal);
color: var(--Base-Button-Tertiary-On-Fill-Normal);
@@ -134,6 +180,25 @@ a.default {
color: var(--Base-Button-Tertiary-On-Fill-Disabled);
}
.icon.baseTertiary svg,
.icon.baseTertiary svg * {
fill: var(--Base-Button-Tertiary-On-Fill-Normal);
}
.icon.baseTertiary:active svg,
.icon.baseTertiary:focus svg,
.icon.baseTertiary:hover svg,
.icon.baseTertiary:active svg *,
.icon.baseTertiary:focus svg *,
.icon.baseTertiary:hover svg * {
fill: var(--Base-Button-Tertiary-On-Fill-Hover);
}
.icon.baseTertiary:disabled svg,
.icon.baseTertiary:disabled svg * {
fill: var(--Base-Button-Tertiary-On-Fill-Disabled);
}
.baseInverted {
background-color: var(--Base-Button-Inverted-Fill-Normal);
color: var(--Base-Button-Inverted-On-Fill-Normal);
@@ -151,6 +216,60 @@ a.default {
color: var(--Base-Button-Inverted-On-Fill-Disabled);
}
.icon.baseInverted svg,
.icon.baseInverted svg * {
fill: var(--Base-Button-Inverted-On-Fill-Normal);
}
.icon.baseInverted:active svg,
.icon.baseInverted:focus svg,
.icon.baseInverted:hover svg,
.icon.baseInverted:active svg *,
.icon.baseInverted:focus svg *,
.icon.baseInverted:hover svg * {
fill: var(--Base-Button-Inverted-On-Fill-Hover);
}
.icon.baseInverted:disabled svg,
.icon.baseInverted:disabled svg * {
fill: var(--Base-Button-Inverted-On-Fill-Disabled);
}
.baseText {
color: var(--Base-Button-Text-On-Fill-Normal);
}
.baseText:active,
.baseText:focus,
.baseText:hover {
color: var(--Base-Button-Text-On-Fill-Hover);
text-decoration: underline;
}
.baseText:disabled {
color: var(--Base-Button-Text-On-Fill-Disabled);
}
.icon.baseText svg,
.icon.baseText svg * {
fill: var(--Base-Button-Text-On-Fill-Normal);
}
.icon.baseText:active svg,
.icon.baseText:focus svg,
.icon.baseText:hover svg,
.icon.baseText:active svg *,
.icon.baseText:focus svg *,
.icon.baseText:hover svg * {
fill: var(--Base-Button-Text-On-Fill-Hover);
text-decoration: underline;
}
.icon.baseText:disabled svg,
.icon.baseText:disabled svg * {
fill: var(--Base-Button-Text-On-Fill-Disabled);
}
.primaryStrongPrimary {
background-color: var(--Theme-Primary-Strong-Button-Primary-Fill-Normal);
color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Normal);
@@ -168,6 +287,25 @@ a.default {
color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Disabled);
}
.icon.primaryStrongPrimary svg,
.icon.primaryStrongPrimary svg * {
fill: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Normal);
}
.icon.primaryStrongPrimary:active svg,
.icon.primaryStrongPrimary:focus svg,
.icon.primaryStrongPrimary:hover svg,
.icon.primaryStrongPrimary:active svg *,
.icon.primaryStrongPrimary:focus svg *,
.icon.primaryStrongPrimary:hover svg * {
fill: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Hover);
}
.icon.primaryStrongPrimary:disabled svg,
.icon.primaryStrongPrimary:disabled svg * {
fill: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Disabled);
}
.primaryStrongSecondary {
background-color: var(--Theme-Primary-Strong-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Primary-Strong-Button-Secondary-Border-Normal);
@@ -188,6 +326,25 @@ a.default {
color: var(--Theme-Primary-Strong-Button-Secondary-On-Fill-Disabled);
}
.icon.primaryStrongSecondary svg,
.icon.primaryStrongSecondary svg * {
fill: var(--Theme-Primary-Strong-Button-Secondary-On-Fill-Normal);
}
.icon.primaryStrongSecondary:active svg,
.icon.primaryStrongSecondary:focus svg,
.icon.primaryStrongSecondary:hover svg,
.icon.primaryStrongSecondary:active svg *,
.icon.primaryStrongSecondary:focus svg *,
.icon.primaryStrongSecondary:hover svg * {
fill: var(--Theme-Primary-Strong-Button-Secondary-On-Fill-Hover);
}
.icon.primaryStrongSecondary:disabled svg,
.icon.primaryStrongSecondary:disabled svg * {
fill: var(--Theme-Primary-Strong-Button-Secondary-On-Fill-Disabled);
}
.primaryDarkPrimary {
background-color: var(--Theme-Primary-Dark-Button-Primary-Fill-Normal);
color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Normal);
@@ -205,6 +362,25 @@ a.default {
color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Disabled);
}
.icon.primaryDarkPrimary svg,
.icon.primaryDarkPrimary svg * {
fill: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Normal);
}
.icon.primaryDarkPrimary:active svg,
.icon.primaryDarkPrimary:focus svg,
.icon.primaryDarkPrimary:hover svg,
.icon.primaryDarkPrimary:active svg *,
.icon.primaryDarkPrimary:focus svg *,
.icon.primaryDarkPrimary:hover svg * {
fill: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Hover);
}
.icon.primaryDarkPrimary:disabled svg,
.icon.primaryDarkPrimary:disabled svg * {
fill: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Disabled);
}
.primaryDarkSecondary {
background-color: var(--Theme-Primary-Dark-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Primary-Dark-Button-Secondary-Border-Normal);
@@ -225,6 +401,25 @@ a.default {
color: var(--Theme-Primary-Dark-Button-Secondary-On-Fill-Disabled);
}
.icon.primaryDarkSecondary svg,
.icon.primaryDarkSecondary svg * {
fill: var(--Theme-Primary-Dark-Button-Secondary-On-Fill-Normal);
}
.icon.primaryDarkSecondary:active svg,
.icon.primaryDarkSecondary:focus svg,
.icon.primaryDarkSecondary:hover svg,
.icon.primaryDarkSecondary:active svg *,
.icon.primaryDarkSecondary:focus svg *,
.icon.primaryDarkSecondary:hover svg * {
fill: var(--Theme-Primary-Dark-Button-Secondary-On-Fill-Hover);
}
.icon.primaryDarkSecondary:disabled svg,
.icon.primaryDarkSecondary:disabled svg * {
fill: var(--Theme-Primary-Dark-Button-Secondary-On-Fill-Disabled);
}
.primaryLightPrimary {
background-color: var(--Theme-Primary-Light-Button-Primary-Fill-Normal);
color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Normal);
@@ -242,6 +437,25 @@ a.default {
color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Disabled);
}
.icon.primaryLightPrimary svg,
.icon.primaryLightPrimary svg * {
fill: var(--Theme-Primary-Light-Button-Primary-On-Fill-Normal);
}
.icon.primaryLightPrimary:active svg,
.icon.primaryLightPrimary:focus svg,
.icon.primaryLightPrimary:hover svg,
.icon.primaryLightPrimary:active svg *,
.icon.primaryLightPrimary:focus svg *,
.icon.primaryLightPrimary:hover svg * {
fill: var(--Theme-Primary-Light-Button-Primary-On-Fill-Hover);
}
.icon.primaryLightPrimary:disabled svg,
.icon.primaryLightPrimary:disabled svg * {
fill: var(--Theme-Primary-Light-Button-Primary-On-Fill-Disabled);
}
.primaryLightSecondary {
background-color: var(--Theme-Primary-Light-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Primary-Light-Button-Secondary-Border-Normal);
@@ -262,6 +476,25 @@ a.default {
color: var(--Theme-Primary-Light-Button-Secondary-On-Fill-Disabled);
}
.icon.primaryLightSecondary svg,
.icon.primaryLightSecondary svg * {
fill: var(--Theme-Primary-Light-Button-Secondary-On-Fill-Normal);
}
.icon.primaryLightSecondary:active svg,
.icon.primaryLightSecondary:focus svg,
.icon.primaryLightSecondary:hover svg,
.icon.primaryLightSecondary:active svg *,
.icon.primaryLightSecondary:focus svg *,
.icon.primaryLightSecondary:hover svg * {
fill: var(--Theme-Primary-Light-Button-Secondary-On-Fill-Hover);
}
.icon.primaryLightSecondary:disabled svg,
.icon.primaryLightSecondary:disabled svg * {
fill: var(--Theme-Primary-Light-Button-Secondary-On-Fill-Disabled);
}
.secondaryDarkPrimary {
background-color: var(--Theme-Secondary-Dark-Button-Primary-Fill-Normal);
color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Normal);
@@ -279,6 +512,25 @@ a.default {
color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Disabled);
}
.icon.secondaryDarkPrimary svg,
.icon.secondaryDarkPrimary svg * {
fill: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Normal);
}
.icon.secondaryDarkPrimary:active svg,
.icon.secondaryDarkPrimary:focus svg,
.icon.secondaryDarkPrimary:hover svg,
.icon.secondaryDarkPrimary:active svg *,
.icon.secondaryDarkPrimary:focus svg *,
.icon.secondaryDarkPrimary:hover svg * {
fill: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Hover);
}
.icon.secondaryDarkPrimary:disabled svg,
.icon.secondaryDarkPrimary:disabled svg * {
fill: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Disabled);
}
.secondaryDarkSecondary {
background-color: var(--Theme-Secondary-Dark-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Secondary-Dark-Button-Secondary-Border-Normal);
@@ -299,6 +551,25 @@ a.default {
color: var(--Theme-Secondary-Dark-Button-Secondary-On-Fill-Disabled);
}
.icon.secondaryDarkSecondary svg,
.icon.secondaryDarkSecondary svg * {
fill: var(--Theme-Secondary-Dark-Button-Secondary-On-Fill-Normal);
}
.icon.secondaryDarkSecondary:active svg,
.icon.secondaryDarkSecondary:focus svg,
.icon.secondaryDarkSecondary:hover svg,
.icon.secondaryDarkSecondary:active svg *,
.icon.secondaryDarkSecondary:focus svg *,
.icon.secondaryDarkSecondary:hover svg * {
fill: var(--Theme-Secondary-Dark-Button-Secondary-On-Fill-Hover);
}
.icon.secondaryDarkSecondary:disabled svg,
.icon.secondaryDarkSecondary:disabled svg * {
fill: var(--Theme-Secondary-Dark-Button-Secondary-On-Fill-Disabled);
}
.secondaryLightPrimary {
background-color: var(--Theme-Secondary-Light-Button-Primary-Fill-Normal);
color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Normal);
@@ -316,6 +587,25 @@ a.default {
color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Disabled);
}
.icon.secondaryLightPrimary svg,
.icon.secondaryLightPrimary svg * {
fill: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Normal);
}
.icon.secondaryLightPrimary:active svg,
.icon.secondaryLightPrimary:focus svg,
.icon.secondaryLightPrimary:hover svg,
.icon.secondaryLightPrimary:active svg *,
.icon.secondaryLightPrimary:focus svg *,
.icon.secondaryLightPrimary:hover svg * {
fill: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Hover);
}
.icon.secondaryLightPrimary:disabled svg,
.icon.secondaryLightPrimary:disabled svg * {
fill: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Disabled);
}
.secondaryLightSecondary {
background-color: var(--Theme-Secondary-Light-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Secondary-Light-Button-Secondary-Border-Normal);
@@ -336,6 +626,25 @@ a.default {
color: var(--Theme-Secondary-Light-Button-Secondary-On-Fill-Disabled);
}
.icon.secondaryLightSecondary svg,
.icon.secondaryLightSecondary svg * {
fill: var(--Theme-Secondary-Light-Button-Secondary-On-Fill-Normal);
}
.icon.secondaryLightSecondary:active svg,
.icon.secondaryLightSecondary:focus svg,
.icon.secondaryLightSecondary:hover svg,
.icon.secondaryLightSecondary:active svg *,
.icon.secondaryLightSecondary:focus svg *,
.icon.secondaryLightSecondary:hover svg * {
fill: var(--Theme-Secondary-Light-Button-Secondary-On-Fill-Hover);
}
.icon.secondaryLightSecondary:disabled svg,
.icon.secondaryLightSecondary:disabled svg * {
fill: var(--Theme-Secondary-Light-Button-Secondary-On-Fill-Disabled);
}
.tertiaryDarkPrimary {
background-color: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Normal);
color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Normal);
@@ -353,6 +662,25 @@ a.default {
color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Disabled);
}
.icon.tertiaryDarkPrimary svg,
.icon.tertiaryDarkPrimary svg * {
fill: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Normal);
}
.icon.tertiaryDarkPrimary:active svg,
.icon.tertiaryDarkPrimary:focus svg,
.icon.tertiaryDarkPrimary:hover svg,
.icon.tertiaryDarkPrimary:active svg *,
.icon.tertiaryDarkPrimary:focus svg *,
.icon.tertiaryDarkPrimary:hover svg * {
fill: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Hover);
}
.icon.tertiaryDarkPrimary:disabled svg,
.icon.tertiaryDarkPrimary:disabled svg * {
fill: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Disabled);
}
.tertiaryDarkSecondary {
background-color: var(--Theme-Tertiary-Dark-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Tertiary-Dark-Button-Secondary-Border-Normal);
@@ -373,6 +701,25 @@ a.default {
color: var(--Theme-Tertiary-Dark-Button-Secondary-On-Fill-Disabled);
}
.icon.tertiaryDarkSecondary svg,
.icon.tertiaryDarkSecondary svg * {
fill: var(--Theme-Tertiary-Dark-Button-Secondary-On-Fill-Normal);
}
.icon.tertiaryDarkSecondary:active svg,
.icon.tertiaryDarkSecondary:focus svg,
.icon.tertiaryDarkSecondary:hover svg,
.icon.tertiaryDarkSecondary:active svg *,
.icon.tertiaryDarkSecondary:focus svg *,
.icon.tertiaryDarkSecondary:hover svg * {
fill: var(--Theme-Tertiary-Dark-Button-Secondary-On-Fill-Hover);
}
.icon.tertiaryDarkSecondary:disabled svg,
.icon.tertiaryDarkSecondary:disabled svg * {
fill: var(--Theme-Tertiary-Dark-Button-Secondary-On-Fill-Disabled);
}
.tertiaryLightPrimary {
background-color: var(--Theme-Tertiary-Light-Button-Primary-Fill-Normal);
color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Normal);
@@ -390,6 +737,25 @@ a.default {
color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Disabled);
}
.icon.tertiaryLightPrimary svg,
.icon.tertiaryLightPrimary svg * {
fill: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Normal);
}
.icon.tertiaryLightPrimary:active svg,
.icon.tertiaryLightPrimary:focus svg,
.icon.tertiaryLightPrimary:hover svg,
.icon.tertiaryLightPrimary:active svg *,
.icon.tertiaryLightPrimary:focus svg *,
.icon.tertiaryLightPrimary:hover svg * {
fill: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Hover);
}
.icon.tertiaryLightPrimary:disabled svg,
.icon.tertiaryLightPrimary:disabled svg * {
fill: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Disabled);
}
.tertiaryLightSecondary {
background-color: var(--Tertiary-Light-Button-Secondary-Fill-Normal);
border-color: var(--Tertiary-Light-Button-Secondary-Border-Normal);
@@ -409,3 +775,22 @@ a.default {
border-color: var(--Tertiary-Light-Button-Secondary-Border-Disabled);
color: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled);
}
.icon.tertiaryLightSecondary svg,
.icon.tertiaryLightSecondary svg * {
fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Normal);
}
.icon.tertiaryLightSecondary:active svg,
.icon.tertiaryLightSecondary:focus svg,
.icon.tertiaryLightSecondary:hover svg,
.icon.tertiaryLightSecondary:active svg *,
.icon.tertiaryLightSecondary:focus svg *,
.icon.tertiaryLightSecondary:hover svg * {
fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Hover);
}
.icon.tertiaryLightSecondary:disabled svg,
.icon.tertiaryLightSecondary:disabled svg * {
fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled);
}

View File

@@ -9,6 +9,7 @@ export const buttonVariants = cva(styles.btn, {
primary: styles.primary,
secondary: styles.secondary,
tertiary: styles.tertiary,
text: styles.text,
},
size: {
small: styles.small,
@@ -128,5 +129,10 @@ export const buttonVariants = cva(styles.btn, {
intent: "secondary",
theme: "tertiaryLight",
},
{
className: styles.baseText,
intent: "text",
theme: "base",
},
],
})

View File

@@ -1,5 +1,4 @@
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
import Body from "@/components/TempDesignSystem/Text/Body"
@@ -47,7 +46,6 @@ export default function Card({
<BiroScript className={styles.scriptedTitle} type="two">
{scriptedTopTitle}
</BiroScript>
<Divider />
</section>
) : null}
<Title as="h5" className={styles.heading} level="h3">

View File

@@ -17,7 +17,7 @@
.icon {
align-items: center;
display: flex;
gap: var(--Spacing-x1);
gap: var(--Spacing-x-half);
}
.myPage {
@@ -102,6 +102,10 @@
color: var(--Scandic-Peach-80);
}
.white {
color: var(--Base-Button-Primary-On-Fill-Normal);
}
.regular {
font-family: var(--typography-Body-Regular-fontFamily);
font-size: var(--typography-Body-Regular-fontSize);

View File

@@ -13,6 +13,7 @@ export const linkVariants = cva(styles.link, {
none: "",
pale: styles.pale,
peach80: styles.peach80,
white: styles.white,
},
size: {
small: styles.small,

View File

@@ -0,0 +1,66 @@
import ArrowRight from "@/components/Icons/ArrowRight"
import Image from "@/components/Image"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { loyaltyCardVariants } from "./variants"
import styles from "./loyaltyCard.module.css"
import type { LoyaltyCardProps } from "./loyaltyCard"
export default function LoyaltyCard({
link,
image,
heading,
bodyText,
theme = "white",
className,
}: LoyaltyCardProps) {
return (
<article
className={loyaltyCardVariants({
className,
theme,
})}
>
{image ? (
<Image
src={image.url}
width={180}
height={160}
className={styles.image}
alt={image.meta.alt || image.title}
/>
) : null}
<Title as="h5" level="h3" textAlign="center">
{heading}
</Title>
{bodyText ? (
<Body textAlign="center" color="red">
{bodyText}
</Body>
) : null}
<div className={styles.buttonContainer}>
{link ? (
<Link
className={styles.link}
color="burgundy"
href={link.href}
target={link.openInNewTab ? "_blank" : undefined}
variant="myPage"
>
<ArrowRight
color="burgundy"
className={styles.icon}
height="20"
width="20"
/>
{link.title}
</Link>
) : null}
</div>
</article>
)
}

View File

@@ -0,0 +1,38 @@
.container {
align-items: center;
display: grid;
border-radius: var(--Corner-radius-xLarge);
gap: var(--Spacing-x2);
height: 480px;
justify-content: space-between;
margin-right: var(--Spacing-x2);
padding: var(--Spacing-x4) var(--Spacing-x3);
text-align: center;
width: 100%;
}
.image {
object-fit: contain;
height: 160px;
width: auto;
justify-self: center;
}
.white {
background-color: var(--Main-Grey-White);
}
.buttonContainer {
display: flex;
gap: var(--Spacing-x1);
justify-content: center;
}
.link {
display: flex;
align-items: center;
}
.icon {
align-self: center;
}

View File

@@ -0,0 +1,20 @@
import { loyaltyCardVariants } from "./variants"
import type { VariantProps } from "class-variance-authority"
import { ImageVaultAsset } from "@/types/components/imageVaultImage"
export interface LoyaltyCardProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof loyaltyCardVariants> {
link?: {
href: string
title: string
openInNewTab?: boolean
isExternal: boolean
}
image?: ImageVaultAsset
heading?: string | null
bodyText?: string | null
backgroundImage?: { url: string }
}

View File

@@ -0,0 +1,14 @@
import { cva } from "class-variance-authority"
import styles from "./loyaltyCard.module.css"
export const loyaltyCardVariants = cva(styles.container, {
variants: {
theme: {
white: styles.white,
},
},
defaultVariants: {
theme: "white",
},
})

View File

@@ -26,6 +26,14 @@
line-height: var(--typography-Script-2-lineHeight);
}
.tiltedSmall {
transform: rotate(-2deg);
}
.tiltedLarge {
transform: rotate(-13deg) translate(0px, -15px);
}
.center {
text-align: center;
}

View File

@@ -10,6 +10,7 @@ export default function BiroScript({
color,
textAlign,
type,
tilted,
...props
}: BiroScriptProps) {
const Comp = asChild ? Slot : "span"
@@ -18,6 +19,7 @@ export default function BiroScript({
color,
textAlign,
type,
tilted,
})
return <Comp className={classNames} {...props} />
}

View File

@@ -19,6 +19,10 @@ const config = {
one: styles.one,
two: styles.two,
},
tilted: {
small: styles.tiltedSmall,
large: styles.tiltedLarge,
},
},
defaultVariants: {
type: "one",

View File

@@ -36,3 +36,15 @@
.pale {
color: var(--Scandic-Brand-Pale-Peach);
}
.textMediumContrast {
color: var(--Base-Text-UI-Medium-contrast);
}
.center {
text-align: center;
}
.left {
text-align: left;
}

View File

@@ -9,19 +9,21 @@ export default function Caption({
className = "",
color,
fontOnly = false,
textAlign,
textTransform,
...props
}: CaptionProps) {
const Comp = asChild ? Slot : "p"
const classNames = fontOnly
? fontOnlycaptionVariants({
className,
textTransform,
})
className,
textTransform,
})
: captionVariants({
className,
color,
textTransform,
})
className,
color,
textTransform,
textAlign,
})
return <Comp className={classNames} {...props} />
}

View File

@@ -8,11 +8,16 @@ const config = {
black: styles.black,
burgundy: styles.burgundy,
pale: styles.pale,
textMediumContrast: styles.textMediumContrast,
},
textTransform: {
bold: styles.bold,
regular: styles.regular,
},
textAlign: {
center: styles.center,
left: styles.left,
},
},
defaultVariants: {
color: "black",

View File

@@ -43,4 +43,8 @@
.pale {
color: var(--Scandic-Brand-Pale-Peach);
}
}
.textMediumContrast {
color: var(--Base-Text-UI-Medium-contrast);
}

View File

@@ -8,6 +8,7 @@ const config = {
black: styles.black,
burgundy: styles.burgundy,
pale: styles.pale,
textMediumContrast: styles.textMediumContrast,
},
textAlign: {
center: styles.center,

5
env/server.ts vendored
View File

@@ -9,7 +9,9 @@ export const env = createEnv({
*/
isServer: typeof window === "undefined" || "Deno" in window,
server: {
APPLICATION_INSIGHTS_CONNECTION_STRING: z.string().optional(),
ADOBE_SCRIPT_SRC: z.string().optional(),
ADOBE_SDK_SCRIPT_SRC: z.string().optional(),
API_BASEURL: z.string(),
BUILD_ID: z.string().default("64rYXBu8o2eHp0Jf"),
CMS_ACCESS_TOKEN: z.string(),
@@ -58,7 +60,10 @@ export const env = createEnv({
},
emptyStringAsUndefined: true,
runtimeEnv: {
APPLICATION_INSIGHTS_CONNECTION_STRING:
process.env.APPLICATION_INSIGHTS_CONNECTION_STRING,
ADOBE_SCRIPT_SRC: process.env.ADOBE_SCRIPT_SRC,
ADOBE_SDK_SCRIPT_SRC: process.env.ADOBE_SDK_SCRIPT_SRC,
API_BASEURL: process.env.API_BASEURL,
BUILD_ID: process.env.BUILD_ID,
CMS_ACCESS_TOKEN: process.env.CMS_ACCESS_TOKEN,

21
instrumentation.ts Normal file
View File

@@ -0,0 +1,21 @@
import { env } from "./env/server"
export async function register() {
if (
process.env.NEXT_RUNTIME === "nodejs" &&
env.APPLICATION_INSIGHTS_CONNECTION_STRING
) {
const { AzureMonitorTraceExporter } = await import(
"@azure/monitor-opentelemetry-exporter"
)
const { registerOTel } = await import("@vercel/otel")
const connectionString = env.APPLICATION_INSIGHTS_CONNECTION_STRING
const traceExporter = new AzureMonitorTraceExporter({ connectionString })
registerOTel({
serviceName: "scandic-web",
traceExporter,
})
}
}

View File

@@ -0,0 +1,28 @@
fragment LoyaltyCardBlock on LoyaltyCard {
heading
body_text
image
title
link {
cta_text
open_in_new_tab
external_link {
title
href
}
linkConnection {
edges {
node {
__typename
...LoyaltyPageLink
...ContentPageLink
...AccountPageLink
}
}
}
}
system {
locale
uid
}
}

View File

@@ -1,4 +1,5 @@
fragment CardBlockRef on Card {
__typename
secondary_button {
linkConnection {
edges {

View File

@@ -0,0 +1,18 @@
fragment LoyaltyCardBlockRef on LoyaltyCard {
__typename
link {
linkConnection {
edges {
node {
__typename
...LoyaltyPageRef
...ContentPageRef
...AccountPageRef
}
}
}
}
system {
...System
}
}

View File

@@ -1,6 +1,10 @@
#import "../Fragments/Image.graphql"
#import "../Fragments/Blocks/Card.graphql"
#import "../Fragments/Blocks/LoyaltyCard.graphql"
#import "../Fragments/Blocks/Refs/Card.graphql"
#import "../Fragments/Blocks/Refs/LoyaltyCard.graphql"
#import "../Fragments/LoyaltyPage/Breadcrumbs.graphql"
#import "../Fragments/PageLink/AccountPageLink.graphql"
#import "../Fragments/PageLink/ContentPageLink.graphql"
@@ -83,7 +87,9 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
cardConnection(limit: 10) {
edges {
node {
__typename
...CardBlock
...LoyaltyCardBlock
}
}
}
@@ -94,6 +100,11 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
heading
sidebar {
__typename
... on LoyaltyPageSidebarDynamicContent {
dynamic_content {
component
}
}
... on LoyaltyPageSidebarJoinLoyaltyContact {
join_loyalty_contact {
title
@@ -104,6 +115,7 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
contact {
display_text
contact_field
footnote
}
}
}
@@ -206,6 +218,7 @@ query GetLoyaltyPageRefs($locale: String!, $uid: String!) {
edges {
node {
...CardBlockRef
...LoyaltyCardBlockRef
}
}
}

View File

@@ -13,6 +13,7 @@ const nextConfig = {
eslint: { ignoreDuringBuilds: true },
trailingSlash: false,
experimental: {
instrumentationHook: true,
serverActions: {
allowedOrigins: [
"*--web-scandic-hotels.netlify.app",

370
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.1.0",
"hasInstallScript": true,
"dependencies": {
"@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.24",
"@contentstack/live-preview-utils": "^1.4.0",
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.3.4",
@@ -20,6 +21,7 @@
"@trpc/client": "^11.0.0-next-beta.318",
"@trpc/react-query": "^11.0.0-next-beta.318",
"@trpc/server": "^11.0.0-next-beta.318",
"@vercel/otel": "^1.9.1",
"class-variance-authority": "^0.7.0",
"clean-deep": "^3.4.0",
"dayjs": "^1.11.10",
@@ -130,6 +132,156 @@
}
}
},
"node_modules/@azure/abort-controller": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-auth": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz",
"integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-util": "^1.1.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-client": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz",
"integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.4.0",
"@azure/core-rest-pipeline": "^1.9.1",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.6.1",
"@azure/logger": "^1.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-rest-pipeline": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.1.tgz",
"integrity": "sha512-ExPSbgjwCoht6kB7B4MeZoBAxcQSIl29r/bPeazZJx50ej4JJCByimLOrZoIsurISNyJQQHf30b3JfqC3Hb88A==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.4.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.9.0",
"@azure/logger": "^1.0.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-rest-pipeline/node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@azure/core-rest-pipeline/node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@azure/core-rest-pipeline/node_modules/https-proxy-agent": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
"integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
"dependencies": {
"agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@azure/core-tracing": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz",
"integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-util": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz",
"integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/logger": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.2.tgz",
"integrity": "sha512-l170uE7bsKpIU6B/giRc9i4NI0Mj+tANMMMxf7Zi/5cKzEqPayP7+X1WPrG7e+91JgY8N+7K7nF2WOi7iVhXvg==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/monitor-opentelemetry-exporter": {
"version": "1.0.0-beta.24",
"resolved": "https://registry.npmjs.org/@azure/monitor-opentelemetry-exporter/-/monitor-opentelemetry-exporter-1.0.0-beta.24.tgz",
"integrity": "sha512-oEYmQxfa40Rcqh358GeVd9MPQd9dHOxQJBsH1BwXW1aZ4cd9QwylEzAMrLvwJiHmaq0g4CzPnbtlpwxfb758Qg==",
"dependencies": {
"@azure/core-auth": "^1.3.0",
"@azure/core-client": "^1.0.0",
"@azure/core-rest-pipeline": "^1.1.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.52.0",
"@opentelemetry/core": "^1.25.0",
"@opentelemetry/resources": "^1.25.0",
"@opentelemetry/sdk-logs": "^0.52.0",
"@opentelemetry/sdk-metrics": "^1.25.0",
"@opentelemetry/sdk-trace-base": "^1.25.0",
"@opentelemetry/semantic-conventions": "^1.25.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@@ -3396,6 +3548,130 @@
"node": ">= 8"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@opentelemetry/api-logs": {
"version": "0.52.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz",
"integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==",
"dependencies": {
"@opentelemetry/api": "^1.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@opentelemetry/core": {
"version": "1.25.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz",
"integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==",
"dependencies": {
"@opentelemetry/semantic-conventions": "1.25.1"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"node_modules/@opentelemetry/instrumentation": {
"version": "0.52.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz",
"integrity": "sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==",
"peer": true,
"dependencies": {
"@opentelemetry/api-logs": "0.52.1",
"@types/shimmer": "^1.0.2",
"import-in-the-middle": "^1.8.1",
"require-in-the-middle": "^7.1.1",
"semver": "^7.5.2",
"shimmer": "^1.2.1"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/resources": {
"version": "1.25.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz",
"integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==",
"dependencies": {
"@opentelemetry/core": "1.25.1",
"@opentelemetry/semantic-conventions": "1.25.1"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"node_modules/@opentelemetry/sdk-logs": {
"version": "0.52.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.52.1.tgz",
"integrity": "sha512-MBYh+WcPPsN8YpRHRmK1Hsca9pVlyyKd4BxOC4SsgHACnl/bPp4Cri9hWhVm5+2tiQ9Zf4qSc1Jshw9tOLGWQA==",
"dependencies": {
"@opentelemetry/api-logs": "0.52.1",
"@opentelemetry/core": "1.25.1",
"@opentelemetry/resources": "1.25.1"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.4.0 <1.10.0"
}
},
"node_modules/@opentelemetry/sdk-metrics": {
"version": "1.25.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz",
"integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==",
"dependencies": {
"@opentelemetry/core": "1.25.1",
"@opentelemetry/resources": "1.25.1",
"lodash.merge": "^4.6.2"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.10.0"
}
},
"node_modules/@opentelemetry/sdk-trace-base": {
"version": "1.25.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz",
"integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==",
"dependencies": {
"@opentelemetry/core": "1.25.1",
"@opentelemetry/resources": "1.25.1",
"@opentelemetry/semantic-conventions": "1.25.1"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"node_modules/@opentelemetry/semantic-conventions": {
"version": "1.25.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz",
"integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==",
"engines": {
"node": ">=14"
}
},
"node_modules/@panva/hkdf": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz",
@@ -5868,6 +6144,12 @@
"@types/node": "*"
}
},
"node_modules/@types/shimmer": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz",
"integrity": "sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==",
"peer": true
},
"node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
@@ -6050,6 +6332,23 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"node_modules/@vercel/otel": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@vercel/otel/-/otel-1.9.1.tgz",
"integrity": "sha512-ZSTqgvd+w/lcB1nxEW8EHSBBqd4ZdeJ1t5op1CFo/nKFdG/EshwMon0qKc2ZxVEZXOZJI/x+LKf8Y5/Y/VHqEA==",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/api-logs": ">=0.46.0 && <1.0.0",
"@opentelemetry/instrumentation": ">=0.46.0 && <1.0.0",
"@opentelemetry/resources": "^1.19.0",
"@opentelemetry/sdk-logs": ">=0.46.0 && <1.0.0",
"@opentelemetry/sdk-metrics": "^1.19.0",
"@opentelemetry/sdk-trace-base": "^1.19.0"
}
},
"node_modules/abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -6074,7 +6373,6 @@
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6092,6 +6390,15 @@
"acorn-walk": "^8.0.2"
}
},
"node_modules/acorn-import-attributes": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
"peer": true,
"peerDependencies": {
"acorn": "^8"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -7236,8 +7543,7 @@
"node_modules/cjs-module-lexer": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz",
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
"dev": true
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q=="
},
"node_modules/class-variance-authority": {
"version": "0.7.0",
@@ -8104,7 +8410,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -9661,7 +9966,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -10108,7 +10412,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -10340,6 +10643,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/import-in-the-middle": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.8.1.tgz",
"integrity": "sha512-yhRwoHtiLGvmSozNOALgjRPFI6uYsds60EoMqqnXyyv+JOIW/BrrLejuTGBt+bq0T5tLzOHrN0T7xYTm4Qt/ng==",
"peer": true,
"dependencies": {
"acorn": "^8.8.2",
"acorn-import-attributes": "^1.9.5",
"cjs-module-lexer": "^1.2.2",
"module-details-from-path": "^1.0.3"
}
},
"node_modules/import-local": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
@@ -10543,7 +10858,6 @@
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dev": true,
"dependencies": {
"hasown": "^2.0.0"
},
@@ -13262,8 +13576,7 @@
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"node_modules/lodash.once": {
"version": "4.1.1",
@@ -13820,6 +14133,12 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true
},
"node_modules/module-details-from-path": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
"integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==",
"peer": true
},
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -13871,8 +14190,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mustache": {
"version": "4.2.0",
@@ -14594,8 +14912,7 @@
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-scurry": {
"version": "1.10.2",
@@ -15635,6 +15952,20 @@
"node": ">=0.10.0"
}
},
"node_modules/require-in-the-middle": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.3.0.tgz",
"integrity": "sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==",
"peer": true,
"dependencies": {
"debug": "^4.1.1",
"module-details-from-path": "^1.0.3",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -15651,7 +15982,6 @@
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
@@ -15978,7 +16308,6 @@
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -15993,7 +16322,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -16134,6 +16462,12 @@
"node": ">=8"
}
},
"node_modules/shimmer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz",
"integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==",
"peer": true
},
"node_modules/side-channel": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
@@ -16759,7 +17093,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -17981,8 +18314,7 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "2.3.4",

View File

@@ -25,6 +25,7 @@
"test:unit:watch": "jest --watch"
},
"dependencies": {
"@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.24",
"@contentstack/live-preview-utils": "^1.4.0",
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.3.4",
@@ -36,6 +37,7 @@
"@trpc/client": "^11.0.0-next-beta.318",
"@trpc/react-query": "^11.0.0-next-beta.318",
"@trpc/server": "^11.0.0-next-beta.318",
"@vercel/otel": "^1.9.1",
"class-variance-authority": "^0.7.0",
"clean-deep": "^3.4.0",
"dayjs": "^1.11.10",

View File

@@ -59,6 +59,7 @@ export type ContactConfig = ContactConfigData["all_contact_config"]["items"][0]
export type ContactFields = {
display_text: string | null
contact_field: string
footnote: string | null
}
export const validateHeaderConfigSchema = z.object({

View File

@@ -2,10 +2,13 @@ import { z } from "zod"
import { Lang } from "@/constants/languages"
import { ImageVaultAsset } from "@/types/components/imageVaultImage"
import {
JoinLoyaltyContactTypenameEnum,
LoyaltyBlocksTypenameEnum,
LoyaltyCardsGridEnum,
LoyaltyComponentEnum,
LoyaltySidebarDynamicComponentEnum,
SidebarTypenameEnum,
} from "@/types/components/loyalty/enums"
import { Embeds } from "@/types/requests/embeds"
@@ -47,6 +50,7 @@ const loyaltyPageShortcuts = z.object({
})
const cardBlock = z.object({
__typename: z.literal(LoyaltyCardsGridEnum.Card),
heading: z.string().nullable(),
body_text: z.string().nullable(),
background_image: z.any(),
@@ -73,6 +77,30 @@ const cardBlock = z.object({
}),
})
const loyaltyCardBlock = z.object({
__typename: z.literal(LoyaltyCardsGridEnum.LoyaltyCard),
heading: z.string().nullable(),
body_text: z.string().nullable(),
image: z.any(),
link: z
.object({
openInNewTab: z.boolean(),
title: z.string(),
href: z.string(),
isExternal: z.boolean(),
})
.optional(),
system: z.object({
locale: z.nativeEnum(Lang),
uid: z.string(),
}),
})
const loyaltyPageCardsItems = z.discriminatedUnion("__typename", [
loyaltyCardBlock,
cardBlock,
])
const loyaltyPageCards = z.object({
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid),
cards_grid: z.object({
@@ -80,7 +108,7 @@ const loyaltyPageCards = z.object({
preamble: z.string().nullable(),
layout: z.enum(["twoColumnGrid", "threeColumnGrid", "twoPlusOne"]),
theme: z.enum(["one", "two", "three"]).nullable(),
cards: z.array(cardBlock),
cards: z.array(loyaltyPageCardsItems),
}),
})
@@ -132,14 +160,23 @@ const loyaltyPageJoinLoyaltyContact = z.object({
contact: z.object({
display_text: z.string().nullable(),
contact_field: z.string(),
footnote: z.string().nullable(),
}),
})
),
}),
})
const loyaltyPageSidebarDynamicContent = z.object({
__typename: z.literal(SidebarTypenameEnum.LoyaltyPageSidebarDynamicContent),
dynamic_content: z.object({
component: z.nativeEnum(LoyaltySidebarDynamicComponentEnum),
}),
})
const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [
loyaltyPageSidebarTextContent,
loyaltyPageSidebarDynamicContent,
loyaltyPageJoinLoyaltyContact,
])
@@ -151,7 +188,6 @@ export const validateLoyaltyPageSchema = z.object({
})
// Block types
export type DynamicContent = z.infer<typeof loyaltyPageDynamicContent>
type BlockContentRaw = z.infer<typeof loyaltyPageBlockTextContent>
@@ -164,11 +200,25 @@ export interface RteBlockContent extends BlockContentRaw {
}
}
type LoyaltyCardRaw = z.infer<typeof loyaltyCardBlock>
type LoyaltyCard = Omit<LoyaltyCardRaw, "image"> & {
image?: ImageVaultAsset
}
type CardRaw = z.infer<typeof cardBlock>
type Card = Omit<CardRaw, "background_image"> & {
backgroundImage?: ImageVaultAsset
}
type CardsGridRaw = z.infer<typeof loyaltyPageCards>
export type CardsRaw = CardsGridRaw["cards_grid"]["cards"][number]
export type CardsGrid = Omit<CardsGridRaw, "cards"> & {
cards: (LoyaltyCard | Card)[]
}
export type CardsGrid = z.infer<typeof loyaltyPageCards>
export type CardsRaw = CardsGrid["cards_grid"]["cards"][number]
export type Shortcuts = z.infer<typeof loyaltyPageShortcuts>
@@ -185,8 +235,13 @@ export type RteSidebarContent = Omit<SidebarContentRaw, "content"> & {
}
}
}
type SideBarDynamicContent = z.infer<typeof loyaltyPageSidebarDynamicContent>
export type JoinLoyaltyContact = z.infer<typeof loyaltyPageJoinLoyaltyContact>
export type Sidebar = JoinLoyaltyContact | RteSidebarContent
export type Sidebar =
| JoinLoyaltyContact
| RteSidebarContent
| SideBarDynamicContent
type LoyaltyPageDataRaw = z.infer<typeof validateLoyaltyPageSchema>
export type LoyaltyPage = Omit<LoyaltyPageDataRaw, "blocks" | "sidebar"> & {
@@ -210,6 +265,7 @@ const pageConnectionRefs = z.object({
})
const cardBlockRefs = z.object({
__typename: z.literal(LoyaltyCardsGridEnum.Card),
primary_button: z
.object({
linkConnection: pageConnectionRefs,
@@ -227,13 +283,32 @@ const cardBlockRefs = z.object({
}),
})
const loyaltyCardBlockRefs = z.object({
__typename: z.literal(LoyaltyCardsGridEnum.LoyaltyCard),
link: z
.object({
linkConnection: pageConnectionRefs,
})
.nullable(),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
const cardGridCardsRef = z.discriminatedUnion("__typename", [
loyaltyCardBlockRefs,
cardBlockRefs,
])
const loyaltyPageCardsRefs = z.object({
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid),
cards_grid: z.object({
cardConnection: z.object({
edges: z.array(
z.object({
node: cardBlockRefs,
node: cardGridCardsRef,
})
),
}),

View File

@@ -11,6 +11,7 @@ import {
generateTag,
generateTags,
} from "@/utils/generateTag"
import { insertResponseToImageVaultAsset } from "@/utils/imageVault"
import { removeMultipleSlashes } from "@/utils/url"
import { removeEmptyObjects } from "../../utils"
@@ -22,7 +23,17 @@ import {
} from "./output"
import { getConnections } from "./utils"
import { LoyaltyBlocksTypenameEnum } from "@/types/components/loyalty/enums"
import { InsertResponse } from "@/types/components/imageVaultImage"
import {
LoyaltyBlocksTypenameEnum,
LoyaltyCardsGridEnum,
} from "@/types/components/loyalty/enums"
function makeImageVaultImage(image: any) {
return image && !!Object.keys(image).length
? insertResponseToImageVaultAsset(image as InsertResponse)
: undefined
}
function makeButtonObject(button: any) {
return {
@@ -35,9 +46,9 @@ function makeButtonObject(button: any) {
href:
button.is_contentstack_link && button.linkConnection.edges.length
? button.linkConnection.edges[0].node.web?.original_url ||
removeMultipleSlashes(
`/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}`
)
removeMultipleSlashes(
`/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}`
)
: button.external_link.href,
isExternal: !button.is_contentstack_link,
}
@@ -96,66 +107,82 @@ export const loyaltyPageQueryRouter = router({
const blocks = response.data.loyalty_page.blocks
? response.data.loyalty_page.blocks.map((block: any) => {
switch (block.__typename) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent:
return {
...block,
dynamic_content: {
...block.dynamic_content,
link: block.dynamic_content.link.pageConnection.edges.length
? {
text: block.dynamic_content.link.text,
href: removeMultipleSlashes(
`/${block.dynamic_content.link.pageConnection.edges[0].node.system.locale}/${block.dynamic_content.link.pageConnection.edges[0].node.url}`
),
title:
block.dynamic_content.link.pageConnection.edges[0]
.node.title,
}
: undefined,
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
return {
...block,
shortcuts: {
...block.shortcuts,
shortcuts: block.shortcuts.shortcuts.map((shortcut: any) => ({
text: shortcut.text,
openInNewTab: shortcut.open_in_new_tab,
...shortcut.linkConnection.edges[0].node,
url:
shortcut.linkConnection.edges[0].node.web?.original_url ||
removeMultipleSlashes(
`/${shortcut.linkConnection.edges[0].node.system.locale}/${shortcut.linkConnection.edges[0].node.url}`
),
})),
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
return {
...block,
cards_grid: {
...block.cards_grid,
cards: block.cards_grid.cardConnection.edges.map(
({ node: card }: { node: any }) => {
return {
...card,
primaryButton: card.has_primary_button
? makeButtonObject(card.primary_button)
: undefined,
secondaryButton: card.has_secondary_button
? makeButtonObject(card.secondary_button)
: undefined,
switch (block.__typename) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent:
return {
...block,
dynamic_content: {
...block.dynamic_content,
link: block.dynamic_content.link.pageConnection.edges.length
? {
text: block.dynamic_content.link.text,
href: removeMultipleSlashes(
`/${block.dynamic_content.link.pageConnection.edges[0].node.system.locale}/${block.dynamic_content.link.pageConnection.edges[0].node.url}`
),
title:
block.dynamic_content.link.pageConnection.edges[0]
.node.title,
}
: undefined,
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
return {
...block,
shortcuts: {
...block.shortcuts,
shortcuts: block.shortcuts.shortcuts.map((shortcut: any) => ({
text: shortcut.text,
openInNewTab: shortcut.open_in_new_tab,
...shortcut.linkConnection.edges[0].node,
url:
shortcut.linkConnection.edges[0].node.web?.original_url ||
removeMultipleSlashes(
`/${shortcut.linkConnection.edges[0].node.system.locale}/${shortcut.linkConnection.edges[0].node.url}`
),
})),
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
return {
...block,
cards_grid: {
...block.cards_grid,
cards: block.cards_grid.cardConnection.edges.map(
({ node: card }: { node: any }) => {
switch (card.__typename) {
case LoyaltyCardsGridEnum.LoyaltyCard:
return {
...card,
image: makeImageVaultImage(card.image),
link: makeButtonObject({
...card.link,
is_contentstack_link:
!!card.link.linkConnection.edges.length,
}),
}
case LoyaltyCardsGridEnum.Card:
return {
...card,
backgroundImage: makeImageVaultImage(
card.background_image
),
primaryButton: card.has_primary_button
? makeButtonObject(card.primary_button)
: undefined,
secondaryButton: card.has_secondary_button
? makeButtonObject(card.secondary_button)
: undefined,
}
}
}
}
),
},
}
default:
return block
}
})
),
},
}
default:
return block
}
})
: null
const loyaltyPage = {

View File

@@ -1,6 +1,9 @@
import { LoyaltyPageRefsDataRaw } from "./output"
import { LoyaltyBlocksTypenameEnum } from "@/types/components/loyalty/enums"
import {
LoyaltyBlocksTypenameEnum,
LoyaltyCardsGridEnum,
} from "@/types/components/loyalty/enums"
import type { Edges } from "@/types/requests/utils/edges"
import type { NodeRefs } from "@/types/requests/utils/refs"
@@ -18,13 +21,23 @@ export function getConnections(refs: LoyaltyPageRefsDataRaw) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid: {
connections.push(item.cards_grid.cardConnection)
item.cards_grid.cardConnection.edges.forEach((card) => {
if (card.node.primary_button) {
connections.push(card.node.primary_button?.linkConnection)
} else if (card.node.secondary_button) {
connections.push(card.node.secondary_button?.linkConnection)
switch (card.node.__typename) {
case LoyaltyCardsGridEnum.LoyaltyCard: {
if (card.node.link) {
connections.push(card.node.link?.linkConnection)
}
break
}
case LoyaltyCardsGridEnum.Card: {
if (card.node.primary_button) {
connections.push(card.node.primary_button?.linkConnection)
} else if (card.node.secondary_button) {
connections.push(card.node.secondary_button?.linkConnection)
}
break
}
}
})
break
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts: {

View File

@@ -24,7 +24,9 @@ export type DynamicComponentProps = {
component: DynamicContent["dynamic_content"]["component"]
}
export type CardsGridProps = Pick<CardsGrid, "cards_grid">
export type CardsGridProps = Pick<CardsGrid, "cards_grid"> & {
firstItem?: boolean
}
export type Content = { content: RteBlockContent["content"]["content"] }

View File

@@ -13,10 +13,15 @@ export type JoinLoyaltyContactContact = Typename<
export enum SidebarTypenameEnum {
LoyaltyPageSidebarJoinLoyaltyContact = "LoyaltyPageSidebarJoinLoyaltyContact",
LoyaltyPageSidebarContent = "LoyaltyPageSidebarContent",
LoyaltyPageSidebarDynamicContent = "LoyaltyPageSidebarDynamicContent",
}
export type SidebarTypename = keyof typeof SidebarTypenameEnum
export enum LoyaltySidebarDynamicComponentEnum {
my_pages_navigation = "my_pages_navigation",
}
export enum LoyaltyComponentEnum {
loyalty_levels = "loyalty_levels",
how_it_works = "how_it_works",
@@ -31,3 +36,8 @@ export enum LoyaltyBlocksTypenameEnum {
LoyaltyPageBlocksShortcuts = "LoyaltyPageBlocksShortcuts",
LoyaltyPageBlocksCardsGrid = "LoyaltyPageBlocksCardsGrid",
}
export enum LoyaltyCardsGridEnum {
LoyaltyCard = "LoyaltyCard",
Card = "Card",
}