import { z } from "zod" import { baseUrls } from "@/constants/routes/baseUrls" import { findMyBooking } from "@/constants/routes/findMyBooking" import { hotelreservation } from "@/constants/routes/hotelReservation" import { myStay } from "@/constants/routes/myStay" import { env } from "@/env/server" import { attributesSchema as hotelAttributesSchema } from "../../hotels/schemas/hotel" import { additionalDataAttributesSchema } from "../../hotels/schemas/hotel/include/additionalData" import { imageSchema } from "../../hotels/schemas/image" import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { systemSchema } from "../schemas/system" import { getDescription } from "./utils/description" import { getImage } from "./utils/image" import { getTitle } from "./utils/title" import type { Metadata } from "next" import type { ImageVaultAsset } from "@/types/components/imageVault" import { Country } from "@/types/enums/country" import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher" import { RTETypeEnum } from "@/types/rte/enums" import type { Lang } from "@/constants/languages" const metaDataJsonSchema = z.object({ children: z.array( z.object({ type: z.nativeEnum(RTETypeEnum), children: z.array( z.object({ text: z.string().optional(), }) ), }) ), }) const metaDataBlocksSchema = z .array( z.object({ content: z .object({ content: z .object({ json: metaDataJsonSchema, }) .optional() .nullable(), }) .optional() .nullable(), }) ) .optional() .nullable() export const rawMetadataSchema = z.object({ web: z .object({ seo_metadata: z .object({ title: z.string().nullish(), description: z.string().nullish(), noindex: z.boolean().nullish(), seo_image: tempImageVaultAssetSchema.nullable(), }) .nullish(), breadcrumbs: z .object({ title: z.string().nullish(), }) .nullish(), }) .nullish(), destination_settings: z .object({ city_denmark: z.string().nullish(), city_finland: z.string().nullish(), city_germany: z.string().nullish(), city_poland: z.string().nullish(), city_norway: z.string().nullish(), city_sweden: z.string().nullish(), country: z.nativeEnum(Country).nullish(), }) .nullish(), heading: z.string().nullish(), preamble: z.string().nullish(), header: z .object({ heading: z.string().nullish(), preamble: z.string().nullish(), hero_image: tempImageVaultAssetSchema.nullable(), }) .nullish(), hero_image: tempImageVaultAssetSchema.nullable(), images: z .array(z.object({ image: tempImageVaultAssetSchema }).nullish()) .transform((images) => images .map((image) => image?.image) .filter((image): image is ImageVaultAsset => !!image) ) .nullish(), blocks: metaDataBlocksSchema, hotel_page_id: z.string().nullish(), hotelData: hotelAttributesSchema .pick({ name: true, address: true, detailedFacilities: true, hotelContent: true, healthFacilities: true, }) .nullish(), additionalHotelData: additionalDataAttributesSchema .pick({ gallery: true, hotelParking: true, healthAndFitness: true, hotelSpecialNeeds: true, meetingRooms: true, parkingImages: true, accessibility: true, conferencesAndMeetings: true, }) .nullish(), hotelRestaurants: z .array( z.object({ nameInUrl: z.string().nullish(), elevatorPitch: z.string().nullish(), name: z.string().nullish(), content: z .object({ images: z.array(imageSchema).nullish(), }) .nullish(), }) ) .nullish(), subpageUrl: z.string().nullish(), destinationData: z .object({ location: z.string().nullish(), filter: z.string().nullish(), filterType: z.enum(["facility", "surroundings"]).nullish(), hotelCount: z.number().nullish(), cities: z.array(z.string()).nullish(), }) .nullish(), system: systemSchema, }) export const metadataSchema = rawMetadataSchema.transform(async (data) => { const noIndex = !!data.web?.seo_metadata?.noindex const metadata: Metadata = { metadataBase: env.PUBLIC_URL ? new URL(env.PUBLIC_URL) : undefined, title: await getTitle(data), description: await getDescription(data), openGraph: { images: getImage(data), }, } if (noIndex) { metadata.robots = { index: false, follow: false, } } return metadata }) // Several pages are not currently routed within contentstack context. // This function is used to generate the urls for these pages. export function getNonContentstackUrls(lang: Lang, pathName: string) { if (Object.values(findMyBooking).includes(pathName)) { const urls: LanguageSwitcherData = {} return Object.entries(findMyBooking).reduce((acc, [lang, url]) => { acc[lang as Lang] = { url } return urls }, urls) } if (Object.values(myStay).includes(pathName)) { const urls: LanguageSwitcherData = {} return Object.entries(myStay).reduce((acc, [lang, url]) => { acc[lang as Lang] = { url } return urls }, urls) } if (pathName.startsWith(hotelreservation(lang))) { return baseUrls } return { [lang]: { url: pathName } } }