feat(SW-200): refactored breadcrumbs fetching and added json schema to layout
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { getBreadcrumbsListSchema } from "@/utils/getJsonSchemas"
|
||||
|
||||
import styles from "./layout.module.css"
|
||||
|
||||
import {
|
||||
@@ -7,7 +9,9 @@ import {
|
||||
UIDParams,
|
||||
} from "@/types/params"
|
||||
|
||||
export default function ContentTypeLayout({
|
||||
export { generateMetadata } from "@/utils/generateMetadata"
|
||||
|
||||
export default async function ContentTypeLayout({
|
||||
breadcrumbs,
|
||||
preview,
|
||||
children,
|
||||
@@ -17,13 +21,25 @@ export default function ContentTypeLayout({
|
||||
preview: React.ReactNode
|
||||
}
|
||||
>) {
|
||||
const breadcrumbsListSchema = await getBreadcrumbsListSchema()
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<section className={styles.layout}>
|
||||
{preview}
|
||||
{breadcrumbs}
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
<>
|
||||
{breadcrumbsListSchema ? (
|
||||
<script
|
||||
type={breadcrumbsListSchema.type}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(breadcrumbsListSchema.jsonLd),
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<div className={styles.container}>
|
||||
<section className={styles.layout}>
|
||||
{preview}
|
||||
{breadcrumbs}
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
UIDParams,
|
||||
} from "@/types/params"
|
||||
|
||||
export { generateMetadata } from "@/utils/generateMetadata"
|
||||
|
||||
export default function ContentTypePage({
|
||||
params,
|
||||
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#import "./AccountPage.graphql"
|
||||
#import "./CollectionPage.graphql"
|
||||
#import "./ContentPage.graphql"
|
||||
#import "./LoyaltyPage.graphql"
|
||||
|
||||
@@ -9,6 +10,7 @@ fragment Breadcrumbs on Breadcrumbs {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageBreadcrumb
|
||||
...CollectionPageBreadcrumb
|
||||
...ContentPageBreadcrumb
|
||||
...LoyaltyPageBreadcrumb
|
||||
}
|
||||
@@ -23,6 +25,7 @@ fragment BreadcrumbsRefs on Breadcrumbs {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageBreadcrumbRef
|
||||
...CollectionPageBreadcrumbRef
|
||||
...ContentPageBreadcrumbRef
|
||||
...LoyaltyPageBreadcrumbRef
|
||||
}
|
||||
|
||||
24
lib/graphql/Fragments/Breadcrumbs/CollectionPage.graphql
Normal file
24
lib/graphql/Fragments/Breadcrumbs/CollectionPage.graphql
Normal file
@@ -0,0 +1,24 @@
|
||||
#import "../System.graphql"
|
||||
|
||||
fragment CollectionPageBreadcrumb on CollectionPage {
|
||||
web {
|
||||
breadcrumbs {
|
||||
title
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
url
|
||||
}
|
||||
|
||||
fragment CollectionPageBreadcrumbRef on CollectionPage {
|
||||
web {
|
||||
breadcrumbs {
|
||||
title
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -81,6 +81,7 @@
|
||||
"lint-staged": "^15.2.2",
|
||||
"netlify-plugin-cypress": "^2.2.1",
|
||||
"prettier": "^3.2.5",
|
||||
"schema-dts": "^1.1.2",
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5",
|
||||
@@ -17973,6 +17974,15 @@
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-dts": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.2.tgz",
|
||||
"integrity": "sha512-MpNwH0dZJHinVxk9bT8XUdjKTxMYrA5bLtrrGmFA6PTLwlOKnhi67XoRd6/ty+Djt6ZC0slR57qFhZDNMI6DhQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"lint-staged": "^15.2.2",
|
||||
"netlify-plugin-cypress": "^2.2.1",
|
||||
"prettier": "^3.2.5",
|
||||
"schema-dts": "^1.1.2",
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5",
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import { systemSchema } from "../schemas/system"
|
||||
import { homeBreadcrumbs } from "./utils"
|
||||
|
||||
export const getBreadcrumbsSchema = z.array(
|
||||
z.object({
|
||||
href: z.string().optional(),
|
||||
title: z.string(),
|
||||
uid: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
const breadcrumbsRefs = z.object({
|
||||
export const breadcrumbsRefsSchema = z.object({
|
||||
web: z
|
||||
.object({
|
||||
breadcrumbs: z
|
||||
@@ -32,43 +27,7 @@ const breadcrumbsRefs = z.object({
|
||||
system: systemSchema,
|
||||
})
|
||||
|
||||
export type BreadcrumbsRefs = z.infer<typeof breadcrumbsRefs>
|
||||
|
||||
export const validateMyPagesBreadcrumbsRefsContentstackSchema = z.object({
|
||||
account_page: breadcrumbsRefs,
|
||||
})
|
||||
|
||||
export type GetMyPagesBreadcrumbsRefsData = z.infer<
|
||||
typeof validateMyPagesBreadcrumbsRefsContentstackSchema
|
||||
>
|
||||
|
||||
export const validateLoyaltyPageBreadcrumbsRefsContentstackSchema = z.object({
|
||||
loyalty_page: breadcrumbsRefs,
|
||||
})
|
||||
|
||||
export type GetLoyaltyPageBreadcrumbsRefsData = z.infer<
|
||||
typeof validateLoyaltyPageBreadcrumbsRefsContentstackSchema
|
||||
>
|
||||
|
||||
export const validateCollectionPageBreadcrumbsRefsContentstackSchema = z.object(
|
||||
{
|
||||
collection_page: breadcrumbsRefs,
|
||||
}
|
||||
)
|
||||
|
||||
export type GetCollectionPageBreadcrumbsRefsData = z.infer<
|
||||
typeof validateCollectionPageBreadcrumbsRefsContentstackSchema
|
||||
>
|
||||
|
||||
export const validateContentPageBreadcrumbsRefsContentstackSchema = z.object({
|
||||
content_page: breadcrumbsRefs,
|
||||
})
|
||||
|
||||
export type GetContentPageBreadcrumbsRefsData = z.infer<
|
||||
typeof validateContentPageBreadcrumbsRefsContentstackSchema
|
||||
>
|
||||
|
||||
const page = z.object({
|
||||
export const rawBreadcrumbsDataSchema = z.object({
|
||||
web: z.object({
|
||||
breadcrumbs: z.object({
|
||||
title: z.string(),
|
||||
@@ -92,36 +51,24 @@ const page = z.object({
|
||||
system: systemSchema,
|
||||
})
|
||||
|
||||
export type Page = z.infer<typeof page>
|
||||
export const breadcrumbsSchema = rawBreadcrumbsDataSchema.transform((data) => {
|
||||
const { parentsConnection, title } = data.web.breadcrumbs
|
||||
const parentBreadcrumbs = parentsConnection.edges.map((breadcrumb) => {
|
||||
return {
|
||||
href: removeMultipleSlashes(
|
||||
`/${breadcrumb.node.system.locale}/${breadcrumb.node.url}`
|
||||
),
|
||||
title: breadcrumb.node.web.breadcrumbs.title,
|
||||
uid: breadcrumb.node.system.uid,
|
||||
}
|
||||
})
|
||||
|
||||
export const validateMyPagesBreadcrumbsContentstackSchema = z.object({
|
||||
account_page: page,
|
||||
const pageBreadcrumb = {
|
||||
title,
|
||||
uid: data.system.uid,
|
||||
href: undefined,
|
||||
}
|
||||
const homeBreadcrumb = homeBreadcrumbs[data.system.locale]
|
||||
|
||||
return [homeBreadcrumb, parentBreadcrumbs, pageBreadcrumb].flat()
|
||||
})
|
||||
|
||||
export type GetMyPagesBreadcrumbsData = z.infer<
|
||||
typeof validateMyPagesBreadcrumbsContentstackSchema
|
||||
>
|
||||
|
||||
export const validateLoyaltyPageBreadcrumbsContentstackSchema = z.object({
|
||||
loyalty_page: page,
|
||||
})
|
||||
|
||||
export type GetLoyaltyPageBreadcrumbsData = z.infer<
|
||||
typeof validateLoyaltyPageBreadcrumbsContentstackSchema
|
||||
>
|
||||
|
||||
export const validateContentPageBreadcrumbsContentstackSchema = z.object({
|
||||
content_page: page,
|
||||
})
|
||||
|
||||
export type GetContentPageBreadcrumbsData = z.infer<
|
||||
typeof validateContentPageBreadcrumbsContentstackSchema
|
||||
>
|
||||
|
||||
export const validateCollectionPageBreadcrumbsContentstackSchema = z.object({
|
||||
collection_page: page,
|
||||
})
|
||||
|
||||
export type GetCollectionPageBreadcrumbsData = z.infer<
|
||||
typeof validateCollectionPageBreadcrumbsContentstackSchema
|
||||
>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
import { cache } from "react"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import {
|
||||
GetMyPagesBreadcrumbs,
|
||||
GetMyPagesBreadcrumbsRefs,
|
||||
@@ -14,237 +18,198 @@ import {
|
||||
GetLoyaltyPageBreadcrumbs,
|
||||
GetLoyaltyPageBreadcrumbsRefs,
|
||||
} from "@/lib/graphql/Query/Breadcrumbs/LoyaltyPage.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
|
||||
|
||||
import {
|
||||
GetCollectionPageBreadcrumbsData,
|
||||
GetCollectionPageBreadcrumbsRefsData,
|
||||
type GetContentPageBreadcrumbsData,
|
||||
type GetContentPageBreadcrumbsRefsData,
|
||||
type GetLoyaltyPageBreadcrumbsData,
|
||||
type GetLoyaltyPageBreadcrumbsRefsData,
|
||||
type GetMyPagesBreadcrumbsData,
|
||||
type GetMyPagesBreadcrumbsRefsData,
|
||||
validateCollectionPageBreadcrumbsContentstackSchema,
|
||||
validateCollectionPageBreadcrumbsRefsContentstackSchema,
|
||||
validateContentPageBreadcrumbsContentstackSchema,
|
||||
validateContentPageBreadcrumbsRefsContentstackSchema,
|
||||
validateLoyaltyPageBreadcrumbsContentstackSchema,
|
||||
validateLoyaltyPageBreadcrumbsRefsContentstackSchema,
|
||||
validateMyPagesBreadcrumbsContentstackSchema,
|
||||
validateMyPagesBreadcrumbsRefsContentstackSchema,
|
||||
} from "./output"
|
||||
import {
|
||||
getBreadcrumbs,
|
||||
getRefsResponse,
|
||||
getResponse,
|
||||
getTags,
|
||||
Variables,
|
||||
} from "./utils"
|
||||
import { breadcrumbsRefsSchema, breadcrumbsSchema } from "./output"
|
||||
import { getTags } from "./utils"
|
||||
|
||||
import { PageTypeEnum } from "@/types/requests/pageType"
|
||||
import {
|
||||
BreadcrumbsRefsSchema,
|
||||
RawBreadcrumbsSchema,
|
||||
} from "@/types/trpc/routers/contentstack/breadcrumbs"
|
||||
|
||||
async function getLoyaltyPageBreadcrumbs(variables: Variables) {
|
||||
const refsResponse = await getRefsResponse<GetLoyaltyPageBreadcrumbsRefsData>(
|
||||
GetLoyaltyPageBreadcrumbsRefs,
|
||||
variables
|
||||
)
|
||||
const meter = metrics.getMeter("trpc.breadcrumbs")
|
||||
|
||||
const validatedRefsData =
|
||||
validateLoyaltyPageBreadcrumbsRefsContentstackSchema.safeParse(
|
||||
refsResponse.data
|
||||
)
|
||||
// OpenTelemetry metrics
|
||||
const getBreadcrumbsRefsCounter = meter.createCounter(
|
||||
"trpc.contentstack.breadcrumbs.refs.get"
|
||||
)
|
||||
const getBreadcrumbsRefsSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.breadcrumbs.refs.get-success"
|
||||
)
|
||||
const getBreadcrumbsRefsFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.breadcrumbs.refs.get-fail"
|
||||
)
|
||||
const getBreadcrumbsCounter = meter.createCounter(
|
||||
"trpc.contentstack.breadcrumbs.get"
|
||||
)
|
||||
const getBreadcrumbsSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.breadcrumbs.get-success"
|
||||
)
|
||||
const getBreadcrumbsFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.breadcrumbs.get-fail"
|
||||
)
|
||||
|
||||
if (!validatedRefsData.success) {
|
||||
console.error(
|
||||
`Failed to validate Loyaltypage Breadcrumbs Refs - (uid: ${variables.uid})`
|
||||
)
|
||||
console.error(validatedRefsData.error)
|
||||
return null
|
||||
}
|
||||
|
||||
const tags = getTags(validatedRefsData.data.loyalty_page, variables)
|
||||
|
||||
const response = await getResponse<GetLoyaltyPageBreadcrumbsData>(
|
||||
GetLoyaltyPageBreadcrumbs,
|
||||
variables,
|
||||
tags
|
||||
)
|
||||
|
||||
if (!response.data.loyalty_page.web?.breadcrumbs?.title) {
|
||||
return null
|
||||
}
|
||||
|
||||
const validatedBreadcrumbsData =
|
||||
validateLoyaltyPageBreadcrumbsContentstackSchema.safeParse(response.data)
|
||||
|
||||
if (!validatedBreadcrumbsData.success) {
|
||||
console.error(
|
||||
`Failed to validate Loyaltypage Breadcrumbs Data - (uid: ${variables.uid})`
|
||||
)
|
||||
console.error(validatedBreadcrumbsData.error)
|
||||
return null
|
||||
}
|
||||
|
||||
return getBreadcrumbs(
|
||||
validatedBreadcrumbsData.data.loyalty_page,
|
||||
variables.locale
|
||||
)
|
||||
interface BreadcrumbsPageData<T> {
|
||||
dataKey: keyof T
|
||||
refQuery: string
|
||||
query: string
|
||||
}
|
||||
|
||||
async function getCollectionPageBreadcrumbs(variables: Variables) {
|
||||
const refsResponse =
|
||||
await getRefsResponse<GetCollectionPageBreadcrumbsRefsData>(
|
||||
GetCollectionPageBreadcrumbsRefs,
|
||||
variables
|
||||
)
|
||||
const validatedRefsData =
|
||||
validateCollectionPageBreadcrumbsRefsContentstackSchema.safeParse(
|
||||
refsResponse.data
|
||||
)
|
||||
const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
|
||||
{ dataKey, refQuery, query }: BreadcrumbsPageData<T>,
|
||||
{ uid, lang }: { uid: string; lang: Lang }
|
||||
) {
|
||||
getBreadcrumbsRefsCounter.add(1, { lang, uid })
|
||||
console.info(
|
||||
"contentstack.breadcrumbs refs get start",
|
||||
JSON.stringify({ query: { lang, uid } })
|
||||
)
|
||||
const refsResponse = await request<{ [K in keyof T]: BreadcrumbsRefsSchema }>(
|
||||
refQuery,
|
||||
{ locale: lang, uid }
|
||||
)
|
||||
|
||||
const validatedRefsData = breadcrumbsRefsSchema.safeParse(
|
||||
refsResponse.data[dataKey]
|
||||
)
|
||||
|
||||
if (!validatedRefsData.success) {
|
||||
getBreadcrumbsRefsFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedRefsData.error),
|
||||
})
|
||||
console.error(
|
||||
`Failed to validate CollectionPpage Breadcrumbs Refs - (uid: ${variables.uid})`
|
||||
"contentstack.breadcrumbs refs validation error",
|
||||
JSON.stringify({
|
||||
error: validatedRefsData.error,
|
||||
})
|
||||
)
|
||||
console.error(validatedRefsData.error)
|
||||
return null
|
||||
}
|
||||
const tags = getTags(validatedRefsData.data.collection_page, variables)
|
||||
const response = await getResponse<GetCollectionPageBreadcrumbsData>(
|
||||
GetCollectionPageBreadcrumbs,
|
||||
variables,
|
||||
tags
|
||||
)
|
||||
if (!response.data.collection_page.web?.breadcrumbs?.title) {
|
||||
return null
|
||||
}
|
||||
const validatedBreadcrumbsData =
|
||||
validateCollectionPageBreadcrumbsContentstackSchema.safeParse(response.data)
|
||||
if (!validatedBreadcrumbsData.success) {
|
||||
console.error(
|
||||
`Failed to validate Collectionpage Breadcrumbs Data - (uid: ${variables.uid})`
|
||||
)
|
||||
console.error(validatedBreadcrumbsData.error)
|
||||
return null
|
||||
}
|
||||
return getBreadcrumbs(
|
||||
validatedBreadcrumbsData.data.collection_page,
|
||||
variables.locale
|
||||
)
|
||||
}
|
||||
|
||||
async function getContentPageBreadcrumbs(variables: Variables) {
|
||||
const refsResponse = await getRefsResponse<GetContentPageBreadcrumbsRefsData>(
|
||||
GetContentPageBreadcrumbsRefs,
|
||||
variables
|
||||
)
|
||||
|
||||
const validatedRefsData =
|
||||
validateContentPageBreadcrumbsRefsContentstackSchema.safeParse(
|
||||
refsResponse.data
|
||||
)
|
||||
|
||||
if (!validatedRefsData.success) {
|
||||
console.error(
|
||||
`Failed to validate Contentpage Breadcrumbs Refs - (uid: ${variables.uid})`
|
||||
)
|
||||
console.error(validatedRefsData.error)
|
||||
return null
|
||||
}
|
||||
|
||||
const tags = getTags(validatedRefsData.data.content_page, variables)
|
||||
|
||||
const response = await getResponse<GetContentPageBreadcrumbsData>(
|
||||
GetContentPageBreadcrumbs,
|
||||
variables,
|
||||
tags
|
||||
)
|
||||
|
||||
if (!response.data.content_page.web?.breadcrumbs?.title) {
|
||||
return null
|
||||
}
|
||||
|
||||
const validatedBreadcrumbsData =
|
||||
validateContentPageBreadcrumbsContentstackSchema.safeParse(response.data)
|
||||
|
||||
if (!validatedBreadcrumbsData.success) {
|
||||
console.error(
|
||||
`Failed to validate Contentpage Breadcrumbs Data - (uid: ${variables.uid})`
|
||||
)
|
||||
console.error(validatedBreadcrumbsData.error)
|
||||
return null
|
||||
}
|
||||
|
||||
return getBreadcrumbs(
|
||||
validatedBreadcrumbsData.data.content_page,
|
||||
variables.locale
|
||||
)
|
||||
}
|
||||
|
||||
async function getMyPagesBreadcrumbs(variables: Variables) {
|
||||
const refsResponse = await getRefsResponse<GetMyPagesBreadcrumbsRefsData>(
|
||||
GetMyPagesBreadcrumbsRefs,
|
||||
variables
|
||||
)
|
||||
|
||||
const validatedRefsData =
|
||||
validateMyPagesBreadcrumbsRefsContentstackSchema.safeParse(
|
||||
refsResponse.data
|
||||
)
|
||||
if (!validatedRefsData.success) {
|
||||
console.error(
|
||||
`Failed to validate My Page Breadcrumbs Refs - (uid: ${variables.uid})`
|
||||
)
|
||||
console.error(validatedRefsData.error)
|
||||
return null
|
||||
}
|
||||
|
||||
const tags = getTags(validatedRefsData.data.account_page, variables)
|
||||
|
||||
const response = await getResponse<GetMyPagesBreadcrumbsData>(
|
||||
GetMyPagesBreadcrumbs,
|
||||
variables,
|
||||
tags
|
||||
)
|
||||
|
||||
if (!response.data.account_page.web?.breadcrumbs?.title) {
|
||||
return []
|
||||
}
|
||||
|
||||
const validatedBreadcrumbsData =
|
||||
validateMyPagesBreadcrumbsContentstackSchema.safeParse(response.data)
|
||||
getBreadcrumbsRefsSuccessCounter.add(1, { lang, uid })
|
||||
console.info(
|
||||
"contentstack.breadcrumbs refs get success",
|
||||
JSON.stringify({ query: { lang, uid } })
|
||||
)
|
||||
|
||||
if (!validatedBreadcrumbsData.success) {
|
||||
const tags = getTags(validatedRefsData.data, lang)
|
||||
|
||||
getBreadcrumbsCounter.add(1, { lang, uid })
|
||||
console.info(
|
||||
"contentstack.breadcrumbs get start",
|
||||
JSON.stringify({ query: { lang, uid } })
|
||||
)
|
||||
const response = await request<T>(
|
||||
query,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: { tags },
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
const notFoundError = notFound(response)
|
||||
getBreadcrumbsFailCounter.add(1, {
|
||||
lang,
|
||||
uid,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
console.error(
|
||||
`Failed to validate My Page Breadcrumbs Data - (uid: ${variables.uid})`
|
||||
"contentstack.breadcrumbs get not found error",
|
||||
JSON.stringify({
|
||||
query: { lang, uid },
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
console.error(validatedBreadcrumbsData.error)
|
||||
return null
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
return getBreadcrumbs(
|
||||
validatedBreadcrumbsData.data.account_page,
|
||||
variables.locale
|
||||
const validatedBreadcrumbs = breadcrumbsSchema.safeParse(
|
||||
response.data[dataKey]
|
||||
)
|
||||
}
|
||||
|
||||
if (!validatedBreadcrumbs.success) {
|
||||
getBreadcrumbsFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedBreadcrumbs.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.breadcrumbs validation error",
|
||||
JSON.stringify({
|
||||
error: validatedBreadcrumbs.error,
|
||||
})
|
||||
)
|
||||
return []
|
||||
}
|
||||
|
||||
getBreadcrumbsSuccessCounter.add(1, { lang, uid })
|
||||
console.info(
|
||||
"contentstack.breadcrumbs get success",
|
||||
JSON.stringify({ query: { lang, uid } })
|
||||
)
|
||||
|
||||
return validatedBreadcrumbs.data
|
||||
})
|
||||
|
||||
export const breadcrumbsQueryRouter = router({
|
||||
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
|
||||
const variables = {
|
||||
locale: ctx.lang,
|
||||
lang: ctx.lang,
|
||||
uid: ctx.uid,
|
||||
}
|
||||
|
||||
switch (ctx.contentType) {
|
||||
case PageTypeEnum.accountPage:
|
||||
return await getMyPagesBreadcrumbs(variables)
|
||||
return await getBreadcrumbs<{
|
||||
account_page: RawBreadcrumbsSchema
|
||||
}>(
|
||||
{
|
||||
dataKey: "account_page",
|
||||
refQuery: GetMyPagesBreadcrumbsRefs,
|
||||
query: GetMyPagesBreadcrumbs,
|
||||
},
|
||||
variables
|
||||
)
|
||||
case PageTypeEnum.collectionPage:
|
||||
return await getCollectionPageBreadcrumbs(variables)
|
||||
return await getBreadcrumbs<{
|
||||
collection_page: RawBreadcrumbsSchema
|
||||
}>(
|
||||
{
|
||||
dataKey: "collection_page",
|
||||
refQuery: GetCollectionPageBreadcrumbsRefs,
|
||||
query: GetCollectionPageBreadcrumbs,
|
||||
},
|
||||
variables
|
||||
)
|
||||
case PageTypeEnum.contentPage:
|
||||
return await getContentPageBreadcrumbs(variables)
|
||||
return await getBreadcrumbs<{
|
||||
content_page: RawBreadcrumbsSchema
|
||||
}>(
|
||||
{
|
||||
dataKey: "content_page",
|
||||
refQuery: GetContentPageBreadcrumbsRefs,
|
||||
query: GetContentPageBreadcrumbs,
|
||||
},
|
||||
variables
|
||||
)
|
||||
case PageTypeEnum.loyaltyPage:
|
||||
return await getLoyaltyPageBreadcrumbs(variables)
|
||||
return await getBreadcrumbs<{
|
||||
loyalty_page: RawBreadcrumbsSchema
|
||||
}>(
|
||||
{
|
||||
dataKey: "loyalty_page",
|
||||
refQuery: GetLoyaltyPageBreadcrumbsRefs,
|
||||
query: GetLoyaltyPageBreadcrumbs,
|
||||
},
|
||||
variables
|
||||
)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { internalServerError, notFound } from "@/server/errors/trpc"
|
||||
|
||||
import {
|
||||
generateRefsResponseTag,
|
||||
generateTag,
|
||||
generateTags,
|
||||
} from "@/utils/generateTag"
|
||||
import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import { type BreadcrumbsRefs, getBreadcrumbsSchema, Page } from "./output"
|
||||
import { generateTag, generateTags } from "@/utils/generateTag"
|
||||
|
||||
import type { Edges } from "@/types/requests/utils/edges"
|
||||
import type { NodeRefs } from "@/types/requests/utils/refs"
|
||||
import { BreadcrumbsRefsSchema } from "@/types/trpc/routers/contentstack/breadcrumbs"
|
||||
|
||||
export function getConnections(refs: BreadcrumbsRefs) {
|
||||
const connections: Edges<NodeRefs>[] = []
|
||||
|
||||
if (refs.web?.breadcrumbs) {
|
||||
connections.push(refs.web.breadcrumbs.parentsConnection)
|
||||
}
|
||||
|
||||
return connections
|
||||
export type Variables = {
|
||||
lang: Lang
|
||||
uid: string
|
||||
}
|
||||
|
||||
export const affix = "breadcrumbs"
|
||||
|
||||
// TODO: Make these editable in CMS?
|
||||
export const homeBreadcrumbs = {
|
||||
export const homeBreadcrumbs: {
|
||||
[key in keyof typeof Lang]: { href: string; title: string; uid: string }
|
||||
} = {
|
||||
[Lang.da]: {
|
||||
href: "/da",
|
||||
title: "Hjem",
|
||||
@@ -60,76 +49,19 @@ export const homeBreadcrumbs = {
|
||||
},
|
||||
}
|
||||
|
||||
export type Variables = {
|
||||
locale: Lang
|
||||
uid: string
|
||||
}
|
||||
export function getConnections(data: BreadcrumbsRefsSchema) {
|
||||
const connections: Edges<NodeRefs>[] = []
|
||||
|
||||
export async function getRefsResponse<T>(query: string, variables: Variables) {
|
||||
const refsResponse = await request<T>(query, variables, {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(variables.locale, variables.uid, affix)],
|
||||
},
|
||||
})
|
||||
if (!refsResponse.data) {
|
||||
throw notFound(refsResponse)
|
||||
if (data.web?.breadcrumbs) {
|
||||
connections.push(data.web.breadcrumbs.parentsConnection)
|
||||
}
|
||||
|
||||
return refsResponse
|
||||
return connections
|
||||
}
|
||||
|
||||
export function getTags(page: BreadcrumbsRefs, variables: Variables) {
|
||||
const connections = getConnections(page)
|
||||
const tags = generateTags(variables.locale, connections)
|
||||
tags.push(generateTag(variables.locale, page.system.uid, affix))
|
||||
export function getTags(data: BreadcrumbsRefsSchema, lang: Lang) {
|
||||
const connections = getConnections(data)
|
||||
const tags = generateTags(lang, connections)
|
||||
tags.push(generateTag(lang, data.system.uid, affix))
|
||||
return tags
|
||||
}
|
||||
|
||||
export async function getResponse<T>(
|
||||
query: string,
|
||||
variables: Variables,
|
||||
tags: string[]
|
||||
) {
|
||||
const response = await request<T>(query, variables, {
|
||||
cache: "force-cache",
|
||||
next: { tags },
|
||||
})
|
||||
if (!response.data) {
|
||||
throw notFound(response)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
export function getBreadcrumbs(page: Page, lang: Lang) {
|
||||
const parentBreadcrumbs = page.web.breadcrumbs.parentsConnection.edges.map(
|
||||
(breadcrumb) => {
|
||||
return {
|
||||
href: removeMultipleSlashes(
|
||||
`/${breadcrumb.node.system.locale}/${breadcrumb.node.url}`
|
||||
),
|
||||
title: breadcrumb.node.web.breadcrumbs.title,
|
||||
uid: breadcrumb.node.system.uid,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const pageBreadcrumb = {
|
||||
title: page.web.breadcrumbs.title,
|
||||
uid: page.system.uid,
|
||||
}
|
||||
|
||||
const breadcrumbs = [
|
||||
homeBreadcrumbs[lang],
|
||||
parentBreadcrumbs,
|
||||
pageBreadcrumb,
|
||||
].flat()
|
||||
|
||||
const validatedBreadcrumbs = getBreadcrumbsSchema.safeParse(breadcrumbs)
|
||||
if (!validatedBreadcrumbs.success) {
|
||||
throw internalServerError(validatedBreadcrumbs.error)
|
||||
}
|
||||
|
||||
return validatedBreadcrumbs.data
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { getBreadcrumbsSchema } from "@/server/routers/contentstack/breadcrumbs/output"
|
||||
|
||||
export interface Breadcrumbs extends z.infer<typeof getBreadcrumbsSchema> {}
|
||||
15
types/trpc/routers/contentstack/breadcrumbs.ts
Normal file
15
types/trpc/routers/contentstack/breadcrumbs.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
breadcrumbsRefsSchema,
|
||||
breadcrumbsSchema,
|
||||
rawBreadcrumbsDataSchema,
|
||||
} from "@/server/routers/contentstack/breadcrumbs/output"
|
||||
|
||||
export interface BreadcrumbsRefsSchema
|
||||
extends z.input<typeof breadcrumbsRefsSchema> {}
|
||||
|
||||
export interface RawBreadcrumbsSchema
|
||||
extends z.input<typeof rawBreadcrumbsDataSchema> {}
|
||||
|
||||
export interface Breadcrumbs extends z.output<typeof breadcrumbsSchema> {}
|
||||
@@ -1,10 +1,6 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
metaDataSchema,
|
||||
rawMetaDataDataSchema,
|
||||
} from "@/server/routers/contentstack/metadata/output"
|
||||
import { rawMetaDataDataSchema } from "@/server/routers/contentstack/metadata/output"
|
||||
|
||||
export interface RawMetaDataSchema
|
||||
extends z.input<typeof rawMetaDataDataSchema> {}
|
||||
export interface MetaDataSchema extends z.output<typeof metaDataSchema> {}
|
||||
|
||||
37
utils/getJsonSchemas.ts
Normal file
37
utils/getJsonSchemas.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { env } from "@/env/server"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import type { BreadcrumbList, ListItem, WithContext } from "schema-dts"
|
||||
|
||||
import { Breadcrumbs } from "@/types/trpc/routers/contentstack/breadcrumbs"
|
||||
|
||||
function generateBreadcrumbsSchema(breadcrumbs: Breadcrumbs) {
|
||||
if (!breadcrumbs.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const itemListElement: ListItem[] = breadcrumbs.map((item, index) => ({
|
||||
"@type": "ListItem",
|
||||
position: index + 1,
|
||||
name: item.title,
|
||||
// Only include "item" if "href" exists; otherwise, omit it
|
||||
...(item.href ? { item: `${env.PUBLIC_URL}${item.href}` } : {}),
|
||||
}))
|
||||
|
||||
const jsonLd: WithContext<BreadcrumbList> = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement,
|
||||
}
|
||||
|
||||
return {
|
||||
key: "breadcrumbs",
|
||||
type: "application/ld+json",
|
||||
jsonLd,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getBreadcrumbsListSchema() {
|
||||
const breadcrumbs = await serverClient().contentstack.breadcrumbs.get()
|
||||
return generateBreadcrumbsSchema(breadcrumbs)
|
||||
}
|
||||
Reference in New Issue
Block a user