feat: change GLA to be more clear to the user

This commit is contained in:
Simon Emanuelsson
2025-06-10 11:26:15 +02:00
committed by Michael Zetterberg
parent 002d5f9c68
commit 2f553bb945
8 changed files with 292 additions and 187 deletions

View File

@@ -0,0 +1,98 @@
@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;
}
.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(--Spacing-x5) var(--Spacing-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;
gap: var(--Spacing-x2);
}
.text {
text-align: center;
}
.closeButton {
margin-top: var(--Space-x15);
outline: none;
width: min(164px, 100%);
}
@media screen and (max-width: 767px) {
.btnText {
display: none;
}
}

View File

@@ -0,0 +1,140 @@
"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 { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { PaymentMethodEnum } from "@/constants/booking"
import MySavedCards from "@/components/HotelReservation/MySavedCards"
import PaymentOption from "@/components/HotelReservation/PaymentOption"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import useSetOverflowVisibleOnRA from "@/hooks/useSetOverflowVisibleOnRA"
import PaymentOptionsGroup from "../../Payment/PaymentOptionsGroup"
import styles from "./guarantee.module.css"
import type { CreditCard } from "@/types/user"
interface GuaranteeProps {
savedCreditCards: CreditCard[] | null
}
export default function Guarantee({ savedCreditCards }: GuaranteeProps) {
const intl = useIntl()
const guarantee = useWatch({ name: "guarantee" })
return (
<div className={styles.guarantee}>
<Checkbox name="guarantee">
<div className={styles.checkboxContainer}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>
{intl.formatMessage({
defaultMessage:
"I may arrive later than 18:00 and want to guarantee my booking.",
})}
</span>
</Typography>
<DialogTrigger>
<Button
className={styles.infoButton}
size="Small"
typography="Body/Supporting text (caption)/smBold"
variant="Text"
>
<MaterialIcon icon="info" size={20} color="CurrentColor" />
<span className={styles.btnText}>
{intl.formatMessage({ defaultMessage: "How it works" })}
</span>
</Button>
<ModalOverlay className={styles.overlay} isDismissable>
<Modal className={styles.modal}>
<Dialog>
{({ close }) => (
<div className={styles.container}>
<Typography variant="Title/Subtitle/lg">
<h3>
{intl.formatMessage({
defaultMessage: "Guarantee for late arrival",
})}
</h3>
</Typography>
<Typography variant="Body/Lead text">
<p className={styles.text}>
{intl.formatMessage({
defaultMessage:
"When guaranteeing your booking with a credit card, we will hold the booking until 07:00 the day after check-in.",
})}
</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.text}>
{intl.formatMessage({
defaultMessage:
"In case of a no-show, your credit card will be charged for the first night.",
})}
</p>
</Typography>
<Button
className={styles.closeButton}
onPress={close}
size="Small"
typography="Body/Paragraph/mdBold"
variant="Secondary"
>
{intl.formatMessage({
defaultMessage: "Close",
})}
</Button>
<RestoreOverflow />
</div>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
</div>
</Checkbox>
{savedCreditCards?.length && guarantee ? (
<MySavedCards savedCreditCards={savedCreditCards} />
) : null}
{guarantee ? (
<PaymentOptionsGroup
name="paymentMethod"
label={
savedCreditCards?.length
? intl.formatMessage({
defaultMessage: "OTHER",
})
: undefined
}
>
<PaymentOption
value={PaymentMethodEnum.card}
label={intl.formatMessage({
defaultMessage: "Credit card",
})}
/>
</PaymentOptionsGroup>
) : null}
</div>
)
}
function RestoreOverflow() {
const setOverflowVisible = useSetOverflowVisibleOnRA()
useLayoutEffect(() => {
setOverflowVisible(true)
}, [setOverflowVisible])
return null
}

View File

@@ -4,25 +4,13 @@
gap: var(--Spacing-x3);
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--Spacing-x-half);
}
.guaranteeContainer {
.selections {
background-color: var(--Surface-Secondary-Default);
border-radius: var(--Corner-radius-Large);
display: flex;
flex-direction: column;
gap: var(--Spacing-x-one-and-half);
background-color: var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Corner-radius-md);
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
width: min(512px, 100%);
}
.checkbox {
display: flex;
gap: var(--Spacing-x-one-and-half);
gap: var(--Spacing-x2);
padding: var(--Spacing-x2);
}
.checkboxContainer {
@@ -31,33 +19,3 @@
gap: var(--Spacing-x2);
width: min(800px, 100%);
}
.modalContainer {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
align-items: center;
width: 100%;
}
.infoButton {
display: flex;
gap: var(--Space-x05);
outline: none;
}
.modalText {
text-align: center;
}
.closeButton {
margin-top: var(--Space-x15);
width: min(164px, 100%);
outline: none;
}
@media screen and (min-width: 768px) {
.modalContainer {
width: 552px;
}
}

