feat(SW-285): Ship support for ContentPageBlocksContent

This commit is contained in:
Chuma McPhoy
2024-08-30 08:10:57 +02:00
parent b806824fde
commit 9a51cc6cb5
9 changed files with 198 additions and 13 deletions

View File

@@ -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 (
<section key={`${block.__typename}-${idx}`}>
<JsonToHtml
nodes={block.content.content.json.children}
embeds={block.content.content.embedded_itemsConnection.edges}
/>
</section>
)
default:
return null
}
})
}

View File

@@ -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 ? <Blocks blocks={contentPage.blocks} /> : null}
</div>
</main>
</section>

View File

@@ -0,0 +1,8 @@
fragment HotelPageLink on HotelPage {
system {
locale
uid
}
title
url
}

View File

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

View File

@@ -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<typeof contentPageBlockTextContent>
export interface RteBlockContent extends BlockContentRaw {
content: {
content: {
json: RTEDocument
embedded_itemsConnection: EdgesWithTotalCount<Embeds>
}
}
}
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<typeof validateContentPageSchema>
type ContentPageRaw = ContentPageDataRaw["content_page"]
export type ContentPage = Omit<ContentPageRaw, "blocks" | "hero_image"> & {
heroImage?: ImageVaultAsset
blocks: Block[]
}

View File

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

View File

@@ -0,0 +1,5 @@
import { Block } from "@/server/routers/contentstack/contentPage/output"
export type BlocksProps = {
blocks: Block[]
}

View File

@@ -0,0 +1,3 @@
export enum ContentBlocksTypenameEnum {
ContentPageBlocksContent = "ContentPageBlocksContent",
}

View File

@@ -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<ContentPageRaw, "hero_image"> & {
heroImage?: ImageVaultAsset
blocks?: Block[]
}