feat(SW-2859): Create trpc package * Add isEdge, safeTry and dataCache to new common package * Add eslint and move prettier config * Clean up tests * Create trpc package and move initialization * Move errors and a few procedures * Move telemetry to common package * Move tokenManager to common package * Add Sentry to procedures * Clean up procedures * Fix self-referencing imports * Add exports to packages and lint rule to prevent relative imports * Add env to trpc package * Add eslint to trpc package * Apply lint rules * Use direct imports from trpc package * Add lint-staged config to trpc * Move lang enum to common * Restructure trpc package folder structure * Fix lang imports Approved-by: Linus Flood
215 lines
5.7 KiB
TypeScript
215 lines
5.7 KiB
TypeScript
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 { Lang } from "@scandic-hotels/common/constants/language"
|
|
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"
|
|
|
|
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
|
|
.union([
|
|
z.string(),
|
|
z.object({
|
|
first_column: z.string(),
|
|
}),
|
|
])
|
|
.transform((preamble) => {
|
|
if (typeof preamble === "string") {
|
|
return preamble
|
|
}
|
|
|
|
return preamble?.first_column || null
|
|
})
|
|
.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 } }
|
|
}
|