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
299 lines
11 KiB
TypeScript
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>
|
|
)
|
|
}
|