feat(SW-266): Replacing static metadata with data from Contentstack on Loyalty pages and Account Pages

This commit is contained in:
Pontus Dreij
2024-08-19 13:03:46 +02:00
parent f561ca750c
commit c730fa7035
16 changed files with 316 additions and 8 deletions

View File

@@ -5,11 +5,20 @@ import Title from "@/components/TempDesignSystem/Text/Title"
import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n"
import { setLang } from "@/i18n/serverContext"
import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata"
import styles from "./page.module.css"
import type { LangParams, PageArgs } from "@/types/params"
export async function generateMetadata({ params }: PageArgs<LangParams>) {
const accountPageRes = await serverClient().contentstack.accountPage.get()
return generateBaseMetadata({
params,
pageTitle: accountPageRes?.accountPage?.title,
})
}
export default async function MyPages({
params,
}: PageArgs<LangParams & { path: string[] }>) {
@@ -34,7 +43,6 @@ export default async function MyPages({
<p>{formatMessage({ id: "No content published" })}</p>
)}
</main>
<TrackingSDK pageData={tracking} />
</>
)

View File

@@ -1,9 +1,12 @@
import { notFound } from "next/navigation"
import { serverClient } from "@/lib/trpc/server"
import ContentPage from "@/components/ContentType/ContentPage"
import HotelPage from "@/components/ContentType/HotelPage/HotelPage"
import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage"
import { setLang } from "@/i18n/serverContext"
import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata"
import {
ContentTypeParams,
@@ -12,6 +15,22 @@ import {
UIDParams,
} from "@/types/params"
export async function generateMetadata({
params,
}: PageArgs<LangParams & ContentTypeParams>) {
switch (params.contentType) {
case "loyalty-page":
const loyaltyPageRes = await serverClient().contentstack.loyaltyPage.get()
return generateBaseMetadata({
params,
pageTitle: loyaltyPageRes?.loyaltyPage?.title,
})
// Add case "hotel-pages" etc when needed
default:
return
}
}
export default async function ContentTypePage({
params,
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {

View File

@@ -12,15 +12,11 @@ import { preloadUserTracking } from "@/components/TrackingSDK"
import { getIntl } from "@/i18n"
import ServerIntlProvider from "@/i18n/Provider"
import { getLang, setLang } from "@/i18n/serverContext"
import type { Metadata } from "next"
import { generateMetadata } from "@/utils/generateMetadata"
import type { LangParams, LayoutArgs } from "@/types/params"
export const metadata: Metadata = {
description: "New web",
title: "Scandic Hotels",
}
export { generateMetadata }
export default async function RootLayout({
children,
@@ -33,8 +29,8 @@ export default async function RootLayout({
>) {
setLang(params.lang)
preloadUserTracking()
const { defaultLocale, locale, messages } = await getIntl()
return (
<html lang={getLang()}>
<head>

View File

@@ -0,0 +1,17 @@
#import "../Image.graphql"
fragment LoyaltyPageMetaData on LoyaltyPage {
web {
seo_metadata {
title
description
imageConnection {
edges {
node {
...Image
}
}
}
}
}
}

View File

@@ -0,0 +1,17 @@
#import "../Image.graphql"
fragment MyPagesMetaData on AccountPage {
web {
seo_metadata {
title
description
imageConnection {
edges {
node {
...Image
}
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
#import "../Fragments/LoyaltyPage/MetaData.graphql"
query GetLoyaltyPageMetaData($locale: String!, $url: String!) {
all_loyalty_page(locale: $locale, where: { url: $url }) {
items {
...LoyaltyPageMetaData
system {
uid
}
}
}
}

View File

@@ -0,0 +1,12 @@
#import "../Fragments/MyPages/MetaData.graphql"
query GetMyPagesMetaData($locale: String!, $url: String!) {
all_account_page(locale: $locale, where: { url: $url }) {
items {
...MyPagesMetaData
system {
uid
}
}
}
}

View File

@@ -6,6 +6,7 @@ import { breadcrumbsRouter } from "./breadcrumbs"
import { hotelPageRouter } from "./hotelPage"
import { languageSwitcherRouter } from "./languageSwitcher"
import { loyaltyPageRouter } from "./loyaltyPage"
import { metaDataRouter } from "./metadata"
import { myPagesRouter } from "./myPages"
export const contentstackRouter = router({
@@ -16,4 +17,5 @@ export const contentstackRouter = router({
languageSwitcher: languageSwitcherRouter,
loyaltyPage: loyaltyPageRouter,
myPages: myPagesRouter,
metaData: metaDataRouter,
})

View File

@@ -190,6 +190,7 @@ const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [
])
export const validateLoyaltyPageSchema = z.object({
title: z.string(),
heading: z.string().nullable(),
blocks: z.array(loyaltyPageBlockItem).nullable(),
sidebar: z.array(loyaltyPageSidebarItem).nullable(),

View File

@@ -209,6 +209,7 @@ export const loyaltyPageQueryRouter = router({
: null
const loyaltyPage = {
title: response.data.loyalty_page.title,
heading: response.data.loyalty_page.heading,
system: response.data.loyalty_page.system,
blocks,

View File

@@ -0,0 +1,5 @@
import { mergeRouters } from "@/server/trpc"
import { metaDataQueryRouter } from "./query"
export const metaDataRouter = mergeRouters(metaDataQueryRouter)

View File

@@ -0,0 +1,62 @@
import { z } from "zod"
export const getMetaDataSchema = z.object({
title: z.string().optional(),
description: z.string().optional(),
imageConnection: z
.object({
edges: z.array(
z.object({
node: z.object({
url: z.string(),
}),
})
),
})
.optional(),
})
const page = z.object({
web: z.object({
seo_metadata: z.object({
title: z.string().optional(),
description: z.string().optional(),
imageConnection: z
.object({
edges: z.array(
z.object({
node: z.object({
url: z.string(),
}),
})
),
})
.optional(),
}),
}),
system: z.object({
uid: z.string(),
}),
})
export type Page = z.infer<typeof page>
const metaDataItems = z.object({
items: z.array(page),
})
export const validateMyPagesMetaDataContentstackSchema = z.object({
all_account_page: metaDataItems,
})
export type GetMyPagesMetaDataData = z.infer<
typeof validateMyPagesMetaDataContentstackSchema
>
export const validateLoyaltyPageMetaDataContentstackSchema = z.object({
all_loyalty_page: metaDataItems,
})
export type GetLoyaltyPageMetaDataData = z.infer<
typeof validateLoyaltyPageMetaDataContentstackSchema
>

View File

@@ -0,0 +1,78 @@
import { GetLoyaltyPageMetaData } from "@/lib/graphql/Query/MetaDataLoyaltyPage.graphql"
import { GetMyPagesMetaData } from "@/lib/graphql/Query/MetaDataMyPages.graphql"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
type GetLoyaltyPageMetaDataData,
type GetMyPagesMetaDataData,
validateLoyaltyPageMetaDataContentstackSchema,
validateMyPagesMetaDataContentstackSchema,
} from "./output"
import { getMetaData, getResponse, Variables } from "./utils"
import { PageTypeEnum } from "@/types/requests/pageType"
async function getLoyaltyPageMetaData(variables: Variables) {
const response = await getResponse<GetLoyaltyPageMetaDataData>(
GetLoyaltyPageMetaData,
variables
)
if (!response.data.all_loyalty_page.items[0].web?.seo_metadata?.title) {
return null
}
const validatedMetaDataData =
validateLoyaltyPageMetaDataContentstackSchema.safeParse(response.data)
if (!validatedMetaDataData.success) {
console.error(
`Failed to validate Loyaltypage MetaData Data - (url: ${variables.url})`
)
console.error(validatedMetaDataData.error)
return null
}
return getMetaData(validatedMetaDataData.data.all_loyalty_page.items[0])
}
async function getMyPagesMetaData(variables: Variables) {
const response = await getResponse<GetMyPagesMetaDataData>(
GetMyPagesMetaData,
variables
)
if (!response.data.all_account_page.items[0].web?.seo_metadata?.title) {
return []
}
const validatedMetaDataData =
validateMyPagesMetaDataContentstackSchema.safeParse(response.data)
if (!validatedMetaDataData.success) {
console.error(
`Failed to validate My Page MetaData Data - (url: ${variables.url})`
)
console.error(validatedMetaDataData.error)
return null
}
return getMetaData(validatedMetaDataData.data.all_account_page.items[0])
}
export const metaDataQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const variables = {
locale: ctx.lang,
url: ctx.pathname,
}
switch (ctx.contentType) {
case PageTypeEnum.accountPage:
return await getMyPagesMetaData(variables)
case PageTypeEnum.loyaltyPage:
return await getLoyaltyPageMetaData(variables)
default:
return []
}
}),
})

View File

@@ -0,0 +1,34 @@
import { Lang } from "@/constants/languages"
import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc"
import { getMetaDataSchema, Page } from "./output"
export type Variables = {
locale: Lang
url: string
}
export async function getResponse<T>(query: string, variables: Variables) {
const response = await request<T>(query, variables)
if (!response.data) {
throw notFound(response)
}
return response
}
export function getMetaData(page: Page) {
const pageMetaData = {
title: page.web.seo_metadata.title,
description: page.web.seo_metadata.description,
imageConnection: page.web.seo_metadata.imageConnection,
uid: page.system.uid,
}
const validatedMetaData = getMetaDataSchema.safeParse(pageMetaData)
if (!validatedMetaData.success) {
throw internalServerError(validatedMetaData.error)
}
return validatedMetaData.data
}

View File

@@ -0,0 +1,5 @@
import { z } from "zod"
import { getMetaDataSchema } from "@/server/routers/contentstack/metadata/output"
export interface MetaData extends z.infer<typeof getMetaDataSchema> {}

39
utils/generateMetadata.ts Normal file
View File

@@ -0,0 +1,39 @@
import { Metadata } from "next"
import { serverClient } from "@/lib/trpc/server"
import { MetaData } from "@/types/components/metadata"
import { LangParams, PageArgs } from "@/types/params"
export async function generateMetadata({
params,
pageTitle,
}: PageArgs<LangParams> & { pageTitle?: string }): Promise<Metadata> {
console.log("PARAMS", params)
const metaData: MetaData | never[] | null =
await serverClient().contentstack.metaData.get()
if (Array.isArray(metaData)) {
return {
title: pageTitle ?? "",
description: "",
openGraph: {
images: [],
},
}
}
const title = metaData?.title ?? pageTitle ?? ""
const description = metaData?.description ?? ""
const images =
metaData?.imageConnection?.edges?.map((edge) => ({
url: edge.node.url,
})) || []
return {
title,
description,
openGraph: {
images,
},
}
}