Files
web/components/HotelReservation/MyStay/Room/index.tsx
Pontus Dreij a0286603db Merged in feat(SW-1275)-cancel-booking-my-stay (pull request #1376)
Feat(SW-1275) cancel booking my stay

* feat(SW-1276) UI implementation Desktop part 1 for MyStay

* feat(SW-1276) UI implementation Desktop part 2 for MyStay

* feat(SW-1276) UI implementation Mobile part 1 for MyStay

* refactor: move files from MyStay/MyStay to MyStay

* feat(SW-1276) Sidepeek implementation

* feat(SW-1276): Refactoring

* feat(SW-1276) UI implementation Mobile part 2 for MyStay

* feat(SW-1276): translations

* feat(SW-1276) fixed skeleton

* feat(SW-1276): Added missing translations

* feat(SW-1276) fixed translations

* feat(SW-1275) cancel modal

* feat(SW-1275): Mutate cancel booking

* feat(SW-1275) added translations

* feat(SW-1275) match current cancellationReason

* feat(SW-1275) Added modal for manage stay

* feat(SW-1275) Added missing icon

* feat(SW-1275) New Dont cancel button

* feat(SW-1275) Added preperation for Cancellation number

* feat(SW-1275): added --modal-box-shadow

* feat(SW-1718) Add to calendar

* feat(SW-1718) general add to calendar


Approved-by: Niclas Edenvin
2025-02-21 09:06:15 +00:00

299 lines
11 KiB
TypeScript

"use client"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
import {
BedDoubleIcon,
CoffeeIcon,
ContractIcon,
DoorOpenIcon,
PersonIcon,
} from "@/components/Icons"
import RocketLaunch from "@/components/Icons/Refresh"
import Image from "@/components/Image"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
import { formatPrice } from "@/utils/numberFormatting"
import ToggleSidePeek from "../../EnterDetails/SelectedRoom/ToggleSidePeek"
import PriceDetailsModal from "../../PriceDetailsModal"
import GuestDetails from "./GuestDetails"
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"
className={styles.roomName}
>
{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)
return (
<div className={styles.roomContainer}>
<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
? intl.formatMessage(
{ id: "{adults} adults, {children} children" },
{
adults: booking.adults,
children: booking.childrenAges.length,
}
)
: intl.formatMessage(
{ id: "{adults} adults" },
{
adults: booking.adults,
}
)}
</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
? ` (${room.bedType.mainBed.widthRange.min} ${intl.formatMessage({ id: "cm" })})`
: ` (${room.bedType.mainBed.widthRange.min} - ${room.bedType.mainBed.widthRange.max} ${intl.formatMessage({ id: "cm" })})`}
</Body>
</div>
</div>
</div>
<GuestDetails user={user} booking={booking} isMobile={false} />
</div>
<div className={styles.bookingInformation}>
<div className={styles.bookingCode}></div>
<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>
)
}