feat: add new cards grid block

This commit is contained in:
Christel Westerberg
2024-05-21 13:45:52 +02:00
parent 92879b623b
commit 7d982aad18
16 changed files with 402 additions and 205 deletions

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
.section {
display: grid;
gap: 2.4rem;
}

View File

@@ -0,0 +1,44 @@
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 && {
href: card.secondaryButton.link.href,
title: card.secondaryButton.link.title,
openInNewTab: card.secondaryButton.open_in_new_tab,
isExternal: card.secondaryButton.isExternal,
}
}
primaryButton={
card.primaryButton && {
href: card.primaryButton.link.href,
title: card.primaryButton.link.title,
openInNewTab: card.primaryButton.open_in_new_tab,
isExternal: card.primaryButton.isExternal,
}
}
/>
))}
</CardGrid>
</section>
)
}

View File

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

View 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",
},
})

View File

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

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

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

View File

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

View File

@@ -13,37 +13,22 @@ 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(),
})
),
}),
url: z.string(),
title: z.string(),
__typename: z.string(),
}),
})
),
})
const loyaltyPageDynamicContent = z.object({
__typename: z.literal(
LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent
@@ -93,7 +78,8 @@ const loyaltyPageShortcuts = z.object({
.object({
original_url: z.string().nullable(),
})
.nullable(),
.nullable()
.optional(),
title: z.string(),
}),
})
@@ -107,6 +93,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 +160,10 @@ const loyaltyPageBlockTextContent = z.object({
})
const loyaltyPageBlockItem = z.discriminatedUnion("__typename", [
loyaltyPageBlockCardGrid,
loyaltyPageDynamicContent,
loyaltyPageBlockTextContent,
loyaltyPageShortcuts,
loyaltyPageCards,
])
const loyaltyPageSidebarTextContent = z.object({
@@ -178,26 +217,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 +241,43 @@ export interface RteBlockContent extends BlockContentRaw {
}
}
type CardsGridRaw = z.infer<typeof loyaltyPageCards>
export type CardsGrid = Omit<CardsGridRaw, "cards_grid"> & {
cards_grid: Omit<CardsGridRaw["cards_grid"], "cardConnection"> & {
cards: {
system: {
locale: Lang
uid: string
}
heading: string | null
body_text: string | null
scripted_top_title: string | null
primaryButton:
| {
open_in_new_tab: boolean
link: {
title: string
href: string
}
isExternal: boolean
}
| undefined
secondaryButton:
| {
open_in_new_tab: boolean
link: {
title: string
href: string
}
isExternal: boolean
}
| undefined
background_image?: any
}[]
}
}
type ShortcutsRaw = z.infer<typeof loyaltyPageShortcuts>
export type Shortcuts = Omit<ShortcutsRaw, "shortcuts"> & {
@@ -235,7 +291,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 +331,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 +394,10 @@ const loyaltyPageBlockTextContentRefs = z.object({
})
const loyaltyPageBlocRefsItem = z.discriminatedUnion("__typename", [
loyaltyPageBlockCardGridRefs,
loyaltyPageDynamicContentRefs,
loyaltyPageBlockTextContentRefs,
loyaltyPageShortcutsRefs,
loyaltyPageCardsRefs,
])
const loyaltyPageSidebarTextContentRef = z.object({

View File

@@ -60,6 +60,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,24 +116,6 @@ 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,
@@ -174,6 +158,70 @@ export const loyaltyPageQueryRouter = router({
})),
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
return {
...block,
cards_grid: {
...block.cards_grid,
cards: block.cards_grid.cardConnection.edges.map(
({ node: card }) => {
const primaryButton = card.primary_button
? {
open_in_new_tab:
card.primary_button.open_in_new_tab,
link: {
title:
card.primary_button.cta_text ||
(card.primary_button.is_contentstack_link &&
card.primary_button.linkConnection.edges.length
? card.primary_button.linkConnection.edges[0]
.node.title
: card.primary_button.external_link.title),
href:
card.primary_button.is_contentstack_link &&
card.primary_button.linkConnection.edges.length
? `/${card.primary_button.linkConnection.edges[0].node.system.locale}${card.primary_button.linkConnection.edges[0].node.url}`
: card.primary_button.external_link.href,
},
isExternal:
!card.primary_button.is_contentstack_link,
}
: undefined
const secondaryButton = card.secondary_button
? {
open_in_new_tab:
card.secondary_button.open_in_new_tab,
link: {
title:
card.secondary_button.cta_text ||
(card.secondary_button.is_contentstack_link &&
card.secondary_button.linkConnection.edges
.length
? card.secondary_button.linkConnection
.edges[0].node.title
: card.secondary_button.external_link.title),
href:
card.secondary_button.is_contentstack_link &&
card.secondary_button.linkConnection.edges
.length
? `/${card.secondary_button.linkConnection.edges[0].node.system.locale}${card.secondary_button.linkConnection.edges[0].node.url}`
: card.secondary_button.external_link.title,
},
isExternal:
!card.secondary_button.is_contentstack_link,
}
: undefined
return {
...card,
primaryButton,
secondaryButton,
}
}
),
},
}
default:
return block
}

View File

@@ -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: {

View File

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

View File

@@ -27,7 +27,7 @@ export type LoyaltyComponent = keyof typeof LoyaltyComponentEnum
export enum LoyaltyBlocksTypenameEnum {
LoyaltyPageBlocksDynamicContent = "LoyaltyPageBlocksDynamicContent",
LoyaltyPageBlocksCardGrid = "LoyaltyPageBlocksCardGrid",
LoyaltyPageBlocksContent = "LoyaltyPageBlocksContent",
LoyaltyPageBlocksShortcuts = "LoyaltyPageBlocksShortcuts",
LoyaltyPageBlocksCardsGrid = "LoyaltyPageBlocksCardsGrid",
}