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 styles from "./layout.module.css"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -7,7 +9,9 @@ import {
|
|||||||
UIDParams,
|
UIDParams,
|
||||||
} from "@/types/params"
|
} from "@/types/params"
|
||||||
|
|
||||||
export default function ContentTypeLayout({
|
export { generateMetadata } from "@/utils/generateMetadata"
|
||||||
|
|
||||||
|
export default async function ContentTypeLayout({
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
preview,
|
preview,
|
||||||
children,
|
children,
|
||||||
@@ -17,13 +21,25 @@ export default function ContentTypeLayout({
|
|||||||
preview: React.ReactNode
|
preview: React.ReactNode
|
||||||
}
|
}
|
||||||
>) {
|
>) {
|
||||||
|
const breadcrumbsListSchema = await getBreadcrumbsListSchema()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<>
|
||||||
<section className={styles.layout}>
|
{breadcrumbsListSchema ? (
|
||||||
{preview}
|
<script
|
||||||
{breadcrumbs}
|
type={breadcrumbsListSchema.type}
|
||||||
{children}
|
dangerouslySetInnerHTML={{
|
||||||
</section>
|
__html: JSON.stringify(breadcrumbsListSchema.jsonLd),
|
||||||
</div>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className={styles.container}>
|
||||||
|
<section className={styles.layout}>
|
||||||
|
{preview}
|
||||||
|
{breadcrumbs}
|
||||||
|
{children}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import {
|
|||||||
UIDParams,
|
UIDParams,
|
||||||
} from "@/types/params"
|
} from "@/types/params"
|
||||||
|
|
||||||
export { generateMetadata } from "@/utils/generateMetadata"
|
|
||||||
|
|
||||||
export default function ContentTypePage({
|
export default function ContentTypePage({
|
||||||
params,
|
params,
|
||||||
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#import "./AccountPage.graphql"
|
#import "./AccountPage.graphql"
|
||||||
|
#import "./CollectionPage.graphql"
|
||||||
#import "./ContentPage.graphql"
|
#import "./ContentPage.graphql"
|
||||||
#import "./LoyaltyPage.graphql"
|
#import "./LoyaltyPage.graphql"
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ fragment Breadcrumbs on Breadcrumbs {
|
|||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
...AccountPageBreadcrumb
|
...AccountPageBreadcrumb
|
||||||
|
...CollectionPageBreadcrumb
|
||||||
...ContentPageBreadcrumb
|
...ContentPageBreadcrumb
|
||||||
...LoyaltyPageBreadcrumb
|
...LoyaltyPageBreadcrumb
|
||||||
}
|
}
|
||||||
@@ -23,6 +25,7 @@ fragment BreadcrumbsRefs on Breadcrumbs {
|
|||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
...AccountPageBreadcrumbRef
|
...AccountPageBreadcrumbRef
|
||||||
|
...CollectionPageBreadcrumbRef
|
||||||
...ContentPageBreadcrumbRef
|
...ContentPageBreadcrumbRef
|
||||||
...LoyaltyPageBreadcrumbRef
|
...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",
|
"lint-staged": "^15.2.2",
|
||||||
"netlify-plugin-cypress": "^2.2.1",
|
"netlify-plugin-cypress": "^2.2.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
|
"schema-dts": "^1.1.2",
|
||||||
"start-server-and-test": "^2.0.3",
|
"start-server-and-test": "^2.0.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
@@ -17973,6 +17974,15 @@
|
|||||||
"loose-envify": "^1.1.0"
|
"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": {
|
"node_modules/semver": {
|
||||||
"version": "7.6.3",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"netlify-plugin-cypress": "^2.2.1",
|
"netlify-plugin-cypress": "^2.2.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
|
"schema-dts": "^1.1.2",
|
||||||
"start-server-and-test": "^2.0.3",
|
"start-server-and-test": "^2.0.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { removeMultipleSlashes } from "@/utils/url"
|
||||||
|
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
import { homeBreadcrumbs } from "./utils"
|
||||||
|
|
||||||
export const getBreadcrumbsSchema = z.array(
|
export const breadcrumbsRefsSchema = z.object({
|
||||||
z.object({
|
|
||||||
href: z.string().optional(),
|
|
||||||
title: z.string(),
|
|
||||||
uid: z.string(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const breadcrumbsRefs = z.object({
|
|
||||||
web: z
|
web: z
|
||||||
.object({
|
.object({
|
||||||
breadcrumbs: z
|
breadcrumbs: z
|
||||||
@@ -32,43 +27,7 @@ const breadcrumbsRefs = z.object({
|
|||||||
system: systemSchema,
|
system: systemSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
export type BreadcrumbsRefs = z.infer<typeof breadcrumbsRefs>
|
export const rawBreadcrumbsDataSchema = z.object({
|
||||||
|
|
||||||
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({
|
|
||||||
web: z.object({
|
web: z.object({
|
||||||
breadcrumbs: z.object({
|
breadcrumbs: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
@@ -92,36 +51,24 @@ const page = z.object({
|
|||||||
system: systemSchema,
|
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({
|
const pageBreadcrumb = {
|
||||||
account_page: page,
|
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 {
|
import {
|
||||||
GetMyPagesBreadcrumbs,
|
GetMyPagesBreadcrumbs,
|
||||||
GetMyPagesBreadcrumbsRefs,
|
GetMyPagesBreadcrumbsRefs,
|
||||||
@@ -14,237 +18,198 @@ import {
|
|||||||
GetLoyaltyPageBreadcrumbs,
|
GetLoyaltyPageBreadcrumbs,
|
||||||
GetLoyaltyPageBreadcrumbsRefs,
|
GetLoyaltyPageBreadcrumbsRefs,
|
||||||
} from "@/lib/graphql/Query/Breadcrumbs/LoyaltyPage.graphql"
|
} 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 { contentstackExtendedProcedureUID, router } from "@/server/trpc"
|
||||||
|
|
||||||
import {
|
import { breadcrumbsRefsSchema, breadcrumbsSchema } from "./output"
|
||||||
GetCollectionPageBreadcrumbsData,
|
import { getTags } from "./utils"
|
||||||
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 { PageTypeEnum } from "@/types/requests/pageType"
|
import { PageTypeEnum } from "@/types/requests/pageType"
|
||||||
|
import {
|
||||||
|
BreadcrumbsRefsSchema,
|
||||||
|
RawBreadcrumbsSchema,
|
||||||
|
} from "@/types/trpc/routers/contentstack/breadcrumbs"
|
||||||
|
|
||||||
async function getLoyaltyPageBreadcrumbs(variables: Variables) {
|
const meter = metrics.getMeter("trpc.breadcrumbs")
|
||||||
const refsResponse = await getRefsResponse<GetLoyaltyPageBreadcrumbsRefsData>(
|
|
||||||
GetLoyaltyPageBreadcrumbsRefs,
|
|
||||||
variables
|
|
||||||
)
|
|
||||||
|
|
||||||
const validatedRefsData =
|
// OpenTelemetry metrics
|
||||||
validateLoyaltyPageBreadcrumbsRefsContentstackSchema.safeParse(
|
const getBreadcrumbsRefsCounter = meter.createCounter(
|
||||||
refsResponse.data
|
"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) {
|
interface BreadcrumbsPageData<T> {
|
||||||
console.error(
|
dataKey: keyof T
|
||||||
`Failed to validate Loyaltypage Breadcrumbs Refs - (uid: ${variables.uid})`
|
refQuery: string
|
||||||
)
|
query: string
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCollectionPageBreadcrumbs(variables: Variables) {
|
const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
|
||||||
const refsResponse =
|
{ dataKey, refQuery, query }: BreadcrumbsPageData<T>,
|
||||||
await getRefsResponse<GetCollectionPageBreadcrumbsRefsData>(
|
{ uid, lang }: { uid: string; lang: Lang }
|
||||||
GetCollectionPageBreadcrumbsRefs,
|
) {
|
||||||
variables
|
getBreadcrumbsRefsCounter.add(1, { lang, uid })
|
||||||
)
|
console.info(
|
||||||
const validatedRefsData =
|
"contentstack.breadcrumbs refs get start",
|
||||||
validateCollectionPageBreadcrumbsRefsContentstackSchema.safeParse(
|
JSON.stringify({ query: { lang, uid } })
|
||||||
refsResponse.data
|
)
|
||||||
)
|
const refsResponse = await request<{ [K in keyof T]: BreadcrumbsRefsSchema }>(
|
||||||
|
refQuery,
|
||||||
|
{ locale: lang, uid }
|
||||||
|
)
|
||||||
|
|
||||||
|
const validatedRefsData = breadcrumbsRefsSchema.safeParse(
|
||||||
|
refsResponse.data[dataKey]
|
||||||
|
)
|
||||||
|
|
||||||
if (!validatedRefsData.success) {
|
if (!validatedRefsData.success) {
|
||||||
|
getBreadcrumbsRefsFailCounter.add(1, {
|
||||||
|
error_type: "validation_error",
|
||||||
|
error: JSON.stringify(validatedRefsData.error),
|
||||||
|
})
|
||||||
console.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 []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedBreadcrumbsData =
|
getBreadcrumbsRefsSuccessCounter.add(1, { lang, uid })
|
||||||
validateMyPagesBreadcrumbsContentstackSchema.safeParse(response.data)
|
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(
|
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)
|
throw notFoundError
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return getBreadcrumbs(
|
const validatedBreadcrumbs = breadcrumbsSchema.safeParse(
|
||||||
validatedBreadcrumbsData.data.account_page,
|
response.data[dataKey]
|
||||||
variables.locale
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
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({
|
export const breadcrumbsQueryRouter = router({
|
||||||
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
|
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
|
||||||
const variables = {
|
const variables = {
|
||||||
locale: ctx.lang,
|
lang: ctx.lang,
|
||||||
uid: ctx.uid,
|
uid: ctx.uid,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ctx.contentType) {
|
switch (ctx.contentType) {
|
||||||
case PageTypeEnum.accountPage:
|
case PageTypeEnum.accountPage:
|
||||||
return await getMyPagesBreadcrumbs(variables)
|
return await getBreadcrumbs<{
|
||||||
|
account_page: RawBreadcrumbsSchema
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
dataKey: "account_page",
|
||||||
|
refQuery: GetMyPagesBreadcrumbsRefs,
|
||||||
|
query: GetMyPagesBreadcrumbs,
|
||||||
|
},
|
||||||
|
variables
|
||||||
|
)
|
||||||
case PageTypeEnum.collectionPage:
|
case PageTypeEnum.collectionPage:
|
||||||
return await getCollectionPageBreadcrumbs(variables)
|
return await getBreadcrumbs<{
|
||||||
|
collection_page: RawBreadcrumbsSchema
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
dataKey: "collection_page",
|
||||||
|
refQuery: GetCollectionPageBreadcrumbsRefs,
|
||||||
|
query: GetCollectionPageBreadcrumbs,
|
||||||
|
},
|
||||||
|
variables
|
||||||
|
)
|
||||||
case PageTypeEnum.contentPage:
|
case PageTypeEnum.contentPage:
|
||||||
return await getContentPageBreadcrumbs(variables)
|
return await getBreadcrumbs<{
|
||||||
|
content_page: RawBreadcrumbsSchema
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
dataKey: "content_page",
|
||||||
|
refQuery: GetContentPageBreadcrumbsRefs,
|
||||||
|
query: GetContentPageBreadcrumbs,
|
||||||
|
},
|
||||||
|
variables
|
||||||
|
)
|
||||||
case PageTypeEnum.loyaltyPage:
|
case PageTypeEnum.loyaltyPage:
|
||||||
return await getLoyaltyPageBreadcrumbs(variables)
|
return await getBreadcrumbs<{
|
||||||
|
loyalty_page: RawBreadcrumbsSchema
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
dataKey: "loyalty_page",
|
||||||
|
refQuery: GetLoyaltyPageBreadcrumbsRefs,
|
||||||
|
query: GetLoyaltyPageBreadcrumbs,
|
||||||
|
},
|
||||||
|
variables
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,22 @@
|
|||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
import { request } from "@/lib/graphql/request"
|
|
||||||
import { internalServerError, notFound } from "@/server/errors/trpc"
|
|
||||||
|
|
||||||
import {
|
import { generateTag, generateTags } from "@/utils/generateTag"
|
||||||
generateRefsResponseTag,
|
|
||||||
generateTag,
|
|
||||||
generateTags,
|
|
||||||
} from "@/utils/generateTag"
|
|
||||||
import { removeMultipleSlashes } from "@/utils/url"
|
|
||||||
|
|
||||||
import { type BreadcrumbsRefs, getBreadcrumbsSchema, Page } from "./output"
|
|
||||||
|
|
||||||
import type { Edges } from "@/types/requests/utils/edges"
|
import type { Edges } from "@/types/requests/utils/edges"
|
||||||
import type { NodeRefs } from "@/types/requests/utils/refs"
|
import type { NodeRefs } from "@/types/requests/utils/refs"
|
||||||
|
import { BreadcrumbsRefsSchema } from "@/types/trpc/routers/contentstack/breadcrumbs"
|
||||||
|
|
||||||
export function getConnections(refs: BreadcrumbsRefs) {
|
export type Variables = {
|
||||||
const connections: Edges<NodeRefs>[] = []
|
lang: Lang
|
||||||
|
uid: string
|
||||||
if (refs.web?.breadcrumbs) {
|
|
||||||
connections.push(refs.web.breadcrumbs.parentsConnection)
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const affix = "breadcrumbs"
|
export const affix = "breadcrumbs"
|
||||||
|
|
||||||
// TODO: Make these editable in CMS?
|
// 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]: {
|
[Lang.da]: {
|
||||||
href: "/da",
|
href: "/da",
|
||||||
title: "Hjem",
|
title: "Hjem",
|
||||||
@@ -60,76 +49,19 @@ export const homeBreadcrumbs = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Variables = {
|
export function getConnections(data: BreadcrumbsRefsSchema) {
|
||||||
locale: Lang
|
const connections: Edges<NodeRefs>[] = []
|
||||||
uid: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRefsResponse<T>(query: string, variables: Variables) {
|
if (data.web?.breadcrumbs) {
|
||||||
const refsResponse = await request<T>(query, variables, {
|
connections.push(data.web.breadcrumbs.parentsConnection)
|
||||||
cache: "force-cache",
|
|
||||||
next: {
|
|
||||||
tags: [generateRefsResponseTag(variables.locale, variables.uid, affix)],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!refsResponse.data) {
|
|
||||||
throw notFound(refsResponse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return refsResponse
|
return connections
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTags(page: BreadcrumbsRefs, variables: Variables) {
|
export function getTags(data: BreadcrumbsRefsSchema, lang: Lang) {
|
||||||
const connections = getConnections(page)
|
const connections = getConnections(data)
|
||||||
const tags = generateTags(variables.locale, connections)
|
const tags = generateTags(lang, connections)
|
||||||
tags.push(generateTag(variables.locale, page.system.uid, affix))
|
tags.push(generateTag(lang, data.system.uid, affix))
|
||||||
return tags
|
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 { z } from "zod"
|
||||||
|
|
||||||
import {
|
import { rawMetaDataDataSchema } from "@/server/routers/contentstack/metadata/output"
|
||||||
metaDataSchema,
|
|
||||||
rawMetaDataDataSchema,
|
|
||||||
} from "@/server/routers/contentstack/metadata/output"
|
|
||||||
|
|
||||||
export interface RawMetaDataSchema
|
export interface RawMetaDataSchema
|
||||||
extends z.input<typeof rawMetaDataDataSchema> {}
|
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