Merged in feat/BOOK-529-update-GLA-design-mystay (pull request #3230)

Feat/BOOK-529 update GLA design mystay

* feat(BOOK-529): update gla design on my stay

* feat(BOOK-529): open gla modal if error

* feat(BOOK-529): add inline accordion to storybook

* feat(529): move errormessage below message

* feat(529): update infomodal

* feat(BOOK-529): update infomodal

* feat(BOOK-529): hide guarantee info for adding ancillaries if prepaid

* feat(BOOK-529): update width on info dialog

* feat(BOOK-529): fix alignment

* feat(BOOK-529): check if member price

* feat(BOOK-529): refactor msg

* feat(BOOK-529): refactor terms and conditions to own component

* feat(BOOK-529): clean up confirmation step


Approved-by: Christel Westerberg
This commit is contained in:
Bianca Widstam
2025-11-28 14:27:25 +00:00
parent 22dd2f60fe
commit 46fa42750f
39 changed files with 681 additions and 485 deletions

View File

@@ -4,12 +4,6 @@
gap: var(--Space-x2);
}
.termsAndConditions {
display: grid;
gap: var(--Space-x2);
color: var(--Text-Secondary);
}
.totalPointsContainer {
display: flex;
justify-content: space-between;
@@ -17,9 +11,27 @@
padding: var(--Space-x1) var(--Space-x15);
border-radius: var(--Corner-radius-md);
}
.guarantee {
display: flex;
flex-direction: column;
gap: var(--Space-x2);
background-color: var(--Surface-Secondary-Default);
border-radius: var(--Corner-radius-lg);
padding: var(--Space-x2);
}
.paymentInfo {
display: flex;
gap: var(--Space-x1);
align-items: flex-start;
}
.totalPoints {
display: flex;
gap: var(--Space-x15);
align-items: center;
}
.accordionItem {
border-radius: var(--Corner-radius-md);
}

View File

