diff --git a/components/Content/Blocks/CardsGrid/index.tsx b/components/Content/Blocks/CardsGrid/index.tsx
new file mode 100644
index 000000000..2eb4b9b4e
--- /dev/null
+++ b/components/Content/Blocks/CardsGrid/index.tsx
@@ -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 (
+
+
+
+ {cards_grid.cards.map((card) => {
+ switch (card.__typename) {
+ case CardsGridEnum.Card: {
+ return (
+
+ )
+ }
+ case CardsGridEnum.LoyaltyCard:
+ return (
+
+ )
+ }
+ })}
+
+
+ )
+}
diff --git a/components/Content/Blocks/index.tsx b/components/Content/Blocks/index.tsx
index 3638d7af6..9c396ed11 100644
--- a/components/Content/Blocks/index.tsx
+++ b/components/Content/Blocks/index.tsx
@@ -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 (
+
+ )
default:
return null
}
diff --git a/lib/graphql/Query/ContentPage.graphql b/lib/graphql/Query/ContentPage.graphql
index 8202e89e1..6dedd3057 100644
--- a/lib/graphql/Query/ContentPage.graphql
+++ b/lib/graphql/Query/ContentPage.graphql
@@ -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
diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts
index b99a873ff..c15d18555 100644
--- a/server/routers/contentstack/contentPage/output.ts
+++ b/server/routers/contentstack/contentPage/output.ts
@@ -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
@@ -57,7 +126,22 @@ export interface RteBlockContent extends BlockContentRaw {
}
export type Shortcuts = z.infer
-export type Block = RteBlockContent | Shortcuts
+
+type LoyaltyCardRaw = z.infer
+type LoyaltyCard = Omit & {
+ image?: ImageVaultAsset
+}
+type CardRaw = z.infer
+type Card = Omit & {
+ backgroundImage?: ImageVaultAsset
+}
+type CardsGridRaw = z.infer
+export type CardsGrid = Omit & {
+ 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 & {
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({
diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts
index b473bb16c..7611ea410 100644
--- a/server/routers/contentstack/contentPage/query.ts
+++ b/server/routers/contentstack/contentPage/query.ts
@@ -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
}
diff --git a/server/routers/contentstack/contentPage/utils.ts b/server/routers/contentstack/contentPage/utils.ts
index 50c49e5e2..3f2689fc0 100644
--- a/server/routers/contentstack/contentPage/utils.ts
+++ b/server/routers/contentstack/contentPage/utils.ts
@@ -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,
+ }
+}
diff --git a/types/components/content/blocks.ts b/types/components/content/blocks.ts
index af4815f69..702a086d4 100644
--- a/types/components/content/blocks.ts
+++ b/types/components/content/blocks.ts
@@ -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 & {
+ firstItem?: boolean
+}
diff --git a/types/components/content/enums.ts b/types/components/content/enums.ts
index 77fad4d68..a629a32c6 100644
--- a/types/components/content/enums.ts
+++ b/types/components/content/enums.ts
@@ -1,4 +1,10 @@
export enum ContentBlocksTypenameEnum {
ContentPageBlocksContent = "ContentPageBlocksContent",
ContentPageBlocksShortcuts = "ContentPageBlocksShortcuts",
+ ContentPageBlocksCardsGrid = "ContentPageBlocksCardsGrid",
+}
+
+export enum CardsGridEnum {
+ LoyaltyCard = "LoyaltyCard",
+ Card = "Card",
}