Merged in fix/STAY-124-change-dates (pull request #3199)
Fix/STAY-124 change dates * fix: handle change dates for different rate types * fix: update wrong spelling in cancellation rules * fix: add hover state on links * fix: handle multiroom scenario Approved-by: Erik Tiekstra
This commit is contained in:
@@ -46,14 +46,12 @@ export default function Steps({ closeModal }: StepsProps) {
|
||||
const stepTwo = confirm
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
{/* Step 1 */}
|
||||
{stepOne ? (
|
||||
<CancelStayConfirmation
|
||||
closeModal={closeModal}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
) : null}
|
||||
{/* Step 2 */}
|
||||
{stepTwo ? <FinalConfirmation closeModal={closeModal} /> : null}
|
||||
</FormProvider>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.textDefault {
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
|
||||
import styles from "./cannotChange.module.css"
|
||||
|
||||
export default function CannotChangeDate({
|
||||
closeModal,
|
||||
}: {
|
||||
closeModal: () => void
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
const cancellationText = useMyStayStore(
|
||||
(state) => state.bookedRoom.rateDefinition.cancellationText
|
||||
)
|
||||
|
||||
const title = intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.changeDates",
|
||||
defaultMessage: "Change dates",
|
||||
})
|
||||
|
||||
const notChangeableText = intl.formatMessage(
|
||||
{
|
||||
id: "myStay.referenceCard.actions.changeDates.cannotChangeDatesInfo",
|
||||
defaultMessage:
|
||||
"Your stay has been booked with <strong>{cancellationText}</strong> terms which unfortunately doesn’t allow for date changes.",
|
||||
},
|
||||
{
|
||||
cancellationText,
|
||||
strong: (str) => <strong>{str}</strong>,
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal.Content>
|
||||
<Modal.Content.Header handleClose={closeModal} title={title} />
|
||||
<Modal.Content.Body>
|
||||
<Typography>
|
||||
<p className={styles.textDefault}>{notChangeableText}</p>
|
||||
</Typography>
|
||||
</Modal.Content.Body>
|
||||
<Modal.Content.Footer>
|
||||
<Modal.Content.Footer.Secondary onClick={closeModal}>
|
||||
{intl.formatMessage({ defaultMessage: "Back", id: "common.back" })}
|
||||
</Modal.Content.Footer.Secondary>
|
||||
</Modal.Content.Footer>
|
||||
</Modal.Content>
|
||||
)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
|
||||
import { Alert } from "@scandic-hotels/design-system/Alert"
|
||||
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
|
||||
export default function CannotChangeDate({
|
||||
closeModal,
|
||||
}: {
|
||||
closeModal: () => void
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
return (
|
||||
<Modal.Content>
|
||||
<Modal.Content.Header
|
||||
handleClose={closeModal}
|
||||
title={intl.formatMessage({
|
||||
id: "myStay.actions.changeDates",
|
||||
defaultMessage: "New dates for the stay",
|
||||
})}
|
||||
/>
|
||||
<Modal.Content.Body>
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.changeDates.contactCustomerService",
|
||||
defaultMessage: "Contact customer service",
|
||||
})}
|
||||
text={intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.changeDates.contactCustomerService.text",
|
||||
defaultMessage:
|
||||
"Please contact customer service to update the dates.",
|
||||
})}
|
||||
/>
|
||||
</Modal.Content.Body>
|
||||
<Modal.Content.Footer>
|
||||
<Modal.Content.Footer.Secondary onClick={closeModal}>
|
||||
{intl.formatMessage({ defaultMessage: "Back", id: "common.back" })}
|
||||
</Modal.Content.Footer.Secondary>
|
||||
</Modal.Content.Footer>
|
||||
</Modal.Content>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
.links {
|
||||
display: grid;
|
||||
gap: var(--Space-x05);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
color: var(--Text-Interactive-Default);
|
||||
align-items: center;
|
||||
background-color: var(--Surface-Feedback-Information-light);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: var(--Corner-radius-md);
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x1);
|
||||
padding: var(--Space-x3);
|
||||
|
||||
&:hover {
|
||||
color: var(--Text-Interactive-Hover);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.links {
|
||||
gap: var(--Space-x3);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
"use client"
|
||||
import Link from "next/link"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
|
||||
import styles from "./customerSupport.module.css"
|
||||
|
||||
export default function CustomerSupport({
|
||||
closeModal,
|
||||
}: {
|
||||
closeModal: () => void
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
const { email, phone } = useMyStayStore((state) => ({
|
||||
email: state.hotel.contactInformation.email,
|
||||
phone: state.hotel.contactInformation.phoneNumber,
|
||||
}))
|
||||
|
||||
const title = intl.formatMessage({
|
||||
id: "common.customerService",
|
||||
defaultMessage: "Customer service",
|
||||
})
|
||||
const contact = intl.formatMessage(
|
||||
{
|
||||
id: "myStay.referenceCard.actions.changeDates.contactCustomerSupport",
|
||||
defaultMessage:
|
||||
"Please call {phone} or email us at {email} to modify your booking dates.",
|
||||
},
|
||||
{ email, phone }
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal.Content>
|
||||
<Modal.Content.Header handleClose={closeModal} title={title}>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p>{contact}</p>
|
||||
</Typography>
|
||||
</Modal.Content.Header>
|
||||
<Modal.Content.Body>
|
||||
<div className={styles.links}>
|
||||
<Link className={styles.link} href={`tel:${phone}`}>
|
||||
<MaterialIcon color="CurrentColor" icon="call" />
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.customerSupport.makeCall",
|
||||
defaultMessage: "Make a call",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</Link>
|
||||
<Link className={styles.link} href={`mailto:${email}`}>
|
||||
<MaterialIcon color="CurrentColor" icon="mail" />
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.customerSupport.sendEmail",
|
||||
defaultMessage: "Send an email",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</Link>
|
||||
</div>
|
||||
</Modal.Content.Body>
|
||||
<Modal.Content.Footer>
|
||||
<Modal.Content.Footer.Secondary onClick={closeModal}>
|
||||
{intl.formatMessage({ defaultMessage: "Back", id: "common.back" })}
|
||||
</Modal.Content.Footer.Secondary>
|
||||
</Modal.Content.Footer>
|
||||
</Modal.Content>
|
||||
)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
|
||||
import { Alert } from "@scandic-hotels/design-system/Alert"
|
||||
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
|
||||
export default function MultiRoomBooking({
|
||||
closeModal,
|
||||
}: {
|
||||
closeModal: () => void
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
return (
|
||||
<Modal.Content>
|
||||
<Modal.Content.Header
|
||||
handleClose={closeModal}
|
||||
title={intl.formatMessage({
|
||||
id: "myStay.actions.changeDates",
|
||||
defaultMessage: "New dates for the stay",
|
||||
})}
|
||||
/>
|
||||
<Modal.Content.Body>
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.changeDates.contactCustomerService",
|
||||
defaultMessage: "Contact customer service",
|
||||
})}
|
||||
text={intl.formatMessage({
|
||||
id: "myStay.actions.contactBooker.multiroom.update",
|
||||
defaultMessage:
|
||||
"As this is a multiroom stay, any dates changes are applicable to all rooms. Please contact customer service to update the dates.",
|
||||
})}
|
||||
/>
|
||||
</Modal.Content.Body>
|
||||
<Modal.Content.Footer>
|
||||
<Modal.Content.Footer.Secondary onClick={closeModal}>
|
||||
{intl.formatMessage({ defaultMessage: "Back", id: "common.back" })}
|
||||
</Modal.Content.Footer.Secondary>
|
||||
</Modal.Content.Footer>
|
||||
</Modal.Content>
|
||||
)
|
||||
}
|
||||
@@ -1,29 +1,53 @@
|
||||
"use client"
|
||||
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
|
||||
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import CannotChangeDate from "./CannotChangeDate"
|
||||
import MultiRoomBooking from "./MultiRoomBooking"
|
||||
import CannotChangeDate from "./CannotChange"
|
||||
import CustomerSupport from "./CustomerSupport"
|
||||
import NotMainRoom from "./NotMainRoom"
|
||||
|
||||
export default function Alerts({
|
||||
children,
|
||||
closeModal,
|
||||
}: React.PropsWithChildren<{ closeModal: () => void }>) {
|
||||
const { canChangeDate, mainRoom, multiRoom } = useMyStayStore((state) => ({
|
||||
canChangeDate: state.bookedRoom.canChangeDate,
|
||||
mainRoom: state.bookedRoom.mainRoom,
|
||||
multiRoom: state.bookedRoom.multiRoom,
|
||||
}))
|
||||
|
||||
if (multiRoom) {
|
||||
return <MultiRoomBooking closeModal={closeModal} />
|
||||
}
|
||||
const { cancellationRule, isModifiable, mainRoom, rooms, multiRoom } =
|
||||
useMyStayStore((state) => ({
|
||||
cancellationRule: state.bookedRoom.rateDefinition.cancellationRule,
|
||||
cancellationText: state.bookedRoom.rateDefinition.cancellationText,
|
||||
isModifiable: state.bookedRoom.isModifiable,
|
||||
mainRoom: state.bookedRoom.mainRoom,
|
||||
rooms: state.rooms,
|
||||
multiRoom: state.bookedRoom.multiRoom,
|
||||
}))
|
||||
|
||||
if (!mainRoom) {
|
||||
return <NotMainRoom closeModal={closeModal} />
|
||||
}
|
||||
|
||||
if (!canChangeDate) {
|
||||
// Multiroom: If any room has rate that allows changing dates needs to contact customer support to change it
|
||||
// Single room: CHANGEABLE bookings needs to contact customer support to change dates
|
||||
const isChangeable = multiRoom
|
||||
? rooms.some(
|
||||
(room) =>
|
||||
room.rateDefinition.cancellationRule ===
|
||||
CancellationRuleEnum.Changeable ||
|
||||
room.rateDefinition.cancellationRule ===
|
||||
CancellationRuleEnum.CancellableBefore6PM
|
||||
)
|
||||
: cancellationRule === CancellationRuleEnum.Changeable
|
||||
|
||||
if (!isModifiable && isChangeable) {
|
||||
return <CustomerSupport closeModal={closeModal} />
|
||||
}
|
||||
|
||||
const isAllNotCancellable = rooms.every(
|
||||
(room) =>
|
||||
room.rateDefinition.cancellationRule ===
|
||||
CancellationRuleEnum.NotCancellable
|
||||
)
|
||||
// For SAVE bookings we show info that they cannot change dates
|
||||
if (!isModifiable && isAllNotCancellable) {
|
||||
return <CannotChangeDate closeModal={closeModal} />
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ export default function NewDates({ checkInDate, checkOutDate }: NewDatesProps) {
|
||||
<MaterialIcon icon="calendar_today" />
|
||||
</ButtonRAC>
|
||||
<Modal>
|
||||
<Dialog>
|
||||
<Dialog className={styles.dialog}>
|
||||
{({ close }) => (
|
||||
<>
|
||||
<DatePickerSingleDesktop
|
||||
|
||||
@@ -33,3 +33,7 @@
|
||||
.textDefault {
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.dialog {
|
||||
max-width: 690px;
|
||||
}
|
||||
@@ -2,34 +2,26 @@
|
||||
import { Dialog, DialogTrigger } from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
|
||||
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
import { trackMyStayPageLink } from "@/utils/tracking"
|
||||
|
||||
import ActionsButton from "../ActionsButton"
|
||||
import { dateHasPassed } from "../utils"
|
||||
import Alerts from "./Alerts"
|
||||
import Steps from "./Steps"
|
||||
|
||||
import styles from "./changeDates.module.css"
|
||||
|
||||
export default function ChangeDates() {
|
||||
const intl = useIntl()
|
||||
|
||||
const { canChangeDate, checkInDate, checkInTime, isCancelled, priceType } =
|
||||
useMyStayStore((state) => ({
|
||||
canChangeDate: state.bookedRoom.canChangeDate,
|
||||
checkInDate: state.bookedRoom.checkInDate,
|
||||
checkInTime: state.hotel.hotelFacts.checkin.checkInTime,
|
||||
isCancelled: state.bookedRoom.isCancelled,
|
||||
priceType: state.bookedRoom.priceType,
|
||||
}))
|
||||
|
||||
const isRewardNight = priceType === "points"
|
||||
const isDisabled =
|
||||
canChangeDate &&
|
||||
!isCancelled &&
|
||||
!isRewardNight &&
|
||||
dateHasPassed(checkInDate, checkInTime)
|
||||
const { isModifiable, cancellationRule } = useMyStayStore((state) => ({
|
||||
isModifiable: state.bookedRoom.isModifiable,
|
||||
cancellationRule: state.bookedRoom.rateDefinition.cancellationRule,
|
||||
}))
|
||||
|
||||
function trackChangeDates() {
|
||||
trackMyStayPageLink("modify dates")
|
||||
@@ -39,16 +31,25 @@ export default function ChangeDates() {
|
||||
defaultMessage: "Change dates",
|
||||
id: "myStay.referenceCard.actions.changeDates",
|
||||
})
|
||||
|
||||
const notMofifiableFlex =
|
||||
!isModifiable &&
|
||||
cancellationRule === CancellationRuleEnum.CancellableBefore6PM
|
||||
|
||||
// For a FLEX booking that for some reason is not modifiable anymore, we do not show the change dates option
|
||||
// Could be that the booking is too close to the check-in time
|
||||
if (notMofifiableFlex) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<ActionsButton
|
||||
icon="edit_calendar"
|
||||
isDisabled={isDisabled}
|
||||
onPress={trackChangeDates}
|
||||
text={text}
|
||||
/>
|
||||
<Modal>
|
||||
<Dialog>
|
||||
<Dialog className={styles.dialog}>
|
||||
{({ close }) => (
|
||||
<Alerts closeModal={close}>
|
||||
<Steps closeModal={close} />
|
||||
|
||||
@@ -106,7 +106,7 @@ export function mapRoomDetails({
|
||||
case CancellationRuleEnum.Changeable:
|
||||
rate = rates.change
|
||||
break
|
||||
case CancellationRuleEnum.NonCancellable:
|
||||
case CancellationRuleEnum.NotCancellable:
|
||||
rate = rates.save
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user