Merged in feat/sw-3472-booking-flow-parameterization (pull request #2811)

feat(SW-3272): Add BookingFlowConfig

* Add BookingFlowConfig

* Rename "provider" to BookingFlowConfig

* Change bookingCode to boolean

* Fix error


Approved-by: Joakim Jäderberg
Approved-by: Linus Flood
This commit is contained in:
Anton Gunnarsson
2025-09-19 11:56:50 +00:00
parent 7c92a8fc9a
commit 7adb9ded46
10 changed files with 178 additions and 65 deletions

View File

@@ -7,6 +7,7 @@ import "../../globals.css"
import { ReactQueryDevtools } from "@tanstack/react-query-devtools" import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import Script from "next/script" import Script from "next/script"
import { BookingFlowConfig } from "@scandic-hotels/booking-flow/BookingFlowConfig"
import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider" import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider"
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider" import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs" import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs"
@@ -56,6 +57,8 @@ type RootLayoutProps = {
bookingwidget: React.ReactNode bookingwidget: React.ReactNode
} }
const bookingFlowConfig = { bookingCodeEnabled: false } as const
export default async function RootLayout(props: RootLayoutProps) { export default async function RootLayout(props: RootLayoutProps) {
const params = await props.params const params = await props.params
const lang = params.lang const lang = params.lang
@@ -87,6 +90,7 @@ export default async function RootLayout(props: RootLayoutProps) {
<NuqsAdapter> <NuqsAdapter>
<TrpcProvider> <TrpcProvider>
<RACRouterProvider> <RACRouterProvider>
<BookingFlowConfig config={bookingFlowConfig}>
<BookingFlowContextProvider <BookingFlowContextProvider
data={{ data={{
// TODO // TODO
@@ -117,6 +121,7 @@ export default async function RootLayout(props: RootLayoutProps) {
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />
</BookingFlowTrackingProvider> </BookingFlowTrackingProvider>
</BookingFlowContextProvider> </BookingFlowContextProvider>
</BookingFlowConfig>
</RACRouterProvider> </RACRouterProvider>
</TrpcProvider> </TrpcProvider>
</NuqsAdapter> </NuqsAdapter>

View File

@@ -8,11 +8,13 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import Script from "next/script" import Script from "next/script"
import { SessionProvider } from "next-auth/react" import { SessionProvider } from "next-auth/react"
import { BookingFlowConfig } from "@scandic-hotels/booking-flow/BookingFlowConfig"
import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner" import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner"
import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs" import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs"
import { Lang } from "@scandic-hotels/common/constants/language" import { Lang } from "@scandic-hotels/common/constants/language"
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler" import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import TrpcProvider from "@/lib/trpc/Provider" import TrpcProvider from "@/lib/trpc/Provider"
import { SessionRefresher } from "@/components/Auth/TokenRefresher" import { SessionRefresher } from "@/components/Auth/TokenRefresher"
@@ -72,6 +74,7 @@ export default async function RootLayout(
<NuqsAdapter> <NuqsAdapter>
<TrpcProvider> <TrpcProvider>
<RACRouterProvider> <RACRouterProvider>
<BookingFlowConfig config={bookingFlowConfig}>
<BookingFlowProviders> <BookingFlowProviders>
<RouteChange /> <RouteChange />
<SitewideAlert /> <SitewideAlert />
@@ -86,6 +89,7 @@ export default async function RootLayout(
<UserExists /> <UserExists />
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />
</BookingFlowProviders> </BookingFlowProviders>
</BookingFlowConfig>
</RACRouterProvider> </RACRouterProvider>
</TrpcProvider> </TrpcProvider>
</NuqsAdapter> </NuqsAdapter>

View File

@@ -8,11 +8,13 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import Script from "next/script" import Script from "next/script"
import { SessionProvider } from "next-auth/react" import { SessionProvider } from "next-auth/react"
import { BookingFlowConfig } from "@scandic-hotels/booking-flow/BookingFlowConfig"
import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner" import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner"
import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs" import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs"
import { Lang } from "@scandic-hotels/common/constants/language" import { Lang } from "@scandic-hotels/common/constants/language"
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler" import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import TrpcProvider from "@/lib/trpc/Provider" import TrpcProvider from "@/lib/trpc/Provider"
import { SessionRefresher } from "@/components/Auth/TokenRefresher" import { SessionRefresher } from "@/components/Auth/TokenRefresher"
@@ -56,6 +58,7 @@ export default async function RootLayout(
> >
<NuqsAdapter> <NuqsAdapter>
<TrpcProvider> <TrpcProvider>
<BookingFlowConfig config={bookingFlowConfig}>
<RouteChange /> <RouteChange />
{children} {children}
<ToastHandler /> <ToastHandler />
@@ -63,6 +66,7 @@ export default async function RootLayout(
<StorageCleaner /> <StorageCleaner />
<CookieBotConsent /> <CookieBotConsent />
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />
</BookingFlowConfig>
</TrpcProvider> </TrpcProvider>
</NuqsAdapter> </NuqsAdapter>
</ClientIntlProvider> </ClientIntlProvider>

View File

@@ -7,11 +7,13 @@ import "@scandic-hotels/design-system/style.css"
import { ReactQueryDevtools } from "@tanstack/react-query-devtools" import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import Script from "next/script" import Script from "next/script"
import { BookingFlowConfig } from "@scandic-hotels/booking-flow/BookingFlowConfig"
import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner" import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner"
import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs" import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs"
import { Lang } from "@scandic-hotels/common/constants/language" import { Lang } from "@scandic-hotels/common/constants/language"
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler" import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import TrpcProvider from "@/lib/trpc/Provider" import TrpcProvider from "@/lib/trpc/Provider"
import TokenRefresher from "@/components/Auth/TokenRefresher" import TokenRefresher from "@/components/Auth/TokenRefresher"
@@ -56,6 +58,7 @@ export default async function RootLayout(
> >
<NuqsAdapter> <NuqsAdapter>
<TrpcProvider> <TrpcProvider>
<BookingFlowConfig config={bookingFlowConfig}>
<RouteChange /> <RouteChange />
{children} {children}
<ToastHandler /> <ToastHandler />
@@ -63,6 +66,7 @@ export default async function RootLayout(
<StorageCleaner /> <StorageCleaner />
<CookieBotConsent /> <CookieBotConsent />
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />
</BookingFlowConfig>
</TrpcProvider> </TrpcProvider>
</NuqsAdapter> </NuqsAdapter>
</ClientIntlProvider> </ClientIntlProvider>

View File

@@ -0,0 +1 @@
export const bookingFlowConfig = { bookingCodeEnabled: true } as const

View File

@@ -0,0 +1,47 @@
import "server-only"
import { cache } from "react"
import { BookingFlowConfigContextProvider } from "./bookingFlowConfigContext"
export type BookingFlowConfig = {
bookingCodeEnabled: boolean
}
const getRef = cache(() => ({
current: undefined as BookingFlowConfig | undefined,
}))
function setBookingFlowConfig(newConfig: BookingFlowConfig) {
getRef().current = newConfig
}
export function getBookingFlowConfig(): BookingFlowConfig {
const contextConfig = getRef().current
if (!contextConfig) {
throw new Error("BookingFlowConfig not set")
}
return contextConfig
}
/*
* Sets up both a server side context and a client side context
* for the booking flow config.
*/
export async function BookingFlowConfig({
children,
config,
}: {
children: React.ReactNode
config: BookingFlowConfig
}) {
setBookingFlowConfig(config)
return (
<BookingFlowConfigContextProvider config={config}>
{children}
</BookingFlowConfigContextProvider>
)
}

View File

@@ -0,0 +1,39 @@
"use client"
import { createContext, useContext } from "react"
import type { BookingFlowConfig } from "./bookingFlowConfig"
type BookingFlowConfigContextData = {
config: BookingFlowConfig
}
const BookingFlowConfigContext = createContext<
BookingFlowConfigContextData | undefined
>(undefined)
export const useBookingFlowConfig = (): BookingFlowConfigContextData => {
const context = useContext(BookingFlowConfigContext)
if (!context) {
throw new Error(
"useBookingFlowConfig must be used within a BookingFlowConfigContextProvider. Did you forget to use BookingFlowConfig in the consuming app?"
)
}
return context
}
export function BookingFlowConfigContextProvider({
children,
config,
}: {
children: React.ReactNode
config: BookingFlowConfig
}) {
return (
<BookingFlowConfigContext.Provider value={{ config }}>
{children}
</BookingFlowConfigContext.Provider>
)
}

View File

@@ -5,15 +5,18 @@ import { useIntl } from "react-intl"
import Caption from "@scandic-hotels/design-system/Caption" import Caption from "@scandic-hotels/design-system/Caption"
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
import BookingCode from "../BookingCode" import BookingCode from "../BookingCode"
import RewardNight from "../RewardNight" import RewardNight from "../RewardNight"
import styles from "./voucher.module.css" import styles from "./voucher.module.css"
export default function Voucher() { export default function Voucher() {
const { config } = useBookingFlowConfig()
return ( return (
<div className={styles.optionsContainer}> <div className={styles.optionsContainer}>
<BookingCode /> {config.bookingCodeEnabled && <BookingCode />}
<div className={styles.options}> <div className={styles.options}>
<div className={styles.option}> <div className={styles.option}>
<RewardNight /> <RewardNight />
@@ -25,6 +28,7 @@ export default function Voucher() {
export function VoucherSkeleton() { export function VoucherSkeleton() {
const intl = useIntl() const intl = useIntl()
const { config } = useBookingFlowConfig()
const vouchers = intl.formatMessage({ const vouchers = intl.formatMessage({
defaultMessage: "Code / Voucher", defaultMessage: "Code / Voucher",
@@ -35,6 +39,7 @@ export function VoucherSkeleton() {
return ( return (
<div className={styles.optionsContainer}> <div className={styles.optionsContainer}>
{config.bookingCodeEnabled && (
<div className={styles.voucherSkeletonContainer}> <div className={styles.voucherSkeletonContainer}>
<label> <label>
<Caption type="bold" color="red" asChild> <Caption type="bold" color="red" asChild>
@@ -43,6 +48,7 @@ export function VoucherSkeleton() {
</label> </label>
<SkeletonShimmer width="100%" display="block" /> <SkeletonShimmer width="100%" display="block" />
</div> </div>
)}
<div className={styles.options}> <div className={styles.options}>
<div className={cx(styles.option, styles.showOnTablet)}> <div className={cx(styles.option, styles.showOnTablet)}>

View File

@@ -11,6 +11,7 @@
"test:watch": "vitest" "test:watch": "vitest"
}, },
"exports": { "exports": {
"./BookingFlowConfig": "./lib/bookingFlowConfig/bookingFlowConfig.tsx",
"./BookingFlowContextProvider": "./lib/components/BookingFlowContextProvider.tsx", "./BookingFlowContextProvider": "./lib/components/BookingFlowContextProvider.tsx",
"./BookingFlowTrackingProvider": "./lib/components/BookingFlowTrackingProvider.tsx", "./BookingFlowTrackingProvider": "./lib/components/BookingFlowTrackingProvider.tsx",
"./BookingWidget": "./lib/components/BookingWidget/index.tsx", "./BookingWidget": "./lib/components/BookingWidget/index.tsx",
@@ -77,6 +78,7 @@
"react-hook-form": "^7.56.2", "react-hook-form": "^7.56.2",
"react-intl": "^7.1.11", "react-intl": "^7.1.11",
"react-to-print": "^3.1.0", "react-to-print": "^3.1.0",
"server-only": "^0.0.1",
"usehooks-ts": "3.1.1", "usehooks-ts": "3.1.1",
"zustand": "^4.5.2" "zustand": "^4.5.2"
}, },

View File

@@ -5906,6 +5906,7 @@ __metadata:
react-hook-form: "npm:^7.56.2" react-hook-form: "npm:^7.56.2"
react-intl: "npm:^7.1.11" react-intl: "npm:^7.1.11"
react-to-print: "npm:^3.1.0" react-to-print: "npm:^3.1.0"
server-only: "npm:^0.0.1"
typescript: "npm:5.8.3" typescript: "npm:5.8.3"
usehooks-ts: "npm:3.1.1" usehooks-ts: "npm:3.1.1"
vitest: "npm:^3.2.4" vitest: "npm:^3.2.4"