feat(SW-2782): Updated header as per design, added language switcher and user menu * feat(SW-2782): Updated header as per design, added language switcher and user menu * feat(SW-2782): Updated UI as per design * feat(SW-2782): Optimised code with use of Popover and modal from RAC Approved-by: Anton Gunnarsson
285 lines
7.0 KiB
TypeScript
285 lines
7.0 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import {
|
|
type FindMyBookingRoute,
|
|
findMyBookingRoutes,
|
|
} from "@scandic-hotels/common/constants/routes/findMyBookingRoutes"
|
|
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
|
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
|
|
|
|
import { Country } from "../../../types/country"
|
|
import { RTETypeEnum } from "../../../types/RTEenums"
|
|
import { destinationFilterSchema } from "../schemas/destinationFilters"
|
|
import { systemSchema } from "../schemas/system"
|
|
|
|
import type {
|
|
Lang,
|
|
LanguageSwitcherData,
|
|
} from "@scandic-hotels/common/constants/language"
|
|
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
|
|
|
|
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,
|
|
})
|
|
.nullish(),
|
|
})
|
|
.nullish(),
|
|
})
|
|
)
|
|
.nullish()
|
|
|
|
const metaDataImageSchema = z.object({
|
|
src: z.string().nullish(),
|
|
altText: z.string().nullish(),
|
|
altText_En: z.string().nullish(),
|
|
})
|
|
|
|
const metaDataHotelDataSchema = z
|
|
.object({
|
|
name: z.string(),
|
|
detailedFacilities: z
|
|
.array(
|
|
z.object({
|
|
name: z.string(),
|
|
})
|
|
)
|
|
.nullish(),
|
|
healthFacilities: z
|
|
.array(
|
|
z
|
|
.object({
|
|
content: z
|
|
.object({
|
|
images: z.array(metaDataImageSchema),
|
|
})
|
|
.nullish(),
|
|
})
|
|
.nullish()
|
|
)
|
|
.nullish(),
|
|
translatedCityName: z.string(),
|
|
})
|
|
.nullish()
|
|
|
|
const metaDataAdditionalHotelDataSchema = z
|
|
.object({
|
|
gallery: z
|
|
.object({
|
|
heroImages: z.array(metaDataImageSchema).nullish(),
|
|
smallerImages: z.array(metaDataImageSchema).nullish(),
|
|
})
|
|
.nullish(),
|
|
hotelParking: z
|
|
.object({
|
|
nameInUrl: z.string().nullish(),
|
|
elevatorPitch: z.string().nullish(),
|
|
})
|
|
.nullish(),
|
|
healthAndFitness: z
|
|
.object({
|
|
nameInUrl: z.string().nullish(),
|
|
elevatorPitch: z.string().nullish(),
|
|
})
|
|
.nullish(),
|
|
hotelSpecialNeeds: z
|
|
.object({
|
|
nameInUrl: z.string().nullish(),
|
|
elevatorPitch: z.string().nullish(),
|
|
})
|
|
.nullish(),
|
|
meetingRooms: z
|
|
.object({
|
|
nameInUrl: z.string().nullish(),
|
|
elevatorPitch: z.string().nullish(),
|
|
})
|
|
.nullish(),
|
|
parkingImages: z
|
|
.object({
|
|
heroImages: z.array(metaDataImageSchema).nullish(),
|
|
})
|
|
.nullish(),
|
|
accessibility: z
|
|
.object({
|
|
heroImages: z.array(metaDataImageSchema).nullish(),
|
|
})
|
|
.nullish(),
|
|
conferencesAndMeetings: z
|
|
.object({
|
|
heroImages: z.array(metaDataImageSchema).nullish(),
|
|
})
|
|
.nullish(),
|
|
})
|
|
.nullish()
|
|
|
|
const metaDataHotelRestaurantsSchema = z
|
|
.array(
|
|
z.object({
|
|
nameInUrl: z.string().nullish(),
|
|
elevatorPitch: z.string().nullish(),
|
|
name: z.string().nullish(),
|
|
content: z
|
|
.object({
|
|
images: z.array(metaDataImageSchema).nullish(),
|
|
})
|
|
.nullish(),
|
|
})
|
|
)
|
|
.nullish()
|
|
|
|
export const seoMetadataSchema = z
|
|
.object({
|
|
title: z.string().nullish(),
|
|
description: z.string().nullish(),
|
|
noindex: z.boolean().nullish(),
|
|
seo_image: transformedImageVaultAssetSchema,
|
|
})
|
|
.nullish()
|
|
|
|
export const rawMetadataSchema = z.object({
|
|
web: z
|
|
.object({
|
|
seo_metadata: seoMetadataSchema,
|
|
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: transformedImageVaultAssetSchema,
|
|
})
|
|
.nullish(),
|
|
hero_image: transformedImageVaultAssetSchema,
|
|
images: z
|
|
.array(z.object({ image: transformedImageVaultAssetSchema }).nullish())
|
|
.transform((images) =>
|
|
images
|
|
.map((image) => image?.image)
|
|
.filter((image): image is ImageVaultAsset => !!image)
|
|
)
|
|
.nullish(),
|
|
blocks: metaDataBlocksSchema,
|
|
hotel_page_id: z
|
|
.string()
|
|
.nullish()
|
|
.transform((id) => id?.trim() || null),
|
|
hotelData: metaDataHotelDataSchema,
|
|
additionalHotelData: metaDataAdditionalHotelDataSchema,
|
|
hotelRestaurants: metaDataHotelRestaurantsSchema,
|
|
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(),
|
|
seo_filters: z
|
|
.array(
|
|
destinationFilterSchema.merge(
|
|
z.object({ seo_metadata: seoMetadataSchema })
|
|
)
|
|
)
|
|
.nullish(),
|
|
system: systemSchema,
|
|
})
|
|
|
|
export interface RawMetadataSchema extends z.output<typeof rawMetadataSchema> {}
|
|
|
|
// 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 (isFindMyBookingUrl(pathName)) {
|
|
const urls: LanguageSwitcherData = {}
|
|
return Object.entries(findMyBookingRoutes).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 } }
|
|
}
|
|
|
|
const baseUrls: LanguageSwitcherData = {
|
|
da: { url: "/da/" },
|
|
de: { url: "/de/" },
|
|
en: { url: "/en/" },
|
|
fi: { url: "/fi/" },
|
|
no: { url: "/no/" },
|
|
sv: { url: "/sv/" },
|
|
}
|
|
|
|
function hotelreservation(lang: Lang) {
|
|
return `/${lang}/hotelreservation`
|
|
}
|
|
|
|
function isFindMyBookingUrl(value: string): value is FindMyBookingRoute {
|
|
return Object.values(
|
|
findMyBookingRoutes as unknown as readonly string[]
|
|
).includes(value)
|
|
}
|