@@ -1,27 +1,23 @@
import { useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
import { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod"
import { bookingTermsAndConditionsRoutes } from "@scandic-hotels/common/constants/routes/bookingTermsAndConditionsRoutes"
import { privacyPolicyRoutes } from "@scandic-hotels/common/constants/routes/privacyPolicyRoutes"
import { dt } from "@scandic-hotels/common/dt"
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
import { Alert } from "@scandic-hotels/design-system/Alert"
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { PaymentOption } from "@scandic-hotels/design-system/Form/PaymentOption"
import { PaymentOptionsGroup } from "@scandic-hotels/design-system/Form/PaymentOptionsGroup"
import { SelectPaymentMethod } from "@scandic-hotels/design-system/Form/SelectPaymentMethod"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow"
import TermsAndConditions from "@/components/HotelReservation/MyStay/TermsAndConditions"
import useLang from "@/hooks/useLang"
import { trackUpdatePaymentMethod } from "@/utils/tracking"
import { ancillaryError } from "../../../schema"
import styles from "./confirmationStep.module.css"
import type { ConfirmationStepProps } from "@/types/components/myPages/myStay/ancillaries"
@@ -33,17 +29,20 @@ export default function ConfirmationStep({
}: ConfirmationStepProps) {
const intl = useIntl()
const lang = useLang()
const { checkInDate, guaranteeInfo, selectedAncillary } =
const { checkInDate, guaranteeInfo, selectedAncillary, booking } =
useAddAncillaryStore((state) => ({
checkInDate: state.booking.checkInDate,
guaranteeInfo: state.booking.guaranteeInfo,
selectedAncillary: state.selectedAncillary,
booking: state.booking,
}))
const refundableDate = dt(checkInDate)
.subtract(1, "day")
.locale(lang)
.format("23:59, dddd, D MMMM YYYY")
const mustBeGuaranteed = !guaranteeInfo && booking.isGuaranteeable
const quantityWithCard = useWatch({ name: "quantityWithCard" })
const quantityWithPoints = useWatch({ name: "quantityWithPoints" })
const currentPoints = user?.membership?.currentPoints ?? 0
@@ -51,20 +50,22 @@ export default function ConfirmationStep({
quantityWithPoints && selectedAncillary?.points
? selectedAncillary.points * quantityWithPoints
: null
const accordionTitle = intl.formatMessage({
id: "myStay.guarantee.guaranteeInformation",
defaultMessage:
"By adding your card, you also guarantee your room booking for late arrival ",
})
const accordionContent = intl.formatMessage({
id: "myStay.guarantee.guaranteeInformation.content",
defaultMessage:
"The hotel will hold your booking, even if you arrive after 18:00. Your card will only be charged in the event of a no-show.",
})
return (
<div className={styles.modalContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage(
{
id: "addAncillary.confirmationStep.refundPolicy",
defaultMessage:
"All ancillaries are fully refundable until {date}. Time selection and special requests are also modifiable.",
},
{ date: refundableDate }
)}
</p>
</Typography>
{error && <Alert type={error.type} text={error.message} />}
{!!quantityWithPoints && (
<>
<Typography variant="Title/Subtitle/md">
@@ -107,118 +108,118 @@ export default function ConfirmationStep({
)}
{!!quantityWithCard && (
<>
<header>
<Typography variant="Title/Subtitle/md">
<h2>
{intl.formatMessage({
id: "addAncillary.confirmationStep.reserveWithCard",
defaultMessage: "Reserve with Card",
})}
</h2>
</Typography>
</header>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
<Typography variant="Title/Subtitle/md">
<h2>
{intl.formatMessage({
id: "addAncillary.confirmationStep.paymentAtCheckInInfo",
defaultMessage:
"Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.",
id: "addAncillary.confirmationStep.reserveWithCard",
defaultMessage: "Reserve with Card",
})}
</h2>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage(
{
id: "addAncillary.confirmationStep.refundPolicy",
defaultMessage:
"All ancillaries are fully refundable until {date}. Time selection and special requests are also modifiable.",
},
{ date: refundableDate }
)}
</p>
</Typography>
{guaranteeInfo ? (
<PaymentOptionsGroup name="paymentMethod">
<PaymentOption
value={PaymentMethodEnum.card}
cardNumber={guaranteeInfo.maskedCard.slice(-4)}
label={intl.formatMessage({
id: "common.creditCard",
defaultMessage: "Credit card",
})}
/>
</PaymentOptionsGroup>
) : (
<>
{error ? (
<Alert type={error.type} text={error.message} />
) : (
<Alert
type={AlertTypeEnum.Info}
text={intl.formatMessage({
id: "addAncillary.confirmationStep.guaranteeAddCard",
defaultMessage:
"By adding a card you also guarantee your room booking for late arrival.",
})}
<div className={styles.guarantee}>
<div className={styles.paymentInfo}>
<MaterialIcon icon="credit_card" size={24} color="CurrentColor" />
<span>
<Typography variant="Body/Supporting text (caption)/smBold">
<p>
{intl.formatMessage({
id: "myStay.ancillary.guarantee.headingText",
defaultMessage: "Payment will be made on check-in",
})}
</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
id: "myStay.ancillary.guarantee.infoText",
defaultMessage:
"The card is used to reserve your extras. You will be charged in case of no-show.",
})}
</p>
</Typography>
</span>
</div>
{guaranteeInfo ? (
<>
<Divider />
<Typography variant="Title/Overline/sm">
<p>
{intl.formatMessage({
id: "payment.savedCard",
defaultMessage: "Saved card",
})}
</p>
</Typography>
<PaymentOptionsGroup name="paymentMethod">
<PaymentOption
value={PaymentMethodEnum.card}
cardNumber={guaranteeInfo.maskedCard.slice(-4)}
label={intl.formatMessage({
id: "common.card",
defaultMessage: "Card",
})}
hideRadioButton
/>
</PaymentOptionsGroup>
</>
) : (
<>
<div className={styles.paymentInfo}>
<MaterialIcon
icon="credit_score"
size={24}
color="CurrentColor"
/>
<Typography variant="Body/Supporting text (caption)/smBold">
<p>
{intl.formatMessage({
id: "myStay.ancillary.guarantee.confirmationText",
defaultMessage:
"Confirm and provide your payment card details in the next step",
})}
</p>
</Typography>
</div>
{mustBeGuaranteed && (
<AccordionItem
title={accordionTitle}
type="inline"
className={styles.accordionItem}
>
<Typography variant="Body/Paragraph/mdRegular">
<p>{accordionContent}</p>
</Typography>
</AccordionItem>
)}
{savedCreditCards && <Divider />}
<SelectPaymentMethod
paymentMethods={(savedCreditCards ?? []).map((card) => ({
...card,
cardType: card.cardType as PaymentMethodEnum,
}))}
onChange={(method) => {
trackUpdatePaymentMethod({ method })
}}
formName={"paymentMethod"}
/>
)}
<SelectPaymentMethod
paymentMethods={(savedCreditCards ?? []).map((card) => ({
...card,
cardType: card.cardType as PaymentMethodEnum,
}))}
onChange={(method) => {
trackUpdatePaymentMethod({ method })
}}
formName={"paymentMethod"}
/>
</>
)}
</>
)}
</div>
</>
)}
<div className={styles.termsAndConditions}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage(
{
id: "addAncillary.confirmationStep.termsAndConditionsNotice",
defaultMessage:
"Yes, I accept the general <termsAndConditionsLink>Booking & Cancellation Terms</termsAndConditionsLink>, and understand that Scandic will process my personal data in accordance with <privacyPolicyLink>Scandic's Privacy policy</privacyPolicyLink>. There you can learn more about what data we process, your rights and where to turn if you have questions.",
},
{
termsAndConditionsLink: (str) => (
<TextLink
typography="Link/sm"
target="_blank"
href={bookingTermsAndConditionsRoutes[lang]}
>
{str}
</TextLink>
),
privacyPolicyLink: (str) => (
<TextLink
typography="Link/sm"
target="_blank"
href={privacyPolicyRoutes[lang]}
>
{str}
</TextLink>
),
}
)}
</p>
</Typography>
<Checkbox
name="termsAndConditions"
registerOptions={{ required: true }}
errorCodeMessages={{
[ancillaryError.TERMS_NOT_ACCEPTED]: intl.formatMessage({
id: "common.mustAcceptTermsError",
defaultMessage: "You must accept the terms and conditions",
}),
}}
>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>
{intl.formatMessage({
id: "booking.acceptBookingTerms",
defaultMessage: "I accept the booking and cancellation terms",
})}
</span>
</Typography>
</Checkbox>
</div>
<TermsAndConditions />
</div>
)
}