feat(SW-285): Ship support for ContentPageBlocksCardsGrid

This commit is contained in:
Chuma McPhoy
2024-09-02 10:23:21 +02:00
parent dd336ca4ab
commit e88e4d92bf
8 changed files with 320 additions and 5 deletions

View File

@@ -0,0 +1,52 @@
import SectionContainer from "@/components/Section/Container"
import SectionHeader 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/content/blocks"
import { CardsGridEnum } from "@/types/components/content/enums"
export default function CardsGrid({
cards_grid,
firstItem = false,
}: CardsGridProps) {
return (
<SectionContainer>
<SectionHeader
title={cards_grid.title}
subtitle={cards_grid.preamble}
topTitle={firstItem}
/>
<Grids.Stackable>
{cards_grid.cards.map((card) => {
switch (card.__typename) {
case CardsGridEnum.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}
/>
)
}
case CardsGridEnum.LoyaltyCard:
return (
<LoyaltyCard
key={card.system.uid}
image={card.image}
heading={card.heading}
bodyText={card.body_text}
link={card.link}
/>
)
}
})}
</Grids.Stackable>
</SectionContainer>
)
}

View File

@@ -2,7 +2,8 @@ import JsonToHtml from "@/components/JsonToHtml"
// import DynamicContentBlock from "@/components/Loyalty/Blocks/DynamicContent"
import Shortcuts from "@/components/MyPages/Blocks/Shortcuts"
// import CardsGrid from "./CardsGrid"
import CardsGrid from "./CardsGrid"
import type { BlocksProps } from "@/types/components/content/blocks"
import { ContentBlocksTypenameEnum } from "@/types/components/content/enums"
@@ -29,6 +30,14 @@ export function Blocks({ blocks }: BlocksProps) {
title={block.shortcuts.title}
/>
)
case ContentBlocksTypenameEnum.ContentPageBlocksCardsGrid:
return (
<CardsGrid
cards_grid={block.cards_grid}
key={`${block.cards_grid.title}-${idx}`}
firstItem={firstItem}
/>
)
default:
return null
}

View File

