Merged in feat/reusable-card (pull request #190)
Feat/reusable card and button design Approved-by: Michael Zetterberg
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
.layout {
|
||||
--header-height: 4.5rem;
|
||||
|
||||
background-color: var(--Brand-Coffee-Subtle);
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
display: grid;
|
||||
font-family: var(--ff-fira-sans);
|
||||
grid-template-rows: var(--header-height) auto 1fr;
|
||||
|
||||
@@ -16,7 +16,6 @@ export default function EditProfile({ params }: PageArgs<LangParams>) {
|
||||
<Button
|
||||
aria-label="Cancel"
|
||||
asChild
|
||||
bgcolor="white"
|
||||
form="edit-profile"
|
||||
size="small"
|
||||
type="reset"
|
||||
@@ -24,12 +23,10 @@ export default function EditProfile({ params }: PageArgs<LangParams>) {
|
||||
<Link href={profile[params.lang]}>{_("Cancel")}</Link>
|
||||
</Button>
|
||||
<Button
|
||||
bgcolor="quarternary"
|
||||
disabled={!isValid || isPending}
|
||||
form="edit-profile"
|
||||
size="small"
|
||||
type="submit"
|
||||
weight="regular"
|
||||
>
|
||||
{_("Save")}
|
||||
</Button>
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default function ProfileView({ params }: PageArgs<LangParams>) {
|
||||
return (
|
||||
<Button asChild bgcolor="quarternary" size="small" weight="regular">
|
||||
<Button asChild size="small">
|
||||
<Link href={profileEdit[params.lang]}>{_("Edit")}</Link>
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.layout {
|
||||
display: grid;
|
||||
font-family: var(--ff-fira-sans);
|
||||
background-color: var(--Brand-Coffee-Subtle);
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { firaMono, firaSans } from "@/app/[lang]/(live)/fonts"
|
||||
|
||||
import styles from "./layout.module.css"
|
||||
|
||||
import {
|
||||
@@ -11,15 +9,8 @@ import {
|
||||
|
||||
export default function ContentTypeLayout({
|
||||
children,
|
||||
params,
|
||||
}: React.PropsWithChildren<
|
||||
LayoutArgs<LangParams & ContentTypeParams & UIDParams>
|
||||
>) {
|
||||
return (
|
||||
<div
|
||||
className={`${firaMono.variable} ${firaSans.variable} ${styles.layout}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
return <div className={styles.layout}>{children}</div>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Fira_Mono, Fira_Sans } from "next/font/google"
|
||||
import localFont from "next/font/local"
|
||||
|
||||
export const firaMono = Fira_Mono({
|
||||
subsets: ["latin"],
|
||||
@@ -11,3 +12,14 @@ export const firaSans = Fira_Sans({
|
||||
weight: ["300", "400", "600", "900"],
|
||||
variable: "--ff-fira-sans",
|
||||
})
|
||||
|
||||
export const biroScriptPlus = localFont({
|
||||
src: [
|
||||
{
|
||||
path: "../../../public/_static/fonts/biro-script-plus/Biro-Script-Plus.ttf",
|
||||
style: "normal",
|
||||
weight: "500",
|
||||
},
|
||||
],
|
||||
variable: "--ff-biro-script-plus",
|
||||
})
|
||||
|
||||
@@ -8,6 +8,8 @@ import TrpcProvider from "@/lib/trpc/Provider"
|
||||
import AdobeScript from "@/components/Current/AdobeScript"
|
||||
import VwoScript from "@/components/Current/VwoScript"
|
||||
|
||||
import { biroScriptPlus, firaMono, firaSans } from "./fonts"
|
||||
|
||||
import type { Metadata } from "next"
|
||||
|
||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
@@ -42,7 +44,9 @@ export default async function RootLayout({
|
||||
<AdobeScript />
|
||||
<VwoScript />
|
||||
</head>
|
||||
<body>
|
||||
<body
|
||||
className={`${firaMono.variable} ${firaSans.variable} ${biroScriptPlus.variable} `}
|
||||
>
|
||||
<TrpcProvider lang={params.lang}>{children}</TrpcProvider>
|
||||
<Script id="page-tracking">{`
|
||||
typeof _satellite !== "undefined" && _satellite.pageBottom();
|
||||
|
||||
@@ -17,7 +17,8 @@ import type {
|
||||
import { RTEMarkType } from "@/types/rte/node"
|
||||
import type { RenderOptions } from "@/types/rte/option"
|
||||
|
||||
function extractPossibleAttributes(attrs: Attributes) {
|
||||
function extractPossibleAttributes(attrs: Attributes | undefined) {
|
||||
if (!attrs) return {}
|
||||
const props: Record<string, any> = {}
|
||||
if (attrs.id) {
|
||||
props.id = attrs.id
|
||||
@@ -252,7 +253,7 @@ export const renderOptions: RenderOptions = {
|
||||
const type = node.attrs.type
|
||||
if (type === RTEItemTypeEnum.asset) {
|
||||
const image = embeds?.[node?.attrs?.["asset-uid"]]
|
||||
if (image.node.__typename === EmbedEnum.SysAsset) {
|
||||
if (image?.node.__typename === EmbedEnum.SysAsset) {
|
||||
const alt = image?.node?.title ?? node.attrs.alt
|
||||
const alignment = node.attrs?.style?.["text-align"]
|
||||
? {
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { RenderOptions } from "@/types/rte/option"
|
||||
|
||||
export function groupEmbedsByUid(embedsArray: Node<Embeds>[]) {
|
||||
const embedsByUid = embedsArray.reduce<EmbedByUid>((acc, embed) => {
|
||||
if (embed.node.system.uid) {
|
||||
if (embed.node.system?.uid) {
|
||||
acc[embed.node.system.uid] = embed
|
||||
}
|
||||
return acc
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
.container {
|
||||
display: grid;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
display: grid;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.cardContainer {
|
||||
display: grid;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.cardContainer {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.cardWrapper:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { _ } from "@/lib/translation"
|
||||
|
||||
import Card from "@/components/TempDesignSystem/Card"
|
||||
import Title from "@/components/Title"
|
||||
|
||||
import styles from "./cardGrid.module.css"
|
||||
|
||||
import { CardGridProps } from "@/types/components/loyalty/blocks"
|
||||
|
||||
export default function CardGrid({ card_grid }: CardGridProps) {
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<header className={styles.titleContainer}>
|
||||
<Title as="h3" level="h2" weight="semiBold" uppercase>
|
||||
{card_grid.title}
|
||||
</Title>
|
||||
{card_grid.subtitle ? (
|
||||
<Title
|
||||
as="h5"
|
||||
level="h3"
|
||||
weight="regular"
|
||||
className={styles.subtitle}
|
||||
>
|
||||
{card_grid.subtitle}
|
||||
</Title>
|
||||
) : null}
|
||||
</header>
|
||||
<div className={styles.cardContainer}>
|
||||
{card_grid.cards.map((card, i) => (
|
||||
<div className={styles.cardWrapper} key={`${card.title}+${i}`}>
|
||||
<Card
|
||||
subtitle={card.subtitle}
|
||||
title={card.title}
|
||||
link={card.link}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
4
components/Loyalty/Blocks/CardsGrid/cardsGrid.module.css
Normal file
4
components/Loyalty/Blocks/CardsGrid/cardsGrid.module.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.section {
|
||||
display: grid;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
30
components/Loyalty/Blocks/CardsGrid/index.tsx
Normal file
30
components/Loyalty/Blocks/CardsGrid/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { _ } from "@/lib/translation"
|
||||
|
||||
import Header from "@/components/MyPages/Blocks/Header"
|
||||
import Card from "@/components/TempDesignSystem/Card"
|
||||
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
||||
|
||||
import styles from "./cardsGrid.module.css"
|
||||
|
||||
import { CardsGridProps } from "@/types/components/loyalty/blocks"
|
||||
|
||||
export default function CardsGrid({ cards_grid }: CardsGridProps) {
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<Header title={cards_grid.title} subtitle={cards_grid.preamble} />
|
||||
<CardGrid variant={cards_grid.layout}>
|
||||
{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}
|
||||
/>
|
||||
))}
|
||||
</CardGrid>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
width: 100%;
|
||||
height: 37rem;
|
||||
border-radius: 1.6rem;
|
||||
background-color: var(--Base-Fill-Normal);
|
||||
background-color: var(--UI-Grey-10);
|
||||
text-align: center;
|
||||
margin-right: 1.6rem;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
height: 37rem;
|
||||
min-width: 32rem;
|
||||
padding: 4rem 1rem;
|
||||
background-color: var(--Base-Fill-Normal);
|
||||
background-color: var(--UI-Grey-10);
|
||||
border-radius: 1.6rem;
|
||||
gap: 1.8rem;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import JsonToHtml from "@/components/JsonToHtml"
|
||||
import DynamicContentBlock from "@/components/Loyalty/Blocks/DynamicContent"
|
||||
import Shortcuts from "@/components/MyPages/Blocks/Shortcuts"
|
||||
|
||||
import CardGrid from "./CardGrid"
|
||||
import CardsGrid from "./CardsGrid"
|
||||
|
||||
import type { BlocksProps } from "@/types/components/loyalty/blocks"
|
||||
import { LoyaltyBlocksTypenameEnum } from "@/types/components/loyalty/enums"
|
||||
@@ -10,8 +10,6 @@ import { LoyaltyBlocksTypenameEnum } from "@/types/components/loyalty/enums"
|
||||
export function Blocks({ blocks }: BlocksProps) {
|
||||
return blocks.map((block) => {
|
||||
switch (block.__typename) {
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid:
|
||||
return <CardGrid card_grid={block.card_grid} />
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent:
|
||||
return (
|
||||
<section>
|
||||
@@ -31,6 +29,8 @@ export function Blocks({ blocks }: BlocksProps) {
|
||||
subtitle={block.shortcuts.preamble}
|
||||
/>
|
||||
)
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
|
||||
return <CardsGrid cards_grid={block.cards_grid} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 0.5px solid var(--Base-Border-Disabled);
|
||||
border-top: 0.5px solid var(--UI-Grey-30);
|
||||
padding: 3.4rem;
|
||||
text-align: center;
|
||||
gap: 6.2rem;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
.contactContainer {
|
||||
display: block;
|
||||
border-top: 0.5px solid var(--Base-Border-Disabled);
|
||||
border-top: 0.5px solid var(--UI-Grey-30);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 3.4rem;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-height: 280px;
|
||||
background-color: var(--Base-Fill-Normal);
|
||||
background-color: var(--UI-Grey-10);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async function NextLevelBenefitsBlock({
|
||||
<CardGrid variant="twoColumnGrid">
|
||||
{perks.map((perk) => (
|
||||
<article key={perk.id} className={styles.card}>
|
||||
<Button type="button" intent="secondary" disabled>
|
||||
<Button type="button" disabled>
|
||||
<Lock height={16} />
|
||||
Level up to unlock
|
||||
</Button>
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function ShowMoreButton({
|
||||
<Button
|
||||
disabled={disabled}
|
||||
intent="primary"
|
||||
bgcolor="white"
|
||||
theme="secondaryDark"
|
||||
type="button"
|
||||
onClick={loadMoreData}
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function EmptyUpcomingStaysBlock() {
|
||||
{_("Where should you go next?")}
|
||||
</span>
|
||||
</Title>
|
||||
<Button intent="primary" bgcolor="quarternary" asChild type="button">
|
||||
<Button intent="primary" asChild type="button">
|
||||
<Link className={styles.link} href={"#"} key="getInspired">
|
||||
{_("Get inspired")}
|
||||
</Link>
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function EmptyUpcomingStaysBlock() {
|
||||
{_("Where should you go next?")}
|
||||
</span>
|
||||
</Title>
|
||||
<Button intent="primary" bgcolor="quarternary" asChild type="button">
|
||||
<Button intent="primary" asChild type="button">
|
||||
<Link className={styles.link} href={"#"} key="getInspired">
|
||||
{_("Get inspired")}
|
||||
</Link>
|
||||
|
||||
@@ -4,21 +4,43 @@
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
font-family: var(--ff-fira-sans);
|
||||
font-weight: 600;
|
||||
line-height: 150%;
|
||||
letter-spacing: 1%;
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: var(--font-color,);
|
||||
background-color: var(--background-color,);
|
||||
}
|
||||
|
||||
.primary:hover,
|
||||
.primary:active,
|
||||
.primary:focus {
|
||||
background-color: var(--hover-background,);
|
||||
color: var(--hover-color,);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background-color: transparent;
|
||||
border: 0.1rem solid var(--background-color);
|
||||
color: var(--background-color,);
|
||||
}
|
||||
|
||||
.secondary:hover,
|
||||
.secondary:active,
|
||||
.secondary:focus {
|
||||
border: 0.1rem solid var(--hover-color,);
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
.default {
|
||||
align-items: center;
|
||||
background-color: var(--some-white-color, #fff);
|
||||
border-radius: 4rem;
|
||||
color: var(--some-grey-color, #111);
|
||||
display: flex;
|
||||
font-family: var(--ff-fira-sans);
|
||||
font-size: 1.8rem;
|
||||
font-weight: 500;
|
||||
gap: 1rem;
|
||||
letter-spacing: 1%;
|
||||
line-height: 2.2rem;
|
||||
padding: 0.75rem 1.65rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -26,125 +48,110 @@
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
/* Primary styles */
|
||||
.primary {
|
||||
background-color: var(--some-grey-color, #444343);
|
||||
border: 2px solid transparent;
|
||||
outline: 1px solid transparent;
|
||||
border-radius: 46px;
|
||||
color: var(--Main-Grey-00, #fff);
|
||||
}
|
||||
|
||||
.primary:hover {
|
||||
background: var(--some-grey-color, #444343);
|
||||
}
|
||||
|
||||
.primary:active,
|
||||
.primary:focus {
|
||||
border: var(--some-grey-color, #444343);
|
||||
outline: var(--some-grey-color, #444343);
|
||||
}
|
||||
|
||||
/* Secondary styles */
|
||||
.secondary {
|
||||
border: 1px solid var(--some-grey-color, #444343);
|
||||
background-color: transparent;
|
||||
color: var(--some-grey-color, #444343);
|
||||
border-radius: 46px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.secondary:hover {
|
||||
border: 1px solid var(--some-grey-color, #444343);
|
||||
}
|
||||
|
||||
.secondary:active,
|
||||
.secondary:focus {
|
||||
border: 1px solid var(--some-grey-color, #444343);
|
||||
}
|
||||
|
||||
/* Disabled styles */
|
||||
.btn:disabled {
|
||||
border: 1px solid var(--some-grey-color, #444343);
|
||||
background-color: transparent;
|
||||
color: var(--some-grey-color, #444343);
|
||||
background-color: var(--disabled-background-color);
|
||||
color: var(--disabled-color);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Sizes */
|
||||
.small {
|
||||
border-radius: 3rem;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2);
|
||||
gap: 0.2rem;
|
||||
|
||||
font-size: 1.4rem;
|
||||
height: 2.6rem;
|
||||
line-height: 1.7rem;
|
||||
padding: 0.8rem 2.2rem;
|
||||
}
|
||||
|
||||
.average {
|
||||
border-radius: 4.7rem;
|
||||
font-size: 1.4rem;
|
||||
height: 3.2rem;
|
||||
letter-spacing: 1%;
|
||||
line-height: 1.6rem;
|
||||
padding: 0.65rem 1.3rem;
|
||||
}
|
||||
|
||||
.light {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.regular {
|
||||
font-weight: 400;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.medium {
|
||||
font-weight: 500;
|
||||
height: 3rem;
|
||||
padding: 1.2rem var(--Spacing-x2,);
|
||||
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.semiBold {
|
||||
font-weight: 600;
|
||||
.large {
|
||||
padding: var(--Spacing-x2) 2.4rem;
|
||||
gap: 0.4rem;
|
||||
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
.primaryLight {
|
||||
--font-color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Normal);
|
||||
--background-color: var(--Theme-Primary-Light-Button-Primary-Fill-Normal,);
|
||||
--hover-background: var(--Theme-Primary-Light-Button-Primary-Fill-Hover,);
|
||||
--hover-color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Hover,);
|
||||
|
||||
--disabled-background-color: var(
|
||||
--Theme-Primary-Light-Button-Primary-Fill-Disabled,
|
||||
);
|
||||
--disabled-color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Disabled,);
|
||||
}
|
||||
|
||||
.black {
|
||||
font-weight: 900;
|
||||
.primaryDark {
|
||||
--font-color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Normal,);
|
||||
--background-color: var(--Theme-Primary-Dark-Button-Primary-Fill-Normal,);
|
||||
--hover-color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Hover,);
|
||||
--hover-background: var(--Theme-Primary-Dark-Button-Primary-Fill-Hover,);
|
||||
|
||||
--disabled-background-color: var(
|
||||
--Theme-Primary-Dark-Button-Primary-Fill-Disabled,
|
||||
);
|
||||
--disabled-color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Disabled,);
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: var(--scandic-blue, #02838e);
|
||||
border: 0.1rem solid var(--scandic-blue, #02838e);
|
||||
color: var(--some-white-color, #fff);
|
||||
.secondaryLight {
|
||||
--font-color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Normal,);
|
||||
--background-color: var(--Theme-Secondary-Light-Button-Primary-Fill-Normal,);
|
||||
--hover-color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Hover,);
|
||||
--hover-background: var(--Theme-Secondary-Light-Button-Primary-Fill-Hover,);
|
||||
|
||||
--disabled-background-color: var(
|
||||
--Theme-Secondary-Light-Button-Primary-Fill-Disabled,
|
||||
);
|
||||
--disabled-color: var(
|
||||
--Theme-Secondary-Light-Button-Primary-On-Fill-Disabled,
|
||||
);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background-color: var(--some-black-color, #000);
|
||||
border: 0.1rem solid var(--some-black-color, #000);
|
||||
color: var(--some-white-color, #fff);
|
||||
.secondaryDark {
|
||||
--font-color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Normal,);
|
||||
--background-color: var(--Theme-Secondary-Dark-Button-Primary-Fill-Normal,);
|
||||
--hover-color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Hover,);
|
||||
--hover-background: var(--Theme-Secondary-Dark-Button-Primary-Fill-Hover,);
|
||||
|
||||
--disabled-background-color: var(
|
||||
--Theme-Secondary-Dark-Button-Primary-Fill-Disabled,
|
||||
);
|
||||
--disabled-color: var(
|
||||
--Theme-Secondary-Dark-Button-Primary-On-Fill-Disabled,
|
||||
);
|
||||
}
|
||||
|
||||
.tertiary {
|
||||
background-color: var(--some-red-color, #d60728);
|
||||
border: 0.1rem solid var(--some-red-color, #d60728);
|
||||
color: var(--some-white-color, #fff);
|
||||
.tertiaryLight {
|
||||
--font-color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Normal,);
|
||||
--background-color: var(--Theme-Tertiary-Light-Button-Primary-Fill-Normal,);
|
||||
--hover-color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Hover,);
|
||||
--hover-background: var(--Theme-Tertiary-Light-Button-Primary-Fill-Hover,);
|
||||
|
||||
--disabled-background-color: var(
|
||||
--Theme-Tertiary-Light-Button-Primary-Fill-Disabled,
|
||||
);
|
||||
--disabled-color: var(
|
||||
--Theme-Tertiary-Light-Button-Primary-On-Fill-Disabled,
|
||||
);
|
||||
}
|
||||
|
||||
.quarternary {
|
||||
background-color: var(--some-grey-color, #727272);
|
||||
border: 0.1rem solid var(--some-black-color, #727272);
|
||||
color: var(--some-white-color, #fff);
|
||||
}
|
||||
.tertiaryDark {
|
||||
--font-color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Normal);
|
||||
--background-color: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Normal);
|
||||
--hover-color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Hover);
|
||||
--hover-background: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Hover);
|
||||
|
||||
.white {
|
||||
background-color: var(--some-white-color, #fff);
|
||||
border: 0.1rem solid var(--some-black-color, #000);
|
||||
color: var(--some-black-color, #000);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background-color: var(--some-grey-color, #d9d9d9);
|
||||
color: var(--some-grey-color, #757575);
|
||||
--disabled-background-color: var(
|
||||
--Theme-Tertiary-Dark-Button-Primary-Fill-Disabled,
|
||||
);
|
||||
--disabled-color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Disabled,);
|
||||
}
|
||||
|
||||
@@ -8,23 +8,21 @@ import type { ButtonProps } from "./button"
|
||||
|
||||
export default function Button({
|
||||
asChild = false,
|
||||
bgcolor,
|
||||
theme,
|
||||
className,
|
||||
disabled,
|
||||
intent,
|
||||
size,
|
||||
variant,
|
||||
weight,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
const classNames = buttonVariants({
|
||||
bgcolor,
|
||||
theme,
|
||||
className,
|
||||
intent,
|
||||
size,
|
||||
variant,
|
||||
weight,
|
||||
})
|
||||
return <Comp className={classNames} disabled={disabled} {...props} />
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import styles from "./button.module.css"
|
||||
|
||||
export const buttonVariants = cva(styles.btn, {
|
||||
variants: {
|
||||
bgcolor: {
|
||||
primary: styles.primary,
|
||||
secondary: styles.secondary,
|
||||
tertiary: styles.tertiary,
|
||||
quarternary: styles.quarternary,
|
||||
white: styles.white,
|
||||
theme: {
|
||||
primaryLight: styles.primaryLight,
|
||||
primaryDark: styles.primaryDark,
|
||||
secondaryLight: styles.secondaryLight,
|
||||
secondaryDark: styles.secondaryDark,
|
||||
tertiaryLight: styles.tertiaryLight,
|
||||
tertiaryDark: styles.tertiaryDark,
|
||||
},
|
||||
intent: {
|
||||
primary: styles.primary,
|
||||
@@ -17,23 +18,18 @@ export const buttonVariants = cva(styles.btn, {
|
||||
},
|
||||
size: {
|
||||
small: styles.small,
|
||||
regular: styles.average,
|
||||
medium: styles.medium,
|
||||
large: styles.large,
|
||||
},
|
||||
variant: {
|
||||
default: styles.default,
|
||||
icon: styles.icon,
|
||||
},
|
||||
weight: {
|
||||
light: styles.light,
|
||||
regular: styles.regular,
|
||||
medium: styles.medium,
|
||||
semiBold: styles.semiBold,
|
||||
bold: styles.bold,
|
||||
black: styles.black,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
weight: "regular",
|
||||
size: "medium",
|
||||
theme: "primaryLight",
|
||||
intent: "primary",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,15 +1,80 @@
|
||||
.linkCard {
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 37rem;
|
||||
height: 48rem;
|
||||
width: 100%;
|
||||
margin-right: 1.6rem;
|
||||
border-radius: 1.6rem;
|
||||
gap: 1rem;
|
||||
padding: 1.6rem;
|
||||
gap: var(--Spacing-x2);
|
||||
padding: 0px 3.2rem;
|
||||
|
||||
background-color: var(--Base-Fill-Normal);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.themeOne {
|
||||
--font-color: var(--Brand-Main-Strong, #4d001b);
|
||||
--script-color: var(--UI-Red-70, #ad0015);
|
||||
--divider-color: var(--UI-Red-10, #f7c1c2);
|
||||
|
||||
background: var(--Theme-Primary-Light-Surface-Normal, #f7e1d5);
|
||||
}
|
||||
|
||||
.themeTwo {
|
||||
--font-color: var(--Brand-Forest-Strong, #093021);
|
||||
--script-color: var(--UI-Green-70, #286806);
|
||||
--divider-color: var(--UI-Green-10, #badda8);
|
||||
|
||||
background: var(--Brand-Forest-Subtle, #d2edaf);
|
||||
}
|
||||
|
||||
.themeThree {
|
||||
--font-color: var(--Brand-Sea-Strong, #0d1440);
|
||||
--script-color: var(--UI-Blue-70, #1555b4);
|
||||
--divider-color: var(--UI-Blue-10, #c7d9f5);
|
||||
|
||||
background: var(--Brand-Sea-Accent, #fff0c2);
|
||||
}
|
||||
|
||||
.scriptContainer {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.scriptedTitle {
|
||||
color: var(--script-color);
|
||||
|
||||
font-family: var(--ff-biro-script-plus);
|
||||
font-size: var(--typography-Script-2-fontSize);
|
||||
font-weight: var(--typography-Script-2-fontWeight);
|
||||
line-height: var(--typography-Script-2-lineHeight);
|
||||
letter-spacing: 0.48px;
|
||||
padding: 1rem;
|
||||
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
.bodyText {
|
||||
color: var(--font-color);
|
||||
text-align: center;
|
||||
|
||||
font-size: var(--typography-Body-Regular-fontSize);
|
||||
font-weight: var(--typography-Body-Regular-fontWeight);
|
||||
line-height: var(--typography-Body-Regular-lineHeight);
|
||||
letter-spacing: 0.096px;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
export type CardProps = {
|
||||
link?: {
|
||||
import { cardVariants } from "./variants"
|
||||
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
|
||||
export interface CardProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof cardVariants> {
|
||||
primaryButton?: {
|
||||
href: string
|
||||
title: string
|
||||
openInNewTab?: boolean
|
||||
isExternal: boolean
|
||||
}
|
||||
title?: string | null
|
||||
subtitle?: string | null
|
||||
openInNewTab?: boolean
|
||||
secondaryButton?: {
|
||||
href: string
|
||||
title: string
|
||||
openInNewTab?: boolean
|
||||
isExternal: boolean
|
||||
}
|
||||
scriptedTopTitle?: string | null
|
||||
heading?: string | null
|
||||
bodyText?: string | null
|
||||
backgroundImage?: { url: string }
|
||||
}
|
||||
|
||||
@@ -1,36 +1,93 @@
|
||||
import Title from "@/components/Title"
|
||||
|
||||
import Button from "../Button"
|
||||
import { ButtonProps } from "../Button/button"
|
||||
import Divider from "../Divider"
|
||||
import Link from "../Link"
|
||||
import { CardProps } from "./card"
|
||||
import { cardVariants } from "./variants"
|
||||
|
||||
import styles from "./card.module.css"
|
||||
|
||||
export default function Card({
|
||||
link,
|
||||
subtitle,
|
||||
title,
|
||||
openInNewTab = false,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
scriptedTopTitle,
|
||||
heading,
|
||||
bodyText,
|
||||
backgroundImage,
|
||||
className,
|
||||
theme,
|
||||
}: CardProps) {
|
||||
let buttonTheme: ButtonProps["theme"] = "primaryLight"
|
||||
|
||||
switch (theme) {
|
||||
case "one":
|
||||
buttonTheme = "primaryLight"
|
||||
break
|
||||
case "two":
|
||||
buttonTheme = "secondaryLight"
|
||||
break
|
||||
case "three":
|
||||
buttonTheme = "tertiaryLight"
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<article className={styles.linkCard}>
|
||||
{title ? (
|
||||
<Title level="h3" weight="semiBold">
|
||||
{title}
|
||||
<article
|
||||
className={cardVariants({
|
||||
className,
|
||||
theme,
|
||||
})}
|
||||
>
|
||||
{scriptedTopTitle ? (
|
||||
<section className={styles.scriptContainer}>
|
||||
<Title level="h3" weight="semiBold" className={styles.scriptedTitle}>
|
||||
{scriptedTopTitle}
|
||||
</Title>
|
||||
<Divider className={styles.divider} />
|
||||
</section>
|
||||
) : null}
|
||||
{heading ? (
|
||||
<Title
|
||||
level="h3"
|
||||
as="h5"
|
||||
weight="semiBold"
|
||||
uppercase
|
||||
className={styles.heading}
|
||||
>
|
||||
{heading}
|
||||
</Title>
|
||||
) : null}
|
||||
{subtitle ? (
|
||||
<Title level="h5" weight="light">
|
||||
{subtitle}
|
||||
</Title>
|
||||
) : null}
|
||||
{link ? (
|
||||
<Button asChild intent="primary">
|
||||
<Link href={link.href} target={openInNewTab ? "_blank" : undefined}>
|
||||
{link.title}
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
{bodyText ? <p className={styles.bodyText}>{bodyText}</p> : null}
|
||||
<div className={styles.buttonContainer}>
|
||||
{primaryButton ? (
|
||||
<Button asChild theme={buttonTheme} size="small">
|
||||
<Link
|
||||
href={primaryButton.href}
|
||||
target={primaryButton.openInNewTab ? "_blank" : undefined}
|
||||
>
|
||||
{primaryButton.title}
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
{secondaryButton ? (
|
||||
<Button
|
||||
asChild
|
||||
theme={buttonTheme}
|
||||
size="small"
|
||||
intent="secondary"
|
||||
disabled
|
||||
>
|
||||
<Link
|
||||
href={secondaryButton.href}
|
||||
target={secondaryButton.openInNewTab ? "_blank" : undefined}
|
||||
>
|
||||
{secondaryButton.title}
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
16
components/TempDesignSystem/Card/variants.ts
Normal file
16
components/TempDesignSystem/Card/variants.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./card.module.css"
|
||||
|
||||
export const cardVariants = cva(styles.container, {
|
||||
variants: {
|
||||
theme: {
|
||||
one: styles.themeOne,
|
||||
two: styles.themeTwo,
|
||||
three: styles.themeThree,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
theme: "one",
|
||||
},
|
||||
})
|
||||
@@ -37,14 +37,19 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.twoColumnGrid {
|
||||
.twoColumnGrid,
|
||||
.twoPlusOne {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.treeColumnGrid {
|
||||
.threeColumnGrid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.twoPlusOne > *:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.carousel {
|
||||
grid-auto-flow: unset;
|
||||
margin: 0;
|
||||
|
||||
@@ -9,10 +9,11 @@ export const cardGridVariants = cva(styles.gridContainer, {
|
||||
},
|
||||
variant: {
|
||||
twoColumnGrid: styles.twoColumnGrid,
|
||||
treeColumnGrid: styles.treeColumnGrid,
|
||||
threeColumnGrid: styles.threeColumnGrid,
|
||||
twoPlusOne: styles.twoPlusOne,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "treeColumnGrid",
|
||||
variant: "threeColumnGrid",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* font-family: var(--ff-brandon-text); */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--Scandic-Brand-Burgundy);
|
||||
}
|
||||
|
||||
.uppercase {
|
||||
|
||||
49
lib/graphql/Fragments/Blocks/Card.graphql
Normal file
49
lib/graphql/Fragments/Blocks/Card.graphql
Normal file
@@ -0,0 +1,49 @@
|
||||
fragment CardBlock on Card {
|
||||
heading
|
||||
body_text
|
||||
background_image
|
||||
scripted_top_title
|
||||
title
|
||||
secondary_button {
|
||||
is_contentstack_link
|
||||
cta_text
|
||||
open_in_new_tab
|
||||
external_link {
|
||||
title
|
||||
href
|
||||
}
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageLink
|
||||
...ContentPageLink
|
||||
...AccountPageLink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
primary_button {
|
||||
is_contentstack_link
|
||||
cta_text
|
||||
open_in_new_tab
|
||||
external_link {
|
||||
title
|
||||
href
|
||||
}
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageLink
|
||||
...ContentPageLink
|
||||
...AccountPageLink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
system {
|
||||
locale
|
||||
uid
|
||||
}
|
||||
}
|
||||
29
lib/graphql/Fragments/Blocks/Refs/Card.graphql
Normal file
29
lib/graphql/Fragments/Blocks/Refs/Card.graphql
Normal file
@@ -0,0 +1,29 @@
|
||||
fragment CardBlockRef on Card {
|
||||
secondary_button {
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageRef
|
||||
...ContentPageRef
|
||||
...AccountPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
primary_button {
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageRef
|
||||
...ContentPageRef
|
||||
...AccountPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
#import "../Fragments/Image.graphql"
|
||||
#import "../Fragments/Blocks/Card.graphql"
|
||||
#import "../Fragments/Blocks/Refs/Card.graphql"
|
||||
#import "../Fragments/PageLink/AccountPageLink.graphql"
|
||||
#import "../Fragments/PageLink/ContentPageLink.graphql"
|
||||
#import "../Fragments/PageLink/LoyaltyPageLink.graphql"
|
||||
@@ -11,8 +13,8 @@
|
||||
query GetLoyaltyPage($locale: String!, $uid: String!) {
|
||||
loyalty_page(uid: $uid, locale: $locale) {
|
||||
blocks {
|
||||
__typename
|
||||
... on LoyaltyPageBlocksShortcuts {
|
||||
__typename
|
||||
shortcuts {
|
||||
title
|
||||
preamble
|
||||
@@ -22,10 +24,9 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageLink
|
||||
...LoyaltyPageLink
|
||||
...ContentPageLink
|
||||
...AccountPageLink
|
||||
}
|
||||
}
|
||||
totalCount
|
||||
@@ -34,6 +35,7 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
... on LoyaltyPageBlocksDynamicContent {
|
||||
__typename
|
||||
dynamic_content {
|
||||
title
|
||||
subtitle
|
||||
@@ -52,30 +54,8 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on LoyaltyPageBlocksCardGrid {
|
||||
card_grid {
|
||||
title
|
||||
subtitle
|
||||
cards {
|
||||
referenceConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageLink
|
||||
...ContentPageLink
|
||||
...AccountPageLink
|
||||
}
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
title
|
||||
subtitle
|
||||
open_in_new_tab
|
||||
cta_text
|
||||
}
|
||||
}
|
||||
}
|
||||
... on LoyaltyPageBlocksContent {
|
||||
__typename
|
||||
content {
|
||||
content {
|
||||
json
|
||||
@@ -83,7 +63,6 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...Image
|
||||
...LoyaltyPageLink
|
||||
...ContentPageLink
|
||||
}
|
||||
@@ -93,6 +72,22 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on LoyaltyPageBlocksCardsGrid {
|
||||
__typename
|
||||
cards_grid {
|
||||
title
|
||||
preamble
|
||||
layout
|
||||
theme
|
||||
cardConnection(limit: 10) {
|
||||
edges {
|
||||
node {
|
||||
...CardBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
title
|
||||
heading
|
||||
@@ -185,23 +180,6 @@ query GetLoyaltyPageRefs($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on LoyaltyPageBlocksCardGrid {
|
||||
__typename
|
||||
card_grid {
|
||||
cards {
|
||||
referenceConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageRef
|
||||
...ContentPageRef
|
||||
...LoyaltyPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on LoyaltyPageBlocksContent {
|
||||
__typename
|
||||
content {
|
||||
@@ -228,6 +206,18 @@ query GetLoyaltyPageRefs($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on LoyaltyPageBlocksCardsGrid {
|
||||
__typename
|
||||
cards_grid {
|
||||
cardConnection(limit: 10) {
|
||||
edges {
|
||||
node {
|
||||
...CardBlockRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sidebar {
|
||||
... on LoyaltyPageSidebarContent {
|
||||
|
||||
BIN
public/_static/fonts/biro-script-plus/Biro-Script-Plus.ttf
Normal file
BIN
public/_static/fonts/biro-script-plus/Biro-Script-Plus.ttf
Normal file
Binary file not shown.
@@ -13,37 +13,28 @@ import { PageLinkEnum } from "@/types/requests/pageLinks"
|
||||
import { EdgesWithTotalCount } from "@/types/requests/utils/edges"
|
||||
import { RTEDocument } from "@/types/rte/node"
|
||||
|
||||
const loyaltyPageBlockCardGrid = z.object({
|
||||
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid),
|
||||
card_grid: z.object({
|
||||
title: z.string().nullable(),
|
||||
subtitle: z.string().nullable(),
|
||||
cards: z.array(
|
||||
z.object({
|
||||
title: z.string().nullable(),
|
||||
subtitle: z.string().nullable(),
|
||||
referenceConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.object({
|
||||
system: z.object({
|
||||
uid: z.string(),
|
||||
locale: z.nativeEnum(Lang),
|
||||
}),
|
||||
url: z.string(),
|
||||
title: z.string(),
|
||||
__typename: z.string(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
totalCount: z.number(),
|
||||
const pageLink = z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.object({
|
||||
system: z.object({
|
||||
uid: z.string(),
|
||||
locale: z.nativeEnum(Lang),
|
||||
}),
|
||||
open_in_new_tab: z.boolean(),
|
||||
cta_text: z.string().nullable(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
web: z
|
||||
.object({
|
||||
original_url: z.string().nullable(),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
url: z.string(),
|
||||
title: z.string(),
|
||||
__typename: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
const loyaltyPageDynamicContent = z.object({
|
||||
__typename: z.literal(
|
||||
LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent
|
||||
@@ -54,21 +45,7 @@ const loyaltyPageDynamicContent = z.object({
|
||||
component: z.nativeEnum(LoyaltyComponentEnum),
|
||||
link: z.object({
|
||||
text: z.string().nullable(),
|
||||
pageConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.object({
|
||||
system: z.object({
|
||||
uid: z.string(),
|
||||
locale: z.nativeEnum(Lang),
|
||||
}),
|
||||
url: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
totalCount: z.number(),
|
||||
}),
|
||||
pageConnection: pageLink,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
@@ -80,26 +57,7 @@ const loyaltyPageShortcuts = z.object({
|
||||
preamble: z.string().nullable(),
|
||||
shortcuts: z.array(
|
||||
z.object({
|
||||
linkConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.object({
|
||||
system: z.object({
|
||||
uid: z.string(),
|
||||
locale: z.nativeEnum(Lang),
|
||||
}),
|
||||
url: z.string(),
|
||||
web: z
|
||||
.object({
|
||||
original_url: z.string().nullable(),
|
||||
})
|
||||
.nullable(),
|
||||
title: z.string(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
totalCount: z.number(),
|
||||
}),
|
||||
linkConnection: pageLink,
|
||||
text: z.string().nullable(),
|
||||
open_in_new_tab: z.boolean(),
|
||||
})
|
||||
@@ -107,6 +65,59 @@ const loyaltyPageShortcuts = z.object({
|
||||
}),
|
||||
})
|
||||
|
||||
const cardBlock = z.object({
|
||||
heading: z.string().nullable(),
|
||||
body_text: z.string().nullable(),
|
||||
background_image: z.any(),
|
||||
scripted_top_title: z.string().nullable(),
|
||||
primary_button: z
|
||||
.object({
|
||||
is_contentstack_link: z.boolean(),
|
||||
cta_text: z.string().nullable(),
|
||||
open_in_new_tab: z.boolean(),
|
||||
external_link: z.object({
|
||||
title: z.string().nullable(),
|
||||
href: z.string().nullable(),
|
||||
}),
|
||||
linkConnection: pageLink,
|
||||
})
|
||||
.nullable(),
|
||||
secondary_button: z
|
||||
.object({
|
||||
is_contentstack_link: z.boolean(),
|
||||
cta_text: z.string().nullable(),
|
||||
open_in_new_tab: z.boolean().nullable(),
|
||||
external_link: z.object({
|
||||
title: z.string().nullable(),
|
||||
href: z.string().nullable(),
|
||||
}),
|
||||
linkConnection: pageLink,
|
||||
})
|
||||
.nullable(),
|
||||
|
||||
system: z.object({
|
||||
locale: z.nativeEnum(Lang),
|
||||
uid: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
const loyaltyPageCards = z.object({
|
||||
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid),
|
||||
cards_grid: z.object({
|
||||
title: z.string().nullable(),
|
||||
preamble: z.string().nullable(),
|
||||
layout: z.enum(["twoColumnGrid", "threeColumnGrid", "twoPlusOne"]),
|
||||
theme: z.enum(["one", "two", "three"]).nullable(),
|
||||
cardConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: cardBlock,
|
||||
})
|
||||
),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
const loyaltyPageBlockTextContent = z.object({
|
||||
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent),
|
||||
content: z.object({
|
||||
@@ -121,10 +132,10 @@ const loyaltyPageBlockTextContent = z.object({
|
||||
})
|
||||
|
||||
const loyaltyPageBlockItem = z.discriminatedUnion("__typename", [
|
||||
loyaltyPageBlockCardGrid,
|
||||
loyaltyPageDynamicContent,
|
||||
loyaltyPageBlockTextContent,
|
||||
loyaltyPageShortcuts,
|
||||
loyaltyPageCards,
|
||||
])
|
||||
|
||||
const loyaltyPageSidebarTextContent = z.object({
|
||||
@@ -154,7 +165,6 @@ const loyaltyPageJoinLoyaltyContact = z.object({
|
||||
),
|
||||
contact: z.object({
|
||||
display_text: z.string().nullable(),
|
||||
|
||||
contact_field: z.string(),
|
||||
}),
|
||||
})
|
||||
@@ -178,26 +188,6 @@ export const validateLoyaltyPageSchema = z.object({
|
||||
})
|
||||
|
||||
// Block types
|
||||
type CardGridRaw = z.infer<typeof loyaltyPageBlockCardGrid>
|
||||
|
||||
export type CardGridCard = Omit<
|
||||
CardGridRaw["card_grid"]["cards"][number],
|
||||
"referenceConnection"
|
||||
> & {
|
||||
link:
|
||||
| {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
|
||||
export type CardGrid = Omit<CardGridRaw, "card_grid"> & {
|
||||
card_grid: Omit<CardGridRaw["card_grid"], "cards"> & {
|
||||
cards: CardGridCard[]
|
||||
}
|
||||
}
|
||||
|
||||
type DynamicContentRaw = z.infer<typeof loyaltyPageDynamicContent>
|
||||
|
||||
export type DynamicContent = Omit<DynamicContentRaw, "dynamic_content"> & {
|
||||
@@ -222,6 +212,34 @@ export interface RteBlockContent extends BlockContentRaw {
|
||||
}
|
||||
}
|
||||
|
||||
type CardsGridRaw = z.infer<typeof loyaltyPageCards>
|
||||
|
||||
export type CardsRaw =
|
||||
CardsGridRaw["cards_grid"]["cardConnection"]["edges"][number]["node"]
|
||||
|
||||
export type CardsGrid = Omit<CardsGridRaw, "cards_grid"> & {
|
||||
cards_grid: Omit<CardsGridRaw["cards_grid"], "cardConnection"> & {
|
||||
cards: (Omit<CardsRaw, "primaryButton" | "secondaryButton"> & {
|
||||
primaryButton:
|
||||
| {
|
||||
openInNewTab: boolean
|
||||
title: string
|
||||
href: string
|
||||
isExternal: boolean
|
||||
}
|
||||
| undefined
|
||||
secondaryButton:
|
||||
| {
|
||||
openInNewTab: boolean
|
||||
title: string
|
||||
href: string
|
||||
isExternal: boolean
|
||||
}
|
||||
| undefined
|
||||
})[]
|
||||
}
|
||||
}
|
||||
|
||||
type ShortcutsRaw = z.infer<typeof loyaltyPageShortcuts>
|
||||
|
||||
export type Shortcuts = Omit<ShortcutsRaw, "shortcuts"> & {
|
||||
@@ -235,7 +253,7 @@ export type Shortcuts = Omit<ShortcutsRaw, "shortcuts"> & {
|
||||
}
|
||||
}
|
||||
|
||||
export type Block = CardGrid | RteBlockContent | DynamicContent | Shortcuts
|
||||
export type Block = RteBlockContent | DynamicContent | Shortcuts | CardsGrid
|
||||
|
||||
// Sidebar block types
|
||||
type SidebarContentRaw = z.infer<typeof loyaltyPageSidebarTextContent>
|
||||
@@ -275,14 +293,34 @@ const pageConnectionRefs = z.object({
|
||||
),
|
||||
})
|
||||
|
||||
const loyaltyPageBlockCardGridRefs = z.object({
|
||||
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid),
|
||||
card_grid: z.object({
|
||||
cards: z.array(
|
||||
z.object({
|
||||
referenceConnection: pageConnectionRefs,
|
||||
})
|
||||
),
|
||||
const cardBlockRefs = z.object({
|
||||
primary_button: z
|
||||
.object({
|
||||
linkConnection: pageConnectionRefs,
|
||||
})
|
||||
.nullable(),
|
||||
secondary_button: z
|
||||
.object({
|
||||
linkConnection: pageConnectionRefs,
|
||||
})
|
||||
.nullable(),
|
||||
|
||||
system: z.object({
|
||||
content_type_uid: z.string(),
|
||||
uid: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
const loyaltyPageCardsRefs = z.object({
|
||||
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid),
|
||||
cards_grid: z.object({
|
||||
cardConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: cardBlockRefs,
|
||||
})
|
||||
),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -318,10 +356,10 @@ const loyaltyPageBlockTextContentRefs = z.object({
|
||||
})
|
||||
|
||||
const loyaltyPageBlocRefsItem = z.discriminatedUnion("__typename", [
|
||||
loyaltyPageBlockCardGridRefs,
|
||||
loyaltyPageDynamicContentRefs,
|
||||
loyaltyPageBlockTextContentRefs,
|
||||
loyaltyPageShortcutsRefs,
|
||||
loyaltyPageCardsRefs,
|
||||
])
|
||||
|
||||
const loyaltyPageSidebarTextContentRef = z.object({
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { _ } from "@/lib/translation"
|
||||
import { internalServerError, notFound } from "@/server/errors/trpc"
|
||||
import { contentstackProcedure, publicProcedure, router } from "@/server/trpc"
|
||||
import { contentstackProcedure, router } from "@/server/trpc"
|
||||
|
||||
import {
|
||||
generateRefsResponseTag,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
|
||||
import { removeEmptyObjects } from "../../utils"
|
||||
import {
|
||||
CardsRaw,
|
||||
type LoyaltyPage,
|
||||
type LoyaltyPageDataRaw,
|
||||
type LoyaltyPageRefsDataRaw,
|
||||
@@ -31,6 +32,26 @@ import { Embeds } from "@/types/requests/embeds"
|
||||
import { Edges } from "@/types/requests/utils/edges"
|
||||
import { RTEDocument } from "@/types/rte/node"
|
||||
|
||||
function makeButtonObject(
|
||||
button: CardsRaw["primary_button" | "secondary_button"]
|
||||
) {
|
||||
if (!button) return undefined
|
||||
return {
|
||||
openInNewTab: button.open_in_new_tab,
|
||||
title:
|
||||
button.cta_text ||
|
||||
(button.is_contentstack_link && button.linkConnection.edges.length
|
||||
? button.linkConnection.edges[0].node.title
|
||||
: button.external_link.title),
|
||||
href:
|
||||
button.is_contentstack_link && button.linkConnection.edges.length
|
||||
? button.linkConnection.edges[0].node.web?.original_url ||
|
||||
`/${button.linkConnection.edges[0].node.system.locale}${button.linkConnection.edges[0].node.url}`
|
||||
: button.external_link.href,
|
||||
isExternal: !button.is_contentstack_link,
|
||||
}
|
||||
}
|
||||
|
||||
export const loyaltyPageQueryRouter = router({
|
||||
get: contentstackProcedure.query(async ({ ctx }) => {
|
||||
const { lang, uid } = ctx
|
||||
@@ -60,6 +81,8 @@ export const loyaltyPageQueryRouter = router({
|
||||
const validatedLoyaltyPageRefs =
|
||||
validateLoyaltyPageRefsSchema.safeParse(cleanedData)
|
||||
if (!validatedLoyaltyPageRefs.success) {
|
||||
console.error("Bad validation for `GetLoyaltyPageRefs`")
|
||||
console.error(validatedLoyaltyPageRefs.error)
|
||||
throw internalServerError(validatedLoyaltyPageRefs.error)
|
||||
}
|
||||
|
||||
@@ -114,30 +137,12 @@ export const loyaltyPageQueryRouter = router({
|
||||
const blocks = validatedLoyaltyPage.data.loyalty_page.blocks
|
||||
? validatedLoyaltyPage.data.loyalty_page.blocks.map((block) => {
|
||||
switch (block.__typename) {
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid:
|
||||
return {
|
||||
...block,
|
||||
card_grid: {
|
||||
...block.card_grid,
|
||||
cards: block.card_grid.cards.map((card) => {
|
||||
return {
|
||||
...card,
|
||||
link: card.referenceConnection.totalCount
|
||||
? {
|
||||
href: `/${card.referenceConnection.edges[0].node.system.locale}${card.referenceConnection.edges[0].node.url}`,
|
||||
title: card.cta_text || _("Read more"),
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent:
|
||||
return {
|
||||
...block,
|
||||
dynamic_content: {
|
||||
...block.dynamic_content,
|
||||
link: block.dynamic_content.link.pageConnection.totalCount
|
||||
link: block.dynamic_content.link.pageConnection.edges.length
|
||||
? {
|
||||
text: block.dynamic_content.link.text,
|
||||
href: `/${block.dynamic_content.link.pageConnection.edges[0].node.system.locale}${block.dynamic_content.link.pageConnection.edges[0].node.url}`,
|
||||
@@ -174,6 +179,24 @@ export const loyaltyPageQueryRouter = router({
|
||||
})),
|
||||
},
|
||||
}
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
|
||||
return {
|
||||
...block,
|
||||
cards_grid: {
|
||||
...block.cards_grid,
|
||||
cards: block.cards_grid.cardConnection.edges.map(
|
||||
({ node: card }) => {
|
||||
return {
|
||||
...card,
|
||||
primaryButton: makeButtonObject(card.primary_button),
|
||||
secondaryButton: makeButtonObject(
|
||||
card.secondary_button
|
||||
),
|
||||
}
|
||||
}
|
||||
),
|
||||
},
|
||||
}
|
||||
default:
|
||||
return block
|
||||
}
|
||||
|
||||
@@ -15,12 +15,16 @@ export function getConnections(refs: LoyaltyPageRefsDataRaw) {
|
||||
}
|
||||
break
|
||||
}
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid: {
|
||||
item.card_grid.cards.forEach((card) => {
|
||||
if (card.referenceConnection.edges.length) {
|
||||
connections.push(card.referenceConnection)
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
Block,
|
||||
CardGrid,
|
||||
CardsGrid,
|
||||
DynamicContent,
|
||||
RteBlockContent,
|
||||
} from "@/server/routers/contentstack/loyaltyPage/output"
|
||||
@@ -17,7 +17,7 @@ export type DynamicComponentProps = {
|
||||
component: DynamicContent["dynamic_content"]["component"]
|
||||
}
|
||||
|
||||
export type CardGridProps = Pick<CardGrid, "card_grid">
|
||||
export type CardsGridProps = Pick<CardsGrid, "cards_grid">
|
||||
|
||||
export type Content = { content: RteBlockContent["content"]["content"] }
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export type LoyaltyComponent = keyof typeof LoyaltyComponentEnum
|
||||
|
||||
export enum LoyaltyBlocksTypenameEnum {
|
||||
LoyaltyPageBlocksDynamicContent = "LoyaltyPageBlocksDynamicContent",
|
||||
LoyaltyPageBlocksCardGrid = "LoyaltyPageBlocksCardGrid",
|
||||
LoyaltyPageBlocksContent = "LoyaltyPageBlocksContent",
|
||||
LoyaltyPageBlocksShortcuts = "LoyaltyPageBlocksShortcuts",
|
||||
LoyaltyPageBlocksCardsGrid = "LoyaltyPageBlocksCardsGrid",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user