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
This commit is contained in:
Pontus Dreij
2025-03-24 09:30:10 +00:00
parent c5e294c7ea
commit 74c5b47319
117 changed files with 5899 additions and 1901 deletions

View File

@@ -0,0 +1,355 @@
"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>
)
}