diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx index 3b6b5a1f7..f534bd1ed 100644 --- a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx @@ -1,9 +1,50 @@ import { AlternativeHotelsMapPage as AlternativeHotelsMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsMapPage" +import { getHotel } from "@/lib/trpc/memoizedRequests/getHotel" + +import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import type { LangParams, PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, + params, +}: PageArgs): Promise { + const intl = await getIntl() + + const { hotel } = await searchParams + const { lang } = await params + + if (!hotel || Array.isArray(hotel)) { + return {} + } + + const hotelData = await getHotel({ + hotelId: hotel, + language: lang, + isCardOnlyPayment: false, + }) + const hotelName = hotelData?.additionalData?.name + + if (!hotelName) { + return {} + } + + const title = intl.formatMessage( + { + defaultMessage: "Alternatives for {value}", + }, + { + value: hotelName, + } + ) + + return { title } +} + export default async function AlternativeHotelsMapPage( props: PageArgs ) { diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx index 8a43ceeaa..51d625b96 100644 --- a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx @@ -1,9 +1,50 @@ import { AlternativeHotelsPage as AlternativeHotelsPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsPage" +import { getHotel } from "@/lib/trpc/memoizedRequests/getHotel" + +import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import { type LangParams, type PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, + params, +}: PageArgs): Promise { + const intl = await getIntl() + + const { hotel } = await searchParams + const { lang } = await params + + if (!hotel || Array.isArray(hotel)) { + return {} + } + + const hotelData = await getHotel({ + hotelId: hotel, + language: lang, + isCardOnlyPayment: false, + }) + const hotelName = hotelData?.additionalData?.name + + if (!hotelName) { + return {} + } + + const title = intl.formatMessage( + { + defaultMessage: "Alternatives for {value}", + }, + { + value: hotelName, + } + ) + + return { title } +} + export default async function AlternativeHotelsPage( props: PageArgs ) { diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx index 56c73d958..970e95c7e 100644 --- a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx @@ -1,9 +1,26 @@ import { SelectHotelMapPage as SelectHotelMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelMapPage" +import { toCapitalCase } from "@scandic-hotels/common/utils/toCapitalCase" import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import type { LangParams, PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, +}: PageArgs): Promise { + const { city } = await searchParams + + if (!city || Array.isArray(city)) { + return {} + } + + return { + title: `${toCapitalCase(city)}`, + } +} + export default async function SelectHotelMapPage(props: PageArgs) { const searchParams = await props.searchParams const lang = await getLang() diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx index 05d73b88c..c7def4927 100644 --- a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx @@ -1,9 +1,26 @@ import { SelectHotelPage as SelectHotelPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelPage" +import { toCapitalCase } from "@scandic-hotels/common/utils/toCapitalCase" import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import type { LangParams, PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, +}: PageArgs): Promise { + const { city } = await searchParams + + if (!city || Array.isArray(city)) { + return {} + } + + return { + title: `${toCapitalCase(city)}`, + } +} + export default async function SelectHotelPage(props: PageArgs) { const searchParams = await props.searchParams const lang = await getLang() diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-rate/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-rate/page.tsx index 7929d8f66..6816f1b02 100644 --- a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-rate/page.tsx +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-rate/page.tsx @@ -1,9 +1,39 @@ import { SelectRatePage as SelectRatePagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectRatePage" +import { getHotel } from "@/lib/trpc/memoizedRequests/getHotel" + import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import { type LangParams, type PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, + params, +}: PageArgs): Promise { + const { hotel } = await searchParams + const { lang } = await params + + if (!hotel || Array.isArray(hotel)) { + return {} + } + + const hotelData = await getHotel({ + hotelId: hotel, + language: lang, + isCardOnlyPayment: false, + }) + + if (!hotelData?.additionalData?.name) { + return {} + } + + return { + title: hotelData.additionalData.name, + } +} + export default async function SelectRatePage(props: PageArgs) { const searchParams = await props.searchParams const lang = await getLang() diff --git a/apps/partner-sas/app/[lang]/layout.tsx b/apps/partner-sas/app/[lang]/layout.tsx index 0c65e00b1..932b8a87b 100644 --- a/apps/partner-sas/app/[lang]/layout.tsx +++ b/apps/partner-sas/app/[lang]/layout.tsx @@ -38,7 +38,6 @@ import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRout import type { Metadata } from "next" export const metadata: Metadata = { - title: "SAS by Scandic Hotels", description: "TODO This text should be updated.", } diff --git a/apps/partner-sas/app/layout.tsx b/apps/partner-sas/app/layout.tsx index c97bcf061..26485055f 100644 --- a/apps/partner-sas/app/layout.tsx +++ b/apps/partner-sas/app/layout.tsx @@ -2,8 +2,25 @@ import "@scandic-hotels/common/polyfills" import { configureTrpc } from "@/lib/trpc" +import { getTitlePrefix } from "@/util/metadata/getTitlePrfiex" + +import type { Metadata } from "next" + configureTrpc() +export async function generateMetadata(): Promise { + return { + title: { + template: combineSegments([ + getTitlePrefix(), + "%s", + "SAS by Scandic Hotels", + ]), + default: combineSegments([getTitlePrefix(), "SAS by Scandic Hotels"]), + }, + } +} + export default function RootLayout({ children, }: { @@ -11,3 +28,10 @@ export default function RootLayout({ }) { return <>{children} } + +function combineSegments( + segments: (string | null | undefined)[], + delimiter = " | " +) { + return segments.filter(Boolean).join(delimiter).trim() +} diff --git a/apps/partner-sas/lib/trpc.ts b/apps/partner-sas/lib/trpc/index.ts similarity index 100% rename from apps/partner-sas/lib/trpc.ts rename to apps/partner-sas/lib/trpc/index.ts diff --git a/apps/partner-sas/lib/trpc/memoizedRequests/getHotel.ts b/apps/partner-sas/lib/trpc/memoizedRequests/getHotel.ts new file mode 100644 index 000000000..5938650fc --- /dev/null +++ b/apps/partner-sas/lib/trpc/memoizedRequests/getHotel.ts @@ -0,0 +1,14 @@ +import { cache } from "react" + +import { serverClient } from ".." + +import type { HotelInput } from "@scandic-hotels/trpc/types/hotel" + +export const getHotel = cache(async function getMemoizedHotelData( + input: HotelInput +) { + input.isCardOnlyPayment ??= false + + const caller = await serverClient() + return caller.hotel.get(input) +}) diff --git a/apps/partner-sas/util/metadata/getTitlePrfiex.ts b/apps/partner-sas/util/metadata/getTitlePrfiex.ts new file mode 100644 index 000000000..33153f551 --- /dev/null +++ b/apps/partner-sas/util/metadata/getTitlePrfiex.ts @@ -0,0 +1,11 @@ +import { env } from "@/env/server" + +export function getTitlePrefix() { + if (env.SENTRY_ENVIRONMENT === "production") { + return "" + } + const environmentShortName = + env.SENTRY_ENVIRONMENT === "development" ? "local" : env.SENTRY_ENVIRONMENT + + return `${environmentShortName}` +} diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx index 173d7fafe..2a029955b 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx @@ -1,11 +1,52 @@ import { AlternativeHotelsMapPage as AlternativeHotelsMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsMapPage" +import { getHotel } from "@/lib/trpc/memoizedRequests" + +import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" import styles from "./page.module.css" +import type { Metadata } from "next" + import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, + params, +}: PageArgs): Promise { + const intl = await getIntl() + + const { hotel } = await searchParams + const { lang } = await params + + if (!hotel || Array.isArray(hotel)) { + return {} + } + + const hotelData = await getHotel({ + hotelId: hotel, + language: lang, + isCardOnlyPayment: false, + }) + const hotelName = hotelData?.additionalData?.name + + if (!hotelName) { + return {} + } + + const title = intl.formatMessage( + { + defaultMessage: "Alternatives for {value}", + }, + { + value: hotelName, + } + ) + + return { title } +} + export default async function AlternativeHotelsMapPage( props: PageArgs ) { diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx index fe899b7ca..a4b458c0f 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx @@ -1,13 +1,54 @@ import { AlternativeHotelsPage as AlternativeHotelsPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsPage" +import { getHotel } from "@/lib/trpc/memoizedRequests" + +import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import { type LangParams, type NextSearchParams, type PageArgs, } from "@/types/params" +export async function generateMetadata({ + searchParams, + params, +}: PageArgs): Promise { + const intl = await getIntl() + + const { hotel } = await searchParams + const { lang } = await params + + if (!hotel || Array.isArray(hotel)) { + return {} + } + + const hotelData = await getHotel({ + hotelId: hotel, + language: lang, + isCardOnlyPayment: false, + }) + const hotelName = hotelData?.additionalData?.name + + if (!hotelName) { + return {} + } + + const title = intl.formatMessage( + { + defaultMessage: "Alternatives for {value}", + }, + { + value: hotelName, + } + ) + + return { title } +} + export default async function AlternativeHotelsPage( props: PageArgs ) { diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx index 948c8cf53..daaee0575 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx @@ -2,6 +2,8 @@ import { EnterDetailsPage as EnterDetailsPagePrimitive } from "@scandic-hotels/b import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" +export { generateMetadata } from "@/utils/metadata/generateMetadata" + export default async function DetailsPage( props: PageArgs ) { diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx index 5d1328559..a08689bd7 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx @@ -8,6 +8,8 @@ import styles from "./page.module.css" import type { LangParams, PageArgs } from "@/types/params" +export { generateMetadata } from "@/utils/metadata/generateMetadata" + export default async function HotelReservationPage( props: PageArgs ) { diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx index 24c6a1248..296c71dac 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx @@ -1,11 +1,24 @@ import { SelectHotelMapPage as SelectHotelMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelMapPage" +import { toCapitalCase } from "@scandic-hotels/common/utils/toCapitalCase" import { getLang } from "@/i18n/serverContext" import styles from "./page.module.css" +import type { Metadata } from "next" + import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, +}: PageArgs): Promise { + const { city } = await searchParams + + return { + title: `${toCapitalCase(city)}`, + } +} + export default async function SelectHotelMapPage( props: PageArgs ) { diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index c4c8f75fa..526b9a73e 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -1,9 +1,22 @@ import { SelectHotelPage as SelectHotelPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelPage" +import { toCapitalCase } from "@scandic-hotels/common/utils/toCapitalCase" import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" +export async function generateMetadata({ + searchParams, +}: PageArgs): Promise { + const { city } = await searchParams + + return { + title: `${toCapitalCase(city)}`, + } +} + export default async function SelectHotelPage( props: PageArgs ) { diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 346b8b109..80f1e63d5 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -1,13 +1,39 @@ import { SelectRatePage as SelectRatePagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectRatePage" +import { getHotel } from "@/lib/trpc/memoizedRequests" + import { getLang } from "@/i18n/serverContext" +import type { Metadata } from "next" + import { type LangParams, type NextSearchParams, type PageArgs, } from "@/types/params" +export async function generateMetadata({ + searchParams, + params, +}: PageArgs): Promise { + const { hotel } = await searchParams + const { lang } = await params + + const hotelData = await getHotel({ + hotelId: hotel, + language: lang, + isCardOnlyPayment: false, + }) + + if (!hotelData?.additionalData?.name) { + return {} + } + + return { + title: hotelData.additionalData.name, + } +} + export default async function SelectRatePage( props: PageArgs ) { diff --git a/apps/scandic-web/app/layout.tsx b/apps/scandic-web/app/layout.tsx new file mode 100644 index 000000000..3897ac07f --- /dev/null +++ b/apps/scandic-web/app/layout.tsx @@ -0,0 +1,27 @@ +import { getTitlePrefix } from "@/utils/metadata/title/getTitlePrefix" + +import type { Metadata } from "next" + +export async function generateMetadata(): Promise { + return { + title: { + template: combineSegments([getTitlePrefix(), "%s", "Scandic Hotels"]), + default: combineSegments([getTitlePrefix(), "Scandic Hotels"]), + }, + } +} + +export default async function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return <>{children} +} + +function combineSegments( + segments: (string | null | undefined)[], + delimiter = " | " +) { + return segments.filter(Boolean).join(delimiter).trim() +} diff --git a/apps/scandic-web/utils/metadata/generateMetadata.ts b/apps/scandic-web/utils/metadata/generateMetadata.ts index 1c3577a85..1f0d8b088 100644 --- a/apps/scandic-web/utils/metadata/generateMetadata.ts +++ b/apps/scandic-web/utils/metadata/generateMetadata.ts @@ -27,6 +27,7 @@ export async function generateMetadata({ const { subpage, filterFromUrl, ...otherSearchParams } = await searchParams // If there are other (real) search params, we don't want to index the page as this will // cause duplicate content issues. + const noIndexOnSearchParams = !!Object.keys(otherSearchParams).length const caller = await serverClient() const { rawMetadata, alternates, robots } = @@ -129,7 +130,7 @@ async function getTransformedMetadata( ) { const metadata: Metadata = { metadataBase: env.PUBLIC_URL ? new URL(env.PUBLIC_URL) : undefined, - title: await getTitle(data), + title: { absolute: await getTitle(data) }, description: await getDescription(data), openGraph: { images: getImage(data), diff --git a/apps/scandic-web/utils/metadata/title/destinationPage.ts b/apps/scandic-web/utils/metadata/title/destinationPage.ts index a736513a5..720e6ed38 100644 --- a/apps/scandic-web/utils/metadata/title/destinationPage.ts +++ b/apps/scandic-web/utils/metadata/title/destinationPage.ts @@ -4,8 +4,7 @@ import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstac export async function getDestinationPageTitle( data: RawMetadataSchema, - pageType: "city" | "country", - suffix: string + pageType: "city" | "country" ) { const intl = await getIntl() const { destinationData } = data @@ -33,13 +32,10 @@ export async function getDestinationPageTitle( { location } ) - return `${destinationTitle}${suffix}` + return destinationTitle } -export function getDestinationFilterSeoMetaTitle( - data: RawMetadataSchema, - suffix: string -) { +export function getDestinationFilterSeoMetaTitle(data: RawMetadataSchema) { const filter = data.destinationData?.filter if (!filter) { @@ -51,10 +47,10 @@ export function getDestinationFilterSeoMetaTitle( if (foundSeoFilter) { if (foundSeoFilter.seo_metadata?.title) { - return `${foundSeoFilter.seo_metadata.title}${suffix}` + return foundSeoFilter.seo_metadata.title } - return `${foundSeoFilter.heading}${suffix}` + return foundSeoFilter.heading } return null diff --git a/apps/scandic-web/utils/metadata/title/getTitlePrefix.ts b/apps/scandic-web/utils/metadata/title/getTitlePrefix.ts new file mode 100644 index 000000000..33153f551 --- /dev/null +++ b/apps/scandic-web/utils/metadata/title/getTitlePrefix.ts @@ -0,0 +1,11 @@ +import { env } from "@/env/server" + +export function getTitlePrefix() { + if (env.SENTRY_ENVIRONMENT === "production") { + return "" + } + const environmentShortName = + env.SENTRY_ENVIRONMENT === "development" ? "local" : env.SENTRY_ENVIRONMENT + + return `${environmentShortName}` +} diff --git a/apps/scandic-web/utils/metadata/title/index.ts b/apps/scandic-web/utils/metadata/title/index.ts index 67f5adadf..39385873e 100644 --- a/apps/scandic-web/utils/metadata/title/index.ts +++ b/apps/scandic-web/utils/metadata/title/index.ts @@ -4,6 +4,7 @@ import { getDestinationFilterSeoMetaTitle, getDestinationPageTitle, } from "./destinationPage" +import { getTitlePrefix } from "./getTitlePrefix" import { getHotelPageTitle } from "./hotelPage" import type { RawMetadataSchema } from "@scandic-hotels/trpc/routers/contentstack/metadata/output" @@ -17,13 +18,14 @@ function getTitleSuffix(contentType: string) { case PageContentTypeEnum.destinationOverviewPage: case PageContentTypeEnum.destinationCityPage: case PageContentTypeEnum.destinationCountryPage: - return " | Scandic Hotels" + return "Scandic Hotels" default: return "" } } export async function getTitle(data: RawMetadataSchema) { + const prefix = getTitlePrefix() const suffix = getTitleSuffix(data.system.content_type_uid) const metadata = data.web?.seo_metadata const isDestinationPage = [ @@ -32,48 +34,41 @@ export async function getTitle(data: RawMetadataSchema) { ].includes(data.system.content_type_uid as PageContentTypeEnum) if (isDestinationPage) { - const destinationFilterSeoMetaTitle = getDestinationFilterSeoMetaTitle( - data, - suffix - ) + const destinationFilterSeoMetaTitle = getDestinationFilterSeoMetaTitle(data) if (destinationFilterSeoMetaTitle) { return destinationFilterSeoMetaTitle } } if (metadata?.title) { - return `${metadata.title}${suffix}` + return combineSegments([prefix, metadata.title, suffix]) } - let title: string | null = null + let title: string | null | undefined = null switch (data.system.content_type_uid) { case PageContentTypeEnum.hotelPage: title = await getHotelPageTitle(data) break case PageContentTypeEnum.destinationCityPage: - title = await getDestinationPageTitle(data, "city", suffix) + title = await getDestinationPageTitle(data, "city") break case PageContentTypeEnum.destinationCountryPage: - title = await getDestinationPageTitle(data, "country", suffix) + title = await getDestinationPageTitle(data, "country") 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 "" + title ||= data.web?.breadcrumbs?.title || data.heading || data.header?.heading + + return combineSegments([prefix, title, suffix]) +} + +function combineSegments( + segments: (string | null | undefined)[], + delimiter = " | " +) { + return segments.filter(Boolean).join(delimiter).trim() } diff --git a/packages/common/package.json b/packages/common/package.json index 5c08590b9..840b611d1 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -57,11 +57,12 @@ "./utils/maskValue": "./utils/maskValue.ts", "./utils/membershipLevels": "./utils/membershipLevels.ts", "./utils/numberFormatting": "./utils/numberFormatting.ts", - "./utils/rangeArray": "./utils/rangeArray.ts", - "./utils/safeTry": "./utils/safeTry.ts", - "./utils/url": "./utils/url.ts", "./utils/phone": "./utils/phone.ts", "./utils/promiseWithTimeout": "./utils/promiseWithTimeout.ts", + "./utils/rangeArray": "./utils/rangeArray.ts", + "./utils/safeTry": "./utils/safeTry.ts", + "./utils/toCapitalCase": "./utils/toCapitalCase.ts", + "./utils/url": "./utils/url.ts", "./utils/zod/*": "./utils/zod/*.ts" }, "dependencies": { diff --git a/packages/common/utils/toCapitalCase.test.ts b/packages/common/utils/toCapitalCase.test.ts new file mode 100644 index 000000000..fa6086d9e --- /dev/null +++ b/packages/common/utils/toCapitalCase.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest" + +import { toCapitalCase } from "./toCapitalCase" + +describe("toCapitalCase", () => { + it("should return same value for null or undefined", () => { + expect(toCapitalCase(null)).toBe(null) + expect(toCapitalCase(undefined)).toBe(undefined) + }) + + it("should return empty string for empty input", () => { + expect(toCapitalCase("")).toBe("") + }) + + it("should capitalize the first letter and lowercase the rest", () => { + expect(toCapitalCase("hello")).toBe("Hello") + expect(toCapitalCase("HELLO")).toBe("Hello") + expect(toCapitalCase("hElLo")).toBe("Hello") + expect(toCapitalCase("h")).toBe("H") + expect(toCapitalCase("H")).toBe("H") + }) + + it("should handle strings with spaces", () => { + expect(toCapitalCase(" hello ")).toBe("Hello") + expect(toCapitalCase(" ")).toBe("") + }) +}) diff --git a/packages/common/utils/toCapitalCase.ts b/packages/common/utils/toCapitalCase.ts new file mode 100644 index 000000000..9cb3df74a --- /dev/null +++ b/packages/common/utils/toCapitalCase.ts @@ -0,0 +1,13 @@ +export function toCapitalCase(value: string): string +export function toCapitalCase(value: null): null +export function toCapitalCase(value: undefined): undefined +export function toCapitalCase( + value: string | null | undefined +): string | null | undefined { + if (!value) return value + + value = value.trim() + if (!value) return value + + return value[0].toUpperCase() + value.slice(1).toLowerCase() +}