feat(SW-266): Replacing static metadata with data from Contentstack on Loyalty pages and Account Pages
Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -12,6 +12,7 @@ CURITY_CLIENT_SECRET_SERVICE="test"
|
||||
CURITY_CLIENT_ID_USER="test"
|
||||
CURITY_CLIENT_SECRET_USER="test"
|
||||
CURITY_ISSUER_USER="test"
|
||||
CURITY_ISSUER_SERVICE="test"
|
||||
CYPRESS_API_BASEURL="test"
|
||||
CYPRESS_CURITY_USERNAME="test"
|
||||
CYPRESS_CURITY_PASSWORD="test"
|
||||
@@ -35,3 +36,4 @@ SEAMLESS_LOGOUT_FI="test"
|
||||
SEAMLESS_LOGOUT_NO="test"
|
||||
SEAMLESS_LOGOUT_SV="test"
|
||||
WEBVIEW_ENCRYPTION_KEY="test"
|
||||
BOOKING_ENCRYPTION_KEY="test"
|
||||
|
||||
@@ -34,7 +34,6 @@ export default async function MyPages({
|
||||
<p>{formatMessage({ id: "No content published" })}</p>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<TrackingSDK pageData={tracking} />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -15,14 +15,9 @@ import { getIntl } from "@/i18n"
|
||||
import ServerIntlProvider from "@/i18n/Provider"
|
||||
import { getLang, setLang } from "@/i18n/serverContext"
|
||||
|
||||
import type { Metadata } from "next"
|
||||
|
||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description: "New web",
|
||||
title: "Scandic Hotels",
|
||||
}
|
||||
export { generateMetadata } from "@/utils/generateMetadata"
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
@@ -35,8 +30,8 @@ export default async function RootLayout({
|
||||
>) {
|
||||
setLang(params.lang)
|
||||
preloadUserTracking()
|
||||
|
||||
const { defaultLocale, locale, messages } = await getIntl()
|
||||
|
||||
return (
|
||||
<html lang={getLang()}>
|
||||
<head>
|
||||
|
||||
20
lib/graphql/Fragments/LoyaltyPage/MetaData.graphql
Normal file
20
lib/graphql/Fragments/LoyaltyPage/MetaData.graphql
Normal file
@@ -0,0 +1,20 @@
|
||||
#import "../Image.graphql"
|
||||
|
||||
fragment LoyaltyPageMetaData on LoyaltyPage {
|
||||
web {
|
||||
seo_metadata {
|
||||
title
|
||||
description
|
||||
imageConnection {
|
||||
edges {
|
||||
node {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
breadcrumbs {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/graphql/Fragments/MyPages/MetaData.graphql
Normal file
20
lib/graphql/Fragments/MyPages/MetaData.graphql
Normal file
@@ -0,0 +1,20 @@
|
||||
#import "../Image.graphql"
|
||||
|
||||
fragment MyPagesMetaData on AccountPage {
|
||||
web {
|
||||
seo_metadata {
|
||||
title
|
||||
description
|
||||
imageConnection {
|
||||
edges {
|
||||
node {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
breadcrumbs {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { contentPageRouter } from "./contentPage"
|
||||
import { hotelPageRouter } from "./hotelPage"
|
||||
import { languageSwitcherRouter } from "./languageSwitcher"
|
||||
import { loyaltyPageRouter } from "./loyaltyPage"
|
||||
import { metaDataRouter } from "./metadata"
|
||||
import { myPagesRouter } from "./myPages"
|
||||
|
||||
export const contentstackRouter = router({
|
||||
@@ -18,4 +19,5 @@ export const contentstackRouter = router({
|
||||
loyaltyPage: loyaltyPageRouter,
|
||||
contentPage: contentPageRouter,
|
||||
myPages: myPagesRouter,
|
||||
metaData: metaDataRouter,
|
||||
})
|
||||
|
||||
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)
|
||||
66
server/routers/contentstack/metadata/output.ts
Normal file
66
server/routers/contentstack/metadata/output.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const getMetaDataSchema = z.object({
|
||||
breadcrumbsTitle: z.string().optional(),
|
||||
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(),
|
||||
}),
|
||||
breadcrumbs: z.object({
|
||||
title: z.string(),
|
||||
}),
|
||||
}),
|
||||
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
|
||||
>
|
||||
71
server/routers/contentstack/metadata/query.ts
Normal file
71
server/routers/contentstack/metadata/query.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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 []
|
||||
}
|
||||
}),
|
||||
})
|
||||
35
server/routers/contentstack/metadata/utils.ts
Normal file
35
server/routers/contentstack/metadata/utils.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
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 = {
|
||||
breadcrumbsTitle: page.web.breadcrumbs.title,
|
||||
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> {}
|
||||
35
utils/generateMetadata.ts
Normal file
35
utils/generateMetadata.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import { MetaData } from "@/types/components/metadata"
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const metaData: MetaData | never[] | null =
|
||||
await serverClient().contentstack.metaData.get()
|
||||
|
||||
if (Array.isArray(metaData)) {
|
||||
return {
|
||||
title: "",
|
||||
description: "",
|
||||
openGraph: {
|
||||
images: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const title = metaData?.breadcrumbsTitle ?? metaData?.title ?? ""
|
||||
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