feat(SW-2007): Added metadata for hotel subpages

Approved-by: Fredrik Thorsson
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-03-26 10:36:58 +00:00
parent c065101b7c
commit ca23589f88
9 changed files with 246 additions and 11 deletions

View File

@@ -8,6 +8,7 @@ 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, getImage, getTitle } from "./utils"
@@ -98,13 +99,44 @@ export const rawMetadataSchema = z.object({
blocks: metaDataBlocksSchema,
hotel_page_id: z.string().optional().nullable(),
hotelData: hotelAttributesSchema
.pick({ name: true, address: true, hotelContent: true })
.pick({
name: true,
address: true,
hotelContent: true,
healthFacilities: true,
})
.optional()
.nullable(),
additionalHotelData: additionalDataAttributesSchema
.pick({ gallery: true })
.pick({
gallery: true,
hotelParking: true,
healthAndFitness: true,
hotelSpecialNeeds: true,
meetingRooms: true,
parkingImages: true,
accessibility: true,
conferencesAndMeetings: true,
})
.optional()
.nullable(),
hotelRestaurants: z
.array(
z.object({
nameInUrl: z.string().optional().nullable(),
elevatorPitch: z.string().optional().nullable(),
name: z.string().optional().nullable(),
content: z
.object({
images: z.array(imageSchema).optional().nullable(),
})
.optional()
.nullable(),
})
)
.optional()
.nullable(),
subpageUrl: z.string().optional().nullable(),
location: z.string().optional().nullable(),
filter: z.string().optional().nullable(),
filterType: z.enum(["facility", "surroundings"]).optional().nullable(),

View File

@@ -250,10 +250,12 @@ export const metadataQueryRouter = router({
...hotelPageData,
...(hotelData
? {
hotelData: hotelData?.hotel,
additionalHotelData: hotelData?.additionalData,
hotelData: hotelData.hotel,
additionalHotelData: hotelData.additionalData,
hotelRestaurants: hotelData.restaurants,
}
: {}),
subpageUrl: input.subpage,
})
break
case PageContentTypeEnum.startPage:

View File

@@ -81,6 +81,62 @@ export async function getTitle(data: RawMetadataSchema) {
return metadata.title
}
if (data.hotelData) {
if (data.subpageUrl) {
const restaurantSubPage = data.hotelRestaurants?.find(
(restaurant) => restaurant.nameInUrl === data.subpageUrl
)
if (restaurantSubPage) {
return intl.formatMessage(
{ id: "Explore {restaurantName} at {hotelName} in {destination}" },
{
restaurantName: restaurantSubPage.name,
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
}
switch (data.subpageUrl) {
case data.additionalHotelData?.hotelParking.nameInUrl:
return intl.formatMessage(
{ id: "Parking information for {hotelName} in {destination}" },
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
case data.additionalHotelData?.healthAndFitness.nameInUrl:
return intl.formatMessage(
{ id: "Gym & Health Facilities at {hotelName} in {destination}" },
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
return intl.formatMessage(
{
id: "Accessibility information for {hotelName} in {destination}",
},
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
case data.additionalHotelData?.meetingRooms.nameInUrl:
return intl.formatMessage(
{
id: "Meetings, Conferences & Events at {hotelName} in {destination}",
},
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
default:
break
}
}
return intl.formatMessage(
{ id: "Stay at {hotelName} | Hotel in {destination}" },
{
@@ -129,6 +185,40 @@ export function getDescription(data: RawMetadataSchema) {
return metadata.description
}
if (data.hotelData) {
if (data.subpageUrl) {
let subpageDescription: string | undefined
const restaurantSubPage = data.hotelRestaurants?.find(
(restaurant) => restaurant.nameInUrl === data.subpageUrl
)
if (restaurantSubPage?.elevatorPitch) {
subpageDescription = restaurantSubPage.elevatorPitch
}
switch (data.subpageUrl) {
case data.additionalHotelData?.hotelParking.nameInUrl:
subpageDescription =
data.additionalHotelData?.hotelParking.elevatorPitch
break
case data.additionalHotelData?.healthAndFitness.nameInUrl:
subpageDescription =
data.additionalHotelData?.healthAndFitness.elevatorPitch
break
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
subpageDescription =
data.additionalHotelData?.hotelSpecialNeeds.elevatorPitch
break
case data.additionalHotelData?.meetingRooms.nameInUrl:
subpageDescription =
data.additionalHotelData?.meetingRooms.elevatorPitch
break
default:
break
}
if (subpageDescription) {
return truncateTextAfterLastPeriod(subpageDescription)
}
}
return data.hotelData.hotelContent.texts.descriptions?.short
}
if (data.preamble) {
@@ -156,9 +246,6 @@ export function getDescription(data: RawMetadataSchema) {
export function getImage(data: RawMetadataSchema) {
const metadataImage = data.web?.seo_metadata?.seo_image
const heroImage = data.hero_image || data.header?.hero_image
const hotelImage =
data.additionalHotelData?.gallery?.heroImages?.[0] ||
data.additionalHotelData?.gallery?.smallerImages?.[0]
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
if (metadataImage) {
@@ -169,10 +256,94 @@ export function getImage(data: RawMetadataSchema) {
height: metadataImage.dimensions.height,
}
}
if (hotelImage) {
return {
url: hotelImage.imageSizes.small,
alt: hotelImage.metaData.altText || undefined,
if (data.hotelData) {
if (data.subpageUrl) {
let subpageImage: { url: string; alt: string } | undefined
const restaurantSubPage = data.hotelRestaurants?.find(
(restaurant) => restaurant.nameInUrl === data.subpageUrl
)
const restaurantImage = restaurantSubPage?.content?.images?.[0]
if (restaurantImage) {
subpageImage = {
url: restaurantImage.imageSizes.small,
alt:
restaurantImage.metaData.altText ||
restaurantImage.metaData.altText_En ||
"",
}
}
switch (data.subpageUrl) {
case data.additionalHotelData?.hotelParking.nameInUrl:
const parkingImage =
data.additionalHotelData?.parkingImages?.heroImages[0]
if (parkingImage) {
subpageImage = {
url: parkingImage.imageSizes.small,
alt:
parkingImage.metaData.altText ||
parkingImage.metaData.altText_En ||
"",
}
}
break
case data.additionalHotelData?.healthAndFitness.nameInUrl:
const wellnessImage = data.hotelData.healthFacilities.find(
(fac) => fac.content.images.length
)?.content.images[0]
if (wellnessImage) {
subpageImage = {
url: wellnessImage.imageSizes.small,
alt:
wellnessImage.metaData.altText ||
wellnessImage.metaData.altText_En ||
"",
}
}
break
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
const accessibilityImage =
data.additionalHotelData?.accessibility?.heroImages[0]
if (accessibilityImage) {
subpageImage = {
url: accessibilityImage.imageSizes.small,
alt:
accessibilityImage.metaData.altText ||
accessibilityImage.metaData.altText_En ||
"",
}
}
break
case data.additionalHotelData?.meetingRooms.nameInUrl:
const meetingImage =
data.additionalHotelData?.conferencesAndMeetings?.heroImages[0]
if (meetingImage) {
subpageImage = {
url: meetingImage.imageSizes.small,
alt:
meetingImage.metaData.altText ||
meetingImage.metaData.altText_En ||
"",
}
}
break
default:
break
}
if (subpageImage) {
return subpageImage
}
}
const hotelImage =
data.additionalHotelData?.gallery?.heroImages?.[0] ||
data.additionalHotelData?.gallery?.smallerImages?.[0]
if (hotelImage) {
return {
url: hotelImage.imageSizes.small,
alt: hotelImage.metaData.altText || undefined,
}
}
}
if (heroImage) {