feat(SW-186): implemented queries and typings for card inside header query
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
#import "../Fragments/Refs/System.graphql"
|
||||
|
||||
#import "../Fragments/Header/InternalOrExternalLink.graphql"
|
||||
#import "../Fragments/PageLink/ContentPageLink.graphql"
|
||||
#import "../Fragments/PageLink/LoyaltyPageLink.graphql"
|
||||
#import "../Fragments/PageLink/AccountPageLink.graphql"
|
||||
#import "../Fragments/Blocks/Card.graphql"
|
||||
|
||||
query GetHeader($locale: String!) {
|
||||
all_header(limit: 1, locale: $locale) {
|
||||
@@ -22,6 +26,13 @@ query GetHeader($locale: String!) {
|
||||
...InternalOrExternalLink
|
||||
}
|
||||
}
|
||||
cardConnection {
|
||||
edges {
|
||||
node {
|
||||
...CardBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ import { z } from "zod"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import { imageVaultAssetTransformedSchema } from "../schemas/imageVault"
|
||||
|
||||
import { Image } from "@/types/image"
|
||||
|
||||
// Help me write this zod schema based on the type ContactConfig
|
||||
@@ -262,120 +266,176 @@ const validateFooterRefConfigSchema = z.object({
|
||||
|
||||
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
|
||||
|
||||
const linkConnectionNodeSchema = z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.object({
|
||||
system: z.object({
|
||||
uid: z.string(),
|
||||
locale: z.nativeEnum(Lang),
|
||||
}),
|
||||
url: z.string(),
|
||||
title: z.string(),
|
||||
web: z.object({
|
||||
original_url: z.string().nullable().optional(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
const internalExternalLinkSchema = z.object({
|
||||
external_link: z.object({
|
||||
href: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
is_external_link: z.boolean(),
|
||||
open_in_new_tab: z.boolean(),
|
||||
page_link: z.object({
|
||||
link_title: z.string(),
|
||||
linkConnection: linkConnectionNodeSchema,
|
||||
}),
|
||||
})
|
||||
|
||||
export type InternalExternalLinkData = z.infer<
|
||||
typeof internalExternalLinkSchema
|
||||
>
|
||||
|
||||
const cardButtonSchema = z.object({
|
||||
cta_text: z.string(),
|
||||
external_link: z.object({
|
||||
href: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
is_contentstack_link: z.boolean(),
|
||||
linkConnection: linkConnectionNodeSchema,
|
||||
open_in_new_tab: z.boolean(),
|
||||
})
|
||||
|
||||
const menuItemSchema = z.object({
|
||||
title: z.string(),
|
||||
link: internalExternalLinkSchema,
|
||||
submenu: z.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
links: z.array(internalExternalLinkSchema),
|
||||
})
|
||||
),
|
||||
see_all_link: internalExternalLinkSchema,
|
||||
// cardConnection: z.array(
|
||||
// z.object({
|
||||
// node: z.object({
|
||||
// title: z.string(),
|
||||
// heading: z.string(),
|
||||
// body_text: z.string(),
|
||||
// background_image: z.any(), // TODO: Any for now, should be Image
|
||||
// has_primary_button: z.boolean(),
|
||||
// has_secondary_button: z.boolean(),
|
||||
// scripted_top_title: z.string(),
|
||||
// primary_button: cardButtonSchema,
|
||||
// secondary_button: cardButtonSchema,
|
||||
// }),
|
||||
// })
|
||||
// ),
|
||||
})
|
||||
|
||||
export const validateHeaderSchema = z.object({
|
||||
all_header: z.object({
|
||||
items: z.array(
|
||||
const linkConnectionNodeSchema = z
|
||||
.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
top_link: internalExternalLinkSchema.optional(),
|
||||
menu_items: z.array(menuItemSchema),
|
||||
node: z.object({
|
||||
system: z.object({
|
||||
uid: z.string(),
|
||||
locale: z.nativeEnum(Lang),
|
||||
}),
|
||||
url: z.string(),
|
||||
title: z.string(),
|
||||
web: z.object({
|
||||
original_url: z.string().nullable().optional(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
})
|
||||
.transform((rawData) => {
|
||||
const node = rawData.edges[0]?.node
|
||||
if (!node) {
|
||||
return null
|
||||
}
|
||||
const url = node.url
|
||||
const originalUrl = node.web?.original_url
|
||||
const lang = node.system.locale
|
||||
return { originalUrl, url: removeMultipleSlashes(`/${lang}/${url}`) }
|
||||
})
|
||||
|
||||
export type HeaderDataRaw = z.infer<typeof validateHeaderSchema>
|
||||
const internalExternalLinkSchema = z
|
||||
.object({
|
||||
external_link: z.object({
|
||||
href: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
is_external_link: z.boolean(),
|
||||
open_in_new_tab: z.boolean(),
|
||||
page_link: z.object({
|
||||
link_title: z.string(),
|
||||
linkConnection: linkConnectionNodeSchema,
|
||||
}),
|
||||
})
|
||||
.transform((rawData) => {
|
||||
if (!rawData) {
|
||||
return null
|
||||
}
|
||||
|
||||
export type InternalExternalLink = {
|
||||
href: string
|
||||
title: string
|
||||
isExternal: boolean
|
||||
openInNewTab: boolean
|
||||
}
|
||||
const linkConnectionData = rawData.page_link.linkConnection
|
||||
const isExternalLink =
|
||||
rawData.is_external_link && rawData.external_link.href
|
||||
const isOriginalLink = linkConnectionData?.originalUrl
|
||||
const externalLink = rawData.external_link
|
||||
const href =
|
||||
isExternalLink || !linkConnectionData
|
||||
? externalLink.href
|
||||
: linkConnectionData.originalUrl || linkConnectionData.url
|
||||
const title = isExternalLink
|
||||
? externalLink.title
|
||||
: rawData.page_link.link_title
|
||||
|
||||
export type SubmenuItem = {
|
||||
title: string
|
||||
links: InternalExternalLink[]
|
||||
}
|
||||
return {
|
||||
openInNewTab: rawData.open_in_new_tab,
|
||||
title,
|
||||
href,
|
||||
isExternal: !!(isExternalLink || isOriginalLink),
|
||||
}
|
||||
})
|
||||
|
||||
export type MenuItem = {
|
||||
title: string
|
||||
link: InternalExternalLink | null
|
||||
seeAllLink: InternalExternalLink | null
|
||||
submenu: SubmenuItem[]
|
||||
}
|
||||
const cardButtonSchema = z
|
||||
.object({
|
||||
cta_text: z.string(),
|
||||
external_link: z.object({
|
||||
href: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
is_contentstack_link: z.boolean(),
|
||||
linkConnection: linkConnectionNodeSchema,
|
||||
open_in_new_tab: z.boolean(),
|
||||
})
|
||||
.transform((rawData) => {
|
||||
if (!rawData) {
|
||||
return null
|
||||
}
|
||||
|
||||
export type HeaderData = Omit<
|
||||
HeaderDataRaw["all_header"]["items"][0],
|
||||
"top_link" | "menu_items"
|
||||
> & {
|
||||
topLink: InternalExternalLink | null
|
||||
menuItems: MenuItem[]
|
||||
}
|
||||
const linkConnectionData = rawData.linkConnection
|
||||
const isExternalLink = !rawData.is_contentstack_link
|
||||
const externalLink = rawData.external_link
|
||||
const href =
|
||||
isExternalLink || !linkConnectionData
|
||||
? externalLink.href
|
||||
: linkConnectionData.originalUrl || linkConnectionData.url
|
||||
const title = isExternalLink ? externalLink.title : rawData.cta_text
|
||||
|
||||
const validateHeaderRefSchema = z.object({
|
||||
return {
|
||||
openInNewTab: rawData.open_in_new_tab,
|
||||
title,
|
||||
href,
|
||||
isExternal: !!(isExternalLink || linkConnectionData?.originalUrl),
|
||||
}
|
||||
})
|
||||
|
||||
const cardSchema = z
|
||||
.object({
|
||||
heading: z.string(),
|
||||
body_text: z.string(),
|
||||
background_image: imageVaultAssetTransformedSchema,
|
||||
has_primary_button: z.boolean(),
|
||||
has_secondary_button: z.boolean(),
|
||||
scripted_top_title: z.string(),
|
||||
primary_button: cardButtonSchema,
|
||||
secondary_button: cardButtonSchema,
|
||||
})
|
||||
.transform((rawData) => {
|
||||
return {
|
||||
scriptedTopTitle: rawData.scripted_top_title,
|
||||
heading: rawData.heading,
|
||||
bodyText: rawData.body_text,
|
||||
backgroundImage: rawData.background_image,
|
||||
primaryButton: rawData.has_primary_button ? rawData.primary_button : null,
|
||||
secondaryButton: rawData.has_secondary_button
|
||||
? rawData.secondary_button
|
||||
: null,
|
||||
}
|
||||
})
|
||||
|
||||
const menuItemSchema = z
|
||||
.object({
|
||||
title: z.string(),
|
||||
link: internalExternalLinkSchema,
|
||||
submenu: z.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
links: z.array(internalExternalLinkSchema),
|
||||
})
|
||||
),
|
||||
see_all_link: internalExternalLinkSchema,
|
||||
cardConnection: z.object({
|
||||
edges: z.array(z.object({ node: cardSchema })),
|
||||
}),
|
||||
})
|
||||
.transform((rawData) => {
|
||||
return {
|
||||
link: rawData.submenu.length ? null : rawData.link,
|
||||
seeAllLink: rawData.submenu.length ? rawData.see_all_link : null,
|
||||
submenu: rawData.submenu,
|
||||
card: rawData.cardConnection.edges[0]?.node,
|
||||
}
|
||||
})
|
||||
|
||||
export const getHeaderSchema = z
|
||||
.object({
|
||||
all_header: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
top_link: internalExternalLinkSchema.optional(),
|
||||
menu_items: z.array(menuItemSchema),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
.transform((rawData) => {
|
||||
const { top_link, menu_items } = rawData.all_header.items[0]
|
||||
|
||||
return {
|
||||
topLink: top_link,
|
||||
menuItems: menu_items,
|
||||
}
|
||||
})
|
||||
|
||||
export const getHeaderRefSchema = z.object({
|
||||
all_header: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
@@ -387,5 +447,3 @@ const validateHeaderRefSchema = z.object({
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
export type HeaderRefDataRaw = z.infer<typeof validateHeaderRefSchema>
|
||||
|
||||
@@ -24,18 +24,13 @@ import {
|
||||
CurrentHeaderRefDataRaw,
|
||||
FooterDataRaw,
|
||||
FooterRefDataRaw,
|
||||
HeaderData,
|
||||
HeaderDataRaw,
|
||||
HeaderRefDataRaw,
|
||||
InternalExternalLink,
|
||||
MenuItem,
|
||||
SubmenuItem,
|
||||
getHeaderSchema,
|
||||
validateContactConfigSchema,
|
||||
validateCurrentHeaderConfigSchema,
|
||||
validateFooterConfigSchema,
|
||||
validateHeaderSchema,
|
||||
} from "./output"
|
||||
import { makeLinkObjectFromInternalExternalLink } from "./utils"
|
||||
|
||||
import { HeaderRefResponse, HeaderResponse } from "@/types/header"
|
||||
|
||||
const meter = metrics.getMeter("trpc.contentstack.base")
|
||||
// OpenTelemetry metrics: ContactConfig
|
||||
@@ -176,7 +171,7 @@ export const baseQueryRouter = router({
|
||||
)
|
||||
|
||||
// TODO: Add better ref types and error handling for responseRef
|
||||
const responseRef = await request<HeaderRefDataRaw>(GetHeaderRef, {
|
||||
const responseRef = await request<HeaderRefResponse>(GetHeaderRef, {
|
||||
locale: lang,
|
||||
})
|
||||
|
||||
@@ -186,7 +181,7 @@ export const baseQueryRouter = router({
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
const response = await request<HeaderDataRaw>(
|
||||
const response = await request<HeaderResponse>(
|
||||
GetHeader,
|
||||
{ locale: lang },
|
||||
{
|
||||
@@ -213,7 +208,7 @@ export const baseQueryRouter = router({
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedHeaderConfig = validateHeaderSchema.safeParse(response.data)
|
||||
const validatedHeaderConfig = getHeaderSchema.safeParse(response.data)
|
||||
|
||||
if (!validatedHeaderConfig.success) {
|
||||
getHeaderFailCounter.add(1, {
|
||||
@@ -236,42 +231,7 @@ export const baseQueryRouter = router({
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
const data = validatedHeaderConfig.data.all_header.items[0]
|
||||
const topLink = makeLinkObjectFromInternalExternalLink(data.top_link)
|
||||
const menuItems: MenuItem[] = data.menu_items.map((menuItem) => {
|
||||
let link = null
|
||||
let seeAllLink = null
|
||||
let submenu: SubmenuItem[] = []
|
||||
|
||||
if (!menuItem.submenu.length) {
|
||||
link = !menuItem.submenu.length
|
||||
? makeLinkObjectFromInternalExternalLink(menuItem.link)
|
||||
: null
|
||||
seeAllLink = makeLinkObjectFromInternalExternalLink(
|
||||
menuItem.see_all_link
|
||||
)
|
||||
} else {
|
||||
submenu = menuItem.submenu.map(({ title, links }) => ({
|
||||
title: title,
|
||||
links: links.map(
|
||||
(link) =>
|
||||
makeLinkObjectFromInternalExternalLink(
|
||||
link
|
||||
) as InternalExternalLink
|
||||
),
|
||||
}))
|
||||
}
|
||||
return {
|
||||
title: menuItem.title,
|
||||
link,
|
||||
seeAllLink,
|
||||
submenu,
|
||||
}
|
||||
})
|
||||
|
||||
const headerData: HeaderData = { topLink, menuItems }
|
||||
|
||||
return headerData
|
||||
return validatedHeaderConfig.data
|
||||
}),
|
||||
currentHeader: contentstackBaseProcedure
|
||||
.input(langInput)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import { InternalExternalLink, InternalExternalLinkData } from "./output"
|
||||
|
||||
export function makeLinkObjectFromInternalExternalLink(
|
||||
data: InternalExternalLinkData | undefined
|
||||
): InternalExternalLink | null {
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
|
||||
const linkConnectionNode =
|
||||
data.page_link.linkConnection.edges[0]?.node || null
|
||||
const isExternalLink = data.is_external_link && data.external_link.href
|
||||
const isOriginalLink = linkConnectionNode?.web?.original_url
|
||||
const externalLink = data.external_link
|
||||
const href =
|
||||
isExternalLink || !linkConnectionNode
|
||||
? externalLink.href
|
||||
: linkConnectionNode.web?.original_url ||
|
||||
removeMultipleSlashes(
|
||||
`/${linkConnectionNode.system.locale}/${linkConnectionNode.url}`
|
||||
)
|
||||
const title = isExternalLink ? externalLink.title : data.page_link.link_title
|
||||
|
||||
return {
|
||||
openInNewTab: data.open_in_new_tab,
|
||||
title,
|
||||
href,
|
||||
isExternal: !!(isExternalLink || isOriginalLink),
|
||||
}
|
||||
}
|
||||
@@ -93,3 +93,30 @@ export const imageVaultAssetSchema = z.object({
|
||||
*/
|
||||
AddedBy: z.string(),
|
||||
})
|
||||
|
||||
export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform(
|
||||
(rawData) => {
|
||||
const alt = rawData.Metadata?.find((meta) =>
|
||||
meta.Name.includes("AltText_")
|
||||
)?.Value
|
||||
|
||||
const caption = rawData.Metadata?.find((meta) =>
|
||||
meta.Name.includes("Title_")
|
||||
)?.Value
|
||||
|
||||
return {
|
||||
url: rawData.MediaConversions[0].Url,
|
||||
id: rawData.Id,
|
||||
meta: {
|
||||
alt,
|
||||
caption,
|
||||
},
|
||||
title: rawData.Name,
|
||||
dimensions: {
|
||||
width: rawData.MediaConversions[0].Width,
|
||||
height: rawData.MediaConversions[0].Height,
|
||||
aspectRatio: rawData.MediaConversions[0].FormatAspectRatio,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
0
types/components/header/header.ts
Normal file
0
types/components/header/header.ts
Normal file
10
types/header.ts
Normal file
10
types/header.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
getHeaderRefSchema,
|
||||
getHeaderSchema,
|
||||
} from "@/server/routers/contentstack/base/output"
|
||||
|
||||
export type HeaderRefResponse = z.input<typeof getHeaderRefSchema>
|
||||
export type HeaderResponse = z.input<typeof getHeaderSchema>
|
||||
export type Header = z.output<typeof getHeaderSchema>
|
||||
Reference in New Issue
Block a user