From 9a51cc6cb554edf645a36e2d069a3b27aab869e8 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 30 Aug 2024 08:10:57 +0200 Subject: [PATCH] feat(SW-285): Ship support for ContentPageBlocksContent --- components/Content/Blocks/index.tsx | 26 +++++++ components/ContentType/ContentPage/index.tsx | 2 + .../Fragments/PageLink/HotelPageLink.graphql | 8 ++ lib/graphql/Query/ContentPage.graphql | 77 +++++++++++++++++++ .../contentstack/contentPage/output.ts | 46 +++++++++++ .../routers/contentstack/contentPage/query.ts | 38 ++++++--- types/components/content/blocks.ts | 5 ++ types/components/content/enums.ts | 3 + .../trpc/routers/contentstack/contentPage.ts | 6 +- 9 files changed, 198 insertions(+), 13 deletions(-) create mode 100644 components/Content/Blocks/index.tsx create mode 100644 lib/graphql/Fragments/PageLink/HotelPageLink.graphql create mode 100644 types/components/content/blocks.ts create mode 100644 types/components/content/enums.ts diff --git a/components/Content/Blocks/index.tsx b/components/Content/Blocks/index.tsx new file mode 100644 index 000000000..8268aad0a --- /dev/null +++ b/components/Content/Blocks/index.tsx @@ -0,0 +1,26 @@ +import JsonToHtml from "@/components/JsonToHtml" + +// import DynamicContentBlock from "@/components/Loyalty/Blocks/DynamicContent" +// import Shortcuts from "@/components/MyPages/Blocks/Shortcuts" +// import CardsGrid from "./CardsGrid" +import type { BlocksProps } from "@/types/components/content/blocks" +import { ContentBlocksTypenameEnum } from "@/types/components/content/enums" + +export function Blocks({ blocks }: BlocksProps) { + return blocks.map((block, idx) => { + const firstItem = idx === 0 + switch (block.__typename) { + case ContentBlocksTypenameEnum.ContentPageBlocksContent: + return ( +
+ +
+ ) + default: + return null + } + }) +} diff --git a/components/ContentType/ContentPage/index.tsx b/components/ContentType/ContentPage/index.tsx index 5f9f27dc9..3a3525766 100644 --- a/components/ContentType/ContentPage/index.tsx +++ b/components/ContentType/ContentPage/index.tsx @@ -1,5 +1,6 @@ import { serverClient } from "@/lib/trpc/server" +import { Blocks } from "@/components/Content/Blocks" import Hero from "@/components/Hero" import Intro from "@/components/Intro" import Preamble from "@/components/TempDesignSystem/Text/Preamble" @@ -36,6 +37,7 @@ export default async function ContentPage() { src={heroImage.url} /> ) : null} + {contentPage.blocks ? : null} diff --git a/lib/graphql/Fragments/PageLink/HotelPageLink.graphql b/lib/graphql/Fragments/PageLink/HotelPageLink.graphql new file mode 100644 index 000000000..619b374d7 --- /dev/null +++ b/lib/graphql/Fragments/PageLink/HotelPageLink.graphql @@ -0,0 +1,8 @@ +fragment HotelPageLink on HotelPage { + system { + locale + uid + } + title + url +} diff --git a/lib/graphql/Query/ContentPage.graphql b/lib/graphql/Query/ContentPage.graphql index dee4d9610..5fc625fa3 100644 --- a/lib/graphql/Query/ContentPage.graphql +++ b/lib/graphql/Query/ContentPage.graphql @@ -1,5 +1,40 @@ +#import "../Fragments/PageLink/AccountPageLink.graphql" +#import "../Fragments/PageLink/ContentPageLink.graphql" +#import "../Fragments/PageLink/LoyaltyPageLink.graphql" + query GetContentPage($locale: String!, $uid: String!) { content_page(uid: $uid, locale: $locale) { + blocks { + ... on ContentPageBlocksContent { + __typename + content { + content { + embedded_itemsConnection { + edges { + node { + __typename + ...LoyaltyPageLink + ...ContentPageLink + ...AccountPageLink + # TODO: Link HotelPage + # ...Image + # ... on ImageContainer { + # title + # image_left + # image_right + # system { + # uid + # } + # } + } + } + totalCount + } + json + } + } + } + } title header { heading @@ -15,6 +50,48 @@ query GetContentPage($locale: String!, $uid: String!) { } } +query GetContentPageRefs($locale: String!, $uid: String!) { + content_page(locale: $locale, uid: $uid) { + blocks { + ... on ContentPageBlocksContent { + __typename + content { + content { + embedded_itemsConnection { + edges { + node { + # No fragments used since we want to include __typename for each type to avoid fetching SystemAsset + ... on ContentPage { + __typename + system { + ...System + } + } + ... on LoyaltyPage { + __typename + system { + ...System + } + } + ... on AccountPage { + __typename + system { + ...System + } + } + } + } + } + } + } + } + } + system { + ...System + } + } +} + query GetDaDeEnUrlsContentPage($uid: String!) { de: all_content_page(where: { uid: $uid }, locale: "de") { items { diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts index 134a179f1..6965b3187 100644 --- a/server/routers/contentstack/contentPage/output.ts +++ b/server/routers/contentstack/contentPage/output.ts @@ -4,6 +4,43 @@ import { Lang } from "@/constants/languages" import { imageVaultAssetSchema } from "../schemas/imageVault" +import { ContentBlocksTypenameEnum } from "@/types/components/content/enums" +import { ImageVaultAsset } from "@/types/components/imageVault" +import { Embeds } from "@/types/requests/embeds" +import { EdgesWithTotalCount } from "@/types/requests/utils/edges" +import { RTEDocument } from "@/types/rte/node" + +// Block Schema and types +const contentPageBlockTextContent = z.object({ + __typename: z.literal(ContentBlocksTypenameEnum.ContentPageBlocksContent), + content: z.object({ + content: z.object({ + embedded_itemsConnection: z.object({ + edges: z.array(z.any()), + totalCount: z.number(), + }), + json: z.any(), + }), + }), +}) + +const contentPageBlockItem = z.discriminatedUnion("__typename", [ + contentPageBlockTextContent, +]) + +type BlockContentRaw = z.infer +export interface RteBlockContent extends BlockContentRaw { + content: { + content: { + json: RTEDocument + embedded_itemsConnection: EdgesWithTotalCount + } + } +} + +export type Block = RteBlockContent + +// Content Page Schema and types export const validateContentPageSchema = z.object({ content_page: z.object({ title: z.string(), @@ -12,6 +49,7 @@ export const validateContentPageSchema = z.object({ preamble: z.string(), }), hero_image: imageVaultAssetSchema.nullable().optional(), + blocks: z.array(contentPageBlockItem).nullable(), system: z.object({ uid: z.string(), locale: z.nativeEnum(Lang), @@ -20,3 +58,11 @@ export const validateContentPageSchema = z.object({ }), }), }) + +export type ContentPageDataRaw = z.infer +type ContentPageRaw = ContentPageDataRaw["content_page"] + +export type ContentPage = Omit & { + heroImage?: ImageVaultAsset + blocks: Block[] +} diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts index 24099a24e..35d864fd5 100644 --- a/server/routers/contentstack/contentPage/query.ts +++ b/server/routers/contentstack/contentPage/query.ts @@ -7,17 +7,19 @@ import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { generateTag } from "@/utils/generateTag" import { makeImageVaultImage } from "@/utils/imageVault" -import { validateContentPageSchema } from "./output" +import { removeEmptyObjects } from "../../utils" +import { + Block, + ContentPage, + ContentPageDataRaw, + validateContentPageSchema, +} from "./output" -import { ImageVaultAsset } from "@/types/components/imageVault" +import { ContentBlocksTypenameEnum } from "@/types/components/content/enums" import { TrackingChannelEnum, TrackingSDKPageData, } from "@/types/components/tracking" -import { - ContentPage, - ContentPageDataRaw, -} from "@/types/trpc/routers/contentstack/contentPage" export const contentPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { @@ -32,15 +34,28 @@ export const contentPageQueryRouter = router({ { cache: "force-cache", next: { tags: [generateTag(lang, uid)] } } ) - const { content_page } = response.data + const { content_page } = removeEmptyObjects(response.data) if (!content_page) { throw notFound(response) } + const processedBlocks = content_page.blocks + ? content_page.blocks.map((block: any) => { + switch (block.__typename) { + case ContentBlocksTypenameEnum.ContentPageBlocksContent: + return block + default: + return block + } + }) + : null + + const heroImage = makeImageVaultImage(content_page.hero_image) const validatedContentPage = validateContentPageSchema.safeParse({ content_page: { ...content_page, - hero_image: makeImageVaultImage(content_page.hero_image), + blocks: processedBlocks, + hero_image: heroImage, }, }) @@ -52,14 +67,13 @@ export const contentPageQueryRouter = router({ return null } - // Destructure hero_image and rename it to heroImage - const { hero_image: heroImage, ...restContentPage } = + const { hero_image, blocks, ...restContentPage } = validatedContentPage.data.content_page - // Construct the contentPage object with the correct structure const contentPage: ContentPage = { ...restContentPage, - heroImage: heroImage as ImageVaultAsset | undefined, + heroImage, + blocks: blocks as Block[], } const tracking: TrackingSDKPageData = { diff --git a/types/components/content/blocks.ts b/types/components/content/blocks.ts new file mode 100644 index 000000000..af4815f69 --- /dev/null +++ b/types/components/content/blocks.ts @@ -0,0 +1,5 @@ +import { Block } from "@/server/routers/contentstack/contentPage/output" + +export type BlocksProps = { + blocks: Block[] +} diff --git a/types/components/content/enums.ts b/types/components/content/enums.ts new file mode 100644 index 000000000..3fa911d6b --- /dev/null +++ b/types/components/content/enums.ts @@ -0,0 +1,3 @@ +export enum ContentBlocksTypenameEnum { + ContentPageBlocksContent = "ContentPageBlocksContent", +} diff --git a/types/trpc/routers/contentstack/contentPage.ts b/types/trpc/routers/contentstack/contentPage.ts index d5a8cc322..49d55e3fa 100644 --- a/types/trpc/routers/contentstack/contentPage.ts +++ b/types/trpc/routers/contentstack/contentPage.ts @@ -1,6 +1,9 @@ import { z } from "zod" -import { validateContentPageSchema } from "@/server/routers/contentstack/contentPage/output" +import { + Block, + validateContentPageSchema, +} from "@/server/routers/contentstack/contentPage/output" import { ImageVaultAsset } from "@/types/components/imageVault" @@ -10,4 +13,5 @@ type ContentPageRaw = ContentPageDataRaw["content_page"] export type ContentPage = Omit & { heroImage?: ImageVaultAsset + blocks?: Block[] }