333 lines
11 KiB
TypeScript
333 lines
11 KiB
TypeScript
"use client"
|
||
|
||
import { useIntl } from "react-intl"
|
||
|
||
import { dt } from "@/lib/dt"
|
||
|
||
import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
|
||
import {
|
||
BedDoubleIcon,
|
||
BookingCodeIcon,
|
||
CoffeeIcon,
|
||
ContractIcon,
|
||
DoorOpenIcon,
|
||
PersonIcon,
|
||
} from "@/components/Icons"
|
||
import RocketLaunch from "@/components/Icons/Refresh"
|
||
import Image from "@/components/Image"
|
||
import IconChip from "@/components/TempDesignSystem/IconChip"
|
||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||
import useLang from "@/hooks/useLang"
|
||
import { formatPrice } from "@/utils/numberFormatting"
|
||
|
||
import PriceDetailsModal from "../../PriceDetailsModal"
|
||
import GuestDetails from "./GuestDetails"
|
||
import ToggleSidePeek from "./ToggleSidePeek"
|
||
|
||
import styles from "./room.module.css"
|
||
|
||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||
import type { Hotel, Room } from "@/types/hotel"
|
||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||
import type { User } from "@/types/user"
|
||
|
||
interface RoomProps {
|
||
booking: BookingConfirmation["booking"]
|
||
room:
|
||
| (Room & {
|
||
bedType: Room["roomTypes"][number]
|
||
})
|
||
| null
|
||
hotel: Hotel
|
||
user: User | null
|
||
}
|
||
|
||
function hasBreakfastPackage(
|
||
packages: BookingConfirmation["booking"]["packages"]
|
||
) {
|
||
return packages.some(
|
||
(p) =>
|
||
p.code === BreakfastPackageEnum.REGULAR_BREAKFAST ||
|
||
p.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST ||
|
||
p.code === BreakfastPackageEnum.SPECIAL_PACKAGE_BREAKFAST
|
||
)
|
||
}
|
||
|
||
function RoomHeader({
|
||
room,
|
||
hotel,
|
||
}: {
|
||
room: RoomProps["room"]
|
||
hotel: Hotel
|
||
}) {
|
||
if (!room) return null
|
||
|
||
return (
|
||
<div className={styles.roomHeader}>
|
||
<Subtitle textTransform="uppercase" color="burgundy">
|
||
{room.name}
|
||
</Subtitle>
|
||
<ToggleSidePeek
|
||
hotelId={hotel.operaId}
|
||
roomTypeCode={room.roomTypes[0].code}
|
||
intent="text"
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export function Room({ booking, room, hotel, user }: RoomProps) {
|
||
const intl = useIntl()
|
||
const lang = useLang()
|
||
|
||
if (!room) return null
|
||
|
||
const fromDate = dt(booking.checkInDate).locale(lang)
|
||
|
||
const mainBedWidthValueMsg = intl.formatMessage(
|
||
{ id: "{value} cm" },
|
||
{
|
||
value: room.bedType.mainBed.widthRange.min,
|
||
}
|
||
)
|
||
|
||
const mainBedWidthRangeMsg = intl.formatMessage(
|
||
{
|
||
id: "{min}–{max} cm",
|
||
},
|
||
{
|
||
min: room.bedType.mainBed.widthRange.min,
|
||
max: room.bedType.mainBed.widthRange.max,
|
||
}
|
||
)
|
||
|
||
const adultsMsg = intl.formatMessage(
|
||
{
|
||
id: "{adults, plural, one {# adult} other {# adults}}",
|
||
},
|
||
{
|
||
adults: booking.adults,
|
||
}
|
||
)
|
||
|
||
const childrenMsg = intl.formatMessage(
|
||
{
|
||
id: "{children, plural, one {# child} other {# children}}",
|
||
},
|
||
{
|
||
children: booking.childrenAges.length,
|
||
}
|
||
)
|
||
|
||
const adultsOnlyMsg = adultsMsg
|
||
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
||
|
||
return (
|
||
<div>
|
||
<article className={styles.room}>
|
||
<RoomHeader room={room} hotel={hotel} />
|
||
<div className={styles.booking}>
|
||
<div className={styles.chipContainer}>
|
||
{booking.packages
|
||
.filter((item) =>
|
||
Object.values(RoomPackageCodeEnum).includes(
|
||
item.code as RoomPackageCodeEnum
|
||
)
|
||
)
|
||
.map((item) => {
|
||
const Icon = getIconForFeatureCode(
|
||
item.code as RoomPackageCodeEnum
|
||
)
|
||
return (
|
||
<span className={styles.chip} key={item.code}>
|
||
<Icon width={16} height={16} color="burgundy" />
|
||
</span>
|
||
)
|
||
})}
|
||
</div>
|
||
<div className={styles.images}>
|
||
{room.images.slice(0, 2).map((image) => (
|
||
<Image
|
||
key={image.imageSizes.large}
|
||
src={image.imageSizes.large}
|
||
className={styles.image}
|
||
alt={room?.name ?? hotel.name}
|
||
width={700}
|
||
height={450}
|
||
/>
|
||
))}
|
||
</div>
|
||
<div className={styles.roomDetails}>
|
||
<div className={styles.bookingDetails}>
|
||
<div className={styles.row}>
|
||
<span className={styles.rowTitle}>
|
||
<ContractIcon color="grey80" width={20} height={20} />
|
||
<Body textTransform="bold" color="uiTextHighContrast">
|
||
{intl.formatMessage({ id: "Booking policy" })}
|
||
</Body>
|
||
</span>
|
||
<div className={styles.rowContent}>
|
||
<Body color="uiTextHighContrast">
|
||
{booking.rateDefinition.cancellationText}
|
||
</Body>
|
||
</div>
|
||
</div>
|
||
<div className={styles.row}>
|
||
<span className={styles.rowTitle}>
|
||
<RocketLaunch color="grey80" width={20} height={20} />
|
||
<Body textTransform="bold" color="uiTextHighContrast">
|
||
{intl.formatMessage({ id: "Rebooking" })}
|
||
</Body>
|
||
</span>
|
||
<div className={styles.rowContent}>
|
||
<Body color="uiTextHighContrast">
|
||
{intl.formatMessage(
|
||
{ id: "Until {time}, {date}" },
|
||
{ time: "18:00", date: fromDate.format("dddd D MMM") }
|
||
)}
|
||
</Body>
|
||
</div>
|
||
</div>
|
||
{booking.packages.some((item) =>
|
||
Object.values(RoomPackageCodeEnum).includes(
|
||
item.code as RoomPackageCodeEnum
|
||
)
|
||
) && (
|
||
<div className={styles.row}>
|
||
<span className={styles.rowTitle}>
|
||
<DoorOpenIcon color="grey80" width={20} height={20} />
|
||
<Body textTransform="bold" color="uiTextHighContrast">
|
||
{intl.formatMessage({ id: "Room type" })}
|
||
</Body>
|
||
</span>
|
||
<div className={styles.rowContent}>
|
||
<Body color="uiTextHighContrast">
|
||
{booking.packages
|
||
.filter((item) =>
|
||
Object.values(RoomPackageCodeEnum).includes(
|
||
item.code as RoomPackageCodeEnum
|
||
)
|
||
)
|
||
.map((item) => item.description)
|
||
.join(", ")}
|
||
</Body>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div className={styles.row}>
|
||
<span className={styles.rowTitle}>
|
||
<PersonIcon color="grey80" width={20} height={20} />
|
||
<Body textTransform="bold" color="uiTextHighContrast">
|
||
{intl.formatMessage({ id: "Guests" })}
|
||
</Body>
|
||
</span>
|
||
<div className={styles.rowContent}>
|
||
<Body color="uiTextHighContrast">
|
||
{booking.childrenAges.length > 0
|
||
? adultsAndChildrenMsg
|
||
: adultsOnlyMsg}
|
||
</Body>
|
||
</div>
|
||
</div>
|
||
<div className={styles.row}>
|
||
<span className={styles.rowTitle}>
|
||
<CoffeeIcon color="grey80" width={20} height={20} />
|
||
<Body textTransform="bold" color="uiTextHighContrast">
|
||
{intl.formatMessage({ id: "Breakfast" })}
|
||
</Body>
|
||
</span>
|
||
<div className={styles.rowContent}>
|
||
<Body color="uiTextHighContrast">
|
||
{hasBreakfastPackage(booking.packages)
|
||
? intl.formatMessage({ id: "Included" })
|
||
: intl.formatMessage({ id: "Not included" })}
|
||
</Body>
|
||
</div>
|
||
</div>
|
||
<div className={styles.row}>
|
||
<span className={styles.rowTitle}>
|
||
<BedDoubleIcon color="grey80" width={20} height={20} />
|
||
<Body textTransform="bold" color="uiTextHighContrast">
|
||
{intl.formatMessage({ id: "Bed preference" })}
|
||
</Body>
|
||
</span>
|
||
<div className={styles.rowContent}>
|
||
<Body color="uiTextHighContrast">
|
||
{room.bedType.mainBed.description}
|
||
{room.bedType.mainBed.widthRange.min ===
|
||
room.bedType.mainBed.widthRange.max
|
||
? ` (${mainBedWidthValueMsg})`
|
||
: ` (${mainBedWidthRangeMsg})`}
|
||
</Body>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<GuestDetails user={user} booking={booking} isMobile={false} />
|
||
</div>
|
||
|
||
<div className={styles.bookingInformation}>
|
||
{booking?.bookingCode && (
|
||
<IconChip color={"blue"} icon={<BookingCodeIcon color="blue" />}>
|
||
<Caption className={styles.bookingCode} color="blue">
|
||
<strong>{intl.formatMessage({ id: "Booking code" })}</strong>
|
||
{booking.bookingCode}
|
||
</Caption>
|
||
</IconChip>
|
||
)}
|
||
<div className={styles.priceDetails}>
|
||
<div className={styles.price}>
|
||
<Body color="uiTextHighContrast">
|
||
{intl.formatMessage({ id: "Room total" })}
|
||
</Body>
|
||
<Body color="uiTextHighContrast" textTransform="bold">
|
||
{formatPrice(intl, booking.totalPrice, booking.currencyCode)}
|
||
</Body>
|
||
</div>
|
||
|
||
<PriceDetailsModal
|
||
fromDate={dt(booking.checkInDate).format("YYYY-MM-DD")}
|
||
toDate={dt(booking.checkOutDate).format("YYYY-MM-DD")}
|
||
rooms={[
|
||
{
|
||
adults: booking.adults,
|
||
childrenInRoom: undefined,
|
||
roomPrice: {
|
||
perNight: {
|
||
requested: undefined,
|
||
local: {
|
||
currency: booking.currencyCode,
|
||
price: booking.totalPrice,
|
||
},
|
||
},
|
||
perStay: {
|
||
requested: undefined,
|
||
local: {
|
||
currency: booking.currencyCode,
|
||
price: booking.totalPrice,
|
||
},
|
||
},
|
||
},
|
||
roomType: room.name,
|
||
},
|
||
]}
|
||
totalPrice={{
|
||
requested: undefined,
|
||
local: {
|
||
currency: booking.currencyCode,
|
||
price: booking.totalPrice,
|
||
},
|
||
}}
|
||
vat={booking.vatPercentage}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
<GuestDetails user={user} booking={booking} isMobile={true} />
|
||
</div>
|
||
)
|
||
}
|