Files
web/apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/index.tsx
Pontus Dreij 74c5b47319 Merged in feat/SW-1737-design-mystay-multiroom (pull request #1565)
Feat/SW-1737 design mystay multiroom

* feat(SW-1737) Fixed member view of guest details

* feat(SW-1737) fix merge issues

* feat(SW-1737) Fixed price details

* feat(SW-1737) removed unused imports

* feat(SW-1737) removed true as statement

* feat(SW-1737) updated store handling

* feat(SW-1737) fixed bug showing double numbers

* feat(SW-1737) small design fixed

* feat(SW-1737) fixed rebase errors

* feat(SW-1737) fixed create booking error with dates

* feat(SW-1737) fixed view multiroom as singleroom

* feat(SW-1737) fixes for multiroom

* feat(SW-1737) fixed bookingsummary

* feat(SW-1737) dont hide modify dates

* feat(SW-1737) updated breakfast to handle number

* feat(SW-1737) Added red color if member rate

* feat(SW-1737) fix PR comments

* feat(SW-1737) updated member tiers svg

* feat(SW-1737) updated how to handle paymentMethodDescription

* feat(SW-1737) fixes after testing mystay

* feat(SW-1737) updated Room type to just use whats used

* feat(SW-1737) fixed access

* feat(SW-1737) refactor my stay after PR comments

* feat(SW-1737) fix roomNumber translation

* feat(SW-1737) removed log


Approved-by: Arvid Norlin
2025-03-24 09:30:10 +00:00

356 lines
12 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 { Typography } from "@scandic-hotels/design-system/Typography"
import { dt } from "@/lib/dt"
import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
import {
BedDoubleIcon,
BookingCodeIcon,
CoffeeIcon,
ContractIcon,
DoorOpenIcon,
PersonIcon,
} from "@/components/Icons"
import Refresh from "@/components/Icons/Refresh"
import Image from "@/components/Image"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import IconChip from "@/components/TempDesignSystem/IconChip"
import useLang from "@/hooks/useLang"
import GuestDetails from "../GuestDetails"
import Price from "../Price"
import PriceDetails from "../PriceDetails"
import { hasBreakfastPackage } from "../utils/hasBreakfastPackage"
import ToggleSidePeek from "./ToggleSidePeek"
import styles from "./room.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { Hotel, Room } from "@/types/hotel"
import type { User } from "@/types/user"
interface RoomProps {
bedType: Room["roomTypes"][number]
image: Room["images"][number]
hotel: Hotel
user: User | null
}
export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
const intl = useIntl()
const lang = useLang()
const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
const updateBookedRoom = useMyStayRoomDetailsStore(
(state) => state.actions.updateBookedRoom
)
if (!bookedRoom.roomNumber) {
return (
<div className={styles.room}>
<SkeletonShimmer width={"200px"} height="30px" />
<SkeletonShimmer width="100%" height="750px" />
</div>
)
}
const fromDate = dt(bookedRoom.checkInDate).locale(lang)
const {
adults,
childrenAges,
confirmationNumber,
bookingCode,
roomPrice,
packages,
rateDefinition,
isCancelled,
} = bookedRoom
const mainBedWidthValueMsg = intl.formatMessage(
{ id: "{value} cm" },
{
value: bedType.mainBed.widthRange.min,
}
)
const mainBedWidthRangeMsg = intl.formatMessage(
{
id: "{min}{max} cm",
},
{
min: bedType.mainBed.widthRange.min,
max: bedType.mainBed.widthRange.max,
}
)
const adultsMsg = intl.formatMessage(
{ id: "{adults, plural, one {# adult} other {# adults}}" },
{
adults,
}
)
const childrenMsg = intl.formatMessage(
{
id: "{children, plural, one {# child} other {# children}}",
},
{
children: childrenAges.length,
}
)
const adultsOnlyMsg = adultsMsg
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
const hasPackages = packages?.some((item) =>
Object.values(RoomPackageCodeEnum).includes(item.code)
)
return (
<div>
<article className={styles.room}>
<Typography variant="Title/Subtitle/lg">
<p className={styles.roomName}>{bookedRoom.roomName}</p>
</Typography>
<div className={styles.roomHeader}>
<div className={styles.roomHeaderContent}>
<div className={styles.chip}>
<Typography variant="Tag/sm">
<span>
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{
roomIndex: bookedRoom.roomNumber,
}
)}
</span>
</Typography>
</div>
<div className={styles.reference}>
<Typography variant="Body/Paragraph/mdBold">
<span>{intl.formatMessage({ id: "Reference" })}:</span>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<span>{confirmationNumber}</span>
</Typography>
</div>
</div>
<div className={styles.sidePeek}>
<ToggleSidePeek
hotelId={hotel.operaId}
roomTypeCode={bookedRoom.roomTypeCode}
intent="text"
/>
</div>
</div>
<div className={styles.booking}>
<div
className={`${styles.content} ${isCancelled ? styles.cancelled : ""}`}
>
{packages &&
packages.some((item) =>
Object.values(RoomPackageCodeEnum).includes(item.code)
) && (
<div className={styles.packages}>
{packages
.filter((item) =>
Object.values(RoomPackageCodeEnum).includes(item.code)
)
.map((item) => {
const Icon = getIconForFeatureCode(item.code)
return (
<span className={styles.package} key={item.code}>
<Icon width={16} height={16} color="burgundy" />
</span>
)
})}
</div>
)}
<div className={styles.imageContainer}>
<Image
key={image.imageSizes.small}
src={image.imageSizes.small}
className={styles.image}
alt={bookedRoom.roomName}
width={640}
height={960}
/>
</div>
<div className={styles.roomDetails}>
<div className={styles.bookingDetails}>
<div className={styles.row}>
<span className={styles.rowTitle}>
<PersonIcon color="grey80" width={20} height={20} />
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Guests" })}</p>
</Typography>
</span>
<div className={styles.rowContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">
{childrenAges.length > 0
? adultsAndChildrenMsg
: adultsOnlyMsg}
</p>
</Typography>
</div>
</div>
<div className={styles.row}>
<span className={styles.rowTitle}>
<ContractIcon color="grey80" width={20} height={20} />
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Terms" })}</p>
</Typography>
</span>
<div className={styles.rowContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">
{rateDefinition.cancellationText}
</p>
</Typography>
</div>
</div>
<div className={styles.row}>
<span className={styles.rowTitle}>
<Refresh color="grey80" width={20} height={20} />
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Modify By" })}</p>
</Typography>
</span>
<div className={styles.rowContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">
{intl.formatMessage(
{ id: "Until {time}, {date}" },
{ time: "18:00", date: fromDate.format("dddd D MMM") }
)}
</p>
</Typography>
</div>
</div>
<div className={styles.row}>
<span className={styles.rowTitle}>
<CoffeeIcon color="grey80" width={20} height={20} />
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Breakfast" })}</p>
</Typography>
</span>
<div className={styles.rowContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">
{hasBreakfastPackage(
packages?.map((pkg) => ({
code: pkg.code,
})) ?? []
)
? intl.formatMessage({ id: "Included" })
: intl.formatMessage({ id: "Not included" })}
</p>
</Typography>
</div>
</div>
{hasPackages && (
<div className={styles.row}>
<span className={styles.rowTitle}>
<DoorOpenIcon color="grey80" width={20} height={20} />
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({ id: "Room classification" })}
</p>
</Typography>
</span>
<div className={styles.rowContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">
{packages!
.filter((item) =>
Object.values(RoomPackageCodeEnum).includes(
item.code
)
)
.map((item) => item.description)
.join(", ")}
</p>
</Typography>
</div>
</div>
)}
<div className={styles.row}>
<span className={styles.rowTitle}>
<BedDoubleIcon color="grey80" width={20} height={20} />
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Bed preference" })}</p>
</Typography>
</span>
<div className={styles.rowContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">
{bedType.mainBed.description}
{bedType.mainBed.widthRange.min ===
bedType.mainBed.widthRange.max
? ` (${mainBedWidthValueMsg})`
: ` (${mainBedWidthRangeMsg})`}
</p>
</Typography>
</div>
</div>
</div>
<div className={styles.guestDetailsDesktopWrapper}>
<GuestDetails
user={user}
booking={bookedRoom}
updateRoom={updateBookedRoom}
/>
</div>
</div>
</div>
<div className={styles.bookingInformation}>
{bookingCode && (
<IconChip color={"blue"} icon={<BookingCodeIcon color="blue" />}>
<Typography variant="Body/Supporting text (caption)/smBold">
<p className={styles.bookingCode}>
<strong>
{intl.formatMessage({ id: "Booking code" })}
</strong>
: {bookingCode}
</p>
</Typography>
</IconChip>
)}
<div className={styles.priceDetails}>
<div className={styles.price}>
<Typography variant="Body/Lead text">
<p color="uiTextHighContrast">
{intl.formatMessage({ id: "Room total" })}
</p>
</Typography>
<Price
price={isCancelled ? 0 : roomPrice.perStay.local.price}
variant="Title/Subtitle/lg"
isMember={rateDefinition.isMemberRate}
/>
</div>
</div>
</div>
</div>
<PriceDetails />
<div className={styles.guestDetailsMobileWrapper}>
<GuestDetails
user={user}
booking={bookedRoom}
updateRoom={updateBookedRoom}
/>
</div>
</article>
</div>
)
}