feat(BOOK-57): Adjusted metadata for destination pages with active seo filter
Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
@@ -13,7 +13,7 @@ export default function CityListingSkeleton() {
|
|||||||
<SkeletonShimmer height="30px" width="200px" />
|
<SkeletonShimmer height="30px" width="200px" />
|
||||||
<SkeletonShimmer height="30px" width="120px" />
|
<SkeletonShimmer height="30px" width="120px" />
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles.cityList}>
|
<ul className={styles.list}>
|
||||||
{Array.from({ length: 3 }).map((_, index) => (
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<CityListingItemSkeleton />
|
<CityListingItemSkeleton />
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function HotelListingSkeleton() {
|
|||||||
<SkeletonShimmer height="30px" width="300px" />
|
<SkeletonShimmer height="30px" width="300px" />
|
||||||
<SkeletonShimmer height="30px" width="100px" />
|
<SkeletonShimmer height="30px" width="100px" />
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles.hotelList}>
|
<ul className={styles.list}>
|
||||||
{Array.from({ length: 3 }).map((_, index) => (
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<HotelListingItemSkeleton />
|
<HotelListingItemSkeleton />
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { getIntl } from "@/i18n"
|
|
||||||
|
|
||||||
import { truncateTextAfterLastPeriod } from "../truncate"
|
|
||||||
|
|
||||||
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
|
||||||
|
|
||||||
export async function getDestinationCityPageDescription(
|
|
||||||
data: RawMetadataSchema
|
|
||||||
) {
|
|
||||||
const intl = await getIntl()
|
|
||||||
|
|
||||||
if (!data.destinationData || !data.destinationData.hotelCount) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const { hotelCount, location } = data.destinationData
|
|
||||||
|
|
||||||
if (hotelCount === 1) {
|
|
||||||
const destinationCitySingleHotelDescription = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Discover our Scandic hotel in {location}. Start your day with a delicious breakfast before exploring {location}. Book your stay at a Scandic hotel now!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
location: location,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return truncateTextAfterLastPeriod(destinationCitySingleHotelDescription)
|
|
||||||
}
|
|
||||||
const destinationCityMultipleHotelDescription = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Discover all our {hotelCount} Scandic hotels in {location}. Start your day with a delicious breakfast before exploring {location}. Book your stay at a Scandic hotel now!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelCount: hotelCount,
|
|
||||||
location: location,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return truncateTextAfterLastPeriod(destinationCityMultipleHotelDescription)
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { getIntl } from "@/i18n"
|
|
||||||
|
|
||||||
import { truncateTextAfterLastPeriod } from "../truncate"
|
|
||||||
|
|
||||||
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
|
||||||
|
|
||||||
export async function getDestinationCountryPageDescription(
|
|
||||||
data: RawMetadataSchema
|
|
||||||
) {
|
|
||||||
const intl = await getIntl()
|
|
||||||
|
|
||||||
if (!data.destinationData?.location) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const { hotelCount, location, cities } = data.destinationData
|
|
||||||
|
|
||||||
let destinationCountryDescription: string | null = null
|
|
||||||
|
|
||||||
if (!hotelCount) {
|
|
||||||
destinationCountryDescription = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Discover {location}. Enjoy your stay at a Scandic hotel. Book now!",
|
|
||||||
},
|
|
||||||
{ location }
|
|
||||||
)
|
|
||||||
} else if (!cities || cities.length < 2) {
|
|
||||||
destinationCountryDescription = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Discover all our {hotelCount} Scandic hotels in {location}. Enjoy your stay at a Scandic hotel. Book now!",
|
|
||||||
},
|
|
||||||
{ hotelCount, location }
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
destinationCountryDescription = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Discover all our {hotelCount} Scandic hotels in {location}. Explore {city1}, {city2}, and more! All while enjoying your stay at a Scandic hotel. Book now!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelCount: hotelCount,
|
|
||||||
location: location,
|
|
||||||
city1: cities[0],
|
|
||||||
city2: cities[1],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return truncateTextAfterLastPeriod(destinationCountryDescription)
|
|
||||||
}
|
|
||||||
110
apps/scandic-web/utils/metadata/description/destinationPage.ts
Normal file
110
apps/scandic-web/utils/metadata/description/destinationPage.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import { truncateTextAfterLastPeriod } from "../truncate"
|
||||||
|
|
||||||
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
|
export async function getDestinationCityPageDescription(
|
||||||
|
data: RawMetadataSchema
|
||||||
|
) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
|
||||||
|
if (!data.destinationData?.location) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hotelCount, location } = data.destinationData
|
||||||
|
|
||||||
|
if (hotelCount === 1) {
|
||||||
|
const destinationCitySingleHotelDescription = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Discover our Scandic hotel in {location}. Start your day with a delicious breakfast before exploring {location}. Book your stay at a Scandic hotel now!",
|
||||||
|
},
|
||||||
|
{ location }
|
||||||
|
)
|
||||||
|
|
||||||
|
return truncateTextAfterLastPeriod(destinationCitySingleHotelDescription)
|
||||||
|
}
|
||||||
|
const destinationCityMultipleHotelDescription = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Discover all our {hotelCount} Scandic hotels in {location}. Start your day with a delicious breakfast before exploring {location}. Book your stay at a Scandic hotel now!",
|
||||||
|
},
|
||||||
|
{ hotelCount, location }
|
||||||
|
)
|
||||||
|
|
||||||
|
return truncateTextAfterLastPeriod(destinationCityMultipleHotelDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDestinationCountryPageDescription(
|
||||||
|
data: RawMetadataSchema
|
||||||
|
) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
|
||||||
|
if (!data.destinationData?.location) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hotelCount, location, cities } = data.destinationData
|
||||||
|
|
||||||
|
let destinationCountryDescription: string | null = null
|
||||||
|
|
||||||
|
if (!hotelCount) {
|
||||||
|
destinationCountryDescription = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Discover {location}. Enjoy your stay at a Scandic hotel. Book now!",
|
||||||
|
},
|
||||||
|
{ location }
|
||||||
|
)
|
||||||
|
} else if (!cities || cities.length < 2) {
|
||||||
|
destinationCountryDescription = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Discover all our {hotelCount} Scandic hotels in {location}. Enjoy your stay at a Scandic hotel. Book now!",
|
||||||
|
},
|
||||||
|
{ hotelCount, location }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
destinationCountryDescription = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Discover all our {hotelCount} Scandic hotels in {location}. Explore {city1}, {city2}, and more! All while enjoying your stay at a Scandic hotel. Book now!",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hotelCount: hotelCount,
|
||||||
|
location: location,
|
||||||
|
city1: cities[0],
|
||||||
|
city2: cities[1],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return truncateTextAfterLastPeriod(destinationCountryDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDestinationFilterSeoMetaDescription(
|
||||||
|
data: RawMetadataSchema
|
||||||
|
) {
|
||||||
|
const filter = data.destinationData?.filter
|
||||||
|
|
||||||
|
if (!filter) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const foundSeoFilter = data.seo_filters?.find(
|
||||||
|
(f) => f.filterConnection.edges[0]?.node?.slug === filter
|
||||||
|
)
|
||||||
|
|
||||||
|
if (foundSeoFilter) {
|
||||||
|
if (foundSeoFilter.seo_metadata?.description) {
|
||||||
|
return foundSeoFilter.seo_metadata.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundSeoFilter.preamble) {
|
||||||
|
return truncateTextAfterLastPeriod(foundSeoFilter.preamble)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -1,15 +1,31 @@
|
|||||||
import { PageContentTypeEnum } from "@scandic-hotels/trpc/enums/contentType"
|
import { PageContentTypeEnum } from "@scandic-hotels/trpc/enums/contentType"
|
||||||
import { RTETypeEnum } from "@scandic-hotels/trpc/types/RTEenums"
|
import { RTETypeEnum } from "@scandic-hotels/trpc/types/RTEenums"
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDestinationCityPageDescription,
|
||||||
|
getDestinationCountryPageDescription,
|
||||||
|
getDestinationFilterSeoMetaDescription,
|
||||||
|
} from "@/utils/metadata/description/destinationPage"
|
||||||
|
|
||||||
import { truncateTextAfterLastPeriod } from "../truncate"
|
import { truncateTextAfterLastPeriod } from "../truncate"
|
||||||
import { getDestinationCityPageDescription } from "./destinationCityPage"
|
|
||||||
import { getDestinationCountryPageDescription } from "./destinationCountryPage"
|
|
||||||
import { getHotelPageDescription } from "./hotelPage"
|
import { getHotelPageDescription } from "./hotelPage"
|
||||||
|
|
||||||
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
export async function getDescription(data: RawMetadataSchema) {
|
export async function getDescription(data: RawMetadataSchema) {
|
||||||
const metadata = data.web?.seo_metadata
|
const metadata = data.web?.seo_metadata
|
||||||
|
const isDestinationPage = [
|
||||||
|
PageContentTypeEnum.destinationCityPage,
|
||||||
|
PageContentTypeEnum.destinationCountryPage,
|
||||||
|
].includes(data.system.content_type_uid as PageContentTypeEnum)
|
||||||
|
|
||||||
|
if (isDestinationPage) {
|
||||||
|
const destinationFilterSeoMetaDescription =
|
||||||
|
getDestinationFilterSeoMetaDescription(data)
|
||||||
|
if (destinationFilterSeoMetaDescription) {
|
||||||
|
return destinationFilterSeoMetaDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (metadata?.description) {
|
if (metadata?.description) {
|
||||||
return metadata.description
|
return metadata.description
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PageContentTypeEnum } from "@scandic-hotels/trpc/enums/contentType"
|
||||||
import { type RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
import { type RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
@@ -99,13 +100,33 @@ function getUrl(alternates: AlternateURLs | null): string | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNoIndexFromMetadata(data: RawMetadataSchema) {
|
||||||
|
const isDestinationPage = [
|
||||||
|
PageContentTypeEnum.destinationCityPage,
|
||||||
|
PageContentTypeEnum.destinationCountryPage,
|
||||||
|
].includes(data.system.content_type_uid as PageContentTypeEnum)
|
||||||
|
|
||||||
|
if (isDestinationPage) {
|
||||||
|
const filter = data.destinationData?.filter
|
||||||
|
if (filter) {
|
||||||
|
const foundSeoFilter = data.seo_filters?.find(
|
||||||
|
(f) => f.filterConnection.edges[0]?.node?.slug === filter
|
||||||
|
)
|
||||||
|
|
||||||
|
if (foundSeoFilter) {
|
||||||
|
return !!foundSeoFilter.seo_metadata?.noindex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!data.web?.seo_metadata?.noindex
|
||||||
|
}
|
||||||
|
|
||||||
async function getTransformedMetadata(
|
async function getTransformedMetadata(
|
||||||
data: RawMetadataSchema,
|
data: RawMetadataSchema,
|
||||||
alternates: Metadata["alternates"] | null,
|
alternates: Metadata["alternates"] | null,
|
||||||
robots: Metadata["robots"] | null = null
|
robots: Metadata["robots"] | null = null
|
||||||
) {
|
) {
|
||||||
const noIndex = !!data.web?.seo_metadata?.noindex
|
|
||||||
|
|
||||||
const metadata: Metadata = {
|
const metadata: Metadata = {
|
||||||
metadataBase: env.PUBLIC_URL ? new URL(env.PUBLIC_URL) : undefined,
|
metadataBase: env.PUBLIC_URL ? new URL(env.PUBLIC_URL) : undefined,
|
||||||
title: await getTitle(data),
|
title: await getTitle(data),
|
||||||
@@ -117,7 +138,7 @@ async function getTransformedMetadata(
|
|||||||
robots,
|
robots,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noIndex) {
|
if (isNoIndexFromMetadata(data)) {
|
||||||
metadata.robots = {
|
metadata.robots = {
|
||||||
index: false,
|
index: false,
|
||||||
follow: false,
|
follow: false,
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
|
||||||
|
|
||||||
export function getImage(data: RawMetadataSchema) {
|
|
||||||
const metadataImage = data.web?.seo_metadata?.seo_image
|
|
||||||
const heroImage =
|
|
||||||
data.hero_image || data.header?.hero_image || data.images?.[0]
|
|
||||||
|
|
||||||
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
|
|
||||||
if (metadataImage) {
|
|
||||||
return {
|
|
||||||
url: metadataImage.url,
|
|
||||||
alt: metadataImage.meta.alt || undefined,
|
|
||||||
width: metadataImage.dimensions.width,
|
|
||||||
height: metadataImage.dimensions.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.system.content_type_uid === "hotel_page" && 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.src,
|
|
||||||
alt: restaurantImage.altText || restaurantImage.altText_En || "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (data.subpageUrl) {
|
|
||||||
case data.additionalHotelData?.hotelParking.nameInUrl:
|
|
||||||
const parkingImage =
|
|
||||||
data.additionalHotelData?.parkingImages?.heroImages[0]
|
|
||||||
if (parkingImage) {
|
|
||||||
subpageImage = {
|
|
||||||
url: parkingImage.src,
|
|
||||||
alt: parkingImage.altText || parkingImage.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.src,
|
|
||||||
alt: wellnessImage.altText || wellnessImage.altText_En || "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
|
|
||||||
const accessibilityImage =
|
|
||||||
data.additionalHotelData?.accessibility?.heroImages[0]
|
|
||||||
if (accessibilityImage) {
|
|
||||||
subpageImage = {
|
|
||||||
url: accessibilityImage.src,
|
|
||||||
alt:
|
|
||||||
accessibilityImage.altText ||
|
|
||||||
accessibilityImage.altText_En ||
|
|
||||||
"",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case data.additionalHotelData?.meetingRooms.nameInUrl:
|
|
||||||
const meetingImage =
|
|
||||||
data.additionalHotelData?.conferencesAndMeetings?.heroImages[0]
|
|
||||||
if (meetingImage) {
|
|
||||||
subpageImage = {
|
|
||||||
url: meetingImage.src,
|
|
||||||
alt: meetingImage.altText || meetingImage.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.src,
|
|
||||||
alt: hotelImage.altText || undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (heroImage) {
|
|
||||||
return {
|
|
||||||
url: heroImage.url,
|
|
||||||
alt: heroImage.meta.alt || undefined,
|
|
||||||
width: heroImage.dimensions.width,
|
|
||||||
height: heroImage.dimensions.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
27
apps/scandic-web/utils/metadata/image/destinationPage.ts
Normal file
27
apps/scandic-web/utils/metadata/image/destinationPage.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
|
export function getDestinationFilterSeoMetaImage(data: RawMetadataSchema) {
|
||||||
|
const filter = data.destinationData?.filter
|
||||||
|
|
||||||
|
if (!filter) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundSeoFilter = data.seo_filters?.find(
|
||||||
|
(f) => f.filterConnection.edges[0]?.node?.slug === filter
|
||||||
|
)
|
||||||
|
|
||||||
|
if (foundSeoFilter) {
|
||||||
|
const metaDataImage = foundSeoFilter.seo_metadata?.seo_image
|
||||||
|
if (metaDataImage) {
|
||||||
|
return {
|
||||||
|
url: metaDataImage.url,
|
||||||
|
alt: metaDataImage.meta.alt || undefined,
|
||||||
|
width: metaDataImage.dimensions.width,
|
||||||
|
height: metaDataImage.dimensions.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
88
apps/scandic-web/utils/metadata/image/hotelPage.ts
Normal file
88
apps/scandic-web/utils/metadata/image/hotelPage.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
|
export function getHotelPageImage(data: RawMetadataSchema) {
|
||||||
|
const { subpageUrl, hotelData, additionalHotelData, hotelRestaurants } = data
|
||||||
|
if (!hotelData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subpageUrl) {
|
||||||
|
let subpageImage: { url: string; alt?: string } | null = null
|
||||||
|
const restaurantSubPage = hotelRestaurants?.find(
|
||||||
|
(restaurant) => restaurant.nameInUrl === subpageUrl
|
||||||
|
)
|
||||||
|
const restaurantImage = restaurantSubPage?.content?.images?.[0]
|
||||||
|
if (restaurantImage) {
|
||||||
|
subpageImage = {
|
||||||
|
url: restaurantImage.src,
|
||||||
|
alt: restaurantImage.altText || restaurantImage.altText_En || undefined,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (subpageUrl) {
|
||||||
|
case additionalHotelData?.hotelParking.nameInUrl:
|
||||||
|
const parkingImage = additionalHotelData?.parkingImages?.heroImages[0]
|
||||||
|
if (parkingImage) {
|
||||||
|
subpageImage = {
|
||||||
|
url: parkingImage.src,
|
||||||
|
alt: parkingImage.altText || parkingImage.altText_En || undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case additionalHotelData?.healthAndFitness.nameInUrl:
|
||||||
|
const wellnessImage = hotelData.healthFacilities.find(
|
||||||
|
(fac) => fac.content.images.length
|
||||||
|
)?.content.images[0]
|
||||||
|
if (wellnessImage) {
|
||||||
|
subpageImage = {
|
||||||
|
url: wellnessImage.src,
|
||||||
|
alt:
|
||||||
|
wellnessImage.altText || wellnessImage.altText_En || undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case additionalHotelData?.hotelSpecialNeeds.nameInUrl:
|
||||||
|
const accessibilityImage =
|
||||||
|
additionalHotelData?.accessibility?.heroImages[0]
|
||||||
|
if (accessibilityImage) {
|
||||||
|
subpageImage = {
|
||||||
|
url: accessibilityImage.src,
|
||||||
|
alt:
|
||||||
|
accessibilityImage.altText ||
|
||||||
|
accessibilityImage.altText_En ||
|
||||||
|
undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case additionalHotelData?.meetingRooms.nameInUrl:
|
||||||
|
const meetingImage =
|
||||||
|
additionalHotelData?.conferencesAndMeetings?.heroImages[0]
|
||||||
|
if (meetingImage) {
|
||||||
|
subpageImage = {
|
||||||
|
url: meetingImage.src,
|
||||||
|
alt: meetingImage.altText || meetingImage.altText_En || undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subpageImage) {
|
||||||
|
return subpageImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hotelImage =
|
||||||
|
additionalHotelData?.gallery?.heroImages?.[0] ||
|
||||||
|
additionalHotelData?.gallery?.smallerImages?.[0]
|
||||||
|
|
||||||
|
if (hotelImage) {
|
||||||
|
return {
|
||||||
|
url: hotelImage.src,
|
||||||
|
alt: hotelImage.altText || undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
59
apps/scandic-web/utils/metadata/image/index.ts
Normal file
59
apps/scandic-web/utils/metadata/image/index.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { PageContentTypeEnum } from "@scandic-hotels/trpc/enums/contentType"
|
||||||
|
|
||||||
|
import { getDestinationFilterSeoMetaImage } from "@/utils/metadata/image/destinationPage"
|
||||||
|
import { getHotelPageImage } from "@/utils/metadata/image/hotelPage"
|
||||||
|
|
||||||
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
|
export function getImage(data: RawMetadataSchema) {
|
||||||
|
const metadataImage = data.web?.seo_metadata?.seo_image
|
||||||
|
const isDestinationPage = [
|
||||||
|
PageContentTypeEnum.destinationCityPage,
|
||||||
|
PageContentTypeEnum.destinationCountryPage,
|
||||||
|
].includes(data.system.content_type_uid as PageContentTypeEnum)
|
||||||
|
|
||||||
|
if (isDestinationPage) {
|
||||||
|
const destinationFilterSeoMetaImage = getDestinationFilterSeoMetaImage(data)
|
||||||
|
if (destinationFilterSeoMetaImage) {
|
||||||
|
return destinationFilterSeoMetaImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
|
||||||
|
if (metadataImage) {
|
||||||
|
return {
|
||||||
|
url: metadataImage.url,
|
||||||
|
alt: metadataImage.meta.alt || undefined,
|
||||||
|
width: metadataImage.dimensions.width,
|
||||||
|
height: metadataImage.dimensions.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentTypeImage: { url: string; alt?: string } | null = null
|
||||||
|
|
||||||
|
switch (data.system.content_type_uid) {
|
||||||
|
case PageContentTypeEnum.hotelPage:
|
||||||
|
contentTypeImage = getHotelPageImage(data)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (contentTypeImage) {
|
||||||
|
return contentTypeImage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to hero image if no other image is found
|
||||||
|
const heroImage =
|
||||||
|
data.hero_image || data.header?.hero_image || data.images?.[0]
|
||||||
|
|
||||||
|
if (heroImage) {
|
||||||
|
return {
|
||||||
|
url: heroImage.url,
|
||||||
|
alt: heroImage.meta.alt || undefined,
|
||||||
|
width: heroImage.dimensions.width,
|
||||||
|
height: heroImage.dimensions.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
import { getIntl } from "@/i18n"
|
|
||||||
|
|
||||||
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
|
||||||
|
|
||||||
function getTitleSuffix(contentType: string) {
|
|
||||||
switch (contentType) {
|
|
||||||
case "content_page":
|
|
||||||
case "collection_page":
|
|
||||||
case "campaign_page":
|
|
||||||
case "campaign_overview_page":
|
|
||||||
case "destination_overview_page":
|
|
||||||
case "destination_city_page":
|
|
||||||
case "destination_country_page":
|
|
||||||
return " | Scandic Hotels"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTitle(data: RawMetadataSchema) {
|
|
||||||
const intl = await getIntl()
|
|
||||||
const suffix = getTitleSuffix(data.system.content_type_uid)
|
|
||||||
const metadata = data.web?.seo_metadata
|
|
||||||
|
|
||||||
if (metadata?.title) {
|
|
||||||
return `${metadata.title}${suffix}`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.system.content_type_uid === "hotel_page" && data.hotelData) {
|
|
||||||
if (data.subpageUrl) {
|
|
||||||
const restaurantSubPage = data.hotelRestaurants?.find(
|
|
||||||
(restaurant) => restaurant.nameInUrl === data.subpageUrl
|
|
||||||
)
|
|
||||||
if (restaurantSubPage) {
|
|
||||||
const restaurantTitleLong = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Explore {restaurantName} at {hotelName} in {destination}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
restaurantName: restaurantSubPage.name,
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
destination: data.hotelData.translatedCityName,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const restaurantTitleShort = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Explore {restaurantName} at {hotelName}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
restaurantName: restaurantSubPage.name,
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (restaurantTitleLong.length > 60) {
|
|
||||||
return restaurantTitleShort
|
|
||||||
}
|
|
||||||
return restaurantTitleLong
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (data.subpageUrl) {
|
|
||||||
case data.additionalHotelData?.hotelParking.nameInUrl:
|
|
||||||
const parkingTitleLong = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Parking information for {hotelName} in {destination}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
destination: data.hotelData.translatedCityName,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const parkingTitleShort = intl.formatMessage(
|
|
||||||
{ defaultMessage: "Parking information for {hotelName}" },
|
|
||||||
{ hotelName: data.hotelData.name }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (parkingTitleLong.length > 60) {
|
|
||||||
return parkingTitleShort
|
|
||||||
}
|
|
||||||
return parkingTitleLong
|
|
||||||
case data.additionalHotelData?.healthAndFitness.nameInUrl:
|
|
||||||
const wellnessTitleLong = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Gym & health facilities at {hotelName} in {destination}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
destination: data.hotelData.translatedCityName,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const wellnessTitleShort = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Gym & health facilities at {hotelName}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (wellnessTitleLong.length > 60) {
|
|
||||||
return wellnessTitleShort
|
|
||||||
}
|
|
||||||
return wellnessTitleLong
|
|
||||||
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
|
|
||||||
const accessibilityTitleLong = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Accessibility information for {hotelName} in {destination}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
destination: data.hotelData.translatedCityName,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const accessibilityTitleShort = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Accessibility information for {hotelName}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (accessibilityTitleLong.length > 60) {
|
|
||||||
return accessibilityTitleShort
|
|
||||||
}
|
|
||||||
return accessibilityTitleLong
|
|
||||||
case data.additionalHotelData?.meetingRooms.nameInUrl:
|
|
||||||
const meetingsTitleLong = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Meetings & conferences at {hotelName} in {destination}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
destination: data.hotelData.translatedCityName,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const meetingsTitleShort = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Meetings & conferences at {hotelName}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (meetingsTitleLong.length > 60) {
|
|
||||||
return meetingsTitleShort
|
|
||||||
}
|
|
||||||
return meetingsTitleLong
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Stay at {hotelName} | Hotel in {destination}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hotelName: data.hotelData.name,
|
|
||||||
destination: data.hotelData.translatedCityName,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
data.system.content_type_uid === "destination_city_page" ||
|
|
||||||
data.system.content_type_uid === "destination_country_page"
|
|
||||||
) {
|
|
||||||
if (data.destinationData) {
|
|
||||||
const { location, filter, filterType } = data.destinationData
|
|
||||||
if (location) {
|
|
||||||
if (filter) {
|
|
||||||
if (filterType === "facility") {
|
|
||||||
return intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Hotels with {filter} in {location}",
|
|
||||||
},
|
|
||||||
{ location, filter }
|
|
||||||
)
|
|
||||||
} else if (filterType === "surroundings") {
|
|
||||||
return intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Hotels near {filter} in {location}",
|
|
||||||
},
|
|
||||||
{ location, filter }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const destinationTitle = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Hotels in {location}",
|
|
||||||
},
|
|
||||||
{ location }
|
|
||||||
)
|
|
||||||
|
|
||||||
return `${destinationTitle}${suffix}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.web?.breadcrumbs?.title) {
|
|
||||||
return `${data.web.breadcrumbs.title}${suffix}`
|
|
||||||
}
|
|
||||||
if (data.heading) {
|
|
||||||
return `${data.heading}${suffix}`
|
|
||||||
}
|
|
||||||
if (data.header?.heading) {
|
|
||||||
return `${data.header.heading}${suffix}`
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
61
apps/scandic-web/utils/metadata/title/destinationPage.ts
Normal file
61
apps/scandic-web/utils/metadata/title/destinationPage.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
|
export async function getDestinationPageTitle(
|
||||||
|
data: RawMetadataSchema,
|
||||||
|
pageType: "city" | "country",
|
||||||
|
suffix: string
|
||||||
|
) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const { destinationData } = data
|
||||||
|
if (!destinationData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const location = destinationData.location
|
||||||
|
|
||||||
|
if (!location) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const destinationTitle =
|
||||||
|
pageType === "country"
|
||||||
|
? intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Destinations in {location}",
|
||||||
|
},
|
||||||
|
{ location }
|
||||||
|
)
|
||||||
|
: intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Hotels in {location}",
|
||||||
|
},
|
||||||
|
{ location }
|
||||||
|
)
|
||||||
|
|
||||||
|
return `${destinationTitle}${suffix}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDestinationFilterSeoMetaTitle(
|
||||||
|
data: RawMetadataSchema,
|
||||||
|
suffix: string
|
||||||
|
) {
|
||||||
|
const filter = data.destinationData?.filter
|
||||||
|
|
||||||
|
if (!filter) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const foundSeoFilter = data.seo_filters?.find(
|
||||||
|
(f) => f.filterConnection.edges[0]?.node?.slug === filter
|
||||||
|
)
|
||||||
|
|
||||||
|
if (foundSeoFilter) {
|
||||||
|
if (foundSeoFilter.seo_metadata?.title) {
|
||||||
|
return `${foundSeoFilter.seo_metadata.title}${suffix}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${foundSeoFilter.heading}${suffix}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
148
apps/scandic-web/utils/metadata/title/hotelPage.ts
Normal file
148
apps/scandic-web/utils/metadata/title/hotelPage.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
|
async function getSubpageTitle(
|
||||||
|
subpageUrl: string,
|
||||||
|
additionalHotelData: RawMetadataSchema["additionalHotelData"],
|
||||||
|
hotelRestaurants: RawMetadataSchema["hotelRestaurants"],
|
||||||
|
hotelName: string,
|
||||||
|
destination: string
|
||||||
|
) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const restaurantSubPage = hotelRestaurants?.find(
|
||||||
|
(restaurant) => restaurant.nameInUrl === subpageUrl
|
||||||
|
)
|
||||||
|
if (restaurantSubPage) {
|
||||||
|
const restaurantTitleLong = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Explore {restaurantName} at {hotelName} in {destination}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
restaurantName: restaurantSubPage.name,
|
||||||
|
hotelName,
|
||||||
|
destination,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const restaurantTitleShort = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Explore {restaurantName} at {hotelName}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
restaurantName: restaurantSubPage.name,
|
||||||
|
hotelName,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (restaurantTitleLong.length > 60) {
|
||||||
|
return restaurantTitleShort
|
||||||
|
}
|
||||||
|
return restaurantTitleLong
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!additionalHotelData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (subpageUrl) {
|
||||||
|
case additionalHotelData.hotelParking.nameInUrl:
|
||||||
|
const parkingTitleLong = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Parking information for {hotelName} in {destination}",
|
||||||
|
},
|
||||||
|
{ hotelName, destination }
|
||||||
|
)
|
||||||
|
const parkingTitleShort = intl.formatMessage(
|
||||||
|
{ defaultMessage: "Parking information for {hotelName}" },
|
||||||
|
{ hotelName }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parkingTitleLong.length > 60) {
|
||||||
|
return parkingTitleShort
|
||||||
|
}
|
||||||
|
return parkingTitleLong
|
||||||
|
case additionalHotelData.healthAndFitness.nameInUrl:
|
||||||
|
const wellnessTitleLong = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Gym & health facilities at {hotelName} in {destination}",
|
||||||
|
},
|
||||||
|
{ hotelName, destination }
|
||||||
|
)
|
||||||
|
const wellnessTitleShort = intl.formatMessage(
|
||||||
|
{ defaultMessage: "Gym & health facilities at {hotelName}" },
|
||||||
|
{ hotelName }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (wellnessTitleLong.length > 60) {
|
||||||
|
return wellnessTitleShort
|
||||||
|
}
|
||||||
|
return wellnessTitleLong
|
||||||
|
case additionalHotelData.hotelSpecialNeeds.nameInUrl:
|
||||||
|
const accessibilityTitleLong = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Accessibility information for {hotelName} in {destination}",
|
||||||
|
},
|
||||||
|
{ hotelName, destination }
|
||||||
|
)
|
||||||
|
const accessibilityTitleShort = intl.formatMessage(
|
||||||
|
{ defaultMessage: "Accessibility information for {hotelName}" },
|
||||||
|
{ hotelName }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (accessibilityTitleLong.length > 60) {
|
||||||
|
return accessibilityTitleShort
|
||||||
|
}
|
||||||
|
return accessibilityTitleLong
|
||||||
|
case additionalHotelData.meetingRooms.nameInUrl:
|
||||||
|
const meetingsTitleLong = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Meetings & conferences at {hotelName} in {destination}",
|
||||||
|
},
|
||||||
|
{ hotelName, destination }
|
||||||
|
)
|
||||||
|
const meetingsTitleShort = intl.formatMessage(
|
||||||
|
{ defaultMessage: "Meetings & conferences at {hotelName}" },
|
||||||
|
{ hotelName }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (meetingsTitleLong.length > 60) {
|
||||||
|
return meetingsTitleShort
|
||||||
|
}
|
||||||
|
return meetingsTitleLong
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getHotelPageTitle(data: RawMetadataSchema) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const { subpageUrl, hotelData, additionalHotelData, hotelRestaurants } = data
|
||||||
|
if (!hotelData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const hotelName = hotelData.name
|
||||||
|
const destination = hotelData.translatedCityName
|
||||||
|
|
||||||
|
if (subpageUrl) {
|
||||||
|
const subpageTitle = await getSubpageTitle(
|
||||||
|
subpageUrl,
|
||||||
|
additionalHotelData,
|
||||||
|
hotelRestaurants,
|
||||||
|
hotelName,
|
||||||
|
destination
|
||||||
|
)
|
||||||
|
|
||||||
|
return subpageTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
return intl.formatMessage(
|
||||||
|
{ defaultMessage: "Stay at {hotelName} | Hotel in {destination}" },
|
||||||
|
{ hotelName, destination }
|
||||||
|
)
|
||||||
|
}
|
||||||
79
apps/scandic-web/utils/metadata/title/index.ts
Normal file
79
apps/scandic-web/utils/metadata/title/index.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { PageContentTypeEnum } from "@scandic-hotels/trpc/enums/contentType"
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDestinationFilterSeoMetaTitle,
|
||||||
|
getDestinationPageTitle,
|
||||||
|
} from "./destinationPage"
|
||||||
|
import { getHotelPageTitle } from "./hotelPage"
|
||||||
|
|
||||||
|
import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output"
|
||||||
|
|
||||||
|
function getTitleSuffix(contentType: string) {
|
||||||
|
switch (contentType) {
|
||||||
|
case PageContentTypeEnum.contentPage:
|
||||||
|
case PageContentTypeEnum.collectionPage:
|
||||||
|
case PageContentTypeEnum.campaignPage:
|
||||||
|
case PageContentTypeEnum.campaignOverviewPage:
|
||||||
|
case PageContentTypeEnum.destinationOverviewPage:
|
||||||
|
case PageContentTypeEnum.destinationCityPage:
|
||||||
|
case PageContentTypeEnum.destinationCountryPage:
|
||||||
|
return " | Scandic Hotels"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTitle(data: RawMetadataSchema) {
|
||||||
|
const suffix = getTitleSuffix(data.system.content_type_uid)
|
||||||
|
const metadata = data.web?.seo_metadata
|
||||||
|
const isDestinationPage = [
|
||||||
|
PageContentTypeEnum.destinationCityPage,
|
||||||
|
PageContentTypeEnum.destinationCountryPage,
|
||||||
|
].includes(data.system.content_type_uid as PageContentTypeEnum)
|
||||||
|
|
||||||
|
if (isDestinationPage) {
|
||||||
|
const destinationFilterSeoMetaTitle = getDestinationFilterSeoMetaTitle(
|
||||||
|
data,
|
||||||
|
suffix
|
||||||
|
)
|
||||||
|
if (destinationFilterSeoMetaTitle) {
|
||||||
|
return destinationFilterSeoMetaTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata?.title) {
|
||||||
|
return `${metadata.title}${suffix}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let title: string | null = null
|
||||||
|
|
||||||
|
switch (data.system.content_type_uid) {
|
||||||
|
case PageContentTypeEnum.hotelPage:
|
||||||
|
title = await getHotelPageTitle(data)
|
||||||
|
break
|
||||||
|
case PageContentTypeEnum.destinationCityPage:
|
||||||
|
title = await getDestinationPageTitle(data, "city", suffix)
|
||||||
|
break
|
||||||
|
case PageContentTypeEnum.destinationCountryPage:
|
||||||
|
title = await getDestinationPageTitle(data, "country", suffix)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback titles from contentstack content
|
||||||
|
if (data.web?.breadcrumbs?.title) {
|
||||||
|
return `${data.web.breadcrumbs.title}${suffix}`
|
||||||
|
}
|
||||||
|
if (data.heading) {
|
||||||
|
return `${data.heading}${suffix}`
|
||||||
|
}
|
||||||
|
if (data.header?.heading) {
|
||||||
|
return `${data.header.heading}${suffix}`
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -24,6 +24,11 @@ query GetDestinationCityPageMetadata($locale: String!, $uid: String!) {
|
|||||||
image
|
image
|
||||||
}
|
}
|
||||||
seo_filters {
|
seo_filters {
|
||||||
|
heading
|
||||||
|
preamble
|
||||||
|
seo_metadata {
|
||||||
|
...Metadata
|
||||||
|
}
|
||||||
filterConnection {
|
filterConnection {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#import "../../Fragments/Metadata.graphql"
|
#import "../../Fragments/Metadata.graphql"
|
||||||
#import "../../Fragments/System.graphql"
|
#import "../../Fragments/System.graphql"
|
||||||
|
#import "../../Fragments/HotelFilter.graphql"
|
||||||
|
|
||||||
query GetDestinationCountryPageMetadata($locale: String!, $uid: String!) {
|
query GetDestinationCountryPageMetadata($locale: String!, $uid: String!) {
|
||||||
destination_country_page(locale: $locale, uid: $uid) {
|
destination_country_page(locale: $locale, uid: $uid) {
|
||||||
@@ -17,6 +18,20 @@ query GetDestinationCountryPageMetadata($locale: String!, $uid: String!) {
|
|||||||
images {
|
images {
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
seo_filters {
|
||||||
|
heading
|
||||||
|
preamble
|
||||||
|
seo_metadata {
|
||||||
|
...Metadata
|
||||||
|
}
|
||||||
|
filterConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...HotelFilter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
system {
|
system {
|
||||||
...System
|
...System
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Country } from "../../../types/country"
|
|||||||
import { RTETypeEnum } from "../../../types/RTEenums"
|
import { RTETypeEnum } from "../../../types/RTEenums"
|
||||||
import { additionalDataAttributesSchema } from "../../hotels/schemas/hotel/include/additionalData"
|
import { additionalDataAttributesSchema } from "../../hotels/schemas/hotel/include/additionalData"
|
||||||
import { imageSchema } from "../../hotels/schemas/image"
|
import { imageSchema } from "../../hotels/schemas/image"
|
||||||
import { destinationFiltersSchema } from "../schemas/destinationFilters"
|
import { destinationFilterSchema } from "../schemas/destinationFilters"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
|
||||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
@@ -52,17 +52,19 @@ const metaDataBlocksSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable()
|
.nullable()
|
||||||
|
|
||||||
|
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({
|
export const rawMetadataSchema = z.object({
|
||||||
web: z
|
web: z
|
||||||
.object({
|
.object({
|
||||||
seo_metadata: z
|
seo_metadata: seoMetadataSchema,
|
||||||
.object({
|
|
||||||
title: z.string().nullish(),
|
|
||||||
description: z.string().nullish(),
|
|
||||||
noindex: z.boolean().nullish(),
|
|
||||||
seo_image: transformedImageVaultAssetSchema,
|
|
||||||
})
|
|
||||||
.nullish(),
|
|
||||||
breadcrumbs: z
|
breadcrumbs: z
|
||||||
.object({
|
.object({
|
||||||
title: z.string().nullish(),
|
title: z.string().nullish(),
|
||||||
@@ -167,7 +169,13 @@ export const rawMetadataSchema = z.object({
|
|||||||
cities: z.array(z.string()).nullish(),
|
cities: z.array(z.string()).nullish(),
|
||||||
})
|
})
|
||||||
.nullish(),
|
.nullish(),
|
||||||
seo_filters: destinationFiltersSchema,
|
seo_filters: z
|
||||||
|
.array(
|
||||||
|
destinationFilterSchema.merge(
|
||||||
|
z.object({ seo_metadata: seoMetadataSchema })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.nullish(),
|
||||||
system: systemSchema,
|
system: systemSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -71,16 +71,17 @@ export async function getCityData(
|
|||||||
hotelFilters,
|
hotelFilters,
|
||||||
seoFilters
|
seoFilters
|
||||||
)
|
)
|
||||||
|
|
||||||
const facilityFilter = allFilters.facilityFilters.find(
|
const facilityFilter = allFilters.facilityFilters.find(
|
||||||
(f) => f.slug === filter
|
(f) => f.slug === filter
|
||||||
)
|
)
|
||||||
const surroudingsFilter = allFilters.surroundingsFilters.find(
|
const surroundingsFilter = allFilters.surroundingsFilters.find(
|
||||||
(f) => f.slug === filter
|
(f) => f.slug === filter
|
||||||
)
|
)
|
||||||
|
|
||||||
if (facilityFilter) {
|
if (facilityFilter) {
|
||||||
filterType = "facility"
|
filterType = "facility"
|
||||||
} else if (surroudingsFilter) {
|
} else if (surroundingsFilter) {
|
||||||
filterType = "surroundings"
|
filterType = "surroundings"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,13 +124,13 @@ export async function getCountryData(
|
|||||||
const facilityFilter = allFilters.facilityFilters.find(
|
const facilityFilter = allFilters.facilityFilters.find(
|
||||||
(f) => f.slug === filter
|
(f) => f.slug === filter
|
||||||
)
|
)
|
||||||
const surroudingsFilter = allFilters.surroundingsFilters.find(
|
const surroundingsFilter = allFilters.surroundingsFilters.find(
|
||||||
(f) => f.slug === filter
|
(f) => f.slug === filter
|
||||||
)
|
)
|
||||||
|
|
||||||
if (facilityFilter) {
|
if (facilityFilter) {
|
||||||
filterType = "facility"
|
filterType = "facility"
|
||||||
} else if (surroudingsFilter) {
|
} else if (surroundingsFilter) {
|
||||||
filterType = "surroundings"
|
filterType = "surroundings"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,29 +30,25 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
|||||||
destinationFilterBlockContent,
|
destinationFilterBlockContent,
|
||||||
])
|
])
|
||||||
|
|
||||||
export const destinationFiltersSchema = z
|
export const destinationFilterSchema = z.object({
|
||||||
.array(
|
heading: z.string().nullish(),
|
||||||
z.object({
|
preamble: z.string().nullish(),
|
||||||
heading: z.string().nullish(),
|
blocks: discriminatedUnionArray(blocksSchema.options).nullish(),
|
||||||
preamble: z.string().nullish(),
|
filterConnection: z.object({
|
||||||
blocks: discriminatedUnionArray(blocksSchema.options).nullish(),
|
edges: z.array(
|
||||||
filterConnection: z.object({
|
z.object({
|
||||||
edges: z.array(
|
node: z.object({
|
||||||
z.object({
|
title: z.string(),
|
||||||
node: z.object({
|
facility_id: z.nativeEnum(FacilityEnum).catch(FacilityEnum.UNKNOWN),
|
||||||
title: z.string(),
|
category: z.string(),
|
||||||
facility_id: z
|
slug: z.string(),
|
||||||
.nativeEnum(FacilityEnum)
|
}),
|
||||||
.catch(FacilityEnum.UNKNOWN),
|
})
|
||||||
category: z.string(),
|
),
|
||||||
slug: z.string(),
|
}),
|
||||||
}),
|
})
|
||||||
})
|
|
||||||
),
|
const destinationFiltersSchema = z.array(destinationFilterSchema).nullish()
|
||||||
}),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.nullish()
|
|
||||||
|
|
||||||
export const transformedDestinationFiltersSchema =
|
export const transformedDestinationFiltersSchema =
|
||||||
destinationFiltersSchema.transform((data) =>
|
destinationFiltersSchema.transform((data) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user