diff --git a/components/Loyalty/Sidebar/index.tsx b/components/Loyalty/Sidebar/index.tsx
index f6128fcbe..85afa27c4 100644
--- a/components/Loyalty/Sidebar/index.tsx
+++ b/components/Loyalty/Sidebar/index.tsx
@@ -2,15 +2,16 @@ import JsonToHtml from "@/components/JsonToHtml"
import JoinLoyaltyContact from "./JoinLoyalty"
-import { Sidebar, SidebarTypenameEnum } from "@/types/requests/loyaltyPage"
+import { SidebarProps } from "@/types/components/loyalty/sidebar"
+import { SidebarTypenameEnum } from "@/types/requests/loyaltyPage"
-export default function SidebarLoyalty({ block }: { block: Sidebar }) {
+export default function SidebarLoyalty({ block }: SidebarProps) {
switch (block.__typename) {
case SidebarTypenameEnum.LoyaltyPageSidebarContent:
return (
)
case SidebarTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContact:
diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql
index 1efefc2c9..743df0539 100644
--- a/lib/graphql/Query/LoyaltyPage.graphql
+++ b/lib/graphql/Query/LoyaltyPage.graphql
@@ -22,6 +22,7 @@ query GetLoyaltyPage($locale: String!, $url: String!) {
...LoyaltyPageLink
}
}
+ totalCount
}
}
}
@@ -41,9 +42,11 @@ query GetLoyaltyPage($locale: String!, $url: String!) {
...AccountPageLink
}
}
+ totalCount
}
title
subtitle
+ open_in_new_tab
}
}
}
@@ -55,16 +58,11 @@ query GetLoyaltyPage($locale: String!, $url: String!) {
embedded_itemsConnection {
edges {
node {
- ... on SysAsset {
- title
- url
- dimension {
- width
- height
- }
- }
+ __typename
+ ...Image
}
}
+ totalCount
}
}
}
@@ -86,7 +84,6 @@ query GetLoyaltyPage($locale: String!, $url: String!) {
}
}
}
- login_button_text
}
}
... on LoyaltyPageSidebarContent {
@@ -101,6 +98,7 @@ query GetLoyaltyPage($locale: String!, $url: String!) {
...Image
}
}
+ totalCount
}
}
}
diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts
new file mode 100644
index 000000000..73a74c072
--- /dev/null
+++ b/server/routers/contentstack/loyaltyPage/output.ts
@@ -0,0 +1,208 @@
+import { z } from "zod"
+
+import { Embeds } from "@/types/requests/embeds"
+import {
+ JoinLoyaltyContactTypenameEnum,
+ LoyaltyBlocksTypenameEnum,
+ LoyaltyComponentEnum,
+ SidebarTypenameEnum,
+} from "@/types/requests/loyaltyPage"
+import { Edges } 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().optional(),
+ subtitle: z.string().optional(),
+ cards: z.array(
+ z.object({
+ title: z.string().optional(),
+ subtitle: z.string().optional(),
+ referenceConnection: z.object({
+ edges: z.array(
+ z.object({
+ node: z.object({
+ system: z.object({
+ uid: z.string(),
+ }),
+ url: z.string(),
+ title: z.string(),
+ __typename: z.string(),
+ }),
+ })
+ ),
+ totalCount: z.number(),
+ }),
+ open_in_new_tab: z.boolean(),
+ })
+ ),
+ }),
+})
+const loyaltyPageDynamicContent = z.object({
+ __typename: z.literal(
+ LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent
+ ),
+ dynamic_content: z.object({
+ title: z.string().optional(),
+ subtitle: z.string().optional(),
+ component: z.nativeEnum(LoyaltyComponentEnum),
+ link: z.object({
+ text: z.string().optional(),
+ pageConnection: z.object({
+ edges: z.array(
+ z.object({
+ node: z.object({
+ system: z.object({
+ uid: z.string(),
+ }),
+ url: z.string(),
+ title: z.string(),
+ }),
+ })
+ ),
+ totalCount: z.number(),
+ }),
+ }),
+ }),
+})
+
+const loyaltyPageBlockTextContent = z.object({
+ __typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent),
+ content: z.object({
+ content: z.object({
+ embedded_itemsConnection: z.object({
+ edges: z.array(z.any()),
+ totalCount: z.number(),
+ }),
+ json: z.any(),
+ }),
+ }),
+})
+
+const loyaltyPageBlockItem = z.discriminatedUnion("__typename", [
+ loyaltyPageBlockCardGrid,
+ loyaltyPageDynamicContent,
+ loyaltyPageBlockTextContent,
+])
+
+const loyaltyPageSidebarTextContent = z.object({
+ __typename: z.literal(SidebarTypenameEnum.LoyaltyPageSidebarContent),
+ content: z.object({
+ content: z.object({
+ embedded_itemsConnection: z.object({
+ edges: z.array(z.any()),
+ totalCount: z.number(),
+ }),
+ json: z.any(),
+ }),
+ }),
+})
+
+const loyaltyPageJoinLoyaltyContact = z.object({
+ __typename: z.literal(
+ SidebarTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContact
+ ),
+ join_loyalty_contact: z.object({
+ title: z.string().optional(),
+ preamble: z.string().optional(),
+ contact: z.array(
+ z.object({
+ __typename: z.literal(
+ JoinLoyaltyContactTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact
+ ),
+ contact: z.object({
+ display_text: z.string().optional(),
+
+ contact_field: z.string(),
+ }),
+ })
+ ),
+ }),
+})
+
+const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [
+ loyaltyPageSidebarTextContent,
+ loyaltyPageJoinLoyaltyContact,
+])
+
+export const validateLoyaltyPageSchema = z.object({
+ all_loyalty_page: z.object({
+ items: z.array(
+ z.object({
+ title: z.string(),
+ blocks: z.array(loyaltyPageBlockItem),
+ sidebar: z.array(loyaltyPageSidebarItem),
+ })
+ ),
+ }),
+})
+
+// Block types
+type CardGridRaw = z.infer
+
+export type CardGridCard = Omit<
+ CardGridRaw["card_grid"]["cards"][number],
+ "referenceConnection"
+> & {
+ link:
+ | {
+ href: string
+ title: string
+ }
+ | undefined
+}
+
+export type CardGrid = Omit & {
+ card_grid: Omit & {
+ cards: CardGridCard[]
+ }
+}
+
+type DynamicContentRaw = z.infer
+
+export type DynamicContent = Omit & {
+ dynamic_content: Omit & {
+ link:
+ | {
+ href: string
+ title: string
+ text?: string
+ }
+ | undefined
+ }
+}
+type BlockContentRaw = z.infer
+
+export interface RteBlockContent extends BlockContentRaw {
+ content: {
+ content: {
+ json: RTEDocument
+ embedded_itemsConnection: Edges
+ }
+ }
+}
+export type Block = CardGrid | RteBlockContent | DynamicContent
+
+// Sidebar block types
+type SidebarContentRaw = z.infer
+
+export type RteSidebarContent = Omit & {
+ content: {
+ content: {
+ json: RTEDocument
+ embedded_itemsConnection: Edges
+ }
+ }
+}
+export type JoinLoyaltyContact = z.infer
+export type Sidebar = JoinLoyaltyContact | RteSidebarContent
+
+type LoyaltyPageDataRaw = z.infer
+
+type LoyaltyPageRaw = LoyaltyPageDataRaw["all_loyalty_page"]["items"][0]
+
+export type LoyaltyPage = Omit & {
+ blocks: Block[]
+ sidebar: Sidebar[]
+}
diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts
index 3d22cf6a3..0169f7b41 100644
--- a/server/routers/contentstack/loyaltyPage/query.ts
+++ b/server/routers/contentstack/loyaltyPage/query.ts
@@ -1,33 +1,132 @@
-import { z } from "zod"
-
+import GetLoyaltyPage from "@/lib/graphql/Query/LoyaltyPage.graphql"
+import { request } from "@/lib/graphql/request"
import { badRequestError } from "@/server/errors/trpc"
import { publicProcedure, router } from "@/server/trpc"
-import { request } from "@/lib/graphql/request"
-import { Lang } from "@/constants/languages"
-import GetLoyaltyPage from "@/lib/graphql/Query/LoyaltyPage.graphql"
+import { getLoyaltyPageInput } from "./input"
+import { type LoyaltyPage, validateLoyaltyPageSchema } from "./output"
-import type { GetLoyaltyPageData } from "@/types/requests/loyaltyPage"
+import { Embeds } from "@/types/requests/embeds"
+import {
+ LoyaltyBlocksTypenameEnum,
+ SidebarTypenameEnum,
+} from "@/types/requests/loyaltyPage"
+import { Edges } from "@/types/requests/utils/edges"
+import { RTEDocument } from "@/types/rte/node"
export const loyaltyPageQueryRouter = router({
- get: publicProcedure
- .input(z.object({ uri: z.string(), lang: z.nativeEnum(Lang) }))
- .query(async ({ input }) => {
- const loyaltyPage = await request(
- GetLoyaltyPage,
- {
- locale: input.lang,
- url: input.uri,
- },
- {
- tags: [`${input.uri}-${input.lang}`],
- }
- )
+ get: publicProcedure.input(getLoyaltyPageInput).query(async ({ input }) => {
+ try {
+ const loyaltyPageRes = await request(GetLoyaltyPage, {
+ locale: input.locale,
+ url: input.href,
+ })
- if (loyaltyPage.data && loyaltyPage.data.all_loyalty_page.items.length) {
- return loyaltyPage.data.all_loyalty_page.items[0]
+ if (!loyaltyPageRes.data) {
+ throw badRequestError()
}
+ const validatedLoyaltyPage = validateLoyaltyPageSchema.safeParse(
+ loyaltyPageRes.data
+ )
+
+ if (!validatedLoyaltyPage.success) {
+ throw badRequestError()
+ }
+
+ const sidebar =
+ validatedLoyaltyPage.data.all_loyalty_page.items[0].sidebar.map(
+ (block) => {
+ if (
+ block.__typename == SidebarTypenameEnum.LoyaltyPageSidebarContent
+ ) {
+ return {
+ ...block,
+ content: {
+ content: {
+ json: block.content.content.json as RTEDocument,
+ embedded_itemsConnection: block.content.content
+ .embedded_itemsConnection as Edges,
+ },
+ },
+ }
+ } else {
+ return block
+ }
+ }
+ )
+
+ const blocks =
+ validatedLoyaltyPage.data.all_loyalty_page.items[0].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 > 0
+ ? {
+ href: card.referenceConnection.edges[0].node
+ .url,
+ title:
+ card.referenceConnection.edges[0].node.title,
+ }
+ : undefined,
+ }
+ }),
+ },
+ }
+ case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent:
+ return {
+ ...block,
+ dynamic_content: {
+ ...block.dynamic_content,
+ link:
+ block.dynamic_content.link.pageConnection.totalCount > 0
+ ? {
+ text: block.dynamic_content.link.text,
+ href: block.dynamic_content.link.pageConnection
+ .edges[0].node.url,
+ title:
+ block.dynamic_content.link.pageConnection.edges[0]
+ .node.title,
+ }
+ : undefined,
+ },
+ }
+ case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent:
+ return {
+ ...block,
+ content: {
+ content: {
+ json: block.content.content.json as RTEDocument,
+ embedded_itemsConnection: block.content.content
+ .embedded_itemsConnection as Edges,
+ },
+ },
+ }
+ default:
+ return block
+ }
+ }
+ )
+
+ const loyaltyPage = {
+ ...validatedLoyaltyPage.data.all_loyalty_page.items[0],
+ blocks,
+ sidebar,
+ } as LoyaltyPage
+
+ return loyaltyPage
+ } catch (error) {
+ console.info(`Get Loyalty Page Error`)
+ console.error(error)
throw badRequestError()
- }),
+ }
+ }),
})
diff --git a/types/components/jsontohtml.ts b/types/components/jsontohtml.ts
index 4f319e6ed..c12ef513f 100644
--- a/types/components/jsontohtml.ts
+++ b/types/components/jsontohtml.ts
@@ -1,8 +1,7 @@
-import type { RTENode } from "../rte/node"
-
-import type { Node } from "@/types/requests/utils/edges"
-import type { RenderOptions } from "../rte/option"
import type { Embeds } from "@/types/requests/embeds"
+import type { Node } from "@/types/requests/utils/edges"
+import type { RTENode } from "../rte/node"
+import type { RenderOptions } from "../rte/option"
export type JsonToHtmlProps = {
embeds: Node[]
diff --git a/types/components/loyalty/blocks.ts b/types/components/loyalty/blocks.ts
index 4efdc7f43..b3fc03c85 100644
--- a/types/components/loyalty/blocks.ts
+++ b/types/components/loyalty/blocks.ts
@@ -1,38 +1,28 @@
-import { Embeds } from "@/types/requests/embeds"
-import { DynamicContentBlock } from "@/types/requests/loyaltyPage"
-import { PageLink } from "@/types/requests/myPages/navigation"
-import { Edges } from "@/types/requests/utils/edges"
-import { RTEDocument } from "@/types/rte/node"
+import {
+ Block,
+ CardGrid,
+ CardGridCard,
+ DynamicContent,
+ RteBlockContent,
+} from "@/server/routers/contentstack/loyaltyPage/output"
+
+export type BlocksProps = {
+ blocks: Block[]
+}
export type DynamicContentProps = {
- dynamicContent: DynamicContentBlock["dynamic_content"]
+ dynamicContent: DynamicContent["dynamic_content"]
}
-type Card = {
- referenceConnection: Edges
- title?: string
- subtitle?: string
- open_in_new_tab: boolean
+export type DynamicComponentProps = {
+ component: DynamicContent["dynamic_content"]["component"]
}
-export type CardProps = { card: Card }
+export type CardProps = { card: CardGridCard }
-export type CardGrid = {
- card_grid: {
- title?: string
- subtitle?: string
- cards: Card[]
- }
-}
+export type CardGridProps = Pick
-export type CardGridProps = CardGrid
-
-export type Content = {
- content: {
- embedded_itemsConnection: Edges
- json: RTEDocument
- }
-}
+export type Content = { content: RteBlockContent["content"]["content"] }
export type LevelCardProps = {
level: {
diff --git a/types/components/loyalty/sidebar.ts b/types/components/loyalty/sidebar.ts
index 2dcb38cbe..e1bdb1098 100644
--- a/types/components/loyalty/sidebar.ts
+++ b/types/components/loyalty/sidebar.ts
@@ -1,20 +1,16 @@
-import { ContactFields } from "@/types/requests/contactConfig"
-import { Embeds } from "@/types/requests/embeds"
-import { JoinLoyaltyContactContact } from "@/types/requests/loyaltyPage"
-import { Edges } from "@/types/requests/utils/edges"
-import { RTEDocument } from "@/types/rte/node"
+import {
+ JoinLoyaltyContact,
+ Sidebar,
+} from "@/server/routers/contentstack/loyaltyPage/output"
-export type SidebarContent = {
- content: {
- embedded_itemsConnection: Edges
- json: RTEDocument
- }
+export type SidebarProps = {
+ block: Sidebar
}
-export type Contact = {
- contact: ContactFields
+export type JoinLoyaltyContactProps = {
+ block: JoinLoyaltyContact["join_loyalty_contact"]
}
export type ContactProps = {
- contactBlock: JoinLoyaltyContactContact[]
+ contactBlock: JoinLoyaltyContact["join_loyalty_contact"]["contact"]
}
diff --git a/types/image.ts b/types/image.ts
index 8cbed40b1..5c442c4df 100644
--- a/types/image.ts
+++ b/types/image.ts
@@ -4,7 +4,7 @@ export type Image = {
height: number
width: number
}
- metadata: JSON
+ metadata: JSON | null
system: {
uid: string
}
diff --git a/types/requests/contactConfig.ts b/types/requests/contactConfig.ts
index 5995d432a..d68090d16 100644
--- a/types/requests/contactConfig.ts
+++ b/types/requests/contactConfig.ts
@@ -47,28 +47,7 @@ export type GetContactConfigData = {
all_contact_config: AllRequestResponse
}
-// Utility types that extract the possible strings of ContactConfigField,
-// Which is all the dot notated values of ContactConfig (for example: 'email.name')
-// From: https://stackoverflow.com/questions/47057649/typescript-string-dot-notation-of-nested-object#47058976
-type PathsToStringProps = T extends string
- ? []
- : {
- [K in Extract]: [K, ...PathsToStringProps]
- }[Extract]
-
-type Join = T extends []
- ? never
- : T extends [infer F]
- ? F
- : T extends [infer F, ...infer R]
- ? F extends string
- ? `${F}${D}${Join, D>}`
- : never
- : string
-
-export type ContactConfigField = Join, ".">
-
export type ContactFields = {
display_text?: string
- contact_field: ContactConfigField
+ contact_field: string
}
diff --git a/types/requests/loyaltyPage.ts b/types/requests/loyaltyPage.ts
index b97669164..2a9016a2c 100644
--- a/types/requests/loyaltyPage.ts
+++ b/types/requests/loyaltyPage.ts
@@ -1,9 +1,4 @@
-import { CardGrid, Content } from "../components/loyalty/blocks"
-import { Contact, SidebarContent } from "../components/loyalty/sidebar"
-import { PageLink } from "./myPages/navigation"
-import { Edges } from "./utils/edges"
-
-import type { AllRequestResponse } from "./utils/all"
+import type { JoinLoyaltyContact } from "@/server/routers/contentstack/loyaltyPage/output"
import type { Typename } from "./utils/typename"
export enum JoinLoyaltyContactTypenameEnum {
@@ -11,18 +6,10 @@ export enum JoinLoyaltyContactTypenameEnum {
}
export type JoinLoyaltyContactContact = Typename<
- Contact,
+ JoinLoyaltyContact["join_loyalty_contact"],
JoinLoyaltyContactTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact
>
-export type JoinLoyaltyContact = {
- join_loyalty_contact: {
- title?: string
- preamble?: string
- contact: JoinLoyaltyContactContact[]
- }
-}
-
export enum SidebarTypenameEnum {
LoyaltyPageSidebarJoinLoyaltyContact = "LoyaltyPageSidebarJoinLoyaltyContact",
LoyaltyPageSidebarContent = "LoyaltyPageSidebarContent",
@@ -30,13 +17,6 @@ export enum SidebarTypenameEnum {
export type SidebarTypename = keyof typeof SidebarTypenameEnum
-export type Sidebar =
- | Typename
- | Typename<
- JoinLoyaltyContact,
- SidebarTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContact
- >
-
export enum LoyaltyComponentEnum {
loyalty_levels = "loyalty_levels",
how_it_works = "how_it_works",
@@ -45,57 +25,8 @@ export enum LoyaltyComponentEnum {
export type LoyaltyComponent = keyof typeof LoyaltyComponentEnum
-export type DynamicContentBlock = {
- dynamic_content: {
- title?: string
- subtitle?: string
- component: LoyaltyComponent
- link: {
- text?: string
- pageConnection: Edges
- }
- }
-}
-
export enum LoyaltyBlocksTypenameEnum {
LoyaltyPageBlocksDynamicContent = "LoyaltyPageBlocksDynamicContent",
LoyaltyPageBlocksCardGrid = "LoyaltyPageBlocksCardGrid",
LoyaltyPageBlocksContent = "LoyaltyPageBlocksContent",
}
-
-export type Blocks =
- | Typename
- | Typename<
- DynamicContentBlock,
- LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent
- >
- | Typename
-
-export type Breadcrumb = {
- href: string
- title: string
-}
-
-export type Breadcrumbs = {
- parents: Breadcrumb[]
- title: string
-}
-
-export type LoyaltyPage = {
- sidebar: Sidebar[]
- blocks: Blocks[]
- web: {
- breadcrumbs: Breadcrumbs
- }
- system: {
- created_at: string
- uid: string
- updated_at: string
- }
- title: string
- url: string
-}
-
-export type GetLoyaltyPageData = {
- all_loyalty_page: AllRequestResponse
-}
diff --git a/types/rte/node.ts b/types/rte/node.ts
index 25e9080bd..c762f33bb 100644
--- a/types/rte/node.ts
+++ b/types/rte/node.ts
@@ -1,11 +1,12 @@
import { RTETypeEnum } from "./enums"
+
+import type { EmbedByUid } from "../components/jsontohtml"
import type {
Attributes,
RTEAnchorAttrs,
RTEAssetAttrs,
RTELinkAttrs,
} from "./attrs"
-import type { EmbedByUid } from "../components/jsontohtml"
import type { RenderOptions } from "./option"
export interface RTEDefaultNode {
diff --git a/utils/contactConfig.ts b/utils/contactConfig.ts
index 83a24abf8..3eed6da76 100644
--- a/utils/contactConfig.ts
+++ b/utils/contactConfig.ts
@@ -1,18 +1,19 @@
import {
ContactConfig,
- ContactConfigField,
ContactFieldGroups,
} from "@/types/requests/contactConfig"
export function getValueFromContactConfig(
- keyStrings: ContactConfigField,
+ keyString: string,
data: ContactConfig
): string | undefined {
- const [groupName, key] = keyStrings.split(".") as [
+ const [groupName, key] = keyString.split(".") as [
ContactFieldGroups,
keyof ContactConfig[ContactFieldGroups],
]
- const fieldGroup = data[groupName]
+ if (data[groupName]) {
+ const fieldGroup = data[groupName]
- return fieldGroup[key]
+ return fieldGroup[key]
+ }
}