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/Refs/System.graphql"
|
||||||
|
|
||||||
#import "../Fragments/Header/InternalOrExternalLink.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!) {
|
query GetHeader($locale: String!) {
|
||||||
all_header(limit: 1, locale: $locale) {
|
all_header(limit: 1, locale: $locale) {
|
||||||
@@ -22,6 +26,13 @@ query GetHeader($locale: String!) {
|
|||||||
...InternalOrExternalLink
|
...InternalOrExternalLink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cardConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...CardBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
|
import { removeMultipleSlashes } from "@/utils/url"
|
||||||
|
|
||||||
|
import { imageVaultAssetTransformedSchema } from "../schemas/imageVault"
|
||||||
|
|
||||||
import { Image } from "@/types/image"
|
import { Image } from "@/types/image"
|
||||||
|
|
||||||
// Help me write this zod schema based on the type ContactConfig
|
// Help me write this zod schema based on the type ContactConfig
|
||||||
@@ -262,7 +266,8 @@ const validateFooterRefConfigSchema = z.object({
|
|||||||
|
|
||||||
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
|
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
|
||||||
|
|
||||||
const linkConnectionNodeSchema = z.object({
|
const linkConnectionNodeSchema = z
|
||||||
|
.object({
|
||||||
edges: z.array(
|
edges: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
node: z.object({
|
node: z.object({
|
||||||
@@ -279,8 +284,19 @@ const linkConnectionNodeSchema = z.object({
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
.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}`) }
|
||||||
|
})
|
||||||
|
|
||||||
const internalExternalLinkSchema = z.object({
|
const internalExternalLinkSchema = z
|
||||||
|
.object({
|
||||||
external_link: z.object({
|
external_link: z.object({
|
||||||
href: z.string(),
|
href: z.string(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
@@ -292,12 +308,34 @@ const internalExternalLinkSchema = z.object({
|
|||||||
linkConnection: linkConnectionNodeSchema,
|
linkConnection: linkConnectionNodeSchema,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
.transform((rawData) => {
|
||||||
|
if (!rawData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export type InternalExternalLinkData = z.infer<
|
const linkConnectionData = rawData.page_link.linkConnection
|
||||||
typeof internalExternalLinkSchema
|
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
|
||||||
|
|
||||||
const cardButtonSchema = z.object({
|
return {
|
||||||
|
openInNewTab: rawData.open_in_new_tab,
|
||||||
|
title,
|
||||||
|
href,
|
||||||
|
isExternal: !!(isExternalLink || isOriginalLink),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const cardButtonSchema = z
|
||||||
|
.object({
|
||||||
cta_text: z.string(),
|
cta_text: z.string(),
|
||||||
external_link: z.object({
|
external_link: z.object({
|
||||||
href: z.string(),
|
href: z.string(),
|
||||||
@@ -307,8 +345,54 @@ const cardButtonSchema = z.object({
|
|||||||
linkConnection: linkConnectionNodeSchema,
|
linkConnection: linkConnectionNodeSchema,
|
||||||
open_in_new_tab: z.boolean(),
|
open_in_new_tab: z.boolean(),
|
||||||
})
|
})
|
||||||
|
.transform((rawData) => {
|
||||||
|
if (!rawData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const menuItemSchema = z.object({
|
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
|
||||||
|
|
||||||
|
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(),
|
title: z.string(),
|
||||||
link: internalExternalLinkSchema,
|
link: internalExternalLinkSchema,
|
||||||
submenu: z.array(
|
submenu: z.array(
|
||||||
@@ -318,24 +402,21 @@ const menuItemSchema = z.object({
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
see_all_link: internalExternalLinkSchema,
|
see_all_link: internalExternalLinkSchema,
|
||||||
// cardConnection: z.array(
|
cardConnection: z.object({
|
||||||
// z.object({
|
edges: z.array(z.object({ node: cardSchema })),
|
||||||
// node: z.object({
|
}),
|
||||||
// title: z.string(),
|
})
|
||||||
// heading: z.string(),
|
.transform((rawData) => {
|
||||||
// body_text: z.string(),
|
return {
|
||||||
// background_image: z.any(), // TODO: Any for now, should be Image
|
link: rawData.submenu.length ? null : rawData.link,
|
||||||
// has_primary_button: z.boolean(),
|
seeAllLink: rawData.submenu.length ? rawData.see_all_link : null,
|
||||||
// has_secondary_button: z.boolean(),
|
submenu: rawData.submenu,
|
||||||
// scripted_top_title: z.string(),
|
card: rawData.cardConnection.edges[0]?.node,
|
||||||
// primary_button: cardButtonSchema,
|
}
|
||||||
// secondary_button: cardButtonSchema,
|
|
||||||
// }),
|
|
||||||
// })
|
|
||||||
// ),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const validateHeaderSchema = z.object({
|
export const getHeaderSchema = z
|
||||||
|
.object({
|
||||||
all_header: z.object({
|
all_header: z.object({
|
||||||
items: z.array(
|
items: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -345,37 +426,16 @@ export const validateHeaderSchema = z.object({
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
.transform((rawData) => {
|
||||||
|
const { top_link, menu_items } = rawData.all_header.items[0]
|
||||||
|
|
||||||
export type HeaderDataRaw = z.infer<typeof validateHeaderSchema>
|
return {
|
||||||
|
topLink: top_link,
|
||||||
export type InternalExternalLink = {
|
menuItems: menu_items,
|
||||||
href: string
|
|
||||||
title: string
|
|
||||||
isExternal: boolean
|
|
||||||
openInNewTab: boolean
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export type SubmenuItem = {
|
export const getHeaderRefSchema = z.object({
|
||||||
title: string
|
|
||||||
links: InternalExternalLink[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MenuItem = {
|
|
||||||
title: string
|
|
||||||
link: InternalExternalLink | null
|
|
||||||
seeAllLink: InternalExternalLink | null
|
|
||||||
submenu: SubmenuItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HeaderData = Omit<
|
|
||||||
HeaderDataRaw["all_header"]["items"][0],
|
|
||||||
"top_link" | "menu_items"
|
|
||||||
> & {
|
|
||||||
topLink: InternalExternalLink | null
|
|
||||||
menuItems: MenuItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateHeaderRefSchema = z.object({
|
|
||||||
all_header: z.object({
|
all_header: z.object({
|
||||||
items: z.array(
|
items: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -387,5 +447,3 @@ const validateHeaderRefSchema = z.object({
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type HeaderRefDataRaw = z.infer<typeof validateHeaderRefSchema>
|
|
||||||
|
|||||||
@@ -24,18 +24,13 @@ import {
|
|||||||
CurrentHeaderRefDataRaw,
|
CurrentHeaderRefDataRaw,
|
||||||
FooterDataRaw,
|
FooterDataRaw,
|
||||||
FooterRefDataRaw,
|
FooterRefDataRaw,
|
||||||
HeaderData,
|
getHeaderSchema,
|
||||||
HeaderDataRaw,
|
|
||||||
HeaderRefDataRaw,
|
|
||||||
InternalExternalLink,
|
|
||||||
MenuItem,
|
|
||||||
SubmenuItem,
|
|
||||||
validateContactConfigSchema,
|
validateContactConfigSchema,
|
||||||
validateCurrentHeaderConfigSchema,
|
validateCurrentHeaderConfigSchema,
|
||||||
validateFooterConfigSchema,
|
validateFooterConfigSchema,
|
||||||
validateHeaderSchema,
|
|
||||||
} from "./output"
|
} from "./output"
|
||||||
import { makeLinkObjectFromInternalExternalLink } from "./utils"
|
|
||||||
|
import { HeaderRefResponse, HeaderResponse } from "@/types/header"
|
||||||
|
|
||||||
const meter = metrics.getMeter("trpc.contentstack.base")
|
const meter = metrics.getMeter("trpc.contentstack.base")
|
||||||
// OpenTelemetry metrics: ContactConfig
|
// OpenTelemetry metrics: ContactConfig
|
||||||
@@ -176,7 +171,7 @@ export const baseQueryRouter = router({
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Add better ref types and error handling for responseRef
|
// TODO: Add better ref types and error handling for responseRef
|
||||||
const responseRef = await request<HeaderRefDataRaw>(GetHeaderRef, {
|
const responseRef = await request<HeaderRefResponse>(GetHeaderRef, {
|
||||||
locale: lang,
|
locale: lang,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -186,7 +181,7 @@ export const baseQueryRouter = router({
|
|||||||
JSON.stringify({ query: { lang } })
|
JSON.stringify({ query: { lang } })
|
||||||
)
|
)
|
||||||
|
|
||||||
const response = await request<HeaderDataRaw>(
|
const response = await request<HeaderResponse>(
|
||||||
GetHeader,
|
GetHeader,
|
||||||
{ locale: lang },
|
{ locale: lang },
|
||||||
{
|
{
|
||||||
@@ -213,7 +208,7 @@ export const baseQueryRouter = router({
|
|||||||
throw notFoundError
|
throw notFoundError
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedHeaderConfig = validateHeaderSchema.safeParse(response.data)
|
const validatedHeaderConfig = getHeaderSchema.safeParse(response.data)
|
||||||
|
|
||||||
if (!validatedHeaderConfig.success) {
|
if (!validatedHeaderConfig.success) {
|
||||||
getHeaderFailCounter.add(1, {
|
getHeaderFailCounter.add(1, {
|
||||||
@@ -236,42 +231,7 @@ export const baseQueryRouter = router({
|
|||||||
JSON.stringify({ query: { lang } })
|
JSON.stringify({ query: { lang } })
|
||||||
)
|
)
|
||||||
|
|
||||||
const data = validatedHeaderConfig.data.all_header.items[0]
|
return validatedHeaderConfig.data
|
||||||
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
|
|
||||||
}),
|
}),
|
||||||
currentHeader: contentstackBaseProcedure
|
currentHeader: contentstackBaseProcedure
|
||||||
.input(langInput)
|
.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(),
|
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