feat(SW-266): Replacing static metadata with data from Contentstack on Loyalty pages and Account Pages
This commit is contained in:
@@ -5,11 +5,20 @@ import Title from "@/components/TempDesignSystem/Text/Title"
|
|||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
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({
|
export default async function MyPages({
|
||||||
params,
|
params,
|
||||||
}: PageArgs<LangParams & { path: string[] }>) {
|
}: PageArgs<LangParams & { path: string[] }>) {
|
||||||
@@ -34,7 +43,6 @@ export default async function MyPages({
|
|||||||
<p>{formatMessage({ id: "No content published" })}</p>
|
<p>{formatMessage({ id: "No content published" })}</p>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<TrackingSDK pageData={tracking} />
|
<TrackingSDK pageData={tracking} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import ContentPage from "@/components/ContentType/ContentPage"
|
import ContentPage from "@/components/ContentType/ContentPage"
|
||||||
import HotelPage from "@/components/ContentType/HotelPage/HotelPage"
|
import HotelPage from "@/components/ContentType/HotelPage/HotelPage"
|
||||||
import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage"
|
import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ContentTypeParams,
|
ContentTypeParams,
|
||||||
@@ -12,6 +15,22 @@ import {
|
|||||||
UIDParams,
|
UIDParams,
|
||||||
} from "@/types/params"
|
} 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({
|
export default async function ContentTypePage({
|
||||||
params,
|
params,
|
||||||
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
||||||
|
|||||||
@@ -12,15 +12,11 @@ import { preloadUserTracking } from "@/components/TrackingSDK"
|
|||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import ServerIntlProvider from "@/i18n/Provider"
|
import ServerIntlProvider from "@/i18n/Provider"
|
||||||
import { getLang, setLang } from "@/i18n/serverContext"
|
import { getLang, setLang } from "@/i18n/serverContext"
|
||||||
|
import { generateMetadata } from "@/utils/generateMetadata"
|
||||||
import type { Metadata } from "next"
|
|
||||||
|
|
||||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export { generateMetadata }
|
||||||
description: "New web",
|
|
||||||
title: "Scandic Hotels",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
@@ -33,8 +29,8 @@ export default async function RootLayout({
|
|||||||
>) {
|
>) {
|
||||||
setLang(params.lang)
|
setLang(params.lang)
|
||||||
preloadUserTracking()
|
preloadUserTracking()
|
||||||
|
|
||||||
const { defaultLocale, locale, messages } = await getIntl()
|
const { defaultLocale, locale, messages } = await getIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={getLang()}>
|
<html lang={getLang()}>
|
||||||
<head>
|
<head>
|
||||||
|
|||||||
17
lib/graphql/Fragments/LoyaltyPage/MetaData.graphql
Normal file
17
lib/graphql/Fragments/LoyaltyPage/MetaData.graphql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#import "../Image.graphql"
|
||||||
|
|
||||||
|
fragment LoyaltyPageMetaData on LoyaltyPage {
|
||||||
|
web {
|
||||||
|
seo_metadata {
|
||||||
|
title
|
||||||
|
description
|
||||||
|
imageConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
lib/graphql/Fragments/MyPages/MetaData.graphql
Normal file
17
lib/graphql/Fragments/MyPages/MetaData.graphql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#import "../Image.graphql"
|
||||||
|
|
||||||
|
fragment MyPagesMetaData on AccountPage {
|
||||||
|
web {
|
||||||
|
seo_metadata {
|
||||||
|
title
|
||||||
|
description
|
||||||
|
imageConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
lib/graphql/Query/MetaDataLoyaltyPage.graphql
Normal file
12
lib/graphql/Query/MetaDataLoyaltyPage.graphql
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
lib/graphql/Query/MetaDataMyPages.graphql
Normal file
12
lib/graphql/Query/MetaDataMyPages.graphql
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { breadcrumbsRouter } from "./breadcrumbs"
|
|||||||
import { hotelPageRouter } from "./hotelPage"
|
import { hotelPageRouter } from "./hotelPage"
|
||||||
import { languageSwitcherRouter } from "./languageSwitcher"
|
import { languageSwitcherRouter } from "./languageSwitcher"
|
||||||
import { loyaltyPageRouter } from "./loyaltyPage"
|
import { loyaltyPageRouter } from "./loyaltyPage"
|
||||||
|
import { metaDataRouter } from "./metadata"
|
||||||
import { myPagesRouter } from "./myPages"
|
import { myPagesRouter } from "./myPages"
|
||||||
|
|
||||||
export const contentstackRouter = router({
|
export const contentstackRouter = router({
|
||||||
@@ -16,4 +17,5 @@ export const contentstackRouter = router({
|
|||||||
languageSwitcher: languageSwitcherRouter,
|
languageSwitcher: languageSwitcherRouter,
|
||||||
loyaltyPage: loyaltyPageRouter,
|
loyaltyPage: loyaltyPageRouter,
|
||||||
myPages: myPagesRouter,
|
myPages: myPagesRouter,
|
||||||
|
metaData: metaDataRouter,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [
|
|||||||
])
|
])
|
||||||
|
|
||||||
export const validateLoyaltyPageSchema = z.object({
|
export const validateLoyaltyPageSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
heading: z.string().nullable(),
|
heading: z.string().nullable(),
|
||||||
blocks: z.array(loyaltyPageBlockItem).nullable(),
|
blocks: z.array(loyaltyPageBlockItem).nullable(),
|
||||||
sidebar: z.array(loyaltyPageSidebarItem).nullable(),
|
sidebar: z.array(loyaltyPageSidebarItem).nullable(),
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ export const loyaltyPageQueryRouter = router({
|
|||||||
: null
|
: null
|
||||||
|
|
||||||
const loyaltyPage = {
|
const loyaltyPage = {
|
||||||
|
title: response.data.loyalty_page.title,
|
||||||
heading: response.data.loyalty_page.heading,
|
heading: response.data.loyalty_page.heading,
|
||||||
system: response.data.loyalty_page.system,
|
system: response.data.loyalty_page.system,
|
||||||
blocks,
|
blocks,
|
||||||
|
|||||||
5
server/routers/contentstack/metadata/index.ts
Normal file
5
server/routers/contentstack/metadata/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { mergeRouters } from "@/server/trpc"
|
||||||
|
|
||||||
|
import { metaDataQueryRouter } from "./query"
|
||||||
|
|
||||||
|
export const metaDataRouter = mergeRouters(metaDataQueryRouter)
|
||||||
62
server/routers/contentstack/metadata/output.ts
Normal file
62
server/routers/contentstack/metadata/output.ts
Normal 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
|
||||||
|
>
|
||||||
78
server/routers/contentstack/metadata/query.ts
Normal file
78
server/routers/contentstack/metadata/query.ts
Normal 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 []
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
34
server/routers/contentstack/metadata/utils.ts
Normal file
34
server/routers/contentstack/metadata/utils.ts
Normal 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
|
||||||
|
}
|
||||||
5
types/components/metadata/index.ts
Normal file
5
types/components/metadata/index.ts
Normal 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
39
utils/generateMetadata.ts
Normal 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user