diff --git a/apps/partner-sas/app/[lang]/layout.tsx b/apps/partner-sas/app/[lang]/layout.tsx index 932b8a87b..4cfb47407 100644 --- a/apps/partner-sas/app/[lang]/layout.tsx +++ b/apps/partner-sas/app/[lang]/layout.tsx @@ -15,7 +15,6 @@ 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" @@ -62,7 +61,7 @@ export default async function RootLayout(props: RootLayoutProps) { const bookingFlowConfig: BookingFlowConfig = { bookingCodeEnabled: false, - partner: ScandicPartnersEnum.sas, + variant: "partner-sas", routes: { myStay: routeToScandicWeb(myStay), bookingTermsAndConditions: routeToScandicWeb( diff --git a/apps/scandic-web/constants/bookingFlowConfig.ts b/apps/scandic-web/constants/bookingFlowConfig.ts index d534bda42..87db81d09 100644 --- a/apps/scandic-web/constants/bookingFlowConfig.ts +++ b/apps/scandic-web/constants/bookingFlowConfig.ts @@ -7,6 +7,7 @@ import type { BookingFlowConfig } from "@scandic-hotels/booking-flow/BookingFlow export const bookingFlowConfig: BookingFlowConfig = { bookingCodeEnabled: true, + variant: "scandic", routes: { myStay, customerService, diff --git a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx index 64af84286..05f94abb6 100644 --- a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx +++ b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfig.tsx @@ -5,11 +5,12 @@ 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" + +import type { BookingFlowVariant } from "./bookingFlowVariants" export type BookingFlowConfig = { bookingCodeEnabled: boolean - partner?: ScandicPartnersEnum + variant: BookingFlowVariant routes: { myStay: LangRoute bookingTermsAndConditions: LangRoute diff --git a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx index 6c6d5c275..bf8d2051f 100644 --- a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx +++ b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowConfigContext.tsx @@ -2,9 +2,8 @@ import { createContext, useContext } from "react" -import type { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners" - import type { BookingFlowConfig } from "./bookingFlowConfig" +import type { BookingFlowVariant } from "./bookingFlowVariants" type BookingFlowConfigContextData = BookingFlowConfig @@ -12,7 +11,7 @@ const BookingFlowConfigContext = createContext< BookingFlowConfigContextData | undefined >(undefined) -export const useIsPartner = (partner: ScandicPartnersEnum) => { +export const useIsPartner = (variant: BookingFlowVariant) => { const context = useContext(BookingFlowConfigContext) if (!context) { @@ -21,7 +20,7 @@ export const useIsPartner = (partner: ScandicPartnersEnum) => { ) } - return context.partner === partner + return context.variant === variant } export const useBookingFlowConfig = (): BookingFlowConfigContextData => { diff --git a/packages/booking-flow/lib/bookingFlowConfig/bookingFlowVariants.ts b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowVariants.ts new file mode 100644 index 000000000..45caeb833 --- /dev/null +++ b/packages/booking-flow/lib/bookingFlowConfig/bookingFlowVariants.ts @@ -0,0 +1,2 @@ +export const bookingFlowVariants = ["scandic", "partner-sas"] as const +export type BookingFlowVariant = (typeof bookingFlowVariants)[number] diff --git a/packages/booking-flow/lib/components/BookingFlowInput/errors.ts b/packages/booking-flow/lib/components/BookingFlowInput/errors.ts index e3d36ad26..150f432cd 100644 --- a/packages/booking-flow/lib/components/BookingFlowInput/errors.ts +++ b/packages/booking-flow/lib/components/BookingFlowInput/errors.ts @@ -1,4 +1,3 @@ -import { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners" import { logger } from "@scandic-hotels/common/logger" import { phoneErrors } from "@scandic-hotels/common/utils/zod/phoneValidator" @@ -10,10 +9,12 @@ import { import type { IntlShape } from "react-intl" +import type { BookingFlowVariant } from "../../bookingFlowConfig/bookingFlowVariants" + export function getErrorMessage( intl: IntlShape, - errorCode?: string, - partner?: ScandicPartnersEnum + variant: BookingFlowVariant, + errorCode?: string ) { switch (errorCode) { case bookingWidgetErrors.BOOKING_CODE_INVALID: @@ -47,7 +48,7 @@ export function getErrorMessage( "Multi-room booking is not available with this booking code.", }) case bookingWidgetErrors.MULTIROOM_REWARD_NIGHT_UNAVAILABLE: - return partner === ScandicPartnersEnum.sas + return variant === "partner-sas" ? intl.formatMessage({ defaultMessage: "Multi-room bookings are not available with EuroBonus points.", diff --git a/packages/booking-flow/lib/components/BookingFlowInput/index.tsx b/packages/booking-flow/lib/components/BookingFlowInput/index.tsx index 8ddc11964..92b72dbd1 100644 --- a/packages/booking-flow/lib/components/BookingFlowInput/index.tsx +++ b/packages/booking-flow/lib/components/BookingFlowInput/index.tsx @@ -17,6 +17,7 @@ import Caption from "@scandic-hotels/design-system/Caption" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Input as InputWithLabel } from "@scandic-hotels/design-system/Input" +import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext" import { getErrorMessage } from "./errors" import styles from "./input.module.css" @@ -51,6 +52,7 @@ const BookingFlowInput = forwardRef( ) { const intl = useIntl() const { control } = useFormContext() + const config = useBookingFlowConfig() return ( ( {fieldState.error && !hideError ? ( - {getErrorMessage(intl, fieldState.error.message)} + {getErrorMessage( + intl, + config.variant, + fieldState.error.message + )} ) : null} 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 9eeee93a5..bf8f04ca9 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 @@ -13,6 +13,7 @@ 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" +import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext" import BookingFlowInput from "../../../../BookingFlowInput" import { getErrorMessage } from "../../../../BookingFlowInput/errors" import { Input as BookingWidgetInput } from "../Input" @@ -282,6 +283,7 @@ function BookingCodeError({ }) { const intl = useIntl() const isMultiroomError = isMultiRoomError(codeError.message) + const config = useBookingFlowConfig() return (
@@ -296,7 +298,7 @@ function BookingCodeError({ color="Icon/Feedback/Error" isFilled={!isDesktop} /> - {getErrorMessage(intl, codeError.message)} + {getErrorMessage(intl, config.variant, codeError.message)} {isMultiroomError ? ( 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 39780f32b..e659dcd07 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 @@ -2,10 +2,9 @@ import { useCallback, useEffect, useRef } from "react" import { useFormContext } from "react-hook-form" -import { useIntl } from "react-intl" +import { type IntlShape, useIntl } from "react-intl" import { useMediaQuery } from "usehooks-ts" -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" @@ -13,10 +12,9 @@ import Modal from "@scandic-hotels/design-system/Modal" 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 { type BookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfig" +import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext" +import { bookingFlowVariants } from "../../../../../bookingFlowConfig/bookingFlowVariants" import { getErrorMessage } from "../../../../BookingFlowInput/errors" import { RemoveExtraRooms } from "../RemoveExtraRooms/RemoveExtraRooms" import { isMultiRoomError } from "../utils" @@ -28,7 +26,6 @@ import type { BookingWidgetSchema } from "../../../Client" export default function RewardNight() { const intl = useIntl() const config = useBookingFlowConfig() - const isPartnerSas = useIsPartner(ScandicPartnersEnum.sas) const { setValue, getValues, @@ -36,22 +33,9 @@ export default function RewardNight() { trigger, } = useFormContext() const ref = useRef(null) - 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 reward = getRewardMessage(config, intl) + const rewardNightTooltip = getRewardNightTooltipMessage(config, intl) + const redemptionErr = errors[SEARCH_TYPE_REDEMPTION] const isDesktop = useMediaQuery("(min-width: 767px)") @@ -149,7 +133,7 @@ export default function RewardNight() { className={styles.errorIcon} isFilled={!isDesktop} /> - {getErrorMessage(intl, redemptionErr.message, config.partner)} + {getErrorMessage(intl, config.variant, redemptionErr.message)} {isMultiRoomError(redemptionErr.message) ? ( @@ -162,3 +146,42 @@ export default function RewardNight() {
) } + +function getRewardMessage(config: BookingFlowConfig, intl: IntlShape): string { + if (!bookingFlowVariants.includes(config.variant)) { + throw new Error("Unknown booking flow variant") + } + + switch (config.variant) { + case "partner-sas": + return intl.formatMessage({ + defaultMessage: "EuroBonus Points", + }) + case "scandic": + return intl.formatMessage({ + defaultMessage: "Reward Night", + }) + } +} + +function getRewardNightTooltipMessage( + config: BookingFlowConfig, + intl: IntlShape +): string { + if (!bookingFlowVariants.includes(config.variant)) { + throw new Error("Unknown booking flow variant") + } + + switch (config.variant) { + case "partner-sas": + return intl.formatMessage({ + defaultMessage: + "To book with EuroBonus points, make sure you're logged into your SAS EuroBonus account.", + }) + case "scandic": + return intl.formatMessage({ + defaultMessage: + "To book a reward night, make sure you're logged in to your Scandic Friends account.", + }) + } +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Details/Multiroom/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Details/Multiroom/index.tsx index e9aacb2ab..e9e42f942 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Details/Multiroom/index.tsx +++ b/packages/booking-flow/lib/components/EnterDetails/Details/Multiroom/index.tsx @@ -12,6 +12,7 @@ import CountrySelect from "@scandic-hotels/design-system/Form/Country" import Phone from "@scandic-hotels/design-system/Form/Phone" import { useFormTracking } from "@scandic-hotels/tracking/useFormTracking" +import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext" import { useRoomContext } from "../../../../contexts/EnterDetails/RoomContext" import useLang from "../../../../hooks/useLang" import { getFormattedCountryList } from "../../../../misc/getFormatedCountryList" @@ -29,6 +30,7 @@ const formID = "enter-details" export default function Details() { const intl = useIntl() const lang = useLang() + const config = useBookingFlowConfig() const { addPreSubmitCallback, rooms } = useEnterDetailsStore((state) => ({ addPreSubmitCallback: state.actions.addPreSubmitCallback, @@ -205,7 +207,11 @@ export default function Details() { + +

+ + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + <> + NOT IMPLEMENTED +
+ {intl.formatMessage({ + defaultMessage: "Get the Scandic Friends room price", + })} + + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + {`: `} +
+ + {formatPrice( + intl, + room.roomRate.member.localPrice.pricePerStay ?? 0, + room.roomRate.member.localPrice.currency ?? CurrencyEnum.Unknown + )} + +

+
+ + +
+ {intl.formatMessage({ + defaultMessage: "Join Scandic Friends now", + })} +
+
+
+ + + +
+ + {intl.formatMessage( + { + defaultMessage: + "By joining you accept the Terms and Conditions. The Scandic Friends Membership is valid until further notice, but can at any time be terminated by contacting Scandic Customer Service.", + }, + { + termsAndConditionsLink: (str) => ( + + {str} + + ), + } + )} + +
+ + ) +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/PartnerSASJoinScandicFriendsCard/partnerSASJoinScandicFriendsCard.module.css b/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/PartnerSASJoinScandicFriendsCard/partnerSASJoinScandicFriendsCard.module.css new file mode 100644 index 000000000..cddbc0d1a --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/PartnerSASJoinScandicFriendsCard/partnerSASJoinScandicFriendsCard.module.css @@ -0,0 +1,55 @@ +.cardContainer { + align-self: flex-start; + background-color: orchid; + color: white; + border-radius: var(--Corner-radius-lg); + display: grid; + gap: var(--Space-x2); + padding: var(--Space-x2); + grid-template-areas: + "price login" + "checkbox checkbox" + "terms terms"; + width: min(100%, 696px); +} + +.priceContainer { + grid-area: price; + margin-bottom: var(--Space-x1); +} + +.price { + color: var(--Text-Accent-Primary); +} + +.login { + grid-area: login; + align-self: center; + justify-self: end; +} + +.checkBox { + align-self: center; + grid-area: checkbox; +} + +.terms { + grid-area: terms; +} + +@media screen and (min-width: 768px) { + .cardContainer { + grid-template-columns: 1fr auto auto; + grid-template-rows: auto auto; + gap: var(--Space-x3); + grid-template-areas: + "price checkbox login" + "terms terms terms"; + } + + .priceContainer { + margin-bottom: 0; + display: flex; + flex-direction: column; + } +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/Signup/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/Signup/index.tsx index de731ecc5..7796645f4 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/Signup/index.tsx +++ b/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/Signup/index.tsx @@ -11,6 +11,7 @@ import { useIntl } from "react-intl" import Caption from "@scandic-hotels/design-system/Caption" import DateSelect from "@scandic-hotels/design-system/Form/Date" +import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext" import useLang from "../../../../../hooks/useLang" import BookingFlowInput from "../../../../BookingFlowInput" import { getErrorMessage } from "../../../../BookingFlowInput/errors" @@ -28,6 +29,7 @@ export default function Signup({ }) { const intl = useIntl() const lang = useLang() + const config = useBookingFlowConfig() const [isJoinChecked, setIsJoinChecked] = useState(false) @@ -65,6 +67,7 @@ export default function Signup({ year: intl.formatMessage({ defaultMessage: "Year" }), errorMessage: getErrorMessage( intl, + config.variant, errors["dateOfBirth"]?.message?.toString() ), }} diff --git a/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/index.tsx index abb92bae6..c6fc437dd 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/index.tsx +++ b/packages/booking-flow/lib/components/EnterDetails/Details/RoomOne/index.tsx @@ -12,6 +12,7 @@ import CountrySelect from "@scandic-hotels/design-system/Form/Country" import Phone from "@scandic-hotels/design-system/Form/Phone" import { useFormTracking } from "@scandic-hotels/tracking/useFormTracking" +import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext" import { useRoomContext } from "../../../../contexts/EnterDetails/RoomContext" import useLang from "../../../../hooks/useLang" import { getFormattedCountryList } from "../../../../misc/getFormatedCountryList" @@ -20,7 +21,8 @@ import BookingFlowInput from "../../../BookingFlowInput" import { getErrorMessage } from "../../../BookingFlowInput/errors" import MemberPriceModal from "../MemberPriceModal" import { SpecialRequests } from "../SpecialRequests" -import JoinScandicFriendsCard from "./JoinScandicFriendsCard" +import { JoinScandicFriendsCard } from "./JoinScandicFriendsCard" +import { PartnerSASJoinScandicFriendsCard } from "./PartnerSASJoinScandicFriendsCard" import { type GuestDetailsSchema, guestDetailsSchema, @@ -40,6 +42,7 @@ const formID = "enter-details" export default function Details({ user }: DetailsProps) { const intl = useIntl() const lang = useLang() + const config = useBookingFlowConfig() const { lastRoom, addPreSubmitCallback } = useEnterDetailsStore((state) => ({ lastRoom: state.lastRoom, @@ -153,7 +156,15 @@ export default function Details({ user }: DetailsProps) { id={`${formID}-room-${roomNr}`} onSubmit={methods.handleSubmit(onSubmit)} > - {user || !memberRate ? null : } + {config.variant === "scandic" && ( + <>{!user || !memberRate ? null : } + )} + + {config.variant === "partner-sas" && ( + <> + {user || !memberRate ? null : } + + )}