From 16e6c1596c4b59c80468b244d6588903899a98d1 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Tue, 23 Sep 2025 08:44:55 +0000 Subject: [PATCH] Merged in feat/SW-3477-hide-voucher-booking-code-sas- (pull request #2836) feat(SW-3477) Updated booking widget for SAS white label Approved-by: Anton Gunnarsson --- apps/partner-sas/app/[lang]/layout.tsx | 2 + .../bookingFlowConfig/bookingFlowConfig.tsx | 2 + .../bookingFlowConfigContext.tsx | 14 ++++ .../lib/components/BookingFlowInput/errors.ts | 20 ++++-- .../BookingCode/booking-code.module.css | 5 ++ .../FormContent/BookingCode/index.tsx | 72 ++++++------------- .../RemoveExtraRooms/RemoveExtraRooms.tsx | 36 ++++++++++ .../FormContent/RewardNight/index.tsx | 50 ++++++++----- .../RewardNight/reward-night.module.css | 4 +- .../FormContent/formContent.module.css | 4 ++ .../BookingWidgetForm/FormContent/index.tsx | 14 +++- .../lib/components/BookingWidget/Client.tsx | 4 +- packages/common/constants/scandicPartners.ts | 3 + packages/common/package.json | 1 + .../lib/components/Button/index.tsx | 2 + .../Form/Checkbox/checkbox.module.css | 2 +- .../IconButton/IconButton.stories.tsx | 5 ++ .../lib/components/IconButton/IconButton.tsx | 2 + .../IconButton/iconButton.module.css | 4 ++ .../lib/components/IconButton/variants.ts | 4 ++ .../lib/components/Switch/switch.module.css | 2 +- 21 files changed, 173 insertions(+), 79 deletions(-) create mode 100644 packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RemoveExtraRooms/RemoveExtraRooms.tsx create mode 100644 packages/common/constants/scandicPartners.ts diff --git a/apps/partner-sas/app/[lang]/layout.tsx b/apps/partner-sas/app/[lang]/layout.tsx index 82bf6fae6..18a2ecbaf 100644 --- a/apps/partner-sas/app/[lang]/layout.tsx +++ b/apps/partner-sas/app/[lang]/layout.tsx @@ -15,6 +15,7 @@ import { bookingTermsAndConditionsRoutes } from "@scandic-hotels/common/constant import { customerService } from "@scandic-hotels/common/constants/routes/customerService" import { myStay } from "@scandic-hotels/common/constants/routes/myStay" import { privacyPolicyRoutes } from "@scandic-hotels/common/constants/routes/privacyPolicyRoutes" +import { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners" import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler" import AdobeSDKScript from "@/components/AdobeSDKScript" @@ -61,6 +62,7 @@ export default async function RootLayout(props: RootLayoutProps) { const bookingFlowConfig: BookingFlowConfig = { bookingCodeEnabled: false, + partner: ScandicPartnersEnum.sas, routes: { myStay: routeToScandicWeb(myStay), bookingTermsAndConditions: routeToScandicWeb( diff --git a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx index 18ba31e1c..64af84286 100644 --- a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx +++ b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx @@ -5,9 +5,11 @@ import { cache } from "react" import { BookingFlowConfigContextProvider } from "./bookingFlowConfigContext" import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute" +import type { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners" export type BookingFlowConfig = { bookingCodeEnabled: boolean + partner?: ScandicPartnersEnum routes: { myStay: LangRoute bookingTermsAndConditions: LangRoute diff --git a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx index 437fcc64d..6c6d5c275 100644 --- a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx +++ b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx @@ -2,6 +2,8 @@ import { createContext, useContext } from "react" +import type { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners" + import type { BookingFlowConfig } from "./bookingFlowConfig" type BookingFlowConfigContextData = BookingFlowConfig @@ -10,6 +12,18 @@ const BookingFlowConfigContext = createContext< BookingFlowConfigContextData | undefined >(undefined) +export const useIsPartner = (partner: ScandicPartnersEnum) => { + 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.partner === partner +} + export const useBookingFlowConfig = (): BookingFlowConfigContextData => { const context = useContext(BookingFlowConfigContext) diff --git a/packages/booking-flow/lib/components/BookingFlowInput/errors.ts b/packages/booking-flow/lib/components/BookingFlowInput/errors.ts index 744d1ae57..60e7259b9 100644 --- a/packages/booking-flow/lib/components/BookingFlowInput/errors.ts +++ b/packages/booking-flow/lib/components/BookingFlowInput/errors.ts @@ -1,3 +1,4 @@ +import { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners" import { logger } from "@scandic-hotels/common/logger" import { phoneErrors } from "@scandic-hotels/common/utils/zod/phoneValidator" @@ -9,7 +10,11 @@ import { import type { IntlShape } from "react-intl" -export function getErrorMessage(intl: IntlShape, errorCode?: string) { +export function getErrorMessage( + intl: IntlShape, + errorCode?: string, + partner?: ScandicPartnersEnum +) { switch (errorCode) { case bookingWidgetErrors.BOOKING_CODE_INVALID: return intl.formatMessage({ @@ -42,10 +47,15 @@ export function getErrorMessage(intl: IntlShape, errorCode?: string) { "Multi-room booking is not available with this booking code.", }) case bookingWidgetErrors.MULTIROOM_REWARD_NIGHT_UNAVAILABLE: - return intl.formatMessage({ - defaultMessage: - "Multi-room booking is not available with reward night.", - }) + return partner === ScandicPartnersEnum.sas + ? intl.formatMessage({ + defaultMessage: + "Multi-room booking is not available with euro bonus points.", + }) + : intl.formatMessage({ + defaultMessage: + "Multi-room booking is not available with reward night.", + }) case bookingWidgetErrors.CODE_VOUCHER_REWARD_NIGHT_UNAVAILABLE: return intl.formatMessage({ defaultMessage: diff --git a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/booking-code.module.css b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/booking-code.module.css index e799b1f10..cc8739b21 100644 --- a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/booking-code.module.css +++ b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/booking-code.module.css @@ -19,6 +19,10 @@ color: var(--Text-Secondary); } +.colorSecondary { + color: var(--Text-Secondary); +} + .errorContainer { display: flex; flex-direction: column; @@ -46,6 +50,7 @@ .bookingCodeTooltip { max-width: 560px; margin-top: var(--Spacing-x2); + color: var(--Text-Secondary); } .bookingCodeRememberVisible label { diff --git a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/index.tsx b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/index.tsx index e463cefba..9eeee93a5 100644 --- a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/index.tsx +++ b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/BookingCode/index.tsx @@ -4,15 +4,11 @@ import { type FieldError, useFormContext } from "react-hook-form" import { useIntl } from "react-intl" import { useMediaQuery } from "usehooks-ts" -import Body from "@scandic-hotels/design-system/Body" -import Caption from "@scandic-hotels/design-system/Caption" +import { Button } from "@scandic-hotels/design-system/Button" import Checkbox from "@scandic-hotels/design-system/Form/Checkbox" +import { IconButton } from "@scandic-hotels/design-system/IconButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import Modal from "@scandic-hotels/design-system/Modal" -import { - type ButtonProps, - OldDSButton as Button, -} from "@scandic-hotels/design-system/OldDSButton" import Switch from "@scandic-hotels/design-system/Switch" import { Typography } from "@scandic-hotels/design-system/Typography" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" @@ -20,6 +16,7 @@ import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" import BookingFlowInput from "../../../../BookingFlowInput" import { getErrorMessage } from "../../../../BookingFlowInput/errors" import { Input as BookingWidgetInput } from "../Input" +import { RemoveExtraRooms } from "../RemoveExtraRooms/RemoveExtraRooms" import { isMultiRoomError } from "../utils" import styles from "./booking-code.module.css" @@ -192,13 +189,15 @@ export default function BookingCode() { } > - + {intl.formatMessage({ defaultMessage: "Remember code", })} - + )} @@ -226,19 +225,19 @@ function CodeRulesModal() { return ( + - + } title={codeVoucher} > - - {bookingCodeTooltipText} - + +

{bookingCodeTooltipText}

+
) } @@ -249,20 +248,19 @@ function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) { return ( <> - + {intl.formatMessage({ defaultMessage: "Remember code", })} - + {bookingCodeValue ? ( - ) -} - function TabletBookingCode({ bookingCode, updateValue, @@ -381,7 +351,7 @@ function TabletBookingCode({ return (
- () + function removeExtraRooms() { + // Timeout to delay the event scheduling issue with touch events on mobile + window.setTimeout(() => { + const rooms = getValues("rooms")[0] + setValue("rooms", [rooms], { shouldValidate: true, shouldDirty: true }) + trigger("bookingCode.value") + trigger(SEARCH_TYPE_REDEMPTION) + }, 300) + } + + return ( + + ) +} diff --git a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/index.tsx b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/index.tsx index 41f6d6add..39780f32b 100644 --- a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/index.tsx +++ b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/index.tsx @@ -5,16 +5,20 @@ import { useFormContext } from "react-hook-form" import { useIntl } from "react-intl" import { useMediaQuery } from "usehooks-ts" -import Caption from "@scandic-hotels/design-system/Caption" +import { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners" import Checkbox from "@scandic-hotels/design-system/Form/Checkbox" +import { IconButton } from "@scandic-hotels/design-system/IconButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import Modal from "@scandic-hotels/design-system/Modal" -import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" import { Typography } from "@scandic-hotels/design-system/Typography" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" +import { + useBookingFlowConfig, + useIsPartner, +} from "../../../../../bookingFlowConfig/bookingFlowConfigContext" import { getErrorMessage } from "../../../../BookingFlowInput/errors" -import { RemoveExtraRooms } from "../BookingCode" +import { RemoveExtraRooms } from "../RemoveExtraRooms/RemoveExtraRooms" import { isMultiRoomError } from "../utils" import styles from "./reward-night.module.css" @@ -23,6 +27,8 @@ import type { BookingWidgetSchema } from "../../../Client" export default function RewardNight() { const intl = useIntl() + const config = useBookingFlowConfig() + const isPartnerSas = useIsPartner(ScandicPartnersEnum.sas) const { setValue, getValues, @@ -30,13 +36,22 @@ export default function RewardNight() { trigger, } = useFormContext() const ref = useRef(null) - const reward = intl.formatMessage({ - defaultMessage: "Reward Night", - }) - const rewardNightTooltip = intl.formatMessage({ - defaultMessage: - "To book a reward night, make sure you're logged in to your Scandic Friends account.", - }) + const reward = isPartnerSas + ? intl.formatMessage({ + defaultMessage: "EuroBonus Points", + }) + : intl.formatMessage({ + defaultMessage: "Reward Night", + }) + const rewardNightTooltip = isPartnerSas + ? intl.formatMessage({ + defaultMessage: + "To book with EuroBonus points, make sure you're logged into your SAS EuroBonus account.", + }) + : intl.formatMessage({ + defaultMessage: + "To book a reward night, make sure you're logged in to your Scandic Friends account.", + }) const redemptionErr = errors[SEARCH_TYPE_REDEMPTION] const isDesktop = useMediaQuery("(min-width: 767px)") @@ -92,19 +107,22 @@ export default function RewardNight() { }} >
- + {reward} - + + - + } title={reward} > @@ -131,12 +149,12 @@ export default function RewardNight() { className={styles.errorIcon} isFilled={!isDesktop} /> - {getErrorMessage(intl, redemptionErr.message)} + {getErrorMessage(intl, redemptionErr.message, config.partner)} {isMultiRoomError(redemptionErr.message) ? (
- +
) : null}
diff --git a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/reward-night.module.css b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/reward-night.module.css index aa54fd4c0..25b86ac5e 100644 --- a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/reward-night.module.css +++ b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/RewardNight/reward-night.module.css @@ -17,7 +17,9 @@ .rewardNightLabel { align-items: center; display: flex; - gap: var(--Spacing-x1); + color: var(--Text-Secondary); + min-width: 160px; + gap: var(--Space-x1); } .rewardNightTooltip { diff --git a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/formContent.module.css b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/formContent.module.css index e96f034bd..076a1e24b 100644 --- a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/formContent.module.css +++ b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/formContent.module.css @@ -185,4 +185,8 @@ .input { gap: var(--Spacing-x2); } + + .bookingCodeDisabled { + flex: none; + } } diff --git a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/index.tsx b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/index.tsx index 0fa254735..4bdf47aa7 100644 --- a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/index.tsx +++ b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/FormContent/index.tsx @@ -13,10 +13,11 @@ import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" import { Typography } from "@scandic-hotels/design-system/Typography" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" +import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext" import useLang from "../../../../hooks/useLang" import GuestsRoomsPickerForm from "../../../BookingWidget/GuestsRoomsPicker" import DatePicker from "../../DatePicker" -import { RemoveExtraRooms } from "./BookingCode" +import { RemoveExtraRooms } from "./RemoveExtraRooms/RemoveExtraRooms" import { Search, SearchSkeleton } from "./Search" import { isMultiRoomError } from "./utils" import ValidationError from "./ValidationError" @@ -40,6 +41,7 @@ export default function FormContent({ const { formState: { errors, isDirty }, } = useFormContext() + const { bookingCodeEnabled } = useBookingFlowConfig() const lang = useLang() const pathName = usePathname() @@ -109,14 +111,20 @@ export default function FormContent({
-
+
{isMultiRoomError(errors.bookingCode?.value?.message) || isMultiRoomError(errors[SEARCH_TYPE_REDEMPTION]?.message) ? (
- +
) : null}