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 ? ( @@ -303,41 +301,13 @@ function BookingCodeError({ {isMultiroomError ? ( - + ) : null} ) } -export function RemoveExtraRooms({ ...props }: ButtonProps) { - const intl = useIntl() - const { getValues, setValue, trigger } = useFormContext() - 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 ( - - {intl.formatMessage({ - defaultMessage: "Remove extra rooms", - })} - - ) -} - function TabletBookingCode({ bookingCode, updateValue, @@ -381,7 +351,7 @@ function TabletBookingCode({ return ( - + {/* For some reason Checkbox click triggers twice modal state change, which returns the modal back to old state. So we are using overlay as trigger for modal */} - - {codeVoucher} - + + {codeVoucher} + () + 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 ( + + {intl.formatMessage({ + defaultMessage: "Remove extra rooms", + })} + + ) +} 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} ( null ) + const { bookingCodeEnabled } = useBookingFlowConfig() const shouldFetchAutoComplete = !!data.hotelId || !!data.city @@ -124,7 +126,7 @@ export default function BookingWidgetClient({ toDate: toDate.format("YYYY-MM-DD"), }, bookingCode: { - value: selectedBookingCode, + value: bookingCodeEnabled ? selectedBookingCode : "", remember: false, }, redemption: data.searchType === SEARCH_TYPE_REDEMPTION, diff --git a/packages/common/constants/scandicPartners.ts b/packages/common/constants/scandicPartners.ts new file mode 100644 index 000000000..ca0fc1e7d --- /dev/null +++ b/packages/common/constants/scandicPartners.ts @@ -0,0 +1,3 @@ +export enum ScandicPartnersEnum { + sas = "sas", +} diff --git a/packages/common/package.json b/packages/common/package.json index bbee83fdb..5ecd2c549 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -28,6 +28,7 @@ "./constants/routes/*": "./constants/routes/*.ts", "./constants/signatureHotels": "./constants/signatureHotels.ts", "./constants/paymentCallbackStatus": "./constants/paymentCallbackStatus.ts", + "./constants/scandicPartners": "./constants/scandicPartners.ts", "./dataCache": "./dataCache/index.ts", "./dt": "./dt/dt.ts", "./dt/utils/hasOverlappingDates": "./dt/utils/hasOverlappingDates.ts", diff --git a/packages/design-system/lib/components/Button/index.tsx b/packages/design-system/lib/components/Button/index.tsx index c23165a07..b28437be0 100644 --- a/packages/design-system/lib/components/Button/index.tsx +++ b/packages/design-system/lib/components/Button/index.tsx @@ -1,3 +1,5 @@ export { Button } from './Button' // eslint-disable-next-line react-refresh/only-export-components export { withButton } from './variants' + +export { type ButtonProps } from './types' diff --git a/packages/design-system/lib/components/Form/Checkbox/checkbox.module.css b/packages/design-system/lib/components/Form/Checkbox/checkbox.module.css index 17df966ac..8a49bc487 100644 --- a/packages/design-system/lib/components/Form/Checkbox/checkbox.module.css +++ b/packages/design-system/lib/components/Form/Checkbox/checkbox.module.css @@ -1,7 +1,7 @@ .container { display: flex; flex-direction: column; - color: var(--text-color); + color: var(--Text-Default); cursor: pointer; } diff --git a/packages/design-system/lib/components/IconButton/IconButton.stories.tsx b/packages/design-system/lib/components/IconButton/IconButton.stories.tsx index a3ef2ca9e..6bc28342b 100644 --- a/packages/design-system/lib/components/IconButton/IconButton.stories.tsx +++ b/packages/design-system/lib/components/IconButton/IconButton.stories.tsx @@ -27,6 +27,11 @@ const meta: Meta = { type: 'string', description: `The style variant is only applied on certain variants. The examples below shows the possible combinations of variants and style variants.`, }, + wrapping: { + control: 'select', + options: Object.keys(config.variants.wrapping), + default: undefined, + }, }, } diff --git a/packages/design-system/lib/components/IconButton/IconButton.tsx b/packages/design-system/lib/components/IconButton/IconButton.tsx index a94ffb08d..a327ed1c1 100644 --- a/packages/design-system/lib/components/IconButton/IconButton.tsx +++ b/packages/design-system/lib/components/IconButton/IconButton.tsx @@ -8,11 +8,13 @@ export function IconButton({ theme, style, className, + wrapping, ...props }: IconButtonProps) { const classNames = variants({ theme, style, + wrapping, className, }) diff --git a/packages/design-system/lib/components/IconButton/iconButton.module.css b/packages/design-system/lib/components/IconButton/iconButton.module.css index 13d09e659..b9432a671 100644 --- a/packages/design-system/lib/components/IconButton/iconButton.module.css +++ b/packages/design-system/lib/components/IconButton/iconButton.module.css @@ -100,3 +100,7 @@ background-color: var(--Component-Button-Muted-Fill-Disabled-inverted); } } + +.no-wrapping { + padding: 0; +} diff --git a/packages/design-system/lib/components/IconButton/variants.ts b/packages/design-system/lib/components/IconButton/variants.ts index cd361d945..c84eca8ad 100644 --- a/packages/design-system/lib/components/IconButton/variants.ts +++ b/packages/design-system/lib/components/IconButton/variants.ts @@ -33,6 +33,10 @@ export const config = { [variantKeys.style.Elevated]: '', [variantKeys.style.Faded]: '', }, + wrapping: { + true: styles['no-wrapping'], + false: undefined, + }, }, compoundVariants: [ // Primary should only use Normal diff --git a/packages/design-system/lib/components/Switch/switch.module.css b/packages/design-system/lib/components/Switch/switch.module.css index 031bd9ca0..6001f487c 100644 --- a/packages/design-system/lib/components/Switch/switch.module.css +++ b/packages/design-system/lib/components/Switch/switch.module.css @@ -1,7 +1,7 @@ .container { display: flex; flex-direction: row; - color: var(--text-color); + color: var(--Text-Default); cursor: pointer; width: 100%; justify-content: space-between;
{bookingCodeTooltipText}