@@ -1,3 +1,8 @@
#import "../Fragments/Blocks/Card.graphql"
#import "../Fragments/Blocks/LoyaltyCard.graphql"
#import "../Fragments/Blocks/Refs/Card.graphql"
#import "../Fragments/Blocks/Refs/LoyaltyCard.graphql"
#import "../Fragments/PageLink/AccountPageLink.graphql"
#import "../Fragments/PageLink/ContentPageLink.graphql"
#import "../Fragments/PageLink/LoyaltyPageLink.graphql"
@@ -60,6 +65,24 @@ query GetContentPage($locale: String!, $uid: String!) {
}
}
}
... on ContentPageBlocksCardsGrid {
__typename
cards_grid {
title
preamble
layout
theme
cardConnection(limit: 10) {
edges {
node {
__typename
...CardBlock
...LoyaltyCardBlock
}
}
}
}
}
}
title
header {
@@ -128,6 +151,19 @@ query GetContentPageRefs($locale: String!, $uid: String!) {
}
}
}
... on ContentPageBlocksCardsGrid {
__typename
cards_grid {
cardConnection(limit: 10) {
edges {
node {
...CardBlockRef
...LoyaltyCardBlockRef
}
}
}
}
}
}
system {
...System

View File

@@ -4,9 +4,13 @@ import { Lang } from "@/constants/languages"
import { imageVaultAssetSchema } from "../schemas/imageVault"
import { ContentBlocksTypenameEnum } from "@/types/components/content/enums"
import {
CardsGridEnum,
ContentBlocksTypenameEnum,
} from "@/types/components/content/enums"
import { ImageVaultAsset } from "@/types/components/imageVault"
import { Embeds } from "@/types/requests/embeds"
import { PageLinkEnum } from "@/types/requests/pageLinks"
import { RTEEmbedsEnum } from "@/types/requests/rte"
import { EdgesWithTotalCount } from "@/types/requests/utils/edges"
import { RTEDocument } from "@/types/rte/node"
@@ -41,9 +45,74 @@ const contentPageShortcuts = z.object({
}),
})
// TODO: this is a separate entity, should be in a separate file.
const cardBlock = z.object({
__typename: z.literal(CardsGridEnum.Card),
heading: z.string().nullable(),
body_text: z.string().nullable(),
background_image: z.any(),
scripted_top_title: z.string().nullable(),
primaryButton: z
.object({
openInNewTab: z.boolean(),
title: z.string(),
href: z.string(),
isExternal: z.boolean(),
})
.optional(),
secondaryButton: 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 loyaltyCardBlock = z.object({
__typename: z.literal(CardsGridEnum.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 contentPageCardsItems = z.discriminatedUnion("__typename", [
loyaltyCardBlock,
cardBlock,
])
const contentPageCards = z.object({
__typename: z.literal(ContentBlocksTypenameEnum.ContentPageBlocksCardsGrid),
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(),
cards: z.array(contentPageCardsItems),
}),
})
const contentPageBlockItem = z.discriminatedUnion("__typename", [
contentPageBlockTextContent,
contentPageShortcuts,
contentPageCards,
])
type BlockContentRaw = z.infer<typeof contentPageBlockTextContent>
@@ -57,7 +126,22 @@ export interface RteBlockContent extends BlockContentRaw {
}
export type Shortcuts = z.infer<typeof contentPageShortcuts>
export type Block = RteBlockContent | Shortcuts
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 contentPageCards>
export type CardsGrid = Omit<CardsGridRaw, "cards"> & {
cards: (LoyaltyCard | Card)[]
}
export type CardsRaw = CardsGrid["cards_grid"]["cards"][number]
export type Block = RteBlockContent | Shortcuts | CardsGrid
// Content Page Schema and types
export const validateContentPageSchema = z.object({
@@ -86,6 +170,20 @@ export type ContentPage = Omit<ContentPageRaw, "blocks" | "hero_image"> & {
blocks: Block[]
}
const pageConnectionRefs = z.object({
edges: z.array(
z.object({
node: z.object({
__typename: z.nativeEnum(PageLinkEnum),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
}),
})
),
})
const rteConnectionRefs = z.object({
edges: z.array(
z.object({
@@ -100,6 +198,42 @@ const rteConnectionRefs = z.object({
),
})
const cardBlockRefs = z.object({
__typename: z.literal(CardsGridEnum.Card),
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 loyaltyCardBlockRefs = z.object({
__typename: z.literal(CardsGridEnum.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 contentPageBlockTextContentRefs = z.object({
__typename: z.literal(ContentBlocksTypenameEnum.ContentPageBlocksContent),
content: z.object({
@@ -109,6 +243,13 @@ const contentPageBlockTextContentRefs = z.object({
}),
})
const contentPageCardsRefs = z.object({
__typename: z.literal(ContentBlocksTypenameEnum.ContentPageBlocksCardsGrid),
cards_grid: z.object({
cardConnection: cardGridCardsRef,
}),
})
const contentPageShortcutsRefs = z.object({
__typename: z.literal(ContentBlocksTypenameEnum.ContentPageBlocksShortcuts),
shortcuts: z.object({
@@ -123,6 +264,7 @@ const contentPageShortcutsRefs = z.object({
const contentPageBlockRefsItem = z.discriminatedUnion("__typename", [
contentPageBlockTextContentRefs,
contentPageShortcutsRefs,
contentPageCardsRefs,
])
export const validateContentPageRefsSchema = z.object({

View File

@@ -18,10 +18,14 @@ import {
fetchContentPageRefs,
generatePageTags,
getContentPageCounter,
makeButtonObject,
validateContentPageRefs,
} from "./utils"
import { ContentBlocksTypenameEnum } from "@/types/components/content/enums"
import {
CardsGridEnum,
ContentBlocksTypenameEnum,
} from "@/types/components/content/enums"
import {
TrackingChannelEnum,
TrackingSDKPageData,
@@ -85,6 +89,38 @@ export const contentPageQueryRouter = router({
})),
},
}
case ContentBlocksTypenameEnum.ContentPageBlocksCardsGrid:
return {
...block,
cards_grid: {
...block.cards_grid,
cards: block.cards_grid.cardConnection.edges.map(
({ node: card }: { node: any }) => {
switch (card.__typename) {
case CardsGridEnum.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,
}
case CardsGridEnum.LoyaltyCard:
return {
...card,
image: makeImageVaultImage(card.image),
link: makeButtonObject(card.link),
}
}
}
),
},
}
default:
return block
}

View File

@@ -6,6 +6,7 @@ import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { generateTag, generateTags } from "@/utils/generateTag"
import { removeMultipleSlashes } from "@/utils/url"
import { removeEmptyObjects } from "../../utils"
import { ContentPageRefsDataRaw, validateContentPageRefsSchema } from "./output"
@@ -130,3 +131,29 @@ export function getConnections(refs: ContentPageRefsDataRaw) {
}
return connections
}
export function makeButtonObject(button: any) {
if (!button) return null
const isContenstackLink =
button?.is_contentstack_link || button.linkConnection?.edges?.length
const linkConnnectionNode = isContenstackLink
? button.linkConnection.edges[0]?.node
: null
return {
openInNewTab: button?.open_in_new_tab,
title:
button.cta_text ||
(linkConnnectionNode
? linkConnnectionNode.title
: button.external_link.title),
href: linkConnnectionNode
? linkConnnectionNode.web?.original_url ||
removeMultipleSlashes(
`/${linkConnnectionNode.system.locale}/${linkConnnectionNode.url}`
)
: button.external_link.href,
isExternal: !isContenstackLink,
}
}

View File

@@ -1,5 +1,12 @@
import { Block } from "@/server/routers/contentstack/contentPage/output"
import {
Block,
CardsGrid,
} from "@/server/routers/contentstack/contentPage/output"
export type BlocksProps = {
blocks: Block[]
}
export type CardsGridProps = Pick<CardsGrid, "cards_grid"> & {
firstItem?: boolean
}

View File

@@ -1,4 +1,10 @@
export enum ContentBlocksTypenameEnum {
ContentPageBlocksContent = "ContentPageBlocksContent",
ContentPageBlocksShortcuts = "ContentPageBlocksShortcuts",
ContentPageBlocksCardsGrid = "ContentPageBlocksCardsGrid",
}
export enum CardsGridEnum {
LoyaltyCard = "LoyaltyCard",
Card = "Card",
}