Files
web/apps/scandic-web/components/HotelReservation/MyStay/Room/index.tsx
2025-03-13 08:29:48 +00:00

333 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
)
}