View File

@@ -1,23 +1,13 @@
"use client"
import { useState } from "react"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { PaymentMethodEnum } from "@/constants/booking"
import MySavedCards from "@/components/HotelReservation/MySavedCards"
import PaymentOption from "@/components/HotelReservation/PaymentOption"
import Modal from "@/components/Modal"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import PaymentOptionsGroup from "../Payment/PaymentOptionsGroup"
import TermsAndConditions from "../Payment/TermsAndConditions"
import Guarantee from "./Guarantee"
import styles from "./confirm.module.css"
@@ -31,108 +21,21 @@ export default function ConfirmBooking({
savedCreditCards,
}: ConfirmBookingProps) {
const intl = useIntl()
const [isModalOpen, setModalOpen] = useState(false)
const { watch } = useFormContext()
const guarantee = watch("guarantee")
return (
<div className={styles.container}>
<div className={styles.guaranteeContainer}>
<div className={styles.title}>
<div className={styles.checkbox}>
<Checkbox name="guarantee" />
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
defaultMessage: "Guarantee booking for late arrival",
})}
</p>
</Typography>
</div>
<Button
variant="Text"
size="Small"
typography="Body/Supporting text (caption)/smBold"
className={styles.infoButton}
onPress={() => setModalOpen(true)}
>
<MaterialIcon icon="info" size={20} color="CurrentColor" />
{intl.formatMessage({
defaultMessage: "How it works",
})}
</Button>
<Modal isOpen={isModalOpen} onToggle={() => setModalOpen(false)}>
<div className={styles.modalContainer}>
<Typography variant="Title/smRegular">
<h3>
{intl.formatMessage({
defaultMessage: "Guarantee for late arrival",
})}
</h3>
</Typography>
<Typography variant="Body/Lead text">
<p className={styles.modalText}>
{intl.formatMessage({
defaultMessage:
"When guaranteeing your booking with a credit card, we will hold the booking until 07:00 the day after check-in.",
})}
</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.modalText}>
{intl.formatMessage({
defaultMessage:
"In case of a no-show, your credit card will be charged for the first night.",
})}
</p>
</Typography>
<Button
typography="Body/Paragraph/mdBold"
variant="Secondary"
size="Small"
onPress={() => setModalOpen(false)}
className={styles.closeButton}
>
{intl.formatMessage({
defaultMessage: "Close",
})}
</Button>
</div>
</Modal>
</div>
<Divider />
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
defaultMessage:
"I may arrive later than 18:00 and want to guarantee my booking with a credit card.",
})}
</p>
</Typography>
{savedCreditCards?.length && guarantee ? (
<MySavedCards savedCreditCards={savedCreditCards} />
) : null}
{guarantee ? (
<PaymentOptionsGroup
name="paymentMethod"
label={
savedCreditCards?.length
? intl.formatMessage({
defaultMessage: "OTHER",
})
: undefined
}
>
<PaymentOption
value={PaymentMethodEnum.card}
label={intl.formatMessage({
defaultMessage: "Credit card",
<div className={styles.selections}>
<Guarantee savedCreditCards={savedCreditCards} />
<Divider color="Border/Divider/Default" />
<Checkbox name="smsConfirmation">
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>
{intl.formatMessage({
defaultMessage:
"I would like to get my booking confirmation via sms",
})}
/>
</PaymentOptionsGroup>
) : null}
</span>
</Typography>
</Checkbox>
</div>
<div className={styles.checkboxContainer}>
<TermsAndConditions isFlexBookingTerms />

View File

@@ -26,8 +26,8 @@ import { trpc } from "@/lib/trpc/client"
import { useEnterDetailsStore } from "@/stores/enter-details"
import PaymentOption from "@/components/HotelReservation/PaymentOption"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { useAvailablePaymentOptions } from "@/hooks/booking/useAvailablePaymentOptions"
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
import useLang from "@/hooks/useLang"
@@ -482,14 +482,9 @@ export default function PaymentClient({
]
)
const paymentGuarantee = intl.formatMessage({
defaultMessage: "Payment Guarantee",
})
const payment = intl.formatMessage({
defaultMessage: "Payment",
})
const confirm = intl.formatMessage({
defaultMessage: "Confirm booking",
const finalStep = intl.formatMessage({ defaultMessage: "Final step" })
const selectPayment = intl.formatMessage({
defaultMessage: "Select payment method",
})
return (
@@ -499,13 +494,9 @@ export default function PaymentClient({
})}
>
<header>
<Title level="h2" as="h4">
{hasOnlyFlexRates && bookingMustBeGuaranteed
? paymentGuarantee
: hasOnlyFlexRates
? confirm
: payment}
</Title>
<Typography variant="Title/Subtitle/md">
<span>{hasOnlyFlexRates ? finalStep : selectPayment}</span>
</Typography>
<BookingAlert isVisible={showBookingAlert} />
</header>
<FormProvider {...methods}>
@@ -611,6 +602,19 @@ export default function PaymentClient({
) : null}
</section>
<div className={styles.checkboxContainer}>
<Checkbox name="smsConfirmation">
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>
{intl.formatMessage({
defaultMessage:
"I would like to get my booking confirmation via sms",
})}
</span>
</Typography>
</Checkbox>
</div>
<section className={styles.section}>
<TermsAndConditions isFlexBookingTerms={hasOnlyFlexRates} />
</section>

View File

@@ -1,5 +1,7 @@
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { bookingTermsAndConditions, privacyPolicy } from "@/constants/webHrefs"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
@@ -87,19 +89,13 @@ export default function TermsAndConditions({
)}
</Caption>
<Checkbox name="termsAndConditions">
<Caption>
{intl.formatMessage({
defaultMessage: "I accept the terms and conditions",
})}
</Caption>
</Checkbox>
<Checkbox name="smsConfirmation">
<Caption>
{intl.formatMessage({
defaultMessage:
"I would like to get my booking confirmation via sms",
})}
</Caption>
<Typography variant="Body/Paragraph/mdBold">
<span>
{intl.formatMessage({
defaultMessage: "I accept the terms and conditions",
})}
</span>
</Typography>
</Checkbox>
</>
)

View File

@@ -43,6 +43,12 @@
gap: var(--Spacing-x-one-and-half);
}
.checkboxContainer {
background-color: var(--Surface-Secondary-Default);
border-radius: var(--Corner-radius-Large);
padding: var(--Spacing-x2);
}
@media screen and (min-width: 1367px) {
.submitButton {
display: flex;

View File

@@ -1,7 +1,7 @@
.paymentOption {
position: relative;
background-color: var(--UI-Input-Controls-Surface-Normal);
padding: var(--Spacing-x3);
padding: var(--Space-x15) var(--Space-x2);
border: 1px solid var(--Base-Border-Normal);
border-radius: var(--Corner-radius-md);
display: flex;