From 7fcd5833bd02f487e187c7067966c2fa22b1cc5d Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 2 Oct 2025 12:34:38 +0000 Subject: [PATCH] feat(BOOK-414): Added hotel branding themes to hotelpages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Approved-by: Matilda Landström --- apps/scandic-web/.env.local.example | 1 + .../(contentTypes)/hotel_page/[uid]/page.tsx | 36 +++++----- .../(contentTypes)/hotel_page/[uid]/page.tsx | 10 +++ .../[lang]/(live)/@theme/[...path]/page.tsx | 1 + .../app/[lang]/(live)/@theme/page.tsx | 6 ++ apps/scandic-web/app/[lang]/(live)/layout.tsx | 8 ++- .../ContentType/HotelMapPage/index.tsx | 29 +++----- .../ContentType/HotelPage/index.tsx | 36 ++++------ .../ContentType/HotelSubpage/index.tsx | 25 +++---- .../components/ThemeUpdater/index.tsx | 18 +++++ apps/scandic-web/env/server.ts | 6 ++ .../types/components/hotelPage/hotelPage.ts | 4 -- .../types/components/hotelPage/subpage.ts | 4 -- apps/scandic-web/utils/theme/index.ts | 28 ++++++++ apps/scandic-web/utils/theme/types.ts | 12 ++++ apps/scandic-web/utils/theme/utils.ts | 67 +++++++++++++++++++ packages/trpc/lib/types/hotelPage.ts | 9 +++ 17 files changed, 217 insertions(+), 83 deletions(-) create mode 100644 apps/scandic-web/app/[lang]/(live)/@theme/(contentTypes)/hotel_page/[uid]/page.tsx create mode 100644 apps/scandic-web/app/[lang]/(live)/@theme/[...path]/page.tsx create mode 100644 apps/scandic-web/app/[lang]/(live)/@theme/page.tsx create mode 100644 apps/scandic-web/components/ThemeUpdater/index.tsx delete mode 100644 apps/scandic-web/types/components/hotelPage/subpage.ts create mode 100644 apps/scandic-web/utils/theme/index.ts create mode 100644 apps/scandic-web/utils/theme/types.ts create mode 100644 apps/scandic-web/utils/theme/utils.ts diff --git a/apps/scandic-web/.env.local.example b/apps/scandic-web/.env.local.example index 30d67d4fe..b6811a9ab 100644 --- a/apps/scandic-web/.env.local.example +++ b/apps/scandic-web/.env.local.example @@ -72,3 +72,4 @@ DTMC_ENTRA_ID_ISSUER="" DTMC_ENTRA_ID_SECRET="" PROMO_CAMPAIGN_PAGES_ENABLED="0" # 0 - disabled, 1 - enabled +HOTEL_BRANDING="0" # 0 - disabled, 1 - enabled diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx index 04c833416..26faa6a3d 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx @@ -1,6 +1,6 @@ import { notFound } from "next/navigation" -import { getHotelPage } from "@/lib/trpc/memoizedRequests" +import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests" import HotelMapPage from "@/components/ContentType/HotelMapPage" import HotelPage from "@/components/ContentType/HotelPage" @@ -8,34 +8,40 @@ import HotelSubpage from "@/components/ContentType/HotelSubpage" import styles from "./page.module.css" -import type { PageArgs } from "@/types/params" +import type { LangParams, PageArgs } from "@/types/params" export { generateMetadata } from "@/utils/metadata/generateMetadata" export default async function HotelPagePage( - props: PageArgs + props: PageArgs ) { const searchParams = await props.searchParams + const params = await props.params const hotelPageData = await getHotelPage() if (!hotelPageData) { return notFound() } + const hotelData = await getHotel({ + hotelId: hotelPageData.hotel_page_id, + isCardOnlyPayment: false, + language: params.lang, + }) + + if (!hotelData) { + return notFound() + } + if (searchParams.subpage) { + return + } else if (searchParams.view === "map") { + return + } else { return ( - +
+ +
) } - if (searchParams.view === "map") { - return - } - return ( -
- -
- ) } diff --git a/apps/scandic-web/app/[lang]/(live)/@theme/(contentTypes)/hotel_page/[uid]/page.tsx b/apps/scandic-web/app/[lang]/(live)/@theme/(contentTypes)/hotel_page/[uid]/page.tsx new file mode 100644 index 000000000..16a090414 --- /dev/null +++ b/apps/scandic-web/app/[lang]/(live)/@theme/(contentTypes)/hotel_page/[uid]/page.tsx @@ -0,0 +1,10 @@ +import ThemeUpdater from "@/components/ThemeUpdater" +import { getLang } from "@/i18n/serverContext" +import { getHotelTheme } from "@/utils/theme/utils" + +export default async function ThemeHotelPage() { + const lang = await getLang() + const hotelTheme = await getHotelTheme(lang) + + return +} diff --git a/apps/scandic-web/app/[lang]/(live)/@theme/[...path]/page.tsx b/apps/scandic-web/app/[lang]/(live)/@theme/[...path]/page.tsx new file mode 100644 index 000000000..03a82e5f5 --- /dev/null +++ b/apps/scandic-web/app/[lang]/(live)/@theme/[...path]/page.tsx @@ -0,0 +1 @@ +export { default } from "../page" diff --git a/apps/scandic-web/app/[lang]/(live)/@theme/page.tsx b/apps/scandic-web/app/[lang]/(live)/@theme/page.tsx new file mode 100644 index 000000000..49e83007f --- /dev/null +++ b/apps/scandic-web/app/[lang]/(live)/@theme/page.tsx @@ -0,0 +1,6 @@ +import ThemeUpdater from "@/components/ThemeUpdater" +import { DEFAULT_THEME } from "@/utils/theme/types" + +export default function ThemePage() { + return +} diff --git a/apps/scandic-web/app/[lang]/(live)/layout.tsx b/apps/scandic-web/app/[lang]/(live)/layout.tsx index 46daebf73..c53975af1 100644 --- a/apps/scandic-web/app/[lang]/(live)/layout.tsx +++ b/apps/scandic-web/app/[lang]/(live)/layout.tsx @@ -34,6 +34,7 @@ import { FontPreload } from "@/fonts/font-preloading" import { getMessages } from "@/i18n" import ClientIntlProvider from "@/i18n/Provider" import { setLang } from "@/i18n/serverContext" +import { getThemeClass } from "@/utils/theme" import type { LangParams, LayoutArgs } from "@/types/params" @@ -41,15 +42,17 @@ export default async function RootLayout( props: React.PropsWithChildren< LayoutArgs & { bookingwidget: React.ReactNode + theme: React.ReactNode } > ) { const params = await props.params - const { bookingwidget, children } = props + const { bookingwidget, theme, children } = props setLang(params.lang) const messages = await getMessages(params.lang) + const themeClass = await getThemeClass(params.lang) return ( @@ -64,7 +67,7 @@ export default async function RootLayout( window.dataLayer = window.dataLayer || [] `} - +
+ {theme} diff --git a/apps/scandic-web/components/ContentType/HotelMapPage/index.tsx b/apps/scandic-web/components/ContentType/HotelMapPage/index.tsx index ec4a344e5..c5aebbe0e 100644 --- a/apps/scandic-web/components/ContentType/HotelMapPage/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelMapPage/index.tsx @@ -1,32 +1,23 @@ -import { notFound } from "next/navigation" +import { type HotelData } from "@scandic-hotels/trpc/types/hotel" import { env } from "@/env/server" -import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests" - -import { getLang } from "@/i18n/serverContext" import HotelMapPageClient from "./Client" import type { HotelType } from "@scandic-hotels/common/constants/hotelType" interface HotelMapPageProps { - hotelId: string + hotelData: HotelData } -export default async function HotelMapPage({ hotelId }: HotelMapPageProps) { - const lang = await getLang() - const hotelPageData = await getHotelPage() - const hotelData = await getHotel({ - hotelId, - isCardOnlyPayment: false, - language: lang, - }) - - if (!hotelPageData || !hotelData) { - notFound() - } - - const { name, location, pointsOfInterest, hotelType } = hotelData.hotel +export default async function HotelMapPage({ hotelData }: HotelMapPageProps) { + const { + name, + location, + pointsOfInterest, + hotelType, + operaId: hotelId, + } = hotelData.hotel const coordinates = { lat: location.latitude, diff --git a/apps/scandic-web/components/ContentType/HotelPage/index.tsx b/apps/scandic-web/components/ContentType/HotelPage/index.tsx index 060f8f59b..a421242b6 100644 --- a/apps/scandic-web/components/ContentType/HotelPage/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelPage/index.tsx @@ -6,12 +6,9 @@ import { safeTry } from "@scandic-hotels/common/utils/safeTry" import { Alert } from "@scandic-hotels/design-system/Alert" import Link from "@scandic-hotels/design-system/Link" import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK" +import { type HotelPageData } from "@scandic-hotels/trpc/types/hotelPage" -import { - getHotel, - getHotelPage, - getMeetingRooms, -} from "@/lib/trpc/memoizedRequests" +import { getMeetingRooms } from "@/lib/trpc/memoizedRequests" import AccordionSection from "@/components/Blocks/Accordion" import Breadcrumbs from "@/components/Breadcrumbs" @@ -52,31 +49,25 @@ import { import styles from "./hotelPage.module.css" import type { HotelType } from "@scandic-hotels/common/constants/hotelType" +import type { HotelData } from "@scandic-hotels/trpc/types/hotel" -import type { HotelPageProps } from "@/types/components/hotelPage/hotelPage" import { AlertName } from "@/types/enums/alert" import { HotelHashValues } from "@/types/enums/hotelPage" -export default async function HotelPage({ hotelId }: HotelPageProps) { +interface HotelPageProps { + hotelData: HotelData + hotelPageData: HotelPageData +} + +export default async function HotelPage({ + hotelData, + hotelPageData, +}: HotelPageProps) { const lang = await getLang() const intl = await getIntl() - void getHotelPage() - void getHotel({ - hotelId, - isCardOnlyPayment: false, - language: lang, - }) - - const hotelPageData = await getHotelPage() - const hotelData = await getHotel({ - hotelId, - isCardOnlyPayment: false, - language: lang, - }) - const [meetingRoomsData] = await safeTry( - getMeetingRooms({ hotelId, language: lang }) + getMeetingRooms({ hotelId: hotelData.hotel.operaId, language: lang }) ) if (!hotelData?.hotel || !hotelPageData) { @@ -106,6 +97,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) { ratings, parking, hotelType, + operaId: hotelId, } = hotel const { healthAndWellness, diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/index.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/index.tsx index 37424833b..fdbf27141 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/index.tsx @@ -1,9 +1,5 @@ import { notFound } from "next/navigation" -import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests" - -import { getLang } from "@/i18n/serverContext" - import AccessibilitySubpage from "./AccessibilitySubpage" import MeetingsSubpage from "./MeetingsSubpage" import ParkingSubpage from "./ParkingSubpage" @@ -11,22 +7,17 @@ import RestaurantSubpage from "./RestaurantSubpage" import { verifySubpageShouldExist } from "./utils" import WellnessSubpage from "./WellnessSubpage" -import type { HotelSubpageProps } from "@/types/components/hotelPage/subpage" +import type { HotelData } from "@scandic-hotels/trpc/types/hotel" + +interface HotelSubpageProps { + hotelData: HotelData + subpage: string +} export default async function HotelSubpage({ - hotelId, + hotelData, subpage, }: HotelSubpageProps) { - const lang = await getLang() - const [hotelPageData, hotelData] = await Promise.all([ - getHotelPage(), - getHotel({ hotelId, language: lang, isCardOnlyPayment: false }), - ]) - - if (!hotelData?.hotel || !hotelPageData) { - notFound() - } - if (!verifySubpageShouldExist(hotelData, subpage)) { notFound() } @@ -49,7 +40,7 @@ export default async function HotelSubpage({ case additionalData.meetingRooms.nameInUrl: return ( diff --git a/apps/scandic-web/components/ThemeUpdater/index.tsx b/apps/scandic-web/components/ThemeUpdater/index.tsx new file mode 100644 index 000000000..404f1f5b6 --- /dev/null +++ b/apps/scandic-web/components/ThemeUpdater/index.tsx @@ -0,0 +1,18 @@ +"use client" + +import { useEffect } from "react" + +import { type Theme, THEMES } from "@/utils/theme/types" + +interface ThemeUpdaterProps { + theme: Theme +} + +export default function ThemeUpdater({ theme }: ThemeUpdaterProps) { + useEffect(() => { + document.body.classList.remove(...THEMES) + document.body.classList.add(theme) + }, [theme]) + + return null +} diff --git a/apps/scandic-web/env/server.ts b/apps/scandic-web/env/server.ts index 00acfc19a..0c63a89cd 100644 --- a/apps/scandic-web/env/server.ts +++ b/apps/scandic-web/env/server.ts @@ -154,6 +154,11 @@ export const env = createEnv({ .refine((s) => s === "1" || s === "0") .transform((s) => s === "1") .default("0"), + HOTEL_BRANDING: z + .string() + .refine((s) => s === "1" || s === "0") + .transform((s) => s === "1") + .default("0"), WEBVIEW_SHOW_OVERVIEW: z .string() .refine((s) => s === "1" || s === "0") @@ -248,6 +253,7 @@ export const env = createEnv({ DTMC_ENTRA_ID_ISSUER: process.env.DTMC_ENTRA_ID_ISSUER, DTMC_ENTRA_ID_SECRET: process.env.DTMC_ENTRA_ID_SECRET, PROMO_CAMPAIGN_PAGES_ENABLED: process.env.PROMO_CAMPAIGN_PAGES_ENABLED, + HOTEL_BRANDING: process.env.HOTEL_BRANDING, WEBVIEW_SHOW_OVERVIEW: process.env.WEBVIEW_SHOW_OVERVIEW, ENABLE_NEW_OVERVIEW_SECTION: process.env.ENABLE_NEW_OVERVIEW_SECTION, CHATBOT_LIVE_LANGS: process.env.CHATBOT_LIVE_LANGS, diff --git a/apps/scandic-web/types/components/hotelPage/hotelPage.ts b/apps/scandic-web/types/components/hotelPage/hotelPage.ts index 0736e2764..53b4ab94c 100644 --- a/apps/scandic-web/types/components/hotelPage/hotelPage.ts +++ b/apps/scandic-web/types/components/hotelPage/hotelPage.ts @@ -1,9 +1,5 @@ import type { HotelHashValues } from "@/types/enums/hotelPage" -export interface HotelPageProps { - hotelId: string -} - // Slugs that are not set elsewhere (dynamically or from CS) export enum SidepeekSlugs { about = "about", diff --git a/apps/scandic-web/types/components/hotelPage/subpage.ts b/apps/scandic-web/types/components/hotelPage/subpage.ts deleted file mode 100644 index c2c66a221..000000000 --- a/apps/scandic-web/types/components/hotelPage/subpage.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface HotelSubpageProps { - hotelId: string - subpage: string -} diff --git a/apps/scandic-web/utils/theme/index.ts b/apps/scandic-web/utils/theme/index.ts new file mode 100644 index 000000000..3ee4f77ce --- /dev/null +++ b/apps/scandic-web/utils/theme/index.ts @@ -0,0 +1,28 @@ +import { headers } from "next/headers" + +import { PageContentTypeEnum } from "@scandic-hotels/trpc/enums/contentType" + +import { env } from "@/env/server" + +import { DEFAULT_THEME } from "./types" +import { getHotelTheme } from "./utils" + +import type { Lang } from "@scandic-hotels/common/constants/language" + +export async function getThemeClass(lang: Lang): Promise { + if (!env.HOTEL_BRANDING) { + return DEFAULT_THEME + } + + const headersList = await headers() + const contentType = headersList.get("x-contenttype") || "" + + const isHotelPage = + contentType && contentType === PageContentTypeEnum.hotelPage + + if (isHotelPage) { + return await getHotelTheme(lang) + } + + return DEFAULT_THEME +} diff --git a/apps/scandic-web/utils/theme/types.ts b/apps/scandic-web/utils/theme/types.ts new file mode 100644 index 000000000..21d2221ea --- /dev/null +++ b/apps/scandic-web/utils/theme/types.ts @@ -0,0 +1,12 @@ +export enum Theme { + downtownCamper = "downtown-camper", + grandHotel = "grand-hotel", + haymarket = "haymarket", + hotelNorge = "hotel-norge", + marski = "marski", + scandic = "scandic", + scandicGo = "scandic-go", +} + +export const DEFAULT_THEME = Theme.scandic +export const THEMES = Object.values(Theme) diff --git a/apps/scandic-web/utils/theme/utils.ts b/apps/scandic-web/utils/theme/utils.ts new file mode 100644 index 000000000..6a9d95b2f --- /dev/null +++ b/apps/scandic-web/utils/theme/utils.ts @@ -0,0 +1,67 @@ +import { SignatureHotelEnum } from "@scandic-hotels/common/constants/signatureHotels" +import { HotelTypeEnum } from "@scandic-hotels/trpc/enums/hotelType" + +import { env } from "@/env/server" +import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests" + +import { DEFAULT_THEME, Theme } from "./types" + +import type { Lang } from "@scandic-hotels/common/constants/language" + +function getSignatureHotelTheme(hotelId: string) { + switch (hotelId) { + case SignatureHotelEnum.Haymarket: + return Theme.haymarket + case SignatureHotelEnum.HotelNorge: + return Theme.hotelNorge + case SignatureHotelEnum.DowntownCamper: + return Theme.downtownCamper + case SignatureHotelEnum.GrandHotelOslo: + return Theme.grandHotel + case SignatureHotelEnum.Marski: + return Theme.marski + default: + return Theme.scandic + } +} + +function getThemeByHotel(hotelId: string, hotelType: string) { + if (hotelType === HotelTypeEnum.ScandicGo) { + return Theme.scandicGo + } + if (hotelType === HotelTypeEnum.Signature) { + return getSignatureHotelTheme(hotelId) + } + + return DEFAULT_THEME +} + +export async function getHotelTheme(language: Lang): Promise { + if (!env.HOTEL_BRANDING) { + return DEFAULT_THEME + } + + try { + const hotelPageData = await getHotelPage() + if (!hotelPageData) { + return DEFAULT_THEME + } + + const hotelData = await getHotel({ + hotelId: hotelPageData.hotel_page_id, + isCardOnlyPayment: false, + language, + }) + + if (!hotelData) { + return DEFAULT_THEME + } + + return getThemeByHotel( + hotelPageData.hotel_page_id, + hotelData.hotel.hotelType + ) + } catch { + return DEFAULT_THEME + } +} diff --git a/packages/trpc/lib/types/hotelPage.ts b/packages/trpc/lib/types/hotelPage.ts index f58fd8519..09455f908 100644 --- a/packages/trpc/lib/types/hotelPage.ts +++ b/packages/trpc/lib/types/hotelPage.ts @@ -9,6 +9,7 @@ import type { } from "../routers/contentstack/hotelPage/output" import type { activitiesCardSchema } from "../routers/contentstack/schemas/blocks/activitiesCard" import type { spaPageSchema } from "../routers/contentstack/schemas/blocks/spaPage" +import type { Campaigns } from "./campaignPage" export interface GetHotelPageData extends z.input {} export interface HotelPage extends z.output {} @@ -28,3 +29,11 @@ export interface GetHotelPageUrlsData export interface GetHotelPageCountData extends z.input {} export type HotelPageUrls = z.output + +export type HotelPageData = HotelPage["hotel_page"] & { + campaignsBlock?: + | (Omit & { + campaigns: Campaigns + }) + | null +}