feat: refactor of my stay
This commit is contained in:
committed by
Simon.Emanuelsson
parent
b5deb84b33
commit
ec087a3d15
@@ -0,0 +1,45 @@
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
|
||||
import styles from "./multiRoom.module.css"
|
||||
|
||||
export default function MultiRoomSkeleton() {
|
||||
return (
|
||||
<div className={styles.multiRoom}>
|
||||
<div className={styles.roomName}>
|
||||
<SkeletonShimmer width="50%" height="30px" />
|
||||
</div>
|
||||
<div className={styles.roomHeader}>
|
||||
<SkeletonShimmer width="100%" height="23px" />
|
||||
</div>
|
||||
<div className={styles.multiRoomCard}>
|
||||
<div className={styles.imageContainer}>
|
||||
<SkeletonShimmer width="100%" height="100%" />
|
||||
</div>
|
||||
<div className={styles.details}>
|
||||
<div className={styles.row}>
|
||||
<SkeletonShimmer width="60px" height="23px" />
|
||||
<SkeletonShimmer width="110px" height="23px" />
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<SkeletonShimmer width="55px" height="23px" />
|
||||
<SkeletonShimmer width="130px" height="23px" />
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<SkeletonShimmer width="75px" height="23px" />
|
||||
<SkeletonShimmer width="155px" height="23px" />
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<SkeletonShimmer width="75px" height="23px" />
|
||||
<SkeletonShimmer width="65px" height="23px" />
|
||||
</div>
|
||||
<Divider color="subtle" />
|
||||
<div className={styles.row}>
|
||||
<SkeletonShimmer width="150px" height="30px" />
|
||||
<SkeletonShimmer width="150px" height="30px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
"use client"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
|
||||
import useSidePeekStore from "@/stores/sidepeek"
|
||||
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
|
||||
import styles from "./toggleSidePeek.module.css"
|
||||
|
||||
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
|
||||
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
|
||||
|
||||
export default function ToggleSidePeek({
|
||||
hotelId,
|
||||
roomTypeCode,
|
||||
user,
|
||||
confirmationNumber,
|
||||
}: ToggleSidePeekProps) {
|
||||
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={() =>
|
||||
openSidePeek({
|
||||
key: SidePeekEnum.bookedRoomDetails,
|
||||
hotelId,
|
||||
roomTypeCode,
|
||||
user,
|
||||
confirmationNumber,
|
||||
})
|
||||
}
|
||||
size="small"
|
||||
variant="icon"
|
||||
intent="text"
|
||||
wrapping
|
||||
>
|
||||
<div className={styles.iconContainer}>
|
||||
<MaterialIcon icon="pan_zoom" color="CurrentColor" />
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
"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 ToggleSidePeek from "./ToggleSidePeek"
|
||||
|
||||
import styles from "./multiRoom.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 MultiRoomProps {
|
||||
booking: Room
|
||||
roomNr: number
|
||||
user: SafeUser
|
||||
}
|
||||
|
||||
export default function MultiRoom({ booking, roomNr, user }: MultiRoomProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
const {
|
||||
adults,
|
||||
breakfast,
|
||||
cancellationNumber,
|
||||
checkInDate,
|
||||
cheques,
|
||||
childrenAges,
|
||||
confirmationNumber,
|
||||
currencyCode,
|
||||
hotelId,
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
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: "Reference",
|
||||
})}
|
||||
{/* 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}>
|
||||
<ToggleSidePeek
|
||||
hotelId={hotelId}
|
||||
roomTypeCode={roomTypeCode}
|
||||
user={user}
|
||||
confirmationNumber={confirmationNumber}
|
||||
/>
|
||||
</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,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,6 @@
|
||||
.iconContainer {
|
||||
display: flex;
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
padding: var(--Spacing-x-half);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
"use client"
|
||||
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 { dt } from "@/lib/dt"
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import GuestDetails from "@/components/HotelReservation/MyStay/GuestDetails"
|
||||
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 SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import IconChip from "@/components/TempDesignSystem/IconChip"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import ToggleSidePeek from "./ToggleSidePeek"
|
||||
|
||||
import styles from "./room.module.css"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import type { Room } from "@/types/hotel"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
|
||||
interface RoomProps {
|
||||
bedType: Room["roomTypes"][number]
|
||||
image: Room["images"][number]
|
||||
user: SafeUser
|
||||
}
|
||||
|
||||
export default function SingleRoom({ bedType, image, user }: RoomProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
const {
|
||||
adults,
|
||||
bookingCode,
|
||||
breakfast,
|
||||
checkInDate,
|
||||
cheques,
|
||||
childrenAges,
|
||||
confirmationNumber,
|
||||
formattedTotalPrice,
|
||||
hotel,
|
||||
isCancelled,
|
||||
packages,
|
||||
priceType,
|
||||
rateDefinition,
|
||||
roomName,
|
||||
roomNumber,
|
||||
roomPoints,
|
||||
roomTypeCode,
|
||||
totalPrice,
|
||||
vouchers,
|
||||
} = useMyStayStore((state) => ({
|
||||
adults: state.bookedRoom.adults,
|
||||
bookingCode: state.bookedRoom.bookingCode,
|
||||
breakfast: state.bookedRoom.breakfast,
|
||||
checkInDate: state.bookedRoom.checkInDate,
|
||||
cheques: state.bookedRoom.cheques,
|
||||
childrenAges: state.bookedRoom.childrenAges,
|
||||
confirmationNumber: state.bookedRoom.confirmationNumber,
|
||||
formattedTotalPrice: state.totalPrice,
|
||||
hotel: state.hotel,
|
||||
isCancelled: state.bookedRoom.isCancelled,
|
||||
packages: state.bookedRoom.packages,
|
||||
priceType: state.bookedRoom.priceType,
|
||||
rateDefinition: state.bookedRoom.rateDefinition,
|
||||
roomName: state.bookedRoom.roomName,
|
||||
roomNumber: state.bookedRoom.roomNumber,
|
||||
roomPoints: state.bookedRoom.roomPoints,
|
||||
roomTypeCode: state.bookedRoom.roomTypeCode,
|
||||
totalPrice: state.bookedRoom.totalPrice,
|
||||
vouchers: state.bookedRoom.vouchers,
|
||||
}))
|
||||
|
||||
if (!roomNumber) {
|
||||
return (
|
||||
<div className={styles.room}>
|
||||
<SkeletonShimmer width={"200px"} height="30px" />
|
||||
<SkeletonShimmer width="100%" height="750px" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<article className={styles.room}>
|
||||
<Typography variant="Title/Subtitle/lg">
|
||||
<p className={styles.roomName}>{roomName}</p>
|
||||
</Typography>
|
||||
<div className={styles.roomHeader}>
|
||||
<div className={styles.roomHeaderContent}>
|
||||
<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: "Reference",
|
||||
})}
|
||||
{/* 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}>
|
||||
<ToggleSidePeek
|
||||
hotelId={hotel.operaId}
|
||||
roomTypeCode={roomTypeCode}
|
||||
intent="text"
|
||||
/>
|
||||
</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 className={styles.guestDetailsDesktopWrapper}>
|
||||
<GuestDetails user={user} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.bookingInformation}>
|
||||
{bookingCode && (
|
||||
<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>
|
||||
)}
|
||||
<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>
|
||||
|
||||
<PriceDetails />
|
||||
<div className={styles.guestDetailsMobileWrapper}>
|
||||
<GuestDetails user={user} />
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
.room {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
background-color: var(--Base-Background-Primary-Normal);
|
||||
padding: var(--Spacing-x3) 0;
|
||||
}
|
||||
|
||||
.roomName {
|
||||
color: var(--Scandic-Brand-Burgundy);
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cancelled {
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
display: block;
|
||||
padding: 0 var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.guestDetailsDesktopWrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.room {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.roomName {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.roomHeader {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidePeek {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.booking {
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
background-color: var(--Base-Background-Primary-Normal);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--Spacing-x2);
|
||||
grid-template-columns: 3fr 2fr;
|
||||
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 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.guestDetailsDesktopWrapper {
|
||||
display: block;
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import PriceType from "../PriceType"
|
||||
|
||||
import type { PriceType as _PriceType } from "@/types/components/hotelReservation/myStay/myStay"
|
||||
|
||||
export default function TotalPrice() {
|
||||
const { bookedRoom, formattedTotalPrice } = useMyStayStore((state) => ({
|
||||
bookedRoom: state.bookedRoom,
|
||||
formattedTotalPrice: state.totalPrice,
|
||||
}))
|
||||
|
||||
return (
|
||||
<PriceType
|
||||
cheques={bookedRoom.cheques}
|
||||
formattedTotalPrice={formattedTotalPrice}
|
||||
isCancelled={bookedRoom.isCancelled}
|
||||
priceType={bookedRoom.priceType}
|
||||
rateDefinition={bookedRoom.rateDefinition}
|
||||
roomPoints={bookedRoom.roomPoints}
|
||||
totalPrice={bookedRoom.totalPrice}
|
||||
vouchers={bookedRoom.vouchers}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
|
||||
import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
|
||||
|
||||
import Points from "../../Points"
|
||||
import Price from "../../Price"
|
||||
|
||||
import styles from "./totalPrice.module.css"
|
||||
|
||||
import type { PriceType } from "@/types/components/hotelReservation/myStay/myStay"
|
||||
|
||||
export type Variant =
|
||||
| "Title/Subtitle/lg"
|
||||
| "Title/Subtitle/md"
|
||||
| "Body/Paragraph/mdBold"
|
||||
|
||||
interface TotalPriceProps {
|
||||
variant: Variant
|
||||
type?: PriceType
|
||||
}
|
||||
|
||||
export default function TotalPrice({
|
||||
variant,
|
||||
type = "money",
|
||||
}: TotalPriceProps) {
|
||||
const totalPrice = useMyStayTotalPriceStore((state) => state.totalPrice)
|
||||
const totalPoints = useMyStayTotalPriceStore((state) => state.totalPoints)
|
||||
const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
|
||||
|
||||
const { vouchers, cheques } = bookedRoom
|
||||
|
||||
const intl = useIntl()
|
||||
if (type === "money") {
|
||||
return <Price price={totalPrice} variant={variant} />
|
||||
}
|
||||
|
||||
if (type === "voucher") {
|
||||
return (
|
||||
<Typography variant={variant}>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{count} voucher",
|
||||
},
|
||||
{ count: vouchers }
|
||||
)}
|
||||
</p>
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === "cheque") {
|
||||
return (
|
||||
<div className={styles.totalPrice}>
|
||||
<Typography variant={variant}>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<p>{cheques} CC + </p>
|
||||
</Typography>
|
||||
<Price price={totalPrice} variant={variant} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (totalPrice && totalPrice > 0 && type === "points") {
|
||||
return (
|
||||
<div className={styles.totalPrice}>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<Points points={totalPoints} variant={variant} /> +{" "}
|
||||
<Price price={totalPrice} variant={variant} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <Points points={totalPoints} variant={variant} />
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.totalPrice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
@@ -1,54 +1,37 @@
|
||||
import { Suspense } from "react"
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import MultiRoom from "../MultiRoom"
|
||||
import MultiRoomSkeleton from "../MultiRoom/MultiRoomSkeleton"
|
||||
import PriceDetails from "../PriceDetails"
|
||||
import { SingleRoom } from "../SingleRoom"
|
||||
import { getPriceType } from "../utils/getPriceType"
|
||||
import MultiRoom from "./MultiRoom"
|
||||
import SingleRoom from "./SingleRoom"
|
||||
import TotalPrice from "./TotalPrice"
|
||||
|
||||
import styles from "./rooms.module.css"
|
||||
|
||||
import { type Hotel, type Room } from "@/types/hotel"
|
||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||
import type { User } from "@/types/user"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
|
||||
interface RoomsProps {
|
||||
booking: BookingConfirmation["booking"]
|
||||
room:
|
||||
| (Room & {
|
||||
bedType: Room["roomTypes"][number]
|
||||
})
|
||||
| null
|
||||
hotel: Hotel
|
||||
user: User | null
|
||||
user: SafeUser
|
||||
}
|
||||
|
||||
export default async function Rooms({
|
||||
booking,
|
||||
room,
|
||||
hotel,
|
||||
user,
|
||||
}: RoomsProps) {
|
||||
const intl = await getIntl()
|
||||
export default function Rooms({ user }: RoomsProps) {
|
||||
const intl = useIntl()
|
||||
const { allRoomsAreCancelled, room, rooms } = useMyStayStore((state) => ({
|
||||
allRoomsAreCancelled: state.allRoomsAreCancelled,
|
||||
hotel: state.hotel,
|
||||
room: state.bookedRoom.room,
|
||||
rooms: state.rooms,
|
||||
}))
|
||||
|
||||
if (!room) {
|
||||
return null
|
||||
}
|
||||
|
||||
const linkedBookingPromises = booking.linkedReservations
|
||||
? booking.linkedReservations.map((linkedBooking) => {
|
||||
return getBookingConfirmation(linkedBooking.confirmationNumber)
|
||||
})
|
||||
: []
|
||||
|
||||
const isMultiRoom = booking.linkedReservations.length > 0
|
||||
const isMultiRoom = rooms.length > 1
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
@@ -66,24 +49,16 @@ export default async function Rooms({
|
||||
<SingleRoom
|
||||
bedType={room.bedType}
|
||||
image={room.images[0]}
|
||||
hotel={hotel}
|
||||
user={user}
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.roomsContainer}>
|
||||
<MultiRoom booking={booking} room={room} user={user} />
|
||||
{booking.linkedReservations.map((linkedRes, index) => (
|
||||
{rooms.map((booking, index) => (
|
||||
<div
|
||||
key={linkedRes.confirmationNumber}
|
||||
key={booking.confirmationNumber}
|
||||
className={styles.roomWrapper}
|
||||
>
|
||||
<Suspense fallback={<MultiRoomSkeleton />}>
|
||||
<MultiRoom
|
||||
bookingPromise={linkedBookingPromises[index]}
|
||||
index={index}
|
||||
user={user}
|
||||
/>
|
||||
</Suspense>
|
||||
<MultiRoom booking={booking} roomNr={index + 1} user={user} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -101,17 +76,10 @@ export default async function Rooms({
|
||||
{":"}
|
||||
</p>
|
||||
</Typography>
|
||||
<TotalPrice
|
||||
variant="Title/Subtitle/lg"
|
||||
type={getPriceType(
|
||||
booking.cheques,
|
||||
booking.roomPoints,
|
||||
booking.vouchers
|
||||
)}
|
||||
/>
|
||||
<TotalPrice />
|
||||
</div>
|
||||
|
||||
<PriceDetails />
|
||||
{allRoomsAreCancelled ? null : <PriceDetails />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user