feat: refactor NewDates, clean up legacy code
This reverts commit 0c7836fa59.
This commit is contained in:
@@ -28,7 +28,8 @@ import BookingSummary from "@/components/HotelReservation/MyStay/BookingSummary"
|
|||||||
import { Header } from "@/components/HotelReservation/MyStay/Header"
|
import { Header } from "@/components/HotelReservation/MyStay/Header"
|
||||||
import Promo from "@/components/HotelReservation/MyStay/Promo"
|
import Promo from "@/components/HotelReservation/MyStay/Promo"
|
||||||
import { ReferenceCard } from "@/components/HotelReservation/MyStay/ReferenceCard"
|
import { ReferenceCard } from "@/components/HotelReservation/MyStay/ReferenceCard"
|
||||||
import Rooms from "@/components/HotelReservation/MyStay/Rooms"
|
import MultiRoom from "@/components/HotelReservation/MyStay/Rooms/MultiRoom"
|
||||||
|
import SingleRoom from "@/components/HotelReservation/MyStay/Rooms/SingleRoom"
|
||||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
@@ -116,6 +117,11 @@ export default async function MyStay({
|
|||||||
rooms: booking.linkedReservations,
|
rooms: booking.linkedReservations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ancillariesInput = {
|
||||||
|
fromDate,
|
||||||
|
hotelId: hotel.operaId,
|
||||||
|
toDate,
|
||||||
|
}
|
||||||
const packagesInput = {
|
const packagesInput = {
|
||||||
adults: booking.adults,
|
adults: booking.adults,
|
||||||
children: booking.childrenAges.length,
|
children: booking.childrenAges.length,
|
||||||
@@ -141,21 +147,27 @@ export default async function MyStay({
|
|||||||
if (shouldFetchBreakfastPackages) {
|
if (shouldFetchBreakfastPackages) {
|
||||||
void getPackages(packagesInput)
|
void getPackages(packagesInput)
|
||||||
}
|
}
|
||||||
void getSavedPaymentCardsSafely(savedPaymentCardsInput)
|
if (user) {
|
||||||
|
void getSavedPaymentCardsSafely(savedPaymentCardsInput)
|
||||||
const ancillaryPackages = await getAncillaryPackages({
|
}
|
||||||
fromDate,
|
if (booking.showAncillaries) {
|
||||||
hotelId: hotel.operaId,
|
void getAncillaryPackages(ancillariesInput)
|
||||||
toDate,
|
}
|
||||||
})
|
|
||||||
|
|
||||||
let breakfastPackages = null
|
let breakfastPackages = null
|
||||||
if (shouldFetchBreakfastPackages) {
|
if (shouldFetchBreakfastPackages) {
|
||||||
breakfastPackages = await getPackages(packagesInput)
|
breakfastPackages = await getPackages(packagesInput)
|
||||||
}
|
}
|
||||||
const savedCreditCards = await getSavedPaymentCardsSafely(
|
let savedCreditCards = null
|
||||||
savedPaymentCardsInput
|
if (user) {
|
||||||
)
|
savedCreditCards = await getSavedPaymentCardsSafely(
|
||||||
|
savedPaymentCardsInput
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let ancillaryPackagesPromise = null
|
||||||
|
if (booking.showAncillaries) {
|
||||||
|
ancillaryPackagesPromise = getAncillaryPackages(ancillariesInput)
|
||||||
|
}
|
||||||
|
|
||||||
const imageSrc =
|
const imageSrc =
|
||||||
hotel.hotelContent.images.imageSizes.large ??
|
hotel.hotelContent.images.imageSizes.large ??
|
||||||
@@ -196,9 +208,9 @@ export default async function MyStay({
|
|||||||
<Header cityName={hotel.cityName} name={hotel.name} />
|
<Header cityName={hotel.cityName} name={hotel.name} />
|
||||||
<ReferenceCard />
|
<ReferenceCard />
|
||||||
</div>
|
</div>
|
||||||
{booking.showAncillaries && (
|
{booking.showAncillaries && ancillaryPackagesPromise && (
|
||||||
<Ancillaries
|
<Ancillaries
|
||||||
ancillaries={ancillaryPackages}
|
ancillariesPromise={ancillaryPackagesPromise}
|
||||||
booking={booking}
|
booking={booking}
|
||||||
packages={breakfastPackages}
|
packages={breakfastPackages}
|
||||||
user={user}
|
user={user}
|
||||||
@@ -207,7 +219,8 @@ export default async function MyStay({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Rooms user={user} />
|
<SingleRoom user={user} />
|
||||||
|
<MultiRoom user={user} />
|
||||||
|
|
||||||
<BookingSummary hotel={hotel} />
|
<BookingSummary hotel={hotel} />
|
||||||
<Promo
|
<Promo
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ import BookingSummary from "@/components/HotelReservation/MyStay/BookingSummary"
|
|||||||
import { Header } from "@/components/HotelReservation/MyStay/Header"
|
import { Header } from "@/components/HotelReservation/MyStay/Header"
|
||||||
import Promo from "@/components/HotelReservation/MyStay/Promo"
|
import Promo from "@/components/HotelReservation/MyStay/Promo"
|
||||||
import { ReferenceCard } from "@/components/HotelReservation/MyStay/ReferenceCard"
|
import { ReferenceCard } from "@/components/HotelReservation/MyStay/ReferenceCard"
|
||||||
import Rooms from "@/components/HotelReservation/MyStay/Rooms"
|
import MultiRoom from "@/components/HotelReservation/MyStay/Rooms/MultiRoom"
|
||||||
|
import SingleRoom from "@/components/HotelReservation/MyStay/Rooms/SingleRoom"
|
||||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
@@ -113,6 +114,11 @@ export default async function MyStay({
|
|||||||
rooms: booking.linkedReservations,
|
rooms: booking.linkedReservations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ancillariesInput = {
|
||||||
|
fromDate,
|
||||||
|
hotelId: hotel.operaId,
|
||||||
|
toDate,
|
||||||
|
}
|
||||||
const packagesInput = {
|
const packagesInput = {
|
||||||
adults: booking.adults,
|
adults: booking.adults,
|
||||||
children: booking.childrenAges.length,
|
children: booking.childrenAges.length,
|
||||||
@@ -133,26 +139,32 @@ export default async function MyStay({
|
|||||||
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
||||||
)
|
)
|
||||||
const breakfastIncluded = booking.rateDefinition.breakfastIncluded
|
const breakfastIncluded = booking.rateDefinition.breakfastIncluded
|
||||||
const alreadyHasABreakfastSelection =
|
const shouldFetchBreakfastPackages =
|
||||||
!hasBreakfastPackage && !breakfastIncluded
|
!hasBreakfastPackage && !breakfastIncluded
|
||||||
if (alreadyHasABreakfastSelection) {
|
if (shouldFetchBreakfastPackages) {
|
||||||
void getPackages(packagesInput)
|
void getPackages(packagesInput)
|
||||||
}
|
}
|
||||||
void getSavedPaymentCardsSafely(savedPaymentCardsInput)
|
if (user) {
|
||||||
|
void getSavedPaymentCardsSafely(savedPaymentCardsInput)
|
||||||
const ancillaryPackages = await getAncillaryPackages({
|
}
|
||||||
fromDate,
|
if (booking.showAncillaries) {
|
||||||
hotelId: hotel.operaId,
|
void getAncillaryPackages(ancillariesInput)
|
||||||
toDate,
|
}
|
||||||
})
|
|
||||||
|
|
||||||
let breakfastPackages = null
|
let breakfastPackages = null
|
||||||
if (alreadyHasABreakfastSelection) {
|
if (shouldFetchBreakfastPackages) {
|
||||||
breakfastPackages = await getPackages(packagesInput)
|
breakfastPackages = await getPackages(packagesInput)
|
||||||
}
|
}
|
||||||
const savedCreditCards = await getSavedPaymentCardsSafely(
|
let savedCreditCards = null
|
||||||
savedPaymentCardsInput
|
if (user) {
|
||||||
)
|
savedCreditCards = await getSavedPaymentCardsSafely(
|
||||||
|
savedPaymentCardsInput
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let ancillaryPackagesPromise = null
|
||||||
|
if (booking.showAncillaries) {
|
||||||
|
ancillaryPackagesPromise = getAncillaryPackages(ancillariesInput)
|
||||||
|
}
|
||||||
|
|
||||||
const imageSrc =
|
const imageSrc =
|
||||||
hotel.hotelContent.images.imageSizes.large ??
|
hotel.hotelContent.images.imageSizes.large ??
|
||||||
@@ -193,9 +205,9 @@ export default async function MyStay({
|
|||||||
<Header cityName={hotel.cityName} name={hotel.name} />
|
<Header cityName={hotel.cityName} name={hotel.name} />
|
||||||
<ReferenceCard />
|
<ReferenceCard />
|
||||||
</div>
|
</div>
|
||||||
{booking.showAncillaries && (
|
{booking.showAncillaries && ancillaryPackagesPromise && (
|
||||||
<Ancillaries
|
<Ancillaries
|
||||||
ancillaries={ancillaryPackages}
|
ancillariesPromise={ancillaryPackagesPromise}
|
||||||
booking={booking}
|
booking={booking}
|
||||||
packages={breakfastPackages}
|
packages={breakfastPackages}
|
||||||
user={user}
|
user={user}
|
||||||
@@ -204,7 +216,8 @@ export default async function MyStay({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Rooms user={user} />
|
<SingleRoom user={user} />
|
||||||
|
<MultiRoom user={user} />
|
||||||
|
|
||||||
<BookingSummary hotel={hotel} />
|
<BookingSummary hotel={hotel} />
|
||||||
<Promo
|
<Promo
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export function LinkedReservation({
|
|||||||
checkOutTime,
|
checkOutTime,
|
||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
roomIndex,
|
roomIndex,
|
||||||
|
roomNumber,
|
||||||
}: LinkedReservationProps) {
|
}: LinkedReservationProps) {
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
|
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
|
||||||
@@ -86,7 +87,7 @@ export function LinkedReservation({
|
|||||||
checkOutTime={checkOutTime}
|
checkOutTime={checkOutTime}
|
||||||
img={data.room.images[0]}
|
img={data.room.images[0]}
|
||||||
roomName={data.room.name}
|
roomName={data.room.name}
|
||||||
roomNumber={roomIndex + 1}
|
roomNumber={roomNumber}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
|
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ import { convertToChildType } from "@/components/HotelReservation/utils/convertT
|
|||||||
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
|
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
|
||||||
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
||||||
|
|
||||||
import styles from "./RoomDetailsSidePeek.module.css"
|
import styles from "./sidePeek.module.css"
|
||||||
|
|
||||||
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
||||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
@@ -26,6 +27,7 @@ interface RoomDetailsSidePeekProps {
|
|||||||
booking: BookingConfirmationSchema
|
booking: BookingConfirmationSchema
|
||||||
roomNumber?: number
|
roomNumber?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RoomDetailsSidePeek({
|
export default function RoomDetailsSidePeek({
|
||||||
booking,
|
booking,
|
||||||
roomNumber = 1,
|
roomNumber = 1,
|
||||||
@@ -36,9 +38,11 @@ export default function RoomDetailsSidePeek({
|
|||||||
(state) => state.roomCategories
|
(state) => state.roomCategories
|
||||||
)
|
)
|
||||||
const hotelRoom = getBookedHotelRoom(roomCategories, booking.roomTypeCode)
|
const hotelRoom = getBookedHotelRoom(roomCategories, booking.roomTypeCode)
|
||||||
|
|
||||||
const breakfastPackage = booking.packages.find(
|
const breakfastPackage = booking.packages.find(
|
||||||
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
||||||
)
|
)
|
||||||
|
|
||||||
const breakfast: Omit<BreakfastPackage, "requestedPrice"> | null =
|
const breakfast: Omit<BreakfastPackage, "requestedPrice"> | null =
|
||||||
breakfastPackage
|
breakfastPackage
|
||||||
? {
|
? {
|
||||||
@@ -52,21 +56,25 @@ export default function RoomDetailsSidePeek({
|
|||||||
packageType: PackageTypeEnum.BreakfastAdult,
|
packageType: PackageTypeEnum.BreakfastAdult,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const childrenInRoom = convertToChildType(
|
const childrenInRoom = convertToChildType(
|
||||||
booking.childrenAges,
|
booking.childrenAges,
|
||||||
booking.childBedPreferences
|
booking.childBedPreferences
|
||||||
)
|
)
|
||||||
|
|
||||||
const priceType = getPriceType(
|
const priceType = getPriceType(
|
||||||
booking.cheques,
|
booking.cheques,
|
||||||
booking.roomPoints,
|
booking.roomPoints,
|
||||||
booking.vouchers
|
booking.vouchers
|
||||||
)
|
)
|
||||||
|
|
||||||
const featuresPackages = booking.packages.filter(
|
const featuresPackages = booking.packages.filter(
|
||||||
(pkg) =>
|
(pkg) =>
|
||||||
pkg.code === RoomPackageCodeEnum.PET_ROOM ||
|
pkg.code === RoomPackageCodeEnum.PET_ROOM ||
|
||||||
pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM ||
|
pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM ||
|
||||||
pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
|
pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
|
||||||
)
|
)
|
||||||
|
|
||||||
const packages = featuresPackages.map((pkg) => ({
|
const packages = featuresPackages.map((pkg) => ({
|
||||||
code: pkg.code as RoomPackageCodeEnum,
|
code: pkg.code as RoomPackageCodeEnum,
|
||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
@@ -83,6 +91,7 @@ export default function RoomDetailsSidePeek({
|
|||||||
totalPrice: pkg.totalPrice,
|
totalPrice: pkg.totalPrice,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const room = {
|
const room = {
|
||||||
...booking,
|
...booking,
|
||||||
bedType: {
|
bedType: {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default async function Rooms({
|
|||||||
checkOutTime={checkOutTime}
|
checkOutTime={checkOutTime}
|
||||||
confirmationNumber={reservation.confirmationNumber}
|
confirmationNumber={reservation.confirmationNumber}
|
||||||
roomIndex={idx + 1}
|
roomIndex={idx + 1}
|
||||||
|
roomNumber={idx + 2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useMemo } from "react"
|
import { use, useMemo } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { Carousel } from "@/components/Carousel"
|
import { Carousel } from "@/components/Carousel"
|
||||||
@@ -78,7 +78,7 @@ function addBreakfastPackage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Ancillaries({
|
export function Ancillaries({
|
||||||
ancillaries,
|
ancillariesPromise,
|
||||||
booking,
|
booking,
|
||||||
packages,
|
packages,
|
||||||
user,
|
user,
|
||||||
@@ -86,6 +86,7 @@ export function Ancillaries({
|
|||||||
refId,
|
refId,
|
||||||
}: AncillariesProps) {
|
}: AncillariesProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const ancillaries = use(ancillariesPromise)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A constructed ancillary for breakfast
|
* A constructed ancillary for breakfast
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Space-x1);
|
gap: var(--Space-x1);
|
||||||
padding: var(--Space-x1) 0;
|
padding: var(--Space-x1) 0;
|
||||||
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Space-x3);
|
gap: var(--Space-x3);
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -56,6 +56,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.modal {
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.overlay {
|
.overlay {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -65,6 +72,8 @@
|
|||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
border-radius: var(--Corner-radius-Large);
|
border-radius: var(--Corner-radius-Large);
|
||||||
width: min(690px, 100dvw);
|
display: flex;
|
||||||
|
min-height: 300px;
|
||||||
|
min-width: 690px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
.dialog {
|
||||||
|
max-width: 690px;
|
||||||
|
}
|
||||||
|
|
||||||
.links {
|
.links {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Space-x05);
|
gap: var(--Space-x05);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
|
|
||||||
import styles from "./customerSupport.module.css"
|
import styles from "./customerSupport.module.css"
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export default function CustomerSupportModal() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog>
|
<Dialog className={styles.dialog}>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<Modal.Content.Header handleClose={close} title={title}>
|
<Modal.Content.Header handleClose={close} title={title}>
|
||||||
|
|||||||
@@ -15,4 +15,5 @@
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: var(--Text-Interactive-Default);
|
color: var(--Text-Interactive-Default);
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
|
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import CancelStayPriceContainer from "../CancelStayPriceContainer"
|
import CancelStayPriceContainer from "../CancelStayPriceContainer"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
.dialog {
|
||||||
|
max-width: 690px;
|
||||||
|
}
|
||||||
|
|
||||||
.modalText {
|
.modalText {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
import { Dialog, DialogTrigger } from "react-aria-components"
|
import { Dialog, DialogTrigger } from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
|
|
||||||
import Alerts from "./Alerts"
|
import Alerts from "./Alerts"
|
||||||
import Steps from "./Steps"
|
import Steps from "./Steps"
|
||||||
|
|
||||||
|
import styles from "./cancelStay.module.css"
|
||||||
|
|
||||||
export default function CancelStay() {
|
export default function CancelStay() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
return (
|
return (
|
||||||
@@ -15,7 +17,7 @@ export default function CancelStay() {
|
|||||||
{intl.formatMessage({ defaultMessage: "Cancel stay" })}
|
{intl.formatMessage({ defaultMessage: "Cancel stay" })}
|
||||||
</Modal.Button>
|
</Modal.Button>
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog>
|
<Dialog className={styles.dialog}>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<Alerts closeModal={close}>
|
<Alerts closeModal={close}>
|
||||||
<Steps closeModal={close} />
|
<Steps closeModal={close} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
|
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
|
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
|
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { dt } from "@/lib/dt"
|
|||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import PriceContainer from "@/components/HotelReservation/MyStay/ReferenceCard/PriceContainer"
|
import PriceContainer from "@/components/HotelReservation/MyStay/ReferenceCard/PriceContainer"
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
.button {
|
|
||||||
background-color: var(--Main-Grey-White);
|
|
||||||
border-color: var(--Scandic-Beige-40);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-radius: var(--Corner-radius-Medium);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
min-width: 0; /* allow shrinkage */
|
|
||||||
height: 60px;
|
|
||||||
padding: var(--Spacing-x1) var(--Spacing-x2);
|
|
||||||
transition: border-color 200ms ease;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Button as ButtonRAC } from "react-aria-components"
|
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
||||||
|
|
||||||
import styles from "./calendarButton.module.css"
|
|
||||||
|
|
||||||
interface CalendarButtonProps {
|
|
||||||
text: string
|
|
||||||
onClick: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CalendarButton({ text, onClick }: CalendarButtonProps) {
|
|
||||||
return (
|
|
||||||
<ButtonRAC onPress={onClick} className={styles.button}>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<span>{text}</span>
|
|
||||||
</Typography>
|
|
||||||
<MaterialIcon icon="calendar_today" />
|
|
||||||
</ButtonRAC>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,39 +1,33 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useState } from "react"
|
import {
|
||||||
import { createPortal } from "react-dom"
|
Button as ButtonRAC,
|
||||||
import { useFormContext } from "react-hook-form"
|
Dialog,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "react-aria-components"
|
||||||
|
import { useFormContext, useWatch } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
|
||||||
|
|
||||||
import DatePickerSingleDesktop from "@/components/DatePicker/Single/Desktop"
|
import DatePickerSingleDesktop from "@/components/DatePicker/Single/Desktop"
|
||||||
import DatePickerSingleMobile from "@/components/DatePicker/Single/Mobile"
|
import DatePickerSingleMobile from "@/components/DatePicker/Single/Mobile"
|
||||||
import Modal from "@/components/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import CalendarButton from "./CalendarButton"
|
|
||||||
|
|
||||||
import styles from "./newDates.module.css"
|
import styles from "./newDates.module.css"
|
||||||
|
|
||||||
import type { DateRange } from "react-day-picker"
|
interface NewDatesProps {
|
||||||
|
checkInDate: Date
|
||||||
export default function NewDates() {
|
checkOutDate: Date
|
||||||
const { checkInDate, checkOutDate } = useMyStayStore((state) => ({
|
}
|
||||||
checkInDate: state.mainRoom.checkInDate,
|
|
||||||
checkOutDate: state.mainRoom.checkOutDate,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const [showCheckInDatePicker, setShowCheckInDatePicker] = useState(false)
|
|
||||||
const [showCheckOutDatePicker, setShowCheckOutDatePicker] = useState(false)
|
|
||||||
const [selectedDates, setSelectedDates] = useState<DateRange>(() => ({
|
|
||||||
from: dt(checkInDate).startOf("day").toDate(),
|
|
||||||
to: dt(checkOutDate).startOf("day").toDate(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
|
export default function NewDates({ checkInDate, checkOutDate }: NewDatesProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
const { setValue } = useFormContext()
|
const { setValue } = useFormContext()
|
||||||
|
|
||||||
// Calculate default number of days between check-in and check-out
|
// Calculate default number of days between check-in and check-out
|
||||||
@@ -41,147 +35,111 @@ export default function NewDates() {
|
|||||||
.startOf("day")
|
.startOf("day")
|
||||||
.diff(dt(checkInDate).startOf("day"), "days")
|
.diff(dt(checkInDate).startOf("day"), "days")
|
||||||
|
|
||||||
function showCheckInPicker() {
|
const fromDate = useWatch({ name: "checkInDate" })
|
||||||
// Update selected dates before showing picker
|
const toDate = useWatch({ name: "checkOutDate" })
|
||||||
setSelectedDates((prev) => ({
|
|
||||||
from: prev.from ?? dt(checkInDate).startOf("day").toDate(),
|
function handleSelectDate(date: Date, name: "checkInDate" | "checkOutDate") {
|
||||||
to: prev.to ?? dt(checkOutDate).startOf("day").toDate(),
|
setValue(name, dt(date).format("YYYY-MM-DD"))
|
||||||
}))
|
|
||||||
setShowCheckInDatePicker(true)
|
|
||||||
setShowCheckOutDatePicker(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCheckOutPicker() {
|
function handleSelectCheckInDate(checkIn: Date) {
|
||||||
// Update selected dates before showing picker
|
handleSelectDate(checkIn, "checkInDate")
|
||||||
setSelectedDates((prev) => ({
|
if (dt(checkIn).isSameOrAfter(toDate)) {
|
||||||
from: prev.from ?? dt(checkInDate).startOf("day").toDate(),
|
handleSelectDate(
|
||||||
to: prev.to ?? dt(checkOutDate).startOf("day").toDate(),
|
dt(checkIn).add(defaultDaysBetween, "days").toDate(),
|
||||||
}))
|
"checkOutDate"
|
||||||
setShowCheckOutDatePicker(true)
|
)
|
||||||
setShowCheckInDatePicker(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCheckInDateSelect(date: Date) {
|
|
||||||
const newCheckIn = dt(date).startOf("day")
|
|
||||||
const currentCheckOut = dt(selectedDates.to).startOf("day")
|
|
||||||
|
|
||||||
// Calculate new check-out date based on defaultDaysBetween, only if new check-in is after current check-out
|
|
||||||
const newCheckOut = newCheckIn.isSameOrAfter(currentCheckOut)
|
|
||||||
? newCheckIn.add(defaultDaysBetween, "days")
|
|
||||||
: currentCheckOut
|
|
||||||
|
|
||||||
// Update selected dates state first
|
|
||||||
const newDates = {
|
|
||||||
from: newCheckIn.toDate(),
|
|
||||||
to: newCheckOut.toDate(),
|
|
||||||
}
|
}
|
||||||
setSelectedDates(newDates)
|
|
||||||
|
|
||||||
// Then update form values
|
|
||||||
setValue("checkInDate", newCheckIn.format("YYYY-MM-DD"))
|
|
||||||
setValue("checkOutDate", newCheckOut.format("YYYY-MM-DD"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCheckOutDateSelect(date: Date) {
|
function handleSelectCheckOutDate(checkOut: Date) {
|
||||||
const newCheckOut = dt(date).startOf("day")
|
handleSelectDate(checkOut, "checkOutDate")
|
||||||
const currentCheckIn = dt(selectedDates.from).startOf("day")
|
if (dt(checkOut).isSameOrBefore(fromDate)) {
|
||||||
|
handleSelectDate(
|
||||||
// Only adjust check-in if new check-out is before current check-in
|
dt(checkOut).subtract(defaultDaysBetween, "days").toDate(),
|
||||||
const newCheckIn = newCheckOut.isBefore(currentCheckIn)
|
"checkInDate"
|
||||||
? newCheckOut.subtract(defaultDaysBetween, "days")
|
)
|
||||||
: currentCheckIn
|
|
||||||
|
|
||||||
// Update selected dates state
|
|
||||||
const newDates = {
|
|
||||||
from: newCheckIn.toDate(),
|
|
||||||
to: newCheckOut.toDate(),
|
|
||||||
}
|
}
|
||||||
setSelectedDates(newDates)
|
|
||||||
|
|
||||||
// Then update form values
|
|
||||||
setValue("checkInDate", newCheckIn.format("YYYY-MM-DD"))
|
|
||||||
setValue("checkOutDate", newCheckOut.format("YYYY-MM-DD"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromDate = selectedDates.from ?? dt(checkInDate).toDate()
|
const checkInLabel = intl.formatMessage({ defaultMessage: "Check-in" })
|
||||||
const toDate = selectedDates.to ?? dt(checkOutDate).toDate()
|
const checkOutLabel = intl.formatMessage({ defaultMessage: "Check-out" })
|
||||||
|
|
||||||
|
const checkInText = dt(fromDate).locale(lang).format("dddd, DD MMM, YYYY")
|
||||||
|
const checkOutText = dt(toDate).locale(lang).format("dddd, DD MMM, YYYY")
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.checkInDate}>
|
<div className={styles.checkInDate}>
|
||||||
<Caption color="uiTextHighContrast" type="bold">
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
{intl.formatMessage({
|
<span className={styles.textDefault}>{checkInLabel}</span>
|
||||||
defaultMessage: "Check-in",
|
</Typography>
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
|
|
||||||
<CalendarButton
|
<DialogTrigger>
|
||||||
text={dt(selectedDates.from ?? new Date())
|
<ButtonRAC className={styles.trigger}>
|
||||||
.locale(lang)
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
.format("dddd, DD MMM, YYYY")}
|
<span>{checkInText}</span>
|
||||||
onClick={showCheckInPicker}
|
</Typography>
|
||||||
/>
|
<MaterialIcon icon="calendar_today" />
|
||||||
|
</ButtonRAC>
|
||||||
|
<Modal>
|
||||||
|
<Dialog>
|
||||||
|
{({ close }) => (
|
||||||
|
<>
|
||||||
|
<DatePickerSingleDesktop
|
||||||
|
close={close}
|
||||||
|
handleOnSelect={handleSelectCheckInDate}
|
||||||
|
selectedDate={fromDate}
|
||||||
|
startMonth={fromDate}
|
||||||
|
/>
|
||||||
|
<DatePickerSingleMobile
|
||||||
|
close={close}
|
||||||
|
handleOnSelect={handleSelectCheckInDate}
|
||||||
|
hideHeader
|
||||||
|
selectedDate={fromDate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
</Modal>
|
||||||
|
</DialogTrigger>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.checkOutDate}>
|
|
||||||
<Caption color="uiTextHighContrast" type="bold">
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Check-out",
|
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
|
|
||||||
<CalendarButton
|
<div className={styles.checkOutDate}>
|
||||||
text={dt(selectedDates.to ?? new Date())
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
.locale(lang)
|
<span className={styles.textDefault}>{checkOutLabel}</span>
|
||||||
.format("dddd, DD MMM, YYYY")}
|
</Typography>
|
||||||
onClick={showCheckOutPicker}
|
|
||||||
/>
|
<DialogTrigger>
|
||||||
|
<ButtonRAC className={styles.trigger}>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<span>{checkOutText}</span>
|
||||||
|
</Typography>
|
||||||
|
<MaterialIcon icon="calendar_today" />
|
||||||
|
</ButtonRAC>
|
||||||
|
<Modal>
|
||||||
|
<Dialog>
|
||||||
|
{({ close }) => (
|
||||||
|
<>
|
||||||
|
<DatePickerSingleDesktop
|
||||||
|
close={close}
|
||||||
|
handleOnSelect={handleSelectCheckOutDate}
|
||||||
|
selectedDate={toDate}
|
||||||
|
startMonth={toDate}
|
||||||
|
/>
|
||||||
|
<DatePickerSingleMobile
|
||||||
|
close={close}
|
||||||
|
handleOnSelect={handleSelectCheckOutDate}
|
||||||
|
hideHeader
|
||||||
|
selectedDate={toDate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
</Modal>
|
||||||
|
</DialogTrigger>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showCheckInDatePicker &&
|
|
||||||
createPortal(
|
|
||||||
<Modal
|
|
||||||
isOpen={showCheckInDatePicker}
|
|
||||||
onToggle={() => setShowCheckInDatePicker(!showCheckInDatePicker)}
|
|
||||||
>
|
|
||||||
<DatePickerSingleDesktop
|
|
||||||
close={() => setShowCheckInDatePicker(false)}
|
|
||||||
handleOnSelect={handleCheckInDateSelect}
|
|
||||||
selectedDate={fromDate}
|
|
||||||
startMonth={fromDate}
|
|
||||||
/>
|
|
||||||
<DatePickerSingleMobile
|
|
||||||
close={() => setShowCheckInDatePicker(false)}
|
|
||||||
handleOnSelect={handleCheckInDateSelect}
|
|
||||||
selectedDate={fromDate}
|
|
||||||
hideHeader
|
|
||||||
/>
|
|
||||||
</Modal>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showCheckOutDatePicker &&
|
|
||||||
createPortal(
|
|
||||||
<Modal
|
|
||||||
isOpen={showCheckOutDatePicker}
|
|
||||||
onToggle={() => setShowCheckOutDatePicker(!showCheckOutDatePicker)}
|
|
||||||
>
|
|
||||||
<DatePickerSingleDesktop
|
|
||||||
close={() => setShowCheckOutDatePicker(false)}
|
|
||||||
handleOnSelect={handleCheckOutDateSelect}
|
|
||||||
selectedDate={toDate}
|
|
||||||
startMonth={toDate}
|
|
||||||
/>
|
|
||||||
<DatePickerSingleMobile
|
|
||||||
close={() => setShowCheckOutDatePicker(false)}
|
|
||||||
handleOnSelect={handleCheckOutDateSelect}
|
|
||||||
selectedDate={toDate}
|
|
||||||
hideHeader
|
|
||||||
/>
|
|
||||||
</Modal>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,3 +13,23 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--Main-Grey-White);
|
||||||
|
border-color: var(--Scandic-Beige-40);
|
||||||
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
display: flex;
|
||||||
|
height: 60px;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-width: 0;
|
||||||
|
/* allow shrinkage */
|
||||||
|
padding: var(--Spacing-x1) var(--Spacing-x2);
|
||||||
|
transition: border-color 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textDefault {
|
||||||
|
color: var(--Text-Default);
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useIntl } from "react-intl"
|
|||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
|
|
||||||
import NoAvailability from "./Alerts/NoAvailability"
|
import NoAvailability from "./Alerts/NoAvailability"
|
||||||
@@ -62,7 +62,7 @@ export default function Form({
|
|||||||
/>
|
/>
|
||||||
<Modal.Content.Body>
|
<Modal.Content.Body>
|
||||||
{noAvailability && <NoAvailability />}
|
{noAvailability && <NoAvailability />}
|
||||||
<NewDates />
|
<NewDates checkInDate={checkInDate} checkOutDate={checkOutDate} />
|
||||||
</Modal.Content.Body>
|
</Modal.Content.Body>
|
||||||
<Modal.Content.Footer>
|
<Modal.Content.Footer>
|
||||||
<Modal.Content.Footer.Secondary onClick={closeModal}>
|
<Modal.Content.Footer.Secondary onClick={closeModal}>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
|
|
||||||
import { dateHasPassed } from "../utils"
|
import { dateHasPassed } from "../utils"
|
||||||
import Alerts from "./Alerts"
|
import Alerts from "./Alerts"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { DialogTrigger } from "react-aria-components"
|
import { DialogTrigger } from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
import CustomerSupportModal from "@/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal"
|
import CustomerSupportModal from "@/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal"
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
|
||||||
|
|
||||||
export default function CustomerSupport() {
|
export default function CustomerSupport() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|||||||
@@ -34,15 +34,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.guaranteeCostText {
|
.guaranteeCostText {
|
||||||
align-items: flex-end;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.baseTextHighContrast {
|
.baseTextHighContrast {
|
||||||
color: var(--Base-Text-High-contrast);
|
color: var(--Base-Text-High-contrast);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textDefault {
|
.textDefault {
|
||||||
color: var(--Text-Default);
|
color: var(--Text-Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.guaranteeCostText {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.dialog {
|
||||||
|
max-width: 690px;
|
||||||
|
}
|
||||||
@@ -6,11 +6,13 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
|
|
||||||
import { dateHasPassed } from "../utils"
|
import { dateHasPassed } from "../utils"
|
||||||
import Form from "./Form"
|
import Form from "./Form"
|
||||||
|
|
||||||
|
import styles from "./guarantee.module.css"
|
||||||
|
|
||||||
export default function GuaranteeLateArrival() {
|
export default function GuaranteeLateArrival() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ export default function GuaranteeLateArrival() {
|
|||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
<Modal.Button icon="check">{text}</Modal.Button>
|
<Modal.Button icon="check">{text}</Modal.Button>
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog>
|
<Dialog className={styles.dialog}>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<Modal.Content.Header handleClose={close} title={text}>
|
<Modal.Content.Header handleClose={close} title={text}>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
background-color: var(--Surface-Primary-OnSurface-Default);
|
background-color: var(--Surface-Primary-OnSurface-Default);
|
||||||
border-radius: var(--Corner-radius-md);
|
border-radius: var(--Corner-radius-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--Space-x2);
|
gap: var(--Space-x2);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: var(--Space-x15) var(--Space-x3);
|
padding: var(--Space-x15) var(--Space-x3);
|
||||||
@@ -27,3 +26,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
|
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||||
|
|
||||||
import Actions from "./Actions"
|
import Actions from "./Actions"
|
||||||
import Info from "./Info"
|
import Info from "./Info"
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Space-x3);
|
flex: 1;
|
||||||
|
gap: var(--Space-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@@ -47,6 +48,16 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Space-x3);
|
gap: var(--Space-x2);
|
||||||
grid-template-columns: 1fr 1fr;
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.dialog {
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
border-radius: var(--Corner-radius-rounded);
|
border-radius: var(--Corner-radius-rounded);
|
||||||
color: var(--Text-Interactive-Default);
|
color: var(--Text-Interactive-Default);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 48px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export default function PriceContainer({
|
|||||||
{totalChildren > 0 ? `, ${childrenText}` : ""}
|
{totalChildren > 0 ? `, ${childrenText}` : ""}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.price}>
|
<div className={styles.wrapper}>
|
||||||
<Subtitle color="burgundy" type="one">
|
<Subtitle className={styles.price} color="burgundy" type="one">
|
||||||
{price}
|
{price}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,8 +15,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.price {
|
.wrapper {
|
||||||
padding-left: var(--Spacing-x2);
|
padding-left: var(--Spacing-x2);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
|
||||||
|
import Image from "@/components/Image"
|
||||||
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
|
import IconChip from "@/components/TempDesignSystem/IconChip"
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import PriceType from "../../PriceType"
|
||||||
|
import { hasModifiableRate } from "../../utils"
|
||||||
|
import RoomDetailsSidePeek from "./RoomDetailsSidePeek"
|
||||||
|
|
||||||
|
import styles from "./room.module.css"
|
||||||
|
|
||||||
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
|
import type { Room } from "@/types/stores/my-stay"
|
||||||
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
|
interface RoomProps {
|
||||||
|
booking: Room
|
||||||
|
roomNr: number
|
||||||
|
user: SafeUser
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Room({ booking, roomNr, user }: RoomProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const lang = useLang()
|
||||||
|
|
||||||
|
const {
|
||||||
|
adults,
|
||||||
|
breakfast,
|
||||||
|
cancellationNumber,
|
||||||
|
checkInDate,
|
||||||
|
cheques,
|
||||||
|
childrenAges,
|
||||||
|
confirmationNumber,
|
||||||
|
currencyCode,
|
||||||
|
packages,
|
||||||
|
rateDefinition,
|
||||||
|
room,
|
||||||
|
roomName,
|
||||||
|
roomPoints,
|
||||||
|
isCancelled,
|
||||||
|
priceType,
|
||||||
|
vouchers,
|
||||||
|
totalPrice,
|
||||||
|
} = booking
|
||||||
|
|
||||||
|
const fromDate = dt(checkInDate).locale(lang)
|
||||||
|
|
||||||
|
const adultsMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: adults,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const childrenMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "{children, plural, one {# child} other {# children}}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: childrenAges.length,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const adultsOnlyMsg = adultsMsg
|
||||||
|
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
||||||
|
|
||||||
|
const formattedTotalPrice = formatPrice(intl, totalPrice, currencyCode)
|
||||||
|
|
||||||
|
let breakfastPrice = intl.formatMessage({
|
||||||
|
defaultMessage: "No breakfast",
|
||||||
|
})
|
||||||
|
if (rateDefinition.breakfastIncluded) {
|
||||||
|
breakfastPrice = intl.formatMessage({
|
||||||
|
defaultMessage: "Included",
|
||||||
|
})
|
||||||
|
} else if (breakfast) {
|
||||||
|
breakfastPrice = formatPrice(
|
||||||
|
intl,
|
||||||
|
breakfast.localPrice.totalPrice,
|
||||||
|
breakfast.localPrice.currency
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className={styles.multiRoom}>
|
||||||
|
<Typography variant="Title/smRegular">
|
||||||
|
<h3 className={styles.roomName}>{roomName}</h3>
|
||||||
|
</Typography>
|
||||||
|
<div className={styles.roomHeader}>
|
||||||
|
{isCancelled ? (
|
||||||
|
<IconChip
|
||||||
|
color={"red"}
|
||||||
|
icon={
|
||||||
|
<MaterialIcon
|
||||||
|
icon="cancel"
|
||||||
|
size={20}
|
||||||
|
color="Icon/Feedback/Error"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Cancelled",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</IconChip>
|
||||||
|
) : (
|
||||||
|
<div className={styles.chip}>
|
||||||
|
<Typography variant="Tag/sm">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Room {roomIndex}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roomIndex: roomNr,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.reference}>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
|
{isCancelled ? (
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Cancellation no",
|
||||||
|
})}
|
||||||
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
|
{":"}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Booking number",
|
||||||
|
})}
|
||||||
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
|
{":"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
{isCancelled ? (
|
||||||
|
<span className={styles.cancellationNumber}>
|
||||||
|
{cancellationNumber}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>{confirmationNumber}</span>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={styles.toggleSidePeek}>
|
||||||
|
<RoomDetailsSidePeek booking={booking} user={user} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.multiRoomCard} ${isCancelled ? styles.cancelled : ""}`}
|
||||||
|
>
|
||||||
|
{packages?.some((item) =>
|
||||||
|
Object.values(RoomPackageCodeEnum).includes(
|
||||||
|
item.code as RoomPackageCodeEnum
|
||||||
|
)
|
||||||
|
) && (
|
||||||
|
<div className={styles.packages}>
|
||||||
|
{packages
|
||||||
|
.filter((item) =>
|
||||||
|
Object.values(RoomPackageCodeEnum).includes(
|
||||||
|
item.code as RoomPackageCodeEnum
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((item) => {
|
||||||
|
return (
|
||||||
|
<span className={styles.package} key={item.code}>
|
||||||
|
<IconForFeatureCode
|
||||||
|
featureCode={item.code}
|
||||||
|
size={16}
|
||||||
|
color="Icon/Interactive/Default"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.imageContainer}>
|
||||||
|
<Image
|
||||||
|
src={room?.images[0]?.imageSizes.small ?? ""}
|
||||||
|
alt={roomName}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.details}>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Guests",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p>
|
||||||
|
{childrenAges.length > 0 ? adultsAndChildrenMsg : adultsOnlyMsg}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
{rateDefinition.cancellationText ? (
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Terms",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p>{rateDefinition.cancellationText}</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{hasModifiableRate(rateDefinition.cancellationRule) && (
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Modify By",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
|
<p color="uiTextHighContrast">
|
||||||
|
18:00, {fromDate.format("dddd D MMM")}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{breakfastPrice !== null && (
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Breakfast",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p color="uiTextHighContrast">{breakfastPrice}</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Divider color="subtle" />
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Typography variant="Body/Lead text">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Room total",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
<PriceType
|
||||||
|
cheques={cheques}
|
||||||
|
formattedTotalPrice={formattedTotalPrice}
|
||||||
|
isCancelled={isCancelled}
|
||||||
|
priceType={priceType}
|
||||||
|
rateDefinition={rateDefinition}
|
||||||
|
roomPoints={roomPoints}
|
||||||
|
totalPrice={totalPrice}
|
||||||
|
vouchers={vouchers}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
|
||||||
|
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
|
||||||
|
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
||||||
|
|
||||||
|
import styles from "./sidePeek.module.css"
|
||||||
|
|
||||||
|
import type { Room as MyStayRoom } from "@/types/stores/my-stay"
|
||||||
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
|
interface RoomDetailsSidePeekProps {
|
||||||
|
booking: MyStayRoom
|
||||||
|
user: SafeUser
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RoomDetailsSidePeek({
|
||||||
|
booking,
|
||||||
|
user,
|
||||||
|
}: RoomDetailsSidePeekProps) {
|
||||||
|
return (
|
||||||
|
<DialogTrigger>
|
||||||
|
<ButtonRAC className={styles.trigger}>
|
||||||
|
<MaterialIcon icon="pan_zoom" color="CurrentColor" />
|
||||||
|
</ButtonRAC>
|
||||||
|
<BookedRoomSidePeek hotelRoom={booking.room} room={booking} user={user} />
|
||||||
|
</DialogTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,317 +1,70 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
import { getBookedHotelRoom } from "@/server/routers/booking/utils"
|
|
||||||
|
|
||||||
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
|
import PriceDetails from "../../PriceDetails"
|
||||||
import Image from "@/components/Image"
|
import TotalPrice from "../TotalPrice"
|
||||||
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
import Room from "./Room"
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
|
||||||
import IconChip from "@/components/TempDesignSystem/IconChip"
|
|
||||||
import useLang from "@/hooks/useLang"
|
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
|
||||||
|
|
||||||
import PriceType from "../../PriceType"
|
|
||||||
import { hasModifiableRate } from "../../utils"
|
|
||||||
|
|
||||||
import styles from "./multiRoom.module.css"
|
import styles from "./multiRoom.module.css"
|
||||||
|
|
||||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
|
||||||
import type { RoomCategories } from "@/types/hotel"
|
|
||||||
import type { Room } from "@/types/stores/my-stay"
|
|
||||||
import type { SafeUser } from "@/types/user"
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
interface MultiRoomProps {
|
interface MultiRoomProps {
|
||||||
booking: Room
|
|
||||||
roomNr: number
|
|
||||||
user: SafeUser
|
user: SafeUser
|
||||||
roomCategories: RoomCategories
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MultiRoom({
|
export default function MultiRoom(props: MultiRoomProps) {
|
||||||
booking,
|
|
||||||
roomNr,
|
|
||||||
user,
|
|
||||||
roomCategories,
|
|
||||||
}: MultiRoomProps) {
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const { allRoomsAreCancelled, rooms } = useMyStayStore((state) => ({
|
||||||
|
allRoomsAreCancelled: state.allRoomsAreCancelled,
|
||||||
|
rooms: state.rooms,
|
||||||
|
}))
|
||||||
|
|
||||||
const {
|
if (rooms.length <= 1) {
|
||||||
adults,
|
return null
|
||||||
breakfast,
|
|
||||||
cancellationNumber,
|
|
||||||
checkInDate,
|
|
||||||
cheques,
|
|
||||||
childrenAges,
|
|
||||||
confirmationNumber,
|
|
||||||
currencyCode,
|
|
||||||
packages,
|
|
||||||
rateDefinition,
|
|
||||||
room,
|
|
||||||
roomName,
|
|
||||||
roomPoints,
|
|
||||||
isCancelled,
|
|
||||||
priceType,
|
|
||||||
roomTypeCode,
|
|
||||||
vouchers,
|
|
||||||
totalPrice,
|
|
||||||
} = booking
|
|
||||||
|
|
||||||
const fromDate = dt(checkInDate).locale(lang)
|
|
||||||
|
|
||||||
const adultsMsg = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
adults: adults,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const childrenMsg = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "{children, plural, one {# child} other {# children}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: childrenAges.length,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const adultsOnlyMsg = adultsMsg
|
|
||||||
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
|
||||||
|
|
||||||
const formattedTotalPrice = formatPrice(intl, totalPrice, currencyCode)
|
|
||||||
|
|
||||||
let breakfastPrice = intl.formatMessage({
|
|
||||||
defaultMessage: "No breakfast",
|
|
||||||
})
|
|
||||||
if (rateDefinition.breakfastIncluded) {
|
|
||||||
breakfastPrice = intl.formatMessage({
|
|
||||||
defaultMessage: "Included",
|
|
||||||
})
|
|
||||||
} else if (breakfast) {
|
|
||||||
breakfastPrice = formatPrice(
|
|
||||||
intl,
|
|
||||||
breakfast.localPrice.totalPrice,
|
|
||||||
breakfast.localPrice.currency
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const hotelRoom = getBookedHotelRoom(roomCategories, roomTypeCode)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={styles.multiRoom}>
|
<div className={styles.wrapper}>
|
||||||
<Typography variant="Title/smRegular">
|
<Typography variant="Title/sm">
|
||||||
<h3 className={styles.roomName}>{roomName}</h3>
|
<h2 className={styles.title}>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Your rooms",
|
||||||
|
})}
|
||||||
|
</h2>
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className={styles.roomHeader}>
|
<div className={styles.container}>
|
||||||
{isCancelled ? (
|
<div className={styles.roomsContainer}>
|
||||||
<IconChip
|
{rooms.map((booking, index) => (
|
||||||
color={"red"}
|
<div
|
||||||
icon={
|
key={booking.confirmationNumber}
|
||||||
<MaterialIcon
|
className={styles.roomWrapper}
|
||||||
icon="cancel"
|
|
||||||
size={20}
|
|
||||||
color="Icon/Feedback/Error"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Cancelled",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</Typography>
|
|
||||||
</IconChip>
|
|
||||||
) : (
|
|
||||||
<div className={styles.chip}>
|
|
||||||
<Typography variant="Tag/sm">
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Room {roomIndex}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
roomIndex: roomNr,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles.reference}>
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
{isCancelled ? (
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Cancellation no",
|
|
||||||
})}
|
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
{":"}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Booking number",
|
|
||||||
})}
|
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
{":"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
||||||
{isCancelled ? (
|
|
||||||
<span className={styles.cancellationNumber}>
|
|
||||||
{cancellationNumber}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>{confirmationNumber}</span>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
<div className={styles.toggleSidePeek}>
|
|
||||||
<DialogTrigger>
|
|
||||||
<ButtonRAC
|
|
||||||
aria-label={intl.formatMessage({
|
|
||||||
defaultMessage: "View room details",
|
|
||||||
})}
|
|
||||||
className={styles.iconContainer}
|
|
||||||
>
|
>
|
||||||
<MaterialIcon icon="pan_zoom" color="CurrentColor" />
|
<Room {...props} booking={booking} roomNr={index + 1} />
|
||||||
</ButtonRAC>
|
</div>
|
||||||
<BookedRoomSidePeek
|
))}
|
||||||
hotelRoom={hotelRoom}
|
|
||||||
room={booking}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
</DialogTrigger>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className={styles.totalContainer}>
|
||||||
className={`${styles.multiRoomCard} ${isCancelled ? styles.cancelled : ""}`}
|
<div className={styles.total}>
|
||||||
>
|
<Typography variant="Body/Lead text">
|
||||||
{packages?.some((item) =>
|
<p>
|
||||||
Object.values(RoomPackageCodeEnum).includes(
|
{intl.formatMessage({
|
||||||
item.code as RoomPackageCodeEnum
|
defaultMessage: "Booking total",
|
||||||
)
|
|
||||||
) && (
|
|
||||||
<div className={styles.packages}>
|
|
||||||
{packages
|
|
||||||
.filter((item) =>
|
|
||||||
Object.values(RoomPackageCodeEnum).includes(
|
|
||||||
item.code as RoomPackageCodeEnum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map((item) => {
|
|
||||||
return (
|
|
||||||
<span className={styles.package} key={item.code}>
|
|
||||||
<IconForFeatureCode
|
|
||||||
featureCode={item.code}
|
|
||||||
size={16}
|
|
||||||
color="Icon/Interactive/Default"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
})}
|
})}
|
||||||
</div>
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
)}
|
{":"}
|
||||||
<div className={styles.imageContainer}>
|
</p>
|
||||||
<Image
|
</Typography>
|
||||||
src={room?.images[0]?.imageSizes.small ?? ""}
|
<TotalPrice />
|
||||||
alt={roomName}
|
|
||||||
fill
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.details}>
|
|
||||||
<div className={styles.row}>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Guests",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p>
|
|
||||||
{childrenAges.length > 0 ? adultsAndChildrenMsg : adultsOnlyMsg}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
{rateDefinition.cancellationText ? (
|
|
||||||
<div className={styles.row}>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Terms",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p>{rateDefinition.cancellationText}</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{hasModifiableRate(rateDefinition.cancellationRule) && (
|
|
||||||
<div className={styles.row}>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Modify By",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
{allRoomsAreCancelled ? null : <PriceDetails />}
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
<p color="uiTextHighContrast">
|
|
||||||
18:00, {fromDate.format("dddd D MMM")}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{breakfastPrice !== null && (
|
|
||||||
<div className={styles.row}>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Breakfast",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p color="uiTextHighContrast">{breakfastPrice}</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Divider color="subtle" />
|
|
||||||
<div className={styles.row}>
|
|
||||||
<Typography variant="Body/Lead text">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Room total",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
<PriceType
|
|
||||||
cheques={cheques}
|
|
||||||
formattedTotalPrice={formattedTotalPrice}
|
|
||||||
isCancelled={isCancelled}
|
|
||||||
priceType={priceType}
|
|
||||||
rateDefinition={rateDefinition}
|
|
||||||
roomPoints={roomPoints}
|
|
||||||
totalPrice={totalPrice}
|
|
||||||
vouchers={vouchers}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,63 @@
|
|||||||
.multiRoom {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--Scandic-Brand-Burgundy);
|
||||||
padding: 0 var(--Spacing-x2);
|
padding: 0 var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancelled {
|
.container {
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancellationNumber {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiRoomCard {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x5);
|
||||||
background-color: var(--Base-Background-Primary-Normal);
|
|
||||||
border-radius: var(--Corner-radius-Large);
|
|
||||||
border: 1px solid var(--Base-Border-Subtle);
|
|
||||||
overflow: hidden;
|
|
||||||
padding-bottom: var(--Spacing-x3);
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.imageContainer {
|
.roomsContainer {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x3);
|
||||||
|
grid-template-columns: 1fr;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 342px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconContainer {
|
.roomWrapper {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomWrapper > * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totalContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
border: 1px solid var(--Base-Border-Subtle);
|
|
||||||
border-radius: var(--Corner-radius-Small);
|
|
||||||
padding: var(--Spacing-x-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomName {
|
|
||||||
color: var(--Scandic-Brand-Burgundy);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomHeader {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--Spacing-x-one-and-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chip {
|
|
||||||
background-color: var(--Scandic-Peach-30);
|
|
||||||
color: var(--Scandic-Red-100);
|
|
||||||
border-radius: var(--Corner-radius-Small);
|
|
||||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
|
||||||
height: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggleSidePeek {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
display: flex;
|
|
||||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) 0;
|
|
||||||
gap: var(--Spacing-x2);
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.packages {
|
|
||||||
position: absolute;
|
|
||||||
top: 304px;
|
|
||||||
left: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
z-index: 100;
|
padding: 0 var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.package {
|
.total {
|
||||||
background-color: var(--Main-Grey-White);
|
display: flex;
|
||||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
justify-content: flex-end;
|
||||||
border-radius: var(--Corner-radius-Small);
|
gap: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.multiRoom {
|
.roomsContainer {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomsContainer:has(> *:nth-child(3):last-child) {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totalContainer {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
.multiRoom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
padding: 0 var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancelled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancellationNumber {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiRoomCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
border: 1px solid var(--Base-Border-Subtle);
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: var(--Spacing-x3);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 342px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomName {
|
||||||
|
color: var(--Scandic-Brand-Burgundy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
background-color: var(--Scandic-Peach-30);
|
||||||
|
color: var(--Scandic-Red-100);
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleSidePeek {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reference {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) 0;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.packages {
|
||||||
|
position: absolute;
|
||||||
|
top: 304px;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package {
|
||||||
|
background-color: var(--Main-Grey-White);
|
||||||
|
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.multiRoom {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.trigger {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--Base-Border-Subtle);
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
padding: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import IconChip from "@/components/TempDesignSystem/IconChip"
|
||||||
|
|
||||||
|
export default function BookingCode() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const bookingCode = useMyStayStore((state) => state.bookedRoom.bookingCode)
|
||||||
|
|
||||||
|
if (!bookingCode) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
|
<IconChip
|
||||||
|
color="blue"
|
||||||
|
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "<strong>Booking code</strong>: {value}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: bookingCode,
|
||||||
|
strong: (text) => (
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
|
<strong>{text}</strong>
|
||||||
|
</Typography>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</IconChip>
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
.priceDetails {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
padding: var(--Spacing-x-one-and-half) 0;
|
||||||
|
width: calc(100% - var(--Spacing-x4));
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.priceDetails {
|
||||||
|
align-items: flex-end;
|
||||||
|
margin: 0 0 0 auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import PriceType from "@/components/HotelReservation/MyStay/PriceType"
|
||||||
|
|
||||||
|
import styles from "./details.module.css"
|
||||||
|
|
||||||
|
export default function PriceDetails() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const pricing = useMyStayStore((state) => ({
|
||||||
|
cheques: state.bookedRoom.cheques,
|
||||||
|
formattedTotalPrice: state.totalPrice,
|
||||||
|
isCancelled: state.bookedRoom.isCancelled,
|
||||||
|
priceType: state.bookedRoom.priceType,
|
||||||
|
rateDefinition: state.bookedRoom.rateDefinition,
|
||||||
|
roomPoints: state.bookedRoom.roomPoints,
|
||||||
|
totalPrice: state.bookedRoom.totalPrice,
|
||||||
|
vouchers: state.bookedRoom.vouchers,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.priceDetails}>
|
||||||
|
<div className={styles.price}>
|
||||||
|
<Typography variant="Body/Lead text">
|
||||||
|
<p color="uiTextHighContrast">
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Room total",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
<PriceType {...pricing} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import BookingCode from "./BookingCode"
|
||||||
|
import PriceDetails from "./PriceDetails"
|
||||||
|
|
||||||
|
import styles from "./information.module.css"
|
||||||
|
|
||||||
|
export default function BookingInformation() {
|
||||||
|
return (
|
||||||
|
<div className={styles.bookingInformation}>
|
||||||
|
<BookingCode />
|
||||||
|
<PriceDetails />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.bookingInformation {
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--Scandic-Beige-10);
|
||||||
|
border: 1px solid var(--Base-Border-Subtle);
|
||||||
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
margin: 0 var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.bookingInformation {
|
||||||
|
align-items: flex-start;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import Row from "./Row"
|
||||||
|
|
||||||
|
export default function BedPreference() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const bedType = useMyStayStore((state) => state.bookedRoom.room?.bedType)
|
||||||
|
|
||||||
|
if (!bedType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainBedWidthValueMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "{value} cm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: bedType.mainBed.widthRange.min,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const mainBedWidthRangeMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "{min}–{max} cm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: bedType.mainBed.widthRange.min,
|
||||||
|
max: bedType.mainBed.widthRange.max,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const sameWidth =
|
||||||
|
bedType.mainBed.widthRange.min === bedType.mainBed.widthRange.max
|
||||||
|
const widthMsg = sameWidth ? mainBedWidthValueMsg : mainBedWidthRangeMsg
|
||||||
|
const text = `${bedType.mainBed.description} (${widthMsg})`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
icon="bed"
|
||||||
|
text={text}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Bed preference",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import Row from "./Row"
|
||||||
|
|
||||||
|
export default function Breakfast() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const { breakfast, rateDefinition } = useMyStayStore((state) => ({
|
||||||
|
breakfast: state.bookedRoom.breakfast,
|
||||||
|
rateDefinition: state.bookedRoom.rateDefinition,
|
||||||
|
}))
|
||||||
|
|
||||||
|
let breakfastPrice = intl.formatMessage({
|
||||||
|
defaultMessage: "No breakfast",
|
||||||
|
})
|
||||||
|
if (rateDefinition.breakfastIncluded) {
|
||||||
|
breakfastPrice = intl.formatMessage({
|
||||||
|
defaultMessage: "Included",
|
||||||
|
})
|
||||||
|
} else if (breakfast) {
|
||||||
|
breakfastPrice = formatPrice(
|
||||||
|
intl,
|
||||||
|
breakfast.localPrice.totalPrice,
|
||||||
|
breakfast.localPrice.currency
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = intl.formatMessage({ defaultMessage: "Breakfast" })
|
||||||
|
return <Row icon="coffee" text={breakfastPrice} title={title} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import Row from "./Row"
|
||||||
|
|
||||||
|
export default function Guests() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const { adults, childrenAges } = useMyStayStore((state) => ({
|
||||||
|
adults: state.bookedRoom.adults,
|
||||||
|
childrenAges: state.bookedRoom.childrenAges,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const adultsMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const childrenMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "{children, plural, one {# child} other {# children}}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: childrenAges.length,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const adultsOnlyMsg = adultsMsg
|
||||||
|
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
icon="person"
|
||||||
|
text={childrenAges.length > 0 ? adultsAndChildrenMsg : adultsOnlyMsg}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Guests",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import { hasModifiableRate } from "@/components/HotelReservation/MyStay/utils"
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import Row from "./Row"
|
||||||
|
|
||||||
|
export default function ModifyBy() {
|
||||||
|
const intl = useIntl()
|
||||||
|
const lang = useLang()
|
||||||
|
|
||||||
|
const { checkInDate, isModifyable } = useMyStayStore((state) => ({
|
||||||
|
checkInDate: state.bookedRoom.checkInDate,
|
||||||
|
isModifyable: hasModifiableRate(
|
||||||
|
state.bookedRoom.rateDefinition.cancellationRule
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (!isModifyable) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromDate = dt(checkInDate).locale(lang)
|
||||||
|
|
||||||
|
const text = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Until {time}, {date}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
time: "18:00",
|
||||||
|
date: fromDate.format("dddd D MMM"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
icon="refresh"
|
||||||
|
text={text}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Modify By",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import Row from "./Row"
|
||||||
|
|
||||||
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
|
|
||||||
|
export default function Packages() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const packages = useMyStayStore(
|
||||||
|
(state) =>
|
||||||
|
state.bookedRoom.packages
|
||||||
|
?.filter((item) =>
|
||||||
|
Object.values(RoomPackageCodeEnum).includes(
|
||||||
|
item.code as RoomPackageCodeEnum
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((item) => item.description) || []
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!packages.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
icon="meeting_room"
|
||||||
|
text={packages.join(", ")}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Room classification",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"use client"
|
||||||
|
import {
|
||||||
|
MaterialIcon,
|
||||||
|
type MaterialIconProps,
|
||||||
|
} from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import styles from "./row.module.css"
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
icon: MaterialIconProps["icon"]
|
||||||
|
text: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Row({ icon, text, title }: RowProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles.row}>
|
||||||
|
<span className={styles.title}>
|
||||||
|
<MaterialIcon icon={icon} color="Icon/Default" size={20} />
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<p>{title}</p>
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p color="uiTextHighContrast">{text}</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: var(--Spacing-x-one-and-half) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title svg {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding-left: var(--Spacing-x4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.row {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title svg {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import Row from "./Row"
|
||||||
|
|
||||||
|
export default function Terms() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const cancellationText = useMyStayStore(
|
||||||
|
(state) => state.bookedRoom.rateDefinition.cancellationText
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!cancellationText) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
icon="contract"
|
||||||
|
text={cancellationText}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Terms",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.details {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0 var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.details {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import BedPreference from "./BedPreference"
|
||||||
|
import Breakfast from "./Breakfast"
|
||||||
|
import Guests from "./Guests"
|
||||||
|
import ModifyBy from "./ModifyBy"
|
||||||
|
import Packages from "./Packages"
|
||||||
|
import Terms from "./Terms"
|
||||||
|
|
||||||
|
import styles from "./details.module.css"
|
||||||
|
|
||||||
|
export default function Details() {
|
||||||
|
return (
|
||||||
|
<div className={styles.details}>
|
||||||
|
<Guests />
|
||||||
|
<Terms />
|
||||||
|
<ModifyBy />
|
||||||
|
<Breakfast />
|
||||||
|
<Packages />
|
||||||
|
<BedPreference />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
padding: 0 var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
background-color: var(--Scandic-Peach-30);
|
||||||
|
color: var(--Scandic-Red-100);
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reference {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidePeek {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.header {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidePeek {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import RoomDetailsSidePeek from "../RoomDetailsSidePeek"
|
||||||
|
|
||||||
|
import styles from "./header.module.css"
|
||||||
|
|
||||||
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
|
export default function Header({ user }: { user: SafeUser }) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const { confirmationNumber, roomNumber } = useMyStayStore((state) => ({
|
||||||
|
confirmationNumber: state.bookedRoom.confirmationNumber,
|
||||||
|
roomNumber: state.bookedRoom.roomNumber,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.chip}>
|
||||||
|
<Typography variant="Tag/sm">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Room {roomIndex}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roomIndex: roomNumber,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={styles.reference}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Booking number",
|
||||||
|
})}
|
||||||
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
|
{":"}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<span>{confirmationNumber}</span>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.sidePeek}>
|
||||||
|
<RoomDetailsSidePeek user={user} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.imageContainer {
|
||||||
|
height: 220px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
height: 220px;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.imageContainer {
|
||||||
|
height: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import Image from "@/components/Image"
|
||||||
|
|
||||||
|
import styles from "./img.module.css"
|
||||||
|
|
||||||
|
export default function Img() {
|
||||||
|
const { room, roomName } = useMyStayStore((state) => ({
|
||||||
|
room: state.bookedRoom.room,
|
||||||
|
roomName: state.bookedRoom.roomName,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = room.images?.[0]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.imageContainer}>
|
||||||
|
<Image
|
||||||
|
alt={roomName}
|
||||||
|
className={styles.image}
|
||||||
|
height={960}
|
||||||
|
src={image.imageSizes.small}
|
||||||
|
width={640}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
|
||||||
|
|
||||||
|
import styles from "./packages.module.css"
|
||||||
|
|
||||||
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
|
|
||||||
|
export default function Packages() {
|
||||||
|
const packages = useMyStayStore(
|
||||||
|
(state) =>
|
||||||
|
state.bookedRoom.packages?.filter((item) =>
|
||||||
|
Object.values(RoomPackageCodeEnum).includes(
|
||||||
|
item.code as RoomPackageCodeEnum
|
||||||
|
)
|
||||||
|
) || []
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!packages.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.packages}>
|
||||||
|
{packages.map((item) => (
|
||||||
|
<span className={styles.package} key={item.code}>
|
||||||
|
<IconForFeatureCode
|
||||||
|
featureCode={item.code}
|
||||||
|
size={16}
|
||||||
|
color="Icon/Interactive/Default"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.packages {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
left: 15px;
|
||||||
|
position: absolute;
|
||||||
|
top: 180px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package {
|
||||||
|
background-color: var(--Main-Grey-White);
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.packages {
|
||||||
|
left: 25px;
|
||||||
|
top: 620px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { DialogTrigger } from "react-aria-components"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
|
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
|
||||||
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
|
interface RoomDetailsSidePeekProps {
|
||||||
|
user: SafeUser
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RoomDetailsSidePeek({
|
||||||
|
user,
|
||||||
|
}: RoomDetailsSidePeekProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const bookedRoom = useMyStayStore((state) => state.bookedRoom)
|
||||||
|
return (
|
||||||
|
<DialogTrigger>
|
||||||
|
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
|
||||||
|
{intl.formatMessage({ defaultMessage: "See room details" })}
|
||||||
|
<MaterialIcon icon="chevron_right" size={14} color="CurrentColor" />
|
||||||
|
</Button>
|
||||||
|
<BookedRoomSidePeek
|
||||||
|
hotelRoom={bookedRoom.room}
|
||||||
|
room={bookedRoom}
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
|
</DialogTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
||||||
|
|
||||||
import useSidePeekStore from "@/stores/sidepeek"
|
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
|
||||||
|
|
||||||
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
|
|
||||||
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
|
|
||||||
|
|
||||||
export default function ToggleSidePeek({
|
|
||||||
hotelId,
|
|
||||||
roomTypeCode,
|
|
||||||
intent = "textInverted",
|
|
||||||
title,
|
|
||||||
}: ToggleSidePeekProps) {
|
|
||||||
const intl = useIntl()
|
|
||||||
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
openSidePeek({
|
|
||||||
key: SidePeekEnum.roomDetails,
|
|
||||||
hotelId,
|
|
||||||
roomTypeCode,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
theme="base"
|
|
||||||
size="small"
|
|
||||||
variant="icon"
|
|
||||||
intent={intent}
|
|
||||||
wrapping
|
|
||||||
>
|
|
||||||
{title
|
|
||||||
? title
|
|
||||||
: intl.formatMessage({
|
|
||||||
defaultMessage: "See room details",
|
|
||||||
})}
|
|
||||||
<MaterialIcon icon="chevron_right" size={14} color="CurrentColor" />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,94 +1,48 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
|
||||||
import { getBookedHotelRoom } from "@/server/routers/booking/utils"
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import GuestDetails from "@/components/HotelReservation/MyStay/GuestDetails"
|
import GuestDetails from "@/components/HotelReservation/MyStay/GuestDetails"
|
||||||
import PriceDetails from "@/components/HotelReservation/MyStay/PriceDetails"
|
import PriceDetails from "@/components/HotelReservation/MyStay/PriceDetails"
|
||||||
import PriceType from "@/components/HotelReservation/MyStay/PriceType"
|
|
||||||
import { hasModifiableRate } from "@/components/HotelReservation/MyStay/utils"
|
|
||||||
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
|
|
||||||
import Image from "@/components/Image"
|
|
||||||
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
|
||||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||||
import IconChip from "@/components/TempDesignSystem/IconChip"
|
|
||||||
import useLang from "@/hooks/useLang"
|
import BookingInformation from "./BookingInformation"
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
import Details from "./Details"
|
||||||
|
import Header from "./Header"
|
||||||
|
import Img from "./Img"
|
||||||
|
import Packages from "./Packages"
|
||||||
|
|
||||||
import styles from "./room.module.css"
|
import styles from "./room.module.css"
|
||||||
|
|
||||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
|
||||||
import type { Room, RoomCategories } from "@/types/hotel"
|
|
||||||
import type { SafeUser } from "@/types/user"
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
interface RoomProps {
|
interface RoomProps {
|
||||||
bedType: Room["roomTypes"][number]
|
|
||||||
image: Room["images"][number]
|
|
||||||
user: SafeUser
|
user: SafeUser
|
||||||
roomCategories: RoomCategories
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SingleRoom({
|
export default function SingleRoom({ user }: RoomProps) {
|
||||||
bedType,
|
|
||||||
image,
|
|
||||||
user,
|
|
||||||
roomCategories,
|
|
||||||
}: RoomProps) {
|
|
||||||
const intl = useIntl()
|
|
||||||
const lang = useLang()
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
adults,
|
|
||||||
bookingCode,
|
|
||||||
breakfast,
|
|
||||||
checkInDate,
|
|
||||||
cheques,
|
|
||||||
childrenAges,
|
|
||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
formattedTotalPrice,
|
|
||||||
guest,
|
guest,
|
||||||
isCancelled,
|
isCancelled,
|
||||||
packages,
|
isMultiRoom,
|
||||||
priceType,
|
|
||||||
rateDefinition,
|
|
||||||
roomName,
|
roomName,
|
||||||
roomNumber,
|
roomNumber,
|
||||||
roomPoints,
|
|
||||||
roomTypeCode,
|
|
||||||
totalPrice,
|
|
||||||
vouchers,
|
|
||||||
bookedRoom,
|
|
||||||
} = useMyStayStore((state) => ({
|
} = useMyStayStore((state) => ({
|
||||||
adults: state.bookedRoom.adults,
|
|
||||||
bookingCode: state.bookedRoom.bookingCode,
|
|
||||||
breakfast: state.bookedRoom.breakfast,
|
|
||||||
guest: state.bookedRoom.guest,
|
|
||||||
checkInDate: state.bookedRoom.checkInDate,
|
|
||||||
cheques: state.bookedRoom.cheques,
|
|
||||||
childrenAges: state.bookedRoom.childrenAges,
|
|
||||||
confirmationNumber: state.bookedRoom.confirmationNumber,
|
confirmationNumber: state.bookedRoom.confirmationNumber,
|
||||||
formattedTotalPrice: state.totalPrice,
|
guest: state.bookedRoom.guest,
|
||||||
hotel: state.hotel,
|
|
||||||
isCancelled: state.bookedRoom.isCancelled,
|
isCancelled: state.bookedRoom.isCancelled,
|
||||||
packages: state.bookedRoom.packages,
|
isMultiRoom: state.rooms.length > 1,
|
||||||
priceType: state.bookedRoom.priceType,
|
|
||||||
rateDefinition: state.bookedRoom.rateDefinition,
|
|
||||||
roomName: state.bookedRoom.roomName,
|
roomName: state.bookedRoom.roomName,
|
||||||
roomNumber: state.bookedRoom.roomNumber,
|
roomNumber: state.bookedRoom.roomNumber,
|
||||||
roomPoints: state.bookedRoom.roomPoints,
|
|
||||||
roomTypeCode: state.bookedRoom.roomTypeCode,
|
|
||||||
totalPrice: state.bookedRoom.totalPrice,
|
|
||||||
vouchers: state.bookedRoom.vouchers,
|
|
||||||
bookedRoom: state.bookedRoom,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
if (isMultiRoom) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
if (!roomNumber) {
|
if (!roomNumber) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.room}>
|
<div className={styles.room}>
|
||||||
@@ -98,403 +52,45 @@ export default function SingleRoom({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromDate = dt(checkInDate).locale(lang)
|
|
||||||
|
|
||||||
const mainBedWidthValueMsg = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "{value} cm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: bedType.mainBed.widthRange.min,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const mainBedWidthRangeMsg = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "{min}–{max} cm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min: bedType.mainBed.widthRange.min,
|
|
||||||
max: bedType.mainBed.widthRange.max,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const adultsMsg = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
adults,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const childrenMsg = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "{children, plural, one {# child} other {# children}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: childrenAges.length,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const adultsOnlyMsg = adultsMsg
|
|
||||||
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
|
||||||
|
|
||||||
const hasPackages = packages?.some((item) =>
|
|
||||||
Object.values(RoomPackageCodeEnum).includes(
|
|
||||||
item.code as RoomPackageCodeEnum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
let breakfastPrice = null
|
|
||||||
if (rateDefinition.breakfastIncluded) {
|
|
||||||
breakfastPrice = intl.formatMessage({
|
|
||||||
defaultMessage: "Included",
|
|
||||||
})
|
|
||||||
} else if (breakfast) {
|
|
||||||
breakfastPrice = formatPrice(
|
|
||||||
intl,
|
|
||||||
breakfast.localPrice.totalPrice,
|
|
||||||
breakfast.localPrice.currency
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const hotelRoom = getBookedHotelRoom(roomCategories, roomTypeCode)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.wrapper}>
|
||||||
<article className={styles.room}>
|
<div className={styles.container}>
|
||||||
<Typography variant="Title/Subtitle/lg">
|
<article className={styles.room}>
|
||||||
<p className={styles.roomName}>{roomName}</p>
|
<Typography variant="Title/Subtitle/lg">
|
||||||
</Typography>
|
<p className={styles.roomName}>{roomName}</p>
|
||||||
<div className={styles.roomHeader}>
|
</Typography>
|
||||||
<div className={styles.roomHeaderContent}>
|
<Header user={user} />
|
||||||
<div className={styles.chip}>
|
<div className={styles.booking}>
|
||||||
<Typography variant="Tag/sm">
|
<div
|
||||||
<span>
|
className={`${styles.content} ${isCancelled ? styles.cancelled : ""}`}
|
||||||
{intl.formatMessage(
|
>
|
||||||
{
|
<Packages />
|
||||||
defaultMessage: "Room {roomIndex}",
|
<Img />
|
||||||
},
|
<div className={styles.roomDetails}>
|
||||||
{
|
<Details />
|
||||||
roomIndex: roomNumber,
|
<div className={styles.guestDetailsDesktopWrapper}>
|
||||||
}
|
<GuestDetails
|
||||||
)}
|
confirmationNumber={confirmationNumber}
|
||||||
</span>
|
guest={guest}
|
||||||
</Typography>
|
isCancelled={isCancelled}
|
||||||
</div>
|
user={user}
|
||||||
<div className={styles.reference}>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Booking number",
|
|
||||||
})}
|
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
{":"}
|
|
||||||
</span>
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<span>{confirmationNumber}</span>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.sidePeek}>
|
|
||||||
<DialogTrigger>
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<ButtonRAC className={styles.trigger}>
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "View room details",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
<MaterialIcon
|
|
||||||
color="CurrentColor"
|
|
||||||
icon="chevron_right"
|
|
||||||
size={20}
|
|
||||||
/>
|
/>
|
||||||
</ButtonRAC>
|
|
||||||
</Typography>
|
|
||||||
<BookedRoomSidePeek
|
|
||||||
hotelRoom={hotelRoom}
|
|
||||||
room={bookedRoom}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
</DialogTrigger>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.booking}>
|
|
||||||
<div
|
|
||||||
className={`${styles.content} ${isCancelled ? styles.cancelled : ""}`}
|
|
||||||
>
|
|
||||||
{packages?.some((item) =>
|
|
||||||
Object.values(RoomPackageCodeEnum).includes(
|
|
||||||
item.code as RoomPackageCodeEnum
|
|
||||||
)
|
|
||||||
) && (
|
|
||||||
<div className={styles.packages}>
|
|
||||||
{packages
|
|
||||||
.filter((item) =>
|
|
||||||
Object.values(RoomPackageCodeEnum).includes(
|
|
||||||
item.code as RoomPackageCodeEnum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map((item) => {
|
|
||||||
return (
|
|
||||||
<span className={styles.package} key={item.code}>
|
|
||||||
<IconForFeatureCode
|
|
||||||
featureCode={item.code}
|
|
||||||
size={16}
|
|
||||||
color="Icon/Interactive/Default"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles.imageContainer}>
|
|
||||||
<Image
|
|
||||||
key={image.imageSizes.small}
|
|
||||||
src={image.imageSizes.small}
|
|
||||||
className={styles.image}
|
|
||||||
alt={roomName}
|
|
||||||
width={640}
|
|
||||||
height={960}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.roomDetails}>
|
|
||||||
<div className={styles.bookingDetails}>
|
|
||||||
<div className={styles.row}>
|
|
||||||
<span className={styles.rowTitle}>
|
|
||||||
<MaterialIcon
|
|
||||||
icon="person"
|
|
||||||
color="Icon/Default"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Guests",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
<div className={styles.rowContent}>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p color="uiTextHighContrast">
|
|
||||||
{childrenAges.length > 0
|
|
||||||
? adultsAndChildrenMsg
|
|
||||||
: adultsOnlyMsg}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{rateDefinition.cancellationText ? (
|
|
||||||
<div className={styles.row}>
|
|
||||||
<span className={styles.rowTitle}>
|
|
||||||
<MaterialIcon
|
|
||||||
icon="contract"
|
|
||||||
color="Icon/Default"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Terms",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
<div className={styles.rowContent}>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p color="uiTextHighContrast">
|
|
||||||
{rateDefinition.cancellationText}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{hasModifiableRate(rateDefinition.cancellationRule) && (
|
|
||||||
<div className={styles.row}>
|
|
||||||
<span className={styles.rowTitle}>
|
|
||||||
<MaterialIcon
|
|
||||||
icon="refresh"
|
|
||||||
color="Icon/Default"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Modify By",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
<div className={styles.rowContent}>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p color="uiTextHighContrast">
|
|
||||||
{intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Until {time}, {date}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "18:00",
|
|
||||||
date: fromDate.format("dddd D MMM"),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{breakfastPrice !== null && (
|
|
||||||
<div className={styles.row}>
|
|
||||||
<span className={styles.rowTitle}>
|
|
||||||
<MaterialIcon
|
|
||||||
icon="coffee"
|
|
||||||
color="Icon/Default"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Breakfast",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
<div className={styles.rowContent}>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p color="uiTextHighContrast">{breakfastPrice}</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasPackages && (
|
|
||||||
<div className={styles.row}>
|
|
||||||
<span className={styles.rowTitle}>
|
|
||||||
<MaterialIcon
|
|
||||||
icon="meeting_room"
|
|
||||||
color="Icon/Default"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Room classification",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
<div className={styles.rowContent}>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p color="uiTextHighContrast">
|
|
||||||
{packages!
|
|
||||||
.filter((item) =>
|
|
||||||
Object.values(RoomPackageCodeEnum).includes(
|
|
||||||
item.code as RoomPackageCodeEnum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map((item) => item.description)
|
|
||||||
.join(", ")}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={styles.row}>
|
|
||||||
<span className={styles.rowTitle}>
|
|
||||||
<MaterialIcon icon="bed" color="Icon/Default" size={20} />
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Bed preference",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
<div className={styles.rowContent}>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p color="uiTextHighContrast">
|
|
||||||
{bedType.mainBed.description}
|
|
||||||
{bedType.mainBed.widthRange.min ===
|
|
||||||
bedType.mainBed.widthRange.max
|
|
||||||
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
|
||||||
` (${mainBedWidthValueMsg})`
|
|
||||||
: // eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
|
||||||
` (${mainBedWidthRangeMsg})`}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.guestDetailsDesktopWrapper}>
|
|
||||||
<GuestDetails
|
|
||||||
confirmationNumber={confirmationNumber}
|
|
||||||
guest={guest}
|
|
||||||
isCancelled={isCancelled}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<BookingInformation />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.bookingInformation}>
|
<PriceDetails />
|
||||||
{bookingCode && (
|
<div className={styles.guestDetailsMobileWrapper}>
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
<GuestDetails
|
||||||
<IconChip
|
confirmationNumber={confirmationNumber}
|
||||||
color="blue"
|
guest={guest}
|
||||||
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
isCancelled={isCancelled}
|
||||||
>
|
user={user}
|
||||||
{intl.formatMessage(
|
/>
|
||||||
{
|
|
||||||
defaultMessage: "<strong>Booking code</strong>: {value}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: bookingCode,
|
|
||||||
strong: (text) => (
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<strong>{text}</strong>
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</IconChip>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<div className={styles.priceDetails}>
|
|
||||||
<div className={styles.price}>
|
|
||||||
<Typography variant="Body/Lead text">
|
|
||||||
<p color="uiTextHighContrast">
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Room total",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
<PriceType
|
|
||||||
cheques={cheques}
|
|
||||||
formattedTotalPrice={formattedTotalPrice}
|
|
||||||
isCancelled={isCancelled}
|
|
||||||
rateDefinition={rateDefinition}
|
|
||||||
priceType={priceType}
|
|
||||||
roomPoints={roomPoints}
|
|
||||||
totalPrice={totalPrice}
|
|
||||||
vouchers={vouchers}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
|
</div>
|
||||||
<PriceDetails />
|
|
||||||
<div className={styles.guestDetailsMobileWrapper}>
|
|
||||||
<GuestDetails
|
|
||||||
confirmationNumber={confirmationNumber}
|
|
||||||
guest={guest}
|
|
||||||
isCancelled={isCancelled}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,28 @@
|
|||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x5);
|
||||||
|
}
|
||||||
|
|
||||||
.room {
|
.room {
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x2);
|
||||||
background-color: var(--Base-Background-Primary-Normal);
|
|
||||||
padding: var(--Spacing-x3) 0;
|
padding: var(--Spacing-x3) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trigger {
|
|
||||||
align-items: center;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--Component-Button-Brand-Secondary-On-fill-Default);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Space-x1);
|
|
||||||
padding: var(--Space-x025) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomName {
|
.roomName {
|
||||||
color: var(--Scandic-Brand-Burgundy);
|
color: var(--Scandic-Brand-Burgundy);
|
||||||
padding: 0 var(--Spacing-x2);
|
padding: 0 var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomHeader {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-one-and-half);
|
|
||||||
padding: 0 var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomHeaderContent {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-one-and-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidePeek {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.booking {
|
.booking {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -56,136 +42,12 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chip {
|
|
||||||
background-color: var(--Scandic-Peach-30);
|
|
||||||
color: var(--Scandic-Red-100);
|
|
||||||
border-radius: var(--Corner-radius-Small);
|
|
||||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.packages {
|
|
||||||
position: absolute;
|
|
||||||
top: 180px;
|
|
||||||
left: 15px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.package {
|
|
||||||
background-color: var(--Main-Grey-White);
|
|
||||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
|
||||||
border-radius: var(--Corner-radius-Small);
|
|
||||||
}
|
|
||||||
|
|
||||||
.imageContainer {
|
|
||||||
height: 220px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
width: 100%;
|
|
||||||
height: 220px;
|
|
||||||
aspect-ratio: 16/9;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagePlaceholder {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #fff;
|
|
||||||
background-image:
|
|
||||||
linear-gradient(45deg, #000000 25%, transparent 25%),
|
|
||||||
linear-gradient(-45deg, #000000 25%, transparent 25%),
|
|
||||||
linear-gradient(45deg, transparent 75%, #000000 75%),
|
|
||||||
linear-gradient(-45deg, transparent 75%, #000000 75%);
|
|
||||||
background-size: 120px 120px;
|
|
||||||
background-position:
|
|
||||||
0 0,
|
|
||||||
0 60px,
|
|
||||||
60px -60px,
|
|
||||||
-60px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomDetails {
|
.roomDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x5);
|
gap: var(--Spacing-x5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookingDetails {
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 0 var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: var(--Spacing-x-one-and-half) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rowTitle {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rowTitle svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rowContent {
|
|
||||||
padding-left: var(--Spacing-x4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bookingInformation {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--Spacing-x2);
|
|
||||||
background-color: var(--Scandic-Beige-10);
|
|
||||||
margin: 0 var(--Spacing-x2);
|
|
||||||
border: 1px solid var(--Base-Border-Subtle);
|
|
||||||
border-radius: var(--Corner-radius-Medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.priceDetails {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
padding: var(--Spacing-x-one-and-half) 0;
|
|
||||||
width: calc(100% - var(--Spacing-x4));
|
|
||||||
justify-content: center;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userDetails {
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
|
||||||
margin-bottom: var(--Spacing-x1);
|
|
||||||
color: var(--Scandic-Brand-Burgundy);
|
|
||||||
}
|
|
||||||
|
|
||||||
.guestDetailsMobileWrapper {
|
.guestDetailsMobileWrapper {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0 var(--Spacing-x2);
|
padding: 0 var(--Spacing-x2);
|
||||||
@@ -205,17 +67,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomHeader {
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidePeek {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.booking {
|
.booking {
|
||||||
border-radius: var(--Corner-radius-Large);
|
border-radius: var(--Corner-radius-Large);
|
||||||
background-color: var(--Base-Background-Primary-Normal);
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
@@ -227,56 +78,6 @@
|
|||||||
width: var(--max-width-content);
|
width: var(--max-width-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
.packages {
|
|
||||||
top: 620px;
|
|
||||||
left: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imageContainer {
|
|
||||||
height: 640px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
height: 100%;
|
|
||||||
border-radius: var(--Corner-radius-Medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bookingDetails {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rowTitle svg {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bookingInformation {
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: var(--Spacing-x-one-and-half);
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.priceDetails {
|
|
||||||
margin: 0 0 0 auto;
|
|
||||||
width: auto;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guestDetailsMobileWrapper {
|
.guestDetailsMobileWrapper {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
|
||||||
|
|
||||||
import PriceDetails from "../PriceDetails"
|
|
||||||
import MultiRoom from "./MultiRoom"
|
|
||||||
import SingleRoom from "./SingleRoom"
|
|
||||||
import TotalPrice from "./TotalPrice"
|
|
||||||
|
|
||||||
import styles from "./rooms.module.css"
|
|
||||||
|
|
||||||
import type { SafeUser } from "@/types/user"
|
|
||||||
|
|
||||||
interface RoomsProps {
|
|
||||||
user: SafeUser
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Rooms({ user }: RoomsProps) {
|
|
||||||
const intl = useIntl()
|
|
||||||
const { allRoomsAreCancelled, room, rooms, roomCategories } = useMyStayStore(
|
|
||||||
(state) => ({
|
|
||||||
allRoomsAreCancelled: state.allRoomsAreCancelled,
|
|
||||||
hotel: state.hotel,
|
|
||||||
room: state.bookedRoom.room,
|
|
||||||
rooms: state.rooms,
|
|
||||||
roomCategories: state.roomCategories,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!room) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMultiRoom = rooms.length > 1
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.wrapper}>
|
|
||||||
{isMultiRoom && (
|
|
||||||
<Typography variant="Title/sm">
|
|
||||||
<h2 className={styles.title}>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Your rooms",
|
|
||||||
})}
|
|
||||||
</h2>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<div className={styles.container}>
|
|
||||||
{!isMultiRoom ? (
|
|
||||||
<SingleRoom
|
|
||||||
bedType={room.bedType}
|
|
||||||
image={room.images[0]}
|
|
||||||
user={user}
|
|
||||||
roomCategories={roomCategories}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className={styles.roomsContainer}>
|
|
||||||
{rooms.map((booking, index) => (
|
|
||||||
<div
|
|
||||||
key={booking.confirmationNumber}
|
|
||||||
className={styles.roomWrapper}
|
|
||||||
>
|
|
||||||
<MultiRoom
|
|
||||||
booking={booking}
|
|
||||||
roomNr={index + 1}
|
|
||||||
user={user}
|
|
||||||
roomCategories={roomCategories}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isMultiRoom && (
|
|
||||||
<div className={styles.totalContainer}>
|
|
||||||
<div className={styles.total}>
|
|
||||||
<Typography variant="Body/Lead text">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Booking total",
|
|
||||||
})}
|
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
{":"}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
<TotalPrice />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{allRoomsAreCancelled ? null : <PriceDetails />}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
.wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--Spacing-x3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--Spacing-x5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomsContainer {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--Spacing-x3);
|
|
||||||
width: 100%;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomWrapper {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomWrapper > * {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--Scandic-Brand-Burgundy);
|
|
||||||
padding: 0 var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.totalContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
padding: 0 var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.total {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.roomsContainer {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomsContainer:has(> *:nth-child(3):last-child) {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.totalContainer {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { BookingStatusEnum, CancellationRuleEnum } from "@/constants/booking"
|
import { BookingStatusEnum, CancellationRuleEnum } from "@/constants/booking"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
import { convertToChildType } from "@/components/HotelReservation/utils/convertToChildType"
|
import { convertToChildType } from "../../utils/convertToChildType"
|
||||||
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
|
import { getPriceType } from "../../utils/getPriceType"
|
||||||
|
|
||||||
import { formatChildBedPreferences } from "../utils"
|
import { formatChildBedPreferences } from "../utils"
|
||||||
|
|
||||||
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import type { Packages } from "@/types/requests/packages"
|
|||||||
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
|
||||||
import type { SafeUser } from "@/types/user"
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
export type PartialHotelRoom = Pick<
|
type PartialHotelRoom = Pick<
|
||||||
HotelRoom,
|
HotelRoom,
|
||||||
"descriptions" | "images" | "name" | "roomFacilities" | "roomTypes"
|
"descriptions" | "images" | "name" | "roomFacilities" | "roomTypes"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ export function AncillaryCard({ ancillary }: AncillaryCardProps) {
|
|||||||
return (
|
return (
|
||||||
<article className={styles.ancillaryCard}>
|
<article className={styles.ancillaryCard}>
|
||||||
<div className={styles.imageContainer}>
|
<div className={styles.imageContainer}>
|
||||||
<Image
|
{ancillary.imageUrl ? (
|
||||||
className={styles.image}
|
<Image
|
||||||
src={ancillary.imageUrl}
|
className={styles.image}
|
||||||
alt={ancillary.title}
|
src={ancillary.imageUrl}
|
||||||
fill
|
alt={ancillary.title}
|
||||||
style={{ opacity: ancillary.imageOpacity ?? 1 }}
|
fill
|
||||||
/>
|
style={{ opacity: ancillary.imageOpacity ?? 1 }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.contentContainer}>
|
<div className={styles.contentContainer}>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { SidePeekSelfControlledProps } from "./sidePeek"
|
import type { SidePeekSelfControlledProps } from "./sidePeek"
|
||||||
|
|
||||||
// Sidepeeks generally have important content that should be indexed by search engines.
|
// Sidepeeks generally have important content that should be indexed by search engines.
|
||||||
// The content is hidden behind a modal, but it is still important for SEO.
|
// The content is hidden behind a modal, but it is still important for SEO.
|
||||||
// This component is used to provide SEO information for the sidepeek content.
|
// This component is used to provide SEO information for the sidepeek content.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
|
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
@@ -54,10 +55,12 @@ export default function SidePeekSelfControlled({
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Modal>
|
</Modal>
|
||||||
</ModalOverlay>
|
</ModalOverlay>
|
||||||
|
|
||||||
<SidePeekSEO title={title}>{children}</SidePeekSEO>
|
<SidePeekSEO title={title}>{children}</SidePeekSEO>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function KeepBodyVisible() {
|
function KeepBodyVisible() {
|
||||||
const toggle = useSetOverflowVisibleOnRA()
|
const toggle = useSetOverflowVisibleOnRA()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: var(--Base-Background-Primary-Normal);
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
z-index: var(--sidepeek-z-index);
|
z-index: var(--sidepeek-z-index);
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal[data-entering] {
|
.modal[data-entering] {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export type SidePeekProps = {
|
|||||||
activeContent: string | null
|
activeContent: string | null
|
||||||
onClose: (isOpen: boolean) => void
|
onClose: (isOpen: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SidePeekContentProps = {
|
export type SidePeekContentProps = {
|
||||||
title?: string
|
title?: string
|
||||||
contentKey: string
|
contentKey: string
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ export default function BookingConfirmationProvider({
|
|||||||
currencyCode,
|
currencyCode,
|
||||||
fromDate,
|
fromDate,
|
||||||
toDate,
|
toDate,
|
||||||
rooms,
|
|
||||||
roomCategories,
|
roomCategories,
|
||||||
|
rooms,
|
||||||
vat,
|
vat,
|
||||||
isVatCurrency,
|
isVatCurrency,
|
||||||
formattedTotalCost,
|
formattedTotalCost,
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmat
|
|||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
export function getBookedHotelRoom(
|
export function getBookedHotelRoom(
|
||||||
rooms: Room[] | undefined,
|
rooms: Room[],
|
||||||
roomTypeCode: BookingConfirmation["booking"]["roomTypeCode"]
|
roomTypeCode: BookingConfirmation["booking"]["roomTypeCode"]
|
||||||
) {
|
) {
|
||||||
if (!rooms?.length || !roomTypeCode) {
|
if (!rooms.length || !roomTypeCode) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const room = rooms?.find((r) => {
|
const room = rooms.find((r) => {
|
||||||
return r.roomTypes.find((roomType) => roomType.code === roomTypeCode)
|
return r.roomTypes.find((roomType) => roomType.code === roomTypeCode)
|
||||||
})
|
})
|
||||||
if (!room) {
|
if (!room) {
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export function createMyStayStore({
|
|||||||
savedCreditCards,
|
savedCreditCards,
|
||||||
totalPoints,
|
totalPoints,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
roomCategories,
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
closeManageStay() {
|
closeManageStay() {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export interface LinkedReservationProps {
|
|||||||
checkOutTime: string
|
checkOutTime: string
|
||||||
confirmationNumber: string
|
confirmationNumber: string
|
||||||
roomIndex: number
|
roomIndex: number
|
||||||
|
roomNumber: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RetryProps {
|
export interface RetryProps {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type SelectedAncillary = Ancillary["ancillaryContent"][number]
|
|||||||
export type Packages = z.output<typeof packagesSchema>
|
export type Packages = z.output<typeof packagesSchema>
|
||||||
|
|
||||||
export interface AncillariesProps extends Pick<BookingConfirmation, "booking"> {
|
export interface AncillariesProps extends Pick<BookingConfirmation, "booking"> {
|
||||||
ancillaries: Ancillaries | null
|
ancillariesPromise: Promise<Ancillaries | null>
|
||||||
packages: Packages | null
|
packages: Packages | null
|
||||||
user: User | null
|
user: User | null
|
||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import type { Room } from "@/types/hotel"
|
import type { Room } from "@/types/hotel"
|
||||||
import type { SafeUser } from "@/types/user"
|
import type { SafeUser } from "@/types/user"
|
||||||
import type { SidePeekEnum } from "../hotelReservation/sidePeek"
|
|
||||||
|
|
||||||
export type BookedRoomSidePeekProps = {
|
export type BookedRoomSidePeekProps = {
|
||||||
room: Room
|
|
||||||
activeSidePeek: SidePeekEnum | null
|
|
||||||
close: () => void
|
close: () => void
|
||||||
user: SafeUser
|
|
||||||
confirmationNumber: string
|
confirmationNumber: string
|
||||||
|
room: Room
|
||||||
|
user: SafeUser
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoomDetailsProps = {
|
export type RoomDetailsProps = {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ export interface BookingConfirmationProviderProps
|
|||||||
bookingCode: string | null
|
bookingCode: string | null
|
||||||
currencyCode: CurrencyEnum
|
currencyCode: CurrencyEnum
|
||||||
fromDate: Date
|
fromDate: Date
|
||||||
rooms: (Room | null)[]
|
|
||||||
roomCategories: RoomCategories
|
roomCategories: RoomCategories
|
||||||
|
rooms: (Room | null)[]
|
||||||
toDate: Date
|
toDate: Date
|
||||||
vat: number
|
vat: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export interface MyStayState {
|
|||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
totalPoints: number
|
totalPoints: number
|
||||||
totalPrice: string
|
totalPrice: string
|
||||||
roomCategories: RoomCategories
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InitialState
|
export interface InitialState
|
||||||
|
|||||||
Reference in New Issue
Block a user