Files
web/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/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

301 lines
9.3 KiB
TypeScript

"use client"
import { use, useEffect } from "react"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { BookingStatusEnum } from "@/constants/booking"
import { dt } from "@/lib/dt"
import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
import CrossCircleIcon from "@/components/Icons/CrossCircle"
import Image from "@/components/Image"
import Divider from "@/components/TempDesignSystem/Divider"
import IconChip from "@/components/TempDesignSystem/IconChip"
import useLang from "@/hooks/useLang"
import { getIconForFeatureCode } from "../../utils"
import Price from "../Price"
import { hasBreakfastPackage } from "../utils/hasBreakfastPackage"
import { mapRoomDetails } from "../utils/mapRoomDetails"
import MultiRoomSkeleton from "./MultiRoomSkeleton"
import ToggleSidePeek from "./ToggleSidePeek"
import styles from "./multiRoom.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { Room } from "@/types/hotel"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
import type { User } from "@/types/user"
interface MultiRoomProps {
booking?: BookingConfirmation["booking"]
room?:
| (Room & {
bedType: Room["roomTypes"][number]
})
| null
bookingPromise?: Promise<BookingConfirmation | null>
index?: number
user?: User | null
}
export default function MultiRoom({
room: initialRoom,
booking: initialBooking,
bookingPromise,
index,
user,
}: MultiRoomProps) {
const intl = useIntl()
const lang = useLang()
const addRoomPrice = useMyStayTotalPriceStore(
(state) => state.actions.addRoomPrice
)
const addLinkedReservationRoom = useMyStayRoomDetailsStore(
(state) => state.actions.addLinkedReservationRoom
)
const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
const linkedReservationRooms = useMyStayRoomDetailsStore(
(state) => state.linkedReservationRooms
)
const allRooms = [bookedRoom, ...linkedReservationRooms]
// Resolve promise data directly without setState
let bookingInfo = initialBooking
let roomInfo = initialRoom
if (bookingPromise) {
const promiseData = use(bookingPromise)
if (promiseData) {
bookingInfo = promiseData.booking
roomInfo = promiseData.room
}
}
const isBookingCancelled =
bookingInfo?.reservationStatus === BookingStatusEnum.Cancelled
const multiRoom = allRooms.find(
(room) => room.confirmationNumber === bookingInfo?.confirmationNumber
)
// Update stores when data is available
useEffect(() => {
if (bookingInfo) {
addRoomPrice({
id: bookingInfo.confirmationNumber,
totalPrice: isBookingCancelled ? 0 : bookingInfo.totalPrice,
currencyCode: bookingInfo.currencyCode,
isMainBooking: false,
})
// Add room details to the store
addLinkedReservationRoom(
mapRoomDetails({
booking: bookingInfo,
room: roomInfo ?? null,
roomNumber: index !== undefined ? index + 2 : 1,
})
)
}
}, [
bookingInfo,
roomInfo,
index,
isBookingCancelled,
addRoomPrice,
addLinkedReservationRoom,
])
if (!multiRoom?.roomNumber) return <MultiRoomSkeleton />
const {
adults,
checkInDate,
childrenAges,
confirmationNumber,
cancellationNumber,
hotelId,
roomPrice,
packages,
rateDefinition,
isCancelled,
} = multiRoom
const fromDate = dt(checkInDate).locale(lang)
const adultsMsg = intl.formatMessage(
{ id: "{adults, plural, one {# adult} other {# adults}}" },
{
adults: adults,
}
)
const childrenMsg = intl.formatMessage(
{
id: "{children, plural, one {# child} other {# children}}",
},
{
children: childrenAges.length,
}
)
const adultsOnlyMsg = adultsMsg
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
return (
<article className={styles.multiRoom}>
<Typography variant="Title/smRegular">
<h3 className={styles.roomName}>{roomInfo?.name}</h3>
</Typography>
<div className={styles.roomHeader}>
{isCancelled ? (
<IconChip
color={"red"}
icon={<CrossCircleIcon width={20} height={20} color="red" />}
>
<Typography variant="Body/Supporting text (caption)/smBold">
<span>{intl.formatMessage({ id: "Cancelled" })}</span>
</Typography>
</IconChip>
) : (
<div className={styles.chip}>
<Typography variant="Tag/sm">
<span>
{intl.formatMessage({ id: "Room" }) +
" " +
(index !== undefined ? index + 2 : 1)}
</span>
</Typography>
</div>
)}
<div className={styles.reference}>
<Typography variant="Body/Supporting text (caption)/smBold">
{isCancelled ? (
<span>{intl.formatMessage({ id: "Cancellation no" })}:</span>
) : (
<span>{intl.formatMessage({ id: "Reference" })}:</span>
)}
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
{isCancelled ? (
<span className={styles.cancellationNumber}>
{cancellationNumber}
</span>
) : (
<span>{confirmationNumber}</span>
)}
</Typography>
</div>
<div className={styles.toggleSidePeek}>
<ToggleSidePeek
hotelId={hotelId}
roomTypeCode={roomInfo?.roomTypes[0].code}
user={user ?? undefined}
confirmationNumber={confirmationNumber}
/>
</div>
</div>
<div
className={`${styles.multiRoomCard} ${isCancelled ? styles.cancelled : ""}`}
>
{packages &&
packages.some((item) =>
Object.values(RoomPackageCodeEnum).includes(
item.code as RoomPackageCodeEnum
)
) && (
<div className={styles.packages}>
{packages
.filter((item) =>
Object.values(RoomPackageCodeEnum).includes(
item.code as RoomPackageCodeEnum
)
)
.map((item) => {
const Icon = getIconForFeatureCode(
item.code as RoomPackageCodeEnum
)
return (
<span className={styles.package} key={item.code}>
<Icon width={16} height={16} color="burgundy" />
</span>
)
})}
</div>
)}
<div className={styles.imageContainer}>
<Image
src={roomInfo?.images[0]?.imageSizes.small ?? ""}
alt={roomInfo?.name ?? ""}
fill
/>
</div>
<div className={styles.details}>
<div className={styles.row}>
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Guests" })}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{childrenAges.length > 0 ? adultsAndChildrenMsg : adultsOnlyMsg}
</p>
</Typography>
</div>
<div className={styles.row}>
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Terms" })}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>{rateDefinition.cancellationText}</p>
</Typography>
</div>
<div className={styles.row}>
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Modify By" })}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">
18:00, {fromDate.format("dddd D MMM")}
</p>
</Typography>
</div>
<div className={styles.row}>
<Typography variant="Body/Paragraph/mdBold">
<p>{intl.formatMessage({ id: "Breakfast" })}</p>
</Typography>
<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>
<Divider color="subtle" />
<div className={styles.row}>
<Typography variant="Body/Lead text">
<p>{intl.formatMessage({ id: "Room total" })}</p>
</Typography>
<Price
price={isCancelled ? 0 : roomPrice.perStay.local.price}
variant="Body/Paragraph/mdBold"
isMember={rateDefinition.isMemberRate}
/>
</div>
</div>
</div>
</article>
)
}