feat(SW-201): Added structured data for hotel pages
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { hotelAttributesSchema } from "../../hotels/output"
|
||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||
import { getDescription, getImage, getTitle } from "./utils"
|
||||
|
||||
@@ -72,13 +73,8 @@ export const rawMetadataSchema = z.object({
|
||||
hero_image: tempImageVaultAssetSchema.nullable(),
|
||||
blocks: metaDataBlocksSchema,
|
||||
hotel_page_id: z.string().optional().nullable(),
|
||||
hotelData: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
city: z.string(),
|
||||
description: z.string(),
|
||||
image: z.object({ url: z.string(), alt: z.string() }).nullable(),
|
||||
})
|
||||
hotelData: hotelAttributesSchema
|
||||
.pick({ name: true, address: true, hotelContent: true, gallery: true })
|
||||
.optional()
|
||||
.nullable(),
|
||||
})
|
||||
@@ -88,7 +84,7 @@ export const metadataSchema = rawMetadataSchema.transform(async (data) => {
|
||||
|
||||
const metadata: Metadata = {
|
||||
title: await getTitle(data),
|
||||
description: await getDescription(data),
|
||||
description: getDescription(data),
|
||||
openGraph: {
|
||||
images: getImage(data),
|
||||
},
|
||||
|
||||
@@ -153,26 +153,10 @@ export const metadataQueryRouter = router({
|
||||
)
|
||||
: null
|
||||
|
||||
const rawHotelData = hotelPageData
|
||||
|
||||
if (hotelData?.data.attributes) {
|
||||
const attributes = hotelData.data.attributes
|
||||
const images = attributes.gallery?.smallerImages
|
||||
|
||||
rawHotelData.hotelData = {
|
||||
name: attributes.name,
|
||||
city: attributes.cityName,
|
||||
description: attributes.hotelContent.texts.descriptions.short,
|
||||
image: images?.length
|
||||
? {
|
||||
url: images[0].imageSizes.small,
|
||||
alt: images[0].metaData.altText,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
return getTransformedMetadata(rawHotelData)
|
||||
return getTransformedMetadata({
|
||||
...hotelPageData,
|
||||
hotelData: hotelData?.data.attributes,
|
||||
})
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -69,7 +69,10 @@ export async function getTitle(data: RawMetadataSchema) {
|
||||
if (data.hotelData) {
|
||||
return intl.formatMessage(
|
||||
{ id: "Stay at HOTEL_NAME | Hotel in DESTINATION" },
|
||||
{ hotelName: data.hotelData.name, destination: data.hotelData.city }
|
||||
{
|
||||
hotelName: data.hotelData.name,
|
||||
destination: data.hotelData.address.city,
|
||||
}
|
||||
)
|
||||
}
|
||||
if (data.web?.breadcrumbs?.title) {
|
||||
@@ -84,14 +87,13 @@ export async function getTitle(data: RawMetadataSchema) {
|
||||
return ""
|
||||
}
|
||||
|
||||
export async function getDescription(data: RawMetadataSchema) {
|
||||
const intl = await getIntl()
|
||||
export function getDescription(data: RawMetadataSchema) {
|
||||
const metadata = data.web?.seo_metadata
|
||||
if (metadata?.description) {
|
||||
return metadata.description
|
||||
}
|
||||
if (data.hotelData) {
|
||||
return data.hotelData.description
|
||||
return data.hotelData.hotelContent.texts.descriptions.short
|
||||
}
|
||||
if (data.preamble) {
|
||||
return truncateTextAfterLastPeriod(data.preamble)
|
||||
@@ -118,6 +120,9 @@ export async function getDescription(data: RawMetadataSchema) {
|
||||
export function getImage(data: RawMetadataSchema) {
|
||||
const metadataImage = data.web?.seo_metadata?.seo_image
|
||||
const heroImage = data.hero_image
|
||||
const hotelImage =
|
||||
data.hotelData?.gallery?.heroImages?.[0] ||
|
||||
data.hotelData?.gallery?.smallerImages?.[0]
|
||||
|
||||
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
|
||||
if (metadataImage) {
|
||||
@@ -128,8 +133,11 @@ export function getImage(data: RawMetadataSchema) {
|
||||
height: metadataImage.dimensions.height,
|
||||
}
|
||||
}
|
||||
if (data.hotelData?.image) {
|
||||
return data.hotelData.image
|
||||
if (hotelImage) {
|
||||
return {
|
||||
url: hotelImage.imageSizes.small,
|
||||
alt: hotelImage.metaData.altText || undefined,
|
||||
}
|
||||
}
|
||||
if (heroImage) {
|
||||
return {
|
||||
|
||||
@@ -423,6 +423,47 @@ const hotelFactsSchema = z.object({
|
||||
yearBuilt: z.string(),
|
||||
})
|
||||
|
||||
export const hotelAttributesSchema = z.object({
|
||||
accessibilityElevatorPitchText: z.string().optional(),
|
||||
address: addressSchema,
|
||||
cityId: z.string(),
|
||||
cityName: z.string(),
|
||||
conferencesAndMeetings: facilitySchema.optional(),
|
||||
contactInformation: contactInformationSchema,
|
||||
detailedFacilities: z
|
||||
.array(detailedFacilitySchema)
|
||||
.transform((facilities) =>
|
||||
facilities.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||
),
|
||||
gallery: gallerySchema.optional(),
|
||||
galleryImages: z.array(imageSchema).optional(),
|
||||
healthAndWellness: facilitySchema.optional(),
|
||||
healthFacilities: z.array(healthFacilitySchema),
|
||||
hotelContent: hotelContentSchema,
|
||||
hotelFacts: hotelFactsSchema,
|
||||
hotelRoomElevatorPitchText: z.string().optional(),
|
||||
hotelType: z.string().optional(),
|
||||
isActive: z.boolean(),
|
||||
isPublished: z.boolean(),
|
||||
keywords: z.array(z.string()),
|
||||
location: locationSchema,
|
||||
merchantInformationData: merchantInformationSchema,
|
||||
name: z.string(),
|
||||
operaId: z.string(),
|
||||
parking: z.array(parkingSchema),
|
||||
pointsOfInterest: z
|
||||
.array(pointOfInterestSchema)
|
||||
.transform((pois) =>
|
||||
pois.sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0))
|
||||
),
|
||||
ratings: ratingsSchema,
|
||||
rewardNight: rewardNightSchema,
|
||||
restaurantImages: facilitySchema.optional(),
|
||||
socialMedia: socialMediaSchema,
|
||||
specialAlerts: specialAlertsSchema,
|
||||
specialNeedGroups: z.array(specialNeedGroupSchema),
|
||||
})
|
||||
|
||||
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
|
||||
export const getHotelDataSchema = z.object({
|
||||
data: z.object({
|
||||
@@ -435,46 +476,7 @@ export const getHotelDataSchema = z.object({
|
||||
}
|
||||
return lang
|
||||
}),
|
||||
attributes: z.object({
|
||||
accessibilityElevatorPitchText: z.string().optional(),
|
||||
address: addressSchema,
|
||||
cityId: z.string(),
|
||||
cityName: z.string(),
|
||||
conferencesAndMeetings: facilitySchema.optional(),
|
||||
contactInformation: contactInformationSchema,
|
||||
detailedFacilities: z
|
||||
.array(detailedFacilitySchema)
|
||||
.transform((facilities) =>
|
||||
facilities.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||
),
|
||||
gallery: gallerySchema.optional(),
|
||||
galleryImages: z.array(imageSchema).optional(),
|
||||
healthAndWellness: facilitySchema.optional(),
|
||||
healthFacilities: z.array(healthFacilitySchema),
|
||||
hotelContent: hotelContentSchema,
|
||||
hotelFacts: hotelFactsSchema,
|
||||
hotelRoomElevatorPitchText: z.string().optional(),
|
||||
hotelType: z.string().optional(),
|
||||
isActive: z.boolean(),
|
||||
isPublished: z.boolean(),
|
||||
keywords: z.array(z.string()),
|
||||
location: locationSchema,
|
||||
merchantInformationData: merchantInformationSchema,
|
||||
name: z.string(),
|
||||
operaId: z.string(),
|
||||
parking: z.array(parkingSchema),
|
||||
pointsOfInterest: z
|
||||
.array(pointOfInterestSchema)
|
||||
.transform((pois) =>
|
||||
pois.sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0))
|
||||
),
|
||||
ratings: ratingsSchema,
|
||||
rewardNight: rewardNightSchema,
|
||||
restaurantImages: facilitySchema.optional(),
|
||||
socialMedia: socialMediaSchema,
|
||||
specialAlerts: specialAlertsSchema,
|
||||
specialNeedGroups: z.array(specialNeedGroupSchema),
|
||||
}),
|
||||
attributes: hotelAttributesSchema,
|
||||
relationships: relationshipsSchema,
|
||||
}),
|
||||
// NOTE: We can pass an "include" param to the hotel API to retrieve
|
||||
|
||||
Reference in New Issue
Block a user