Feat/sw-1839 show added breakfast * Fix wrong space character * Change to correct CSS variable * Show added breakfast ancillary in the "My add-ons" section * Show breakfast info in room card * Show breakfast in price details table * Format price Approved-by: Pontus Dreij
325 lines
9.9 KiB
TypeScript
325 lines
9.9 KiB
TypeScript
"use client"
|
|
import { use, useEffect } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
|
|
import { BookingStatusEnum } from "@/constants/booking"
|
|
import { dt } from "@/lib/dt"
|
|
import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
|
|
import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
|
|
|
|
import Image from "@/components/Image"
|
|
import Divider from "@/components/TempDesignSystem/Divider"
|
|
import IconChip from "@/components/TempDesignSystem/IconChip"
|
|
import useLang from "@/hooks/useLang"
|
|
|
|
import { IconForFeatureCode } from "../../utils"
|
|
import Points from "../Points"
|
|
import Price from "../Price"
|
|
import { hasBreakfastPackageFromBookingFlow } from "../utils/hasBreakfastPackage"
|
|
import { mapRoomDetails } from "../utils/mapRoomDetails"
|
|
import MultiRoomSkeleton from "./MultiRoomSkeleton"
|
|
import ToggleSidePeek from "./ToggleSidePeek"
|
|
|
|
import styles from "./multiRoom.module.css"
|
|
|
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
|
import type { Room } from "@/types/hotel"
|
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
|
import type { User } from "@/types/user"
|
|
|
|
interface MultiRoomProps {
|
|
booking?: BookingConfirmation["booking"]
|
|
room?:
|
|
| (Room & {
|
|
bedType: Room["roomTypes"][number]
|
|
})
|
|
| null
|
|
bookingPromise?: Promise<BookingConfirmation | null>
|
|
index?: number
|
|
user: User | null
|
|
}
|
|
|
|
export default function MultiRoom({
|
|
room: initialRoom,
|
|
booking: initialBooking,
|
|
bookingPromise,
|
|
index,
|
|
user,
|
|
}: MultiRoomProps) {
|
|
const intl = useIntl()
|
|
const lang = useLang()
|
|
const addRoomPrice = useMyStayTotalPriceStore(
|
|
(state) => state.actions.addRoomPrice
|
|
)
|
|
|
|
const addLinkedReservationRoom = useMyStayRoomDetailsStore(
|
|
(state) => state.actions.addLinkedReservationRoom
|
|
)
|
|
|
|
const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
|
|
const linkedReservationRooms = useMyStayRoomDetailsStore(
|
|
(state) => state.linkedReservationRooms
|
|
)
|
|
|
|
const allRooms = [bookedRoom, ...linkedReservationRooms]
|
|
|
|
// Resolve promise data directly without setState
|
|
let bookingInfo = initialBooking
|
|
let roomInfo = initialRoom
|
|
|
|
if (bookingPromise) {
|
|
const promiseData = use(bookingPromise)
|
|
if (promiseData) {
|
|
bookingInfo = promiseData.booking
|
|
roomInfo = promiseData.room
|
|
}
|
|
}
|
|
const isBookingCancelled =
|
|
bookingInfo?.reservationStatus === BookingStatusEnum.Cancelled
|
|
|
|
const multiRoom = allRooms.find(
|
|
(room) => room.confirmationNumber === bookingInfo?.confirmationNumber
|
|
)
|
|
|
|
// Update stores when data is available
|
|
useEffect(() => {
|
|
if (bookingInfo) {
|
|
addRoomPrice({
|
|
id: bookingInfo.confirmationNumber,
|
|
totalPrice: isBookingCancelled ? 0 : bookingInfo.totalPrice,
|
|
currencyCode: bookingInfo.currencyCode,
|
|
isMainBooking: false,
|
|
roomPoints: bookingInfo.roomPoints,
|
|
})
|
|
|
|
// Add room details to the store
|
|
addLinkedReservationRoom(
|
|
mapRoomDetails({
|
|
booking: bookingInfo,
|
|
room: roomInfo ?? null,
|
|
roomNumber: index !== undefined ? index + 2 : 1,
|
|
})
|
|
)
|
|
}
|
|
}, [
|
|
bookingInfo,
|
|
roomInfo,
|
|
index,
|
|
isBookingCancelled,
|
|
addRoomPrice,
|
|
addLinkedReservationRoom,
|
|
])
|
|
|
|
if (!multiRoom?.roomNumber) return <MultiRoomSkeleton />
|
|
|
|
const {
|
|
adults,
|
|
checkInDate,
|
|
childrenAges,
|
|
confirmationNumber,
|
|
cancellationNumber,
|
|
hotelId,
|
|
roomPoints,
|
|
roomPrice,
|
|
packages,
|
|
rateDefinition,
|
|
isCancelled,
|
|
priceType,
|
|
vouchers,
|
|
} = multiRoom
|
|
|
|
const fromDate = dt(checkInDate).locale(lang)
|
|
|
|
const adultsMsg = intl.formatMessage(
|
|
{ id: "{adults, plural, one {# adult} other {# adults}}" },
|
|
{
|
|
adults: adults,
|
|
}
|
|
)
|
|
|
|
const childrenMsg = intl.formatMessage(
|
|
{
|
|
id: "{children, plural, one {# child} other {# children}}",
|
|
},
|
|
{
|
|
children: childrenAges.length,
|
|
}
|
|
)
|
|
|
|
const adultsOnlyMsg = adultsMsg
|
|
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
|
|
|
return (
|
|
<article className={styles.multiRoom}>
|
|
<Typography variant="Title/smRegular">
|
|
<h3 className={styles.roomName}>{roomInfo?.name}</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({ id: "Cancelled" })}</span>
|
|
</Typography>
|
|
</IconChip>
|
|
) : (
|
|
<div className={styles.chip}>
|
|
<Typography variant="Tag/sm">
|
|
<span>
|
|
{intl.formatMessage({ id: "Room" }) +
|
|
" " +
|
|
(index !== undefined ? index + 2 : 1)}
|
|
</span>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
<div className={styles.reference}>
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
{isCancelled ? (
|
|
<span>{intl.formatMessage({ id: "Cancellation no" })}:</span>
|
|
) : (
|
|
<span>{intl.formatMessage({ id: "Reference" })}:</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={roomInfo?.roomTypes[0].code}
|
|
user={user ?? undefined}
|
|
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={roomInfo?.images[0]?.imageSizes.small ?? ""}
|
|
alt={roomInfo?.name ?? ""}
|
|
fill
|
|
/>
|
|
</div>
|
|
<div className={styles.details}>
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>{intl.formatMessage({ id: "Guests" })}</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{childrenAges.length > 0 ? adultsAndChildrenMsg : adultsOnlyMsg}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>{intl.formatMessage({ id: "Terms" })}</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>{rateDefinition.cancellationText}</p>
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>{intl.formatMessage({ id: "Modify By" })}</p>
|
|
</Typography>
|
|
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p color="uiTextHighContrast">
|
|
18:00, {fromDate.format("dddd D MMM")}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>{intl.formatMessage({ id: "Breakfast" })}</p>
|
|
</Typography>
|
|
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p color="uiTextHighContrast">
|
|
{hasBreakfastPackageFromBookingFlow(
|
|
packages?.map((pkg) => ({
|
|
code: pkg.code,
|
|
})) ?? []
|
|
)
|
|
? intl.formatMessage({ id: "Included" })
|
|
: intl.formatMessage({ id: "Not included" })}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
<Divider color="subtle" />
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Lead text">
|
|
<p>{intl.formatMessage({ id: "Room total" })}</p>
|
|
</Typography>
|
|
{priceType === "points" ? (
|
|
<Points points={roomPoints} variant="Body/Paragraph/mdBold" />
|
|
) : priceType === "voucher" ? (
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{intl.formatMessage(
|
|
{ id: "{count} voucher" },
|
|
{ count: vouchers }
|
|
)}
|
|
</p>
|
|
</Typography>
|
|
) : (
|
|
<Price
|
|
price={isCancelled ? 0 : roomPrice.perStay.local.price}
|
|
variant="Body/Paragraph/mdBold"
|
|
isMember={rateDefinition.isMemberRate}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|