Merged in fix/STAY-65-manage-stay (pull request #3089)
Fix/STAY-65 manage stay * fix: Disable manage stay for past bookings * fix: handle past and cancelled stay the same * fix: indentify past booking * fix: refactor to use design system components Approved-by: Erik Tiekstra
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
div a.link {
|
||||
align-items: center;
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Default);
|
||||
border: 2px solid var(--Component-Button-Brand-Tertiary-Border-Default);
|
||||
border-radius: var(--Corner-radius-rounded);
|
||||
color: var(--Text-Inverted);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
height: 48px;
|
||||
justify-content: center;
|
||||
padding: var(--Space-x2) var(--Space-x4);
|
||||
transition: background-color 200ms ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Hover);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
"use client"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
||||
|
||||
import { isWebview } from "@/constants/routes/webviews"
|
||||
|
||||
import CustomerSupport from "./CustomerSupport"
|
||||
|
||||
import styles from "./cancelled.module.css"
|
||||
|
||||
export default function Cancelled() {
|
||||
const intl = useIntl()
|
||||
const pathname = usePathname()
|
||||
return (
|
||||
<>
|
||||
{/* (S) TODO - Link to where?? */}
|
||||
{!isWebview(pathname) && (
|
||||
<Link className={styles.link} href="#">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Rebook",
|
||||
id: "myStay.referenceCard.actions.rebook",
|
||||
})}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<CustomerSupport />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
.trigger {
|
||||
align-items: center;
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Default);
|
||||
border: 2px solid var(--Component-Button-Brand-Tertiary-Border-Default);
|
||||
border-radius: var(--Corner-radius-rounded);
|
||||
color: var(--Text-Inverted);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
height: 48px;
|
||||
justify-content: center;
|
||||
padding: var(--Space-x2) var(--Space-x4);
|
||||
transition: background-color 200ms ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog {
|
||||
display: grid;
|
||||
flex: 1;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--Space-x2);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.close {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.dialog {
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
|
||||
.content {
|
||||
gap: var(--Space-x3);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
"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 ManageStay from "./ManageStay"
|
||||
|
||||
import styles from "./notCancelled.module.css"
|
||||
|
||||
export default function NotCancelled() {
|
||||
const intl = useIntl()
|
||||
const { hotel } = useMyStayStore((state) => state)
|
||||
|
||||
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(
|
||||
`${hotel.name}, ${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`
|
||||
)}`
|
||||
return (
|
||||
<>
|
||||
<ManageStay />
|
||||
<Link className={styles.link} href={directionsUrl} target="_blank">
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.findUs",
|
||||
defaultMessage: "Find us",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
<MaterialIcon color="Icon/Interactive/Default" icon="location_on" />
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.link {
|
||||
align-items: center;
|
||||
border: 2px solid var(--Component-Button-Brand-Secondary-Border-Default);
|
||||
border-radius: var(--Corner-radius-rounded);
|
||||
color: var(--Text-Interactive-Default);
|
||||
display: flex;
|
||||
height: 48px;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
import { DialogTrigger } from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
|
||||
import CustomerSupportModal from "@/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal"
|
||||
|
||||
@@ -11,7 +11,11 @@ export default function CustomerSupport() {
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button fullWidth intent="secondary" size="small">
|
||||
<Button
|
||||
variant="Secondary"
|
||||
size="Small"
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: "common.customerSupport",
|
||||
defaultMessage: "Customer support",
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { serializeBookingSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
||||
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
|
||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||
|
||||
import { isWebview } from "@/constants/routes/webviews"
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import CustomerSupport from "./CustomerSupport"
|
||||
|
||||
import type { BookingWidgetSearchData } from "@scandic-hotels/booking-flow/BookingWidget"
|
||||
|
||||
export default function NotUpcoming() {
|
||||
const intl = useIntl()
|
||||
const pathname = usePathname()
|
||||
const { rooms, mainRoom } = useMyStayStore((state) => ({
|
||||
rooms: state.rooms,
|
||||
mainRoom: state.mainRoom,
|
||||
}))
|
||||
const lang = useLang()
|
||||
|
||||
const bookingData: BookingWidgetSearchData = {
|
||||
hotelId: mainRoom.hotelId,
|
||||
rooms: [mainRoom, ...rooms].map((room) => ({
|
||||
adults: room.adults,
|
||||
childrenInRoom: room.childrenInRoom,
|
||||
})),
|
||||
}
|
||||
|
||||
if (mainRoom.bookingCode) {
|
||||
bookingData.bookingCode = mainRoom.bookingCode
|
||||
}
|
||||
if (mainRoom.bookingType === RateTypeEnum.Redemption) {
|
||||
bookingData.searchType = "redemption"
|
||||
}
|
||||
|
||||
const rebookUrl = serializeBookingSearchParams(bookingData)
|
||||
|
||||
const url = `/${lang}?${rebookUrl}`
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isWebview(pathname) && (
|
||||
<ButtonLink
|
||||
variant="Tertiary"
|
||||
size="Medium"
|
||||
href={url}
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Rebook",
|
||||
id: "myStay.referenceCard.actions.rebook",
|
||||
})}
|
||||
</ButtonLink>
|
||||
)}
|
||||
|
||||
<CustomerSupport />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,12 @@
|
||||
"use client"
|
||||
import {
|
||||
Button as ButtonRAC,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
} from "react-aria-components"
|
||||
import { Dialog, DialogTrigger } from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
||||
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 Actions from "./Actions"
|
||||
@@ -20,13 +16,6 @@ import styles from "./manageStay.module.css"
|
||||
|
||||
export default function ManageStay() {
|
||||
const intl = useIntl()
|
||||
const allRoomsAreCancelled = useMyStayStore(
|
||||
(state) => state.allRoomsAreCancelled
|
||||
)
|
||||
|
||||
const color = allRoomsAreCancelled
|
||||
? "Icon/Interactive/Disabled"
|
||||
: "Icon/Inverted"
|
||||
|
||||
const manageStay = intl.formatMessage({
|
||||
id: "myStay.manageStay.manageStay",
|
||||
@@ -35,12 +24,14 @@ export default function ManageStay() {
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<ButtonRAC className={styles.trigger} isDisabled={allRoomsAreCancelled}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{manageStay}</span>
|
||||
</Typography>
|
||||
<MaterialIcon color={color} icon="keyboard_arrow_down" />
|
||||
</ButtonRAC>
|
||||
<Button
|
||||
size="Medium"
|
||||
variant="Tertiary"
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
>
|
||||
<span>{manageStay}</span>
|
||||
<MaterialIcon color="CurrentColor" icon="keyboard_arrow_down" />
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog className={styles.dialog}>
|
||||
{({ close }) => (
|
||||
@@ -49,9 +40,9 @@ export default function ManageStay() {
|
||||
<Typography variant="Title/Subtitle/lg">
|
||||
<span className={styles.title}>{manageStay}</span>
|
||||
</Typography>
|
||||
<ButtonRAC className={styles.close} onPress={close}>
|
||||
<MaterialIcon color="Icon/Feedback/Neutral" icon="close" />
|
||||
</ButtonRAC>
|
||||
<IconButton onPress={close} theme="Inverted">
|
||||
<MaterialIcon color="CurrentColor" icon="close" />
|
||||
</IconButton>
|
||||
</header>
|
||||
<div className={styles.content}>
|
||||
<Actions onClose={close} />
|
||||
@@ -0,0 +1,32 @@
|
||||
.dialog {
|
||||
display: grid;
|
||||
flex: 1;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--Space-x2);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.dialog {
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
|
||||
.content {
|
||||
gap: var(--Space-x3);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import ManageStay from "./ManageStay"
|
||||
|
||||
export default function Upcoming() {
|
||||
const intl = useIntl()
|
||||
const hotel = useMyStayStore((state) => state.hotel)
|
||||
|
||||
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(
|
||||
`${hotel.name}, ${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`
|
||||
)}`
|
||||
return (
|
||||
<>
|
||||
<ManageStay />
|
||||
<ButtonLink
|
||||
variant="Secondary"
|
||||
size="Medium"
|
||||
target="_blank"
|
||||
href={directionsUrl}
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
>
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: "myStay.referenceCard.actions.findUs",
|
||||
defaultMessage: "Find us",
|
||||
})}
|
||||
</span>
|
||||
<MaterialIcon color="CurrentColor" icon="location_on" />
|
||||
</ButtonLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client"
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import Cancelled from "./Cancelled"
|
||||
import NotCancelled from "./NotCancelled"
|
||||
import NotUpcoming from "./NotUpcoming"
|
||||
import Upcoming from "./Upcoming"
|
||||
|
||||
import styles from "./actions.module.css"
|
||||
|
||||
export default function Actions() {
|
||||
const isCancelled = useMyStayStore((state) => state.bookedRoom.isCancelled)
|
||||
const isPastBooking = useMyStayStore((state) => state.isPastBooking)
|
||||
|
||||
const isActionable = !isCancelled && !isPastBooking
|
||||
return (
|
||||
<div className={styles.actionArea}>
|
||||
{isCancelled ? <Cancelled /> : <NotCancelled />}
|
||||
{isActionable ? <Upcoming /> : <NotUpcoming />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
|
||||
import { use, useRef } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@scandic-hotels/common/dt"
|
||||
import { trpc } from "@scandic-hotels/trpc/client"
|
||||
|
||||
import { createMyStayStore } from "@/stores/my-stay"
|
||||
@@ -91,9 +92,15 @@ export default function MyStayProvider({
|
||||
|
||||
const rooms = [data.booking, ...linkedReservations]
|
||||
|
||||
// Following the API logic for determining if booking is in the past.
|
||||
// This is the same logic as used for separating stays in /future and /past endpoints on the API
|
||||
const now = dt()
|
||||
const isPastBooking = dt(data.booking.checkInDate).isBefore(now, "day")
|
||||
|
||||
const hasInvalidatedQueryAndRefetched =
|
||||
(isFetchedAfterMount && data) ||
|
||||
(linkedReservationsIsFetchedAfterMount && linkedReservations)
|
||||
|
||||
if (!storeRef.current || hasInvalidatedQueryAndRefetched) {
|
||||
storeRef.current = createMyStayStore({
|
||||
breakfastPackages,
|
||||
@@ -104,6 +111,7 @@ export default function MyStayProvider({
|
||||
rooms,
|
||||
savedCreditCards,
|
||||
isLoggedIn,
|
||||
isPastBooking,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export function createMyStayStore({
|
||||
rooms,
|
||||
savedCreditCards,
|
||||
isLoggedIn,
|
||||
isPastBooking,
|
||||
}: InitialState) {
|
||||
const rates = {
|
||||
change: intl.formatMessage({
|
||||
@@ -79,6 +80,7 @@ export function createMyStayStore({
|
||||
savedCreditCards,
|
||||
totalPoints,
|
||||
totalPrice,
|
||||
isPastBooking,
|
||||
|
||||
actions: {
|
||||
closeManageStay() {
|
||||
|
||||
@@ -57,12 +57,18 @@ export interface MyStayState {
|
||||
savedCreditCards: CreditCard[] | null
|
||||
totalPoints: number
|
||||
totalPrice: string
|
||||
isPastBooking: boolean
|
||||
}
|
||||
|
||||
export interface InitialState
|
||||
extends Pick<
|
||||
MyStayState,
|
||||
"breakfastPackages" | "hotel" | "refId" | "savedCreditCards" | "isLoggedIn"
|
||||
| "breakfastPackages"
|
||||
| "hotel"
|
||||
| "refId"
|
||||
| "savedCreditCards"
|
||||
| "isLoggedIn"
|
||||
| "isPastBooking"
|
||||
> {
|
||||
intl: IntlShape
|
||||
roomCategories: RoomCategories
|
||||
|
||||
Reference in New Issue
Block a user