From 02aac9006ee617d35ec33b1c5a2eaca936ba1b5a Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Mon, 24 Nov 2025 07:24:52 +0000 Subject: [PATCH] feat(BOOK-469): Enter details design changes with guarantee/non-guarantee flow Approved-by: Bianca Widstam --- .../Confirm/Guarantee/guarantee.module.css | 128 +++-------- .../EnterDetails/Confirm/Guarantee/index.tsx | 189 +++++++-------- .../Confirm/PaymentOptions/index.tsx | 112 +++++++++ .../PaymentOptions/paymentOptions.module.css | 4 + .../EnterDetails/Confirm/SmsConfirmation.tsx | 21 ++ .../EnterDetails/Confirm/confirm.module.css | 7 +- .../components/EnterDetails/Confirm/index.tsx | 105 +++++---- .../GuaranteeInfo/guaranteeInfo.module.css | 21 ++ .../Payment/GuaranteeInfo/index.tsx | 75 ++++++ .../EnterDetails/Payment/PaymentClient.tsx | 215 ++++-------------- .../Payment/TermsAndConditions/index.tsx | 151 ++++++------ .../termsAndConditions.module.css | 5 + .../EnterDetails/Payment/payment.module.css | 78 +++---- .../components/EnterDetails/Payment/utils.ts | 41 ++++ .../lib/components/Modal/index.tsx | 38 ++-- .../lib/components/Modal/modal.module.css | 16 +- .../components/TextLink/textLink.module.css | 8 + .../lib/components/TextLink/variants.ts | 1 + 18 files changed, 646 insertions(+), 569 deletions(-) create mode 100644 packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/index.tsx create mode 100644 packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/paymentOptions.module.css create mode 100644 packages/booking-flow/lib/components/EnterDetails/Confirm/SmsConfirmation.tsx create mode 100644 packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/guaranteeInfo.module.css create mode 100644 packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/index.tsx create mode 100644 packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/termsAndConditions.module.css create mode 100644 packages/booking-flow/lib/components/EnterDetails/Payment/utils.ts diff --git a/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/guarantee.module.css b/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/guarantee.module.css index 4a25818ef..b912585e0 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/guarantee.module.css +++ b/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/guarantee.module.css @@ -1,102 +1,46 @@ -@keyframes overlay-fade { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@keyframes modal-anim { - from { - transform: translateY(100%); - } - - to { - transform: translateY(0); - } -} - .guarantee { - display: flex; - flex-direction: column; - gap: var(--Space-x1); -} - -.checkboxContainer { - align-items: center; - display: flex; - flex: 1; - gap: var(--Space-x1); - justify-content: space-between; -} - -.infoButton { - display: flex; - gap: var(--Space-x05); - justify-self: flex-end; - outline: none; -} -.infoButton:focus-visible { - outline: 2px auto -webkit-focus-ring-color; - outline-offset: 1px; -} - -.overlay { - align-items: center; - background-color: var(--Overlay-40); - display: flex; - inset: 0; - justify-content: center; - position: fixed; - z-index: var(--default-modal-overlay-z-index); - - &[data-entering] { - animation: overlay-fade 200ms; - } - - &[data-exiting] { - animation: overlay-fade 150ms reverse ease-in; - } -} - -.modal { - background-color: var(--Base-Surface-Primary-light-Normal); - border-radius: var(--Corner-radius-lg); - box-shadow: 0px 4px 24px 0px rgba(38, 32, 30, 0.08); - overflow: hidden; - padding: var(--Space-x5) var(--Space-x3); - width: min(90dvw, 560px); - - &[data-entering] { - animation: modal-anim 200ms; - } - - &[data-exiting] { - animation: modal-anim 150ms reverse ease-in; - } -} - -.container { - align-items: center; - display: flex; - flex-direction: column; + display: grid; gap: var(--Space-x2); + background-color: var(--Surface-Secondary-Default); + border-radius: var(--Corner-radius-lg); + padding: var(--Space-x2); } -.text { - text-align: center; +.paymentRequired { + display: flex; + gap: var(--Space-x15); + align-items: flex-start; } -.closeButton { - margin-top: var(--Space-x15); - outline: none; - width: min(164px, 100%); +.guaranteeQuestion { + display: grid; + gap: var(--Space-x1); + justify-items: start; } -@media screen and (max-width: 767px) { - .btnText { - display: none; +.textWrapper { + display: grid; + gap: var(--Space-x025); + flex-grow: 1; +} + +.checkbox { + flex-grow: 1; +} + +.guaranteeInfoButton { + flex-shrink: 0; + margin-left: calc(var(--Space-x3) + var(--Space-x15)); /* Align with checkbox */ +} + +@media screen and (min-width: 768px) { + .guaranteeQuestion { + display: flex; + gap: var(--Space-x15); + align-items: center; + } + + .guaranteeInfoButton { + margin-left: 0; } } diff --git a/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/index.tsx index b55feb826..09edbe89a 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/index.tsx +++ b/packages/booking-flow/lib/components/EnterDetails/Confirm/Guarantee/index.tsx @@ -1,21 +1,13 @@ -"use client" -import { useLayoutEffect } from "react" -import { - Dialog, - DialogTrigger, - Modal, - ModalOverlay, -} from "react-aria-components" import { useWatch } from "react-hook-form" import { useIntl } from "react-intl" -import useSetOverflowVisibleOnRA from "@scandic-hotels/common/hooks/useSetOverflowVisibleOnRA" -import { Button } from "@scandic-hotels/design-system/Button" +import { Divider } from "@scandic-hotels/design-system/Divider" import Checkbox from "@scandic-hotels/design-system/Form/Checkbox" -import { SelectPaymentMethod } from "@scandic-hotels/design-system/Form/SelectPaymentMethod" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" -import { trackUpdatePaymentMethod } from "@scandic-hotels/tracking/payment" + +import { GuaranteeInfo } from "../../Payment/GuaranteeInfo" +import { ConfirmBookingPaymentOptions } from "../PaymentOptions" import styles from "./guarantee.module.css" @@ -23,114 +15,89 @@ import type { PaymentMethodEnum } from "@scandic-hotels/common/constants/payment import type { CreditCard } from "@scandic-hotels/trpc/types/user" interface GuaranteeProps { + mustBeGuaranteed: boolean savedCreditCards: CreditCard[] | null + otherPaymentOptions: PaymentMethodEnum[] } -export default function Guarantee({ savedCreditCards }: GuaranteeProps) { +export function Guarantee({ + mustBeGuaranteed, + savedCreditCards, + otherPaymentOptions, +}: GuaranteeProps) { const intl = useIntl() const guarantee = useWatch({ name: "guarantee" }) return (
- -
- - - {intl.formatMessage({ - id: "enterDetails.confirmBooking.guaranteeLabel", - defaultMessage: - "I may arrive later than 18:00 and want to guarantee my booking.", - })} - - - - - - - - {({ close }) => ( -
- -

- {intl.formatMessage({ - id: "enterDetails.confirmBooking.guaranteeInfoModalTitle", - defaultMessage: "Guarantee for late arrival", - })} -

-
- -

- {intl.formatMessage({ - id: "enterDetails.confirmBooking.guaranteeInfoModalDescription", - defaultMessage: - "When guaranteeing your booking with a credit card, we will hold the booking until 07:00 the day after check-in.", - })} -

-
- -

- {intl.formatMessage({ - id: "enterDetails.confirmBooking.guaranteeInfoModalNoShowInfo", - defaultMessage: - "In case of a no-show, your credit card will be charged for the first night.", - })} -

-
- - -
- )} -
-
-
-
-
-
+ {!mustBeGuaranteed ? ( + <> +
+ +
+ +

+ {intl.formatMessage({ + id: "enterDetails.guarantee.guaranteeLabel", + defaultMessage: "Guarantee for late arrival", + })} +

+
+ +

+ {intl.formatMessage({ + id: "enterDetails.guarantee.guaranteeInfo", + defaultMessage: + "I may arrive later than 18:00 and want the hotel to hold my booking.", + })} +

+
+
+
+ +
+ {guarantee ? : null} + + ) : null} - {guarantee && ( - ({ - ...card, - cardType: card.cardType as PaymentMethodEnum, - }))} - onChange={(method) => { - trackUpdatePaymentMethod({ method }) - }} - formName={"paymentMethod"} - /> - )} + {mustBeGuaranteed || guarantee ? ( + <> +
+ +
+ +

+ {intl.formatMessage({ + id: "enterDetails.guarantee.paymentCardRequiredLabel", + defaultMessage: + "Payment card required to hold your booking", + })} +

+
+ +

+ {intl.formatMessage({ + id: "enterDetails.guarantee.paymentCardRequiredInfo", + defaultMessage: + "Complete the booking and provide your payment card details in the next step.", + })} +

+
+
+
+ + {savedCreditCards && savedCreditCards.length ? ( + <> + + + + ) : null} + + ) : null}
) } - -function RestoreOverflow() { - const setOverflowVisible = useSetOverflowVisibleOnRA() - useLayoutEffect(() => { - setOverflowVisible(true) - }, [setOverflowVisible]) - return null -} diff --git a/packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/index.tsx new file mode 100644 index 000000000..80c63dcf3 --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/index.tsx @@ -0,0 +1,112 @@ +"use client" + +import { Label } from "react-aria-components" +import { useIntl } from "react-intl" + +import { + PAYMENT_METHOD_TITLES, + PaymentMethodEnum, +} from "@scandic-hotels/common/constants/paymentMethod" +import { PaymentOption } from "@scandic-hotels/design-system/Form/PaymentOption" +import { PaymentOptionsGroup } from "@scandic-hotels/design-system/Form/PaymentOptionsGroup" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { useAvailablePaymentOptions } from "../../../../hooks/useAvailablePaymentOptions" +import { useEnterDetailsStore } from "../../../../stores/enter-details" +import MixedRatePaymentBreakdown from "../../Payment/MixedRatePaymentBreakdown" + +import styles from "./paymentOptions.module.css" + +import type { CreditCard } from "@scandic-hotels/trpc/types/user" + +interface ConfirmBookingPaymentOptionsProps { + savedCreditCards: CreditCard[] | null + hasMixedRates: boolean + otherPaymentOptions: PaymentMethodEnum[] +} + +export function ConfirmBookingPaymentOptions({ + savedCreditCards, + hasMixedRates, + otherPaymentOptions, +}: ConfirmBookingPaymentOptionsProps) { + const intl = useIntl() + const { rooms, currency } = useEnterDetailsStore((state) => ({ + rooms: state.rooms, + currency: state.totalPrice.local.currency, + })) + const availablePaymentOptions = + useAvailablePaymentOptions(otherPaymentOptions) + + return ( + <> + + + + {savedCreditCards?.length ? ( + <> + + + {intl.formatMessage({ + id: "payment.mySavedCards", + defaultMessage: "My saved cards", + })} + + + + {savedCreditCards.map((savedCreditCard) => ( + + ))} + + + + {intl.formatMessage({ + id: "enterDetails.payment.otherPaymentMethods", + defaultMessage: "Other payment methods", + })} + + + + ) : null} + + + {!hasMixedRates + ? availablePaymentOptions.map((paymentMethod) => ( + + )) + : null} + + + {hasMixedRates ? ( + + ) : null} + + ) +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/paymentOptions.module.css b/packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/paymentOptions.module.css new file mode 100644 index 000000000..30b5814ad --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Confirm/PaymentOptions/paymentOptions.module.css @@ -0,0 +1,4 @@ +.paymentOptions { + display: grid; + gap: var(--Space-x15); +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Confirm/SmsConfirmation.tsx b/packages/booking-flow/lib/components/EnterDetails/Confirm/SmsConfirmation.tsx new file mode 100644 index 000000000..d40a41c19 --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Confirm/SmsConfirmation.tsx @@ -0,0 +1,21 @@ +import { useIntl } from "react-intl" + +import Checkbox from "@scandic-hotels/design-system/Form/Checkbox" +import { Typography } from "@scandic-hotels/design-system/Typography" + +export default function SmsConfirmation() { + const intl = useIntl() + return ( + + + + {intl.formatMessage({ + id: "booking.smsConfirmationLabel", + defaultMessage: + "I would like to get my booking confirmation via sms", + })} + + + + ) +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Confirm/confirm.module.css b/packages/booking-flow/lib/components/EnterDetails/Confirm/confirm.module.css index f65888f59..1a79a636b 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Confirm/confirm.module.css +++ b/packages/booking-flow/lib/components/EnterDetails/Confirm/confirm.module.css @@ -1,12 +1,11 @@ -.container { - display: flex; - flex-direction: column; +.confirmBooking { + display: grid; gap: var(--Space-x3); } .selections { background-color: var(--Surface-Secondary-Default); - border-radius: var(--Corner-radius-Large); + border-radius: var(--Corner-radius-lg); display: flex; flex-direction: column; gap: var(--Space-x2); diff --git a/packages/booking-flow/lib/components/EnterDetails/Confirm/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Confirm/index.tsx index 9d7b55731..9981fd640 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Confirm/index.tsx +++ b/packages/booking-flow/lib/components/EnterDetails/Confirm/index.tsx @@ -2,67 +2,41 @@ import { useIntl } from "react-intl" import { Divider } from "@scandic-hotels/design-system/Divider" -import Checkbox from "@scandic-hotels/design-system/Form/Checkbox" import { Typography } from "@scandic-hotels/design-system/Typography" import TermsAndConditions from "../Payment/TermsAndConditions" -import Guarantee from "./Guarantee" +import { Guarantee } from "./Guarantee" +import { ConfirmBookingPaymentOptions } from "./PaymentOptions" +import SmsConfirmation from "./SmsConfirmation" import styles from "./confirm.module.css" +import type { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod" import type { CreditCard } from "@scandic-hotels/trpc/types/user" interface ConfirmBookingProps { savedCreditCards: CreditCard[] | null + otherPaymentOptions: PaymentMethodEnum[] + hasOnlyFlexRates: boolean + hasMixedRates: boolean + isRedemptionBooking: boolean + bookingMustBeGuaranteed: boolean } export default function ConfirmBooking({ savedCreditCards, + otherPaymentOptions, + hasOnlyFlexRates, + hasMixedRates, + isRedemptionBooking, + bookingMustBeGuaranteed, }: ConfirmBookingProps) { const intl = useIntl() - return ( -
-
- - - - - - {intl.formatMessage({ - id: "booking.smsConfirmationLabel", - defaultMessage: - "I would like to get my booking confirmation via sms", - })} - - - -
-
- -
-
- ) -} -export function ConfirmBookingRedemption() { - const intl = useIntl() - return ( -
-
- - - - {intl.formatMessage({ - id: "booking.smsConfirmationLabel", - defaultMessage: - "I would like to get my booking confirmation via sms", - })} - - - -
-
- + if (isRedemptionBooking) { + return ( +
+

{intl.formatMessage({ id: "enterDetails.confirmBooking.redemptionGuaranteeInfo", @@ -71,10 +45,49 @@ export function ConfirmBookingRedemption() { })}

-
-
+
+ ) + } + + if (hasOnlyFlexRates) { + return ( +
+ + + + +
+ ) + } + + return ( +
+ {hasMixedRates ? ( + +

+ {intl.formatMessage({ + id: "enterDetails.payment.mixedRatesInfo", + defaultMessage: + "As your booking includes rooms with different terms, we will be charging part of the booking now and the remainder will be collected by the reception at check-in.", + })} +

+
+ ) : null} + + + + +
) } diff --git a/packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/guaranteeInfo.module.css b/packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/guaranteeInfo.module.css new file mode 100644 index 000000000..3bf74b719 --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/guaranteeInfo.module.css @@ -0,0 +1,21 @@ +.content { + display: grid; + gap: var(--Space-x3); + align-content: start; + margin-top: var(--Space-x2); +} + +.closeButton { + justify-self: stretch; +} + +@media screen and (min-width: 768px) { + .dialog { + width: 560px; + } + + .closeButton { + justify-self: end; + min-width: 150px; + } +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/index.tsx new file mode 100644 index 000000000..ed9d9d5f6 --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Payment/GuaranteeInfo/index.tsx @@ -0,0 +1,75 @@ +"use client" + +import { useState } from "react" +import { useIntl } from "react-intl" + +import { Button } from "@scandic-hotels/design-system/Button" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import Modal from "@scandic-hotels/design-system/Modal" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import styles from "./guaranteeInfo.module.css" + +interface GuaranteeInfoProps { + buttonClassName?: string +} + +export function GuaranteeInfo({ buttonClassName }: GuaranteeInfoProps) { + const [isOpen, setIsOpen] = useState(false) + const intl = useIntl() + + return ( + <> + + +
+ +

+ {intl.formatMessage({ + id: "enterDetails.guaranteeInfo.description", + defaultMessage: + "The hotel will hold your booking, even if you arrive after 18:00. In case of a no-show, your credit card will be charged for the first night.", + })} +

+
+ +
+
+ + ) +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Payment/PaymentClient.tsx b/packages/booking-flow/lib/components/EnterDetails/Payment/PaymentClient.tsx index 822b774e5..77cf5a17a 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Payment/PaymentClient.tsx +++ b/packages/booking-flow/lib/components/EnterDetails/Payment/PaymentClient.tsx @@ -4,14 +4,10 @@ import { zodResolver } from "@hookform/resolvers/zod" import { cx } from "class-variance-authority" import { usePathname, useRouter, useSearchParams } from "next/navigation" import { useCallback, useEffect, useState } from "react" -import { Label } from "react-aria-components" import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" -import { - PAYMENT_METHOD_TITLES, - PaymentMethodEnum, -} from "@scandic-hotels/common/constants/paymentMethod" +import { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod" import { bookingConfirmation, selectRate, @@ -19,11 +15,7 @@ import { import useStickyPosition from "@scandic-hotels/common/hooks/useStickyPosition" import { logger } from "@scandic-hotels/common/logger" import { formatPhoneNumber } from "@scandic-hotels/common/utils/phone" -import Body from "@scandic-hotels/design-system/Body" import { Button } from "@scandic-hotels/design-system/Button" -import Checkbox from "@scandic-hotels/design-system/Form/Checkbox" -import { PaymentOption } from "@scandic-hotels/design-system/Form/PaymentOption" -import { PaymentOptionsGroup } from "@scandic-hotels/design-system/Form/PaymentOptionsGroup" import { Typography } from "@scandic-hotels/design-system/Typography" import { trackEvent } from "@scandic-hotels/tracking/base" import { @@ -37,27 +29,25 @@ import { BookingStatusEnum } from "@scandic-hotels/trpc/enums/bookingStatus" import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" import { env } from "../../../../env/client" -import { useAvailablePaymentOptions } from "../../../hooks/useAvailablePaymentOptions" import { useBookingFlowContext } from "../../../hooks/useBookingFlowContext" import { clearBookingWidgetState } from "../../../hooks/useBookingWidgetState" import { useHandleBookingStatus } from "../../../hooks/useHandleBookingStatus" import { useIsLoggedIn } from "../../../hooks/useIsLoggedIn" import useLang from "../../../hooks/useLang" import { useEnterDetailsStore } from "../../../stores/enter-details" -import ConfirmBooking, { ConfirmBookingRedemption } from "../Confirm" +import ConfirmBooking from "../Confirm" import PriceChangeDialog from "../PriceChangeDialog" import { writeGlaToSessionStorage } from "./PaymentCallback/helpers" import BookingAlert from "./BookingAlert" -import GuaranteeDetails from "./GuaranteeDetails" +import { GuaranteeInfo } from "./GuaranteeInfo" import { hasFlexibleRate, hasPrepaidRate, isPaymentMethodEnum, writePaymentInfoToSessionStorage, } from "./helpers" -import MixedRatePaymentBreakdown from "./MixedRatePaymentBreakdown" import { type PaymentFormData, paymentSchema } from "./schema" -import TermsAndConditions from "./TermsAndConditions" +import { getPaymentHeadingConfig } from "./utils" import styles from "./payment.module.css" @@ -125,8 +115,6 @@ export default function PaymentClient({ const [isPollingForBookingStatus, setIsPollingForBookingStatus] = useState(false) - const availablePaymentOptions = - useAvailablePaymentOptions(otherPaymentOptions) const [priceChangeData, setPriceChangeData] = useState(null) @@ -136,6 +124,7 @@ export default function PaymentClient({ const hasFlexRates = rooms.some(hasFlexibleRate) const hasOnlyFlexRates = rooms.every(hasFlexibleRate) const hasMixedRates = hasPrepaidRates && hasFlexRates + const isRedemptionBooking = booking.searchType === SEARCH_TYPE_REDEMPTION const methods = useForm({ defaultValues: { @@ -527,15 +516,6 @@ export default function PaymentClient({ ] ) - const finalStep = intl.formatMessage({ - id: "enterDetails.payment.onlyFlexRatesTitle", - defaultMessage: "Final step", - }) - const selectPayment = intl.formatMessage({ - id: "enterDetails.payment.title", - defaultMessage: "Select payment method", - }) - const handleInvalidSubmit = async () => { const valid = await methods.trigger() if (!valid) { @@ -543,159 +523,62 @@ export default function PaymentClient({ } } + const { preHeading, heading, subHeading, showLearnMore } = + getPaymentHeadingConfig(intl, bookingMustBeGuaranteed, hasOnlyFlexRates) + return (
-
- - {hasOnlyFlexRates ? finalStep : selectPayment} - - +
+
+ {preHeading ? ( + +

{preHeading}

+
+ ) : null} + +

{heading}

+
+ {subHeading ? ( + +

{subHeading}

+
+ ) : null} +
+ {showLearnMore ? : null}
+
- {booking.searchType === SEARCH_TYPE_REDEMPTION ? ( - - ) : hasOnlyFlexRates && !bookingMustBeGuaranteed ? ( - - ) : ( - <> - {hasOnlyFlexRates && bookingMustBeGuaranteed ? ( -
- - {intl.formatMessage({ - id: "enterDetails.payment.guaranteeInfo", - defaultMessage: - "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.", - })} - - -
- ) : null} + - {hasMixedRates ? ( - - {intl.formatMessage({ - id: "enterDetails.payment.mixedRatesInfo", - defaultMessage: - "As your booking includes rooms with different terms, we will be charging part of the booking now and the remainder will be collected by the reception at check-in.", - })} - - ) : null} - -
- - - - {savedCreditCards?.length ? ( - <> - - - {intl.formatMessage({ - id: "payment.mySavedCards", - defaultMessage: "My saved cards", - })} - - - - {savedCreditCards.map((savedCreditCard) => ( - - ))} - - - - {intl.formatMessage({ - id: "enterDetails.payment.otherPaymentMethods", - defaultMessage: "Other payment methods", - })} - - - - ) : null} - - {!hasMixedRates && - availablePaymentOptions.map((paymentMethod) => ( - - ))} - - {hasMixedRates ? ( - - ) : null} -
- -
- - - - {intl.formatMessage({ - id: "booking.smsConfirmationLabel", - defaultMessage: - "I would like to get my booking confirmation via sms", - })} - - - -
- -
- -
- - )} -
- -
+
{priceChangeData ? ( diff --git a/packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/index.tsx b/packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/index.tsx index 4ef537f41..a43b7c623 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/index.tsx +++ b/packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/index.tsx @@ -1,15 +1,14 @@ import { useIntl } from "react-intl" -import Caption from "@scandic-hotels/design-system/Caption" import Checkbox from "@scandic-hotels/design-system/Form/Checkbox" -import Link from "@scandic-hotels/design-system/OldDSLink" +import { TextLink } from "@scandic-hotels/design-system/TextLink" import { Typography } from "@scandic-hotels/design-system/Typography" import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext" import useLang from "../../../../hooks/useLang" import { paymentError } from "../schema" -import styles from "../payment.module.css" +import styles from "./termsAndConditions.module.css" type TermsAndConditionsProps = { isFlexBookingTerms: boolean @@ -22,81 +21,10 @@ export default function TermsAndConditions({ const { routes } = useBookingFlowConfig() return ( - <> - - {isFlexBookingTerms - ? intl.formatMessage( - { - id: "enterDetails.payment.flexBookingTermsAndConditions", - defaultMessage: - "I accept the terms for this booking and the general Booking & Cancellation Terms, and understand that Scandic will process my personal data for this booking in accordance with Scandic's Privacy policy.", - }, - { - termsAndConditionsLink: (str) => ( - - {str} - - ), - privacyPolicyLink: (str) => ( - - {str} - - ), - } - ) - : intl.formatMessage( - { - id: "enterDetails.payment.termsAndConditions", - defaultMessage: - "By paying with any of the payment methods available, I accept the terms for this booking and the general Booking & Cancellation Terms, and understand that Scandic will process my personal data for this booking in accordance with Scandic's Privacy policy. I also accept that Scandic requires a valid payment card during my visit in case anything is left unpaid.", - }, - { - termsAndConditionsLink: (str) => ( - - {str} - - ), - privacyPolicyLink: (str) => ( - - {str} - - ), - } - )} - +
- + {intl.formatMessage({ id: "booking.acceptBookingTerms", @@ -113,6 +41,73 @@ export default function TermsAndConditions({ - + +

+ {isFlexBookingTerms + ? intl.formatMessage( + { + id: "enterDetails.payment.flexBookingTermsAndConditions", + defaultMessage: + "I accept the terms for this booking and the general Booking & Cancellation Terms, and understand that Scandic will process my personal data for this booking in accordance with Scandic's Privacy policy.", + }, + { + termsAndConditionsLink: (str) => ( + + {str} + + ), + privacyPolicyLink: (str) => ( + + {str} + + ), + } + ) + : intl.formatMessage( + { + id: "enterDetails.payment.termsAndConditions", + defaultMessage: + "By paying with any of the payment methods available, I accept the terms for this booking and the general Booking & Cancellation Terms, and understand that Scandic will process my personal data for this booking in accordance with Scandic's Privacy policy. I also accept that Scandic requires a valid payment card during my visit in case anything is left unpaid.", + }, + { + termsAndConditionsLink: (str) => ( + + {str} + + ), + privacyPolicyLink: (str) => ( + + {str} + + ), + } + )} +

+
+
) } diff --git a/packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/termsAndConditions.module.css b/packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/termsAndConditions.module.css new file mode 100644 index 000000000..7795376bd --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Payment/TermsAndConditions/termsAndConditions.module.css @@ -0,0 +1,5 @@ +.termsAndConditions { + display: grid; + gap: var(--Space-x1); + justify-items: start; +} diff --git a/packages/booking-flow/lib/components/EnterDetails/Payment/payment.module.css b/packages/booking-flow/lib/components/EnterDetails/Payment/payment.module.css index a32535341..a78fd0446 100644 --- a/packages/booking-flow/lib/components/EnterDetails/Payment/payment.module.css +++ b/packages/booking-flow/lib/components/EnterDetails/Payment/payment.module.css @@ -1,63 +1,51 @@ .paymentSection { - display: flex; - flex-direction: column; - gap: var(--Space-x4); + display: grid; + gap: var(--Space-x2); + width: min(100%, 696px); + + &.isSubmitting { + opacity: 0.5; + pointer-events: none; + } } -.disabled { - opacity: 0.5; - pointer-events: none; +.header { + display: flex; + gap: var(--Space-x1); + align-items: flex-start; } -.paymentContainer { - display: flex; - flex-direction: column; +.paymentForm { + display: grid; gap: var(--Space-x4); max-width: 696px; } -.section { - display: flex; - flex-direction: column; - gap: var(--Space-x2); +@media screen and (max-width: 767px) { + .header { + flex-direction: column; + } } -.paymentOptionContainer { - display: flex; - flex-direction: column; - gap: var(--Space-x15); -} - -.submitButton { - display: none; -} - -.paymentContainer .link { - font-weight: 500; - font-size: var(--Typography-Caption-Regular-fontSize); -} - -.terms { - display: flex; - flex-direction: row; - gap: var(--Space-x15); -} - -.checkboxContainer { - background-color: var(--Surface-Secondary-Default); - border-radius: var(--Corner-radius-Large); - padding: var(--Space-x2); -} - -@media screen and (min-width: 1367px) { - .submitButton { - display: flex; - align-self: flex-start; +@media screen and (min-width: 768px) { + .header { + justify-content: space-between; } } @media screen and (max-width: 1366px) { - .paymentContainer { + .paymentForm { margin-bottom: 200px; } + + .submitButton { + display: none; + } } + +@media screen and (min-width: 1367px) { + .submitButton { + justify-self: start; + } +} + diff --git a/packages/booking-flow/lib/components/EnterDetails/Payment/utils.ts b/packages/booking-flow/lib/components/EnterDetails/Payment/utils.ts new file mode 100644 index 000000000..5ad4c1a87 --- /dev/null +++ b/packages/booking-flow/lib/components/EnterDetails/Payment/utils.ts @@ -0,0 +1,41 @@ +import type { IntlShape } from "react-intl" + +export function getPaymentHeadingConfig( + intl: IntlShape, + bookingMustBeGuaranteed: boolean, + hasOnlyFlexRates: boolean +) { + if (hasOnlyFlexRates) { + return bookingMustBeGuaranteed + ? { + heading: intl.formatMessage({ + id: "enterDetails.payment.guaranteePaymentHeading", + defaultMessage: "Guarantee with card", + }), + subHeading: intl.formatMessage({ + id: "enterDetails.payment.guaranteePaymentSubheading", + defaultMessage: "(your card won't be charged now)", + }), + showLearnMore: true, + } + : { + heading: intl.formatMessage({ + id: "enterDetails.payment.onlyFlexRatesTitle", + defaultMessage: "Final step", + }), + showLearnMore: false, + } + } + + return { + preHeading: intl.formatMessage({ + id: "enterDetails.payment.label", + defaultMessage: "Payment", + }), + heading: intl.formatMessage({ + id: "enterDetails.payment.title", + defaultMessage: "Select payment method", + }), + showLearnMore: false, + } +} diff --git a/packages/design-system/lib/components/Modal/index.tsx b/packages/design-system/lib/components/Modal/index.tsx index 6783ac806..bebac53e9 100644 --- a/packages/design-system/lib/components/Modal/index.tsx +++ b/packages/design-system/lib/components/Modal/index.tsx @@ -4,15 +4,14 @@ import { cx } from 'class-variance-authority' import { AnimatePresence, motion } from 'motion/react' import { type PropsWithChildren, useEffect, useState } from 'react' import { + Modal as AriaModal, Dialog, DialogTrigger, - Modal as AriaModal, ModalOverlay, } from 'react-aria-components' import { useIntl } from 'react-intl' import { MaterialIcon } from '../Icons/MaterialIcon' -import Subtitle from '../Subtitle' import { type AnimationState, @@ -23,8 +22,9 @@ import { import { fade, slideInOut } from './motionVariants' import { modalContentVariants } from './variants' -import styles from './modal.module.css' +import { IconButton } from '../IconButton' import { Typography } from '../Typography' +import styles from './modal.module.css' const MotionOverlay = motion.create(ModalOverlay) const MotionModal = motion.create(AriaModal) @@ -45,7 +45,7 @@ function InnerModal({ const intl = useIntl() const contentClassNames = modalContentVariants({ - withActions: withActions, + withActions, }) function modalStateHandler(newAnimationState: AnimationState) { @@ -97,13 +97,15 @@ function InnerModal({ <> {!hideHeader && (
{title && ( - - {title} - + +

{title}

+
)} {subtitle && ( @@ -112,13 +114,23 @@ function InnerModal({ )}
- + +
)} diff --git a/packages/design-system/lib/components/Modal/modal.module.css b/packages/design-system/lib/components/Modal/modal.module.css index 271598231..36db5624c 100644 --- a/packages/design-system/lib/components/Modal/modal.module.css +++ b/packages/design-system/lib/components/Modal/modal.module.css @@ -30,14 +30,10 @@ } .header { - --button-dimension: 32px; - - box-sizing: content-box; display: flex; align-items: flex-start; - min-height: var(--button-dimension); position: relative; - padding: var(--Space-x3) var(--Space-x3) 0; + padding: var(--Space-x3) var(--Space-x7) 0 var(--Space-x3); } .content { @@ -57,17 +53,9 @@ } .close { - background: none; - border: none; - cursor: pointer; position: absolute; + top: var(--Space-x2); right: var(--Space-x2); - width: var(--button-dimension); - height: var(--button-dimension); - display: flex; - align-items: center; - padding: 0; - justify-content: center; } .verticalCenter { diff --git a/packages/design-system/lib/components/TextLink/textLink.module.css b/packages/design-system/lib/components/TextLink/textLink.module.css index 2fe3fe46e..4be5925cb 100644 --- a/packages/design-system/lib/components/TextLink/textLink.module.css +++ b/packages/design-system/lib/components/TextLink/textLink.module.css @@ -32,3 +32,11 @@ opacity: 0.7; } } + +.theme-interactive-default:not(.disabled) { + color: var(--Text-Interactive-Default); + + &:hover { + color: var(--Text-Interactive-Default-Hover); + } +} diff --git a/packages/design-system/lib/components/TextLink/variants.ts b/packages/design-system/lib/components/TextLink/variants.ts index c3f6e1210..a6c1393d5 100644 --- a/packages/design-system/lib/components/TextLink/variants.ts +++ b/packages/design-system/lib/components/TextLink/variants.ts @@ -13,6 +13,7 @@ export const config = { theme: { Primary: styles['theme-primary'], Inverted: styles['theme-inverted'], + InteractiveDefault: styles['theme-interactive-default'], }, }, defaultVariants: {