Merged in SW-2591-test-confirmation-page-incorrect-side-peek-is-displayed-upon-tapping-the-view-room-details-link (pull request #2057)

SW-2591 test confirmation page incorrect side peek is displayed upon tapping the view room details link
* fix(SW-2591): remove redundant div


Approved-by: Simon.Emanuelsson
This commit is contained in:
Bianca Widstam
2025-05-12 14:07:54 +00:00
parent 91f9cebadb
commit e78d9f2a86
12 changed files with 190 additions and 274 deletions

View File

@@ -22,7 +22,6 @@ export function LinkedReservation({
checkOutTime,
refId,
roomIndex,
roomNumber,
}: LinkedReservationProps) {
const lang = useLang()
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
@@ -87,7 +86,6 @@ export function LinkedReservation({
checkOutTime={checkOutTime}
img={data.room.images[0]}
roomName={data.room.name}
roomNumber={roomNumber}
/>
)
}

View File

@@ -1,127 +0,0 @@
"use client"
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { BookingStatusEnum } from "@/constants/booking"
import { trpc } from "@/lib/trpc/client"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import { convertToChildType } from "@/components/HotelReservation/utils/convertToChildType"
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
import { getBookedHotelRoom } from "@/utils/booking"
import styles from "./sidePeek.module.css"
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import { PackageTypeEnum } from "@/types/enums/packages"
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
interface RoomDetailsSidePeekProps {
booking: BookingConfirmationSchema
roomNumber?: number
}
export default function RoomDetailsSidePeek({
booking,
roomNumber = 1,
}: RoomDetailsSidePeekProps) {
const intl = useIntl()
const user = trpc.user.getSafely.useQuery()
const roomCategories = useBookingConfirmationStore(
(state) => state.roomCategories
)
const hotelRoom = getBookedHotelRoom(roomCategories, booking.roomTypeCode)
const breakfastPackage = booking.packages.find(
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
)
const breakfast: Omit<BreakfastPackage, "requestedPrice"> | null =
breakfastPackage
? {
code: breakfastPackage.code,
description: breakfastPackage.description,
localPrice: {
currency: breakfastPackage.currency,
price: breakfastPackage.unitPrice,
totalPrice: breakfastPackage.totalPrice,
},
packageType: PackageTypeEnum.BreakfastAdult,
}
: null
const childrenInRoom = convertToChildType(
booking.childrenAges,
booking.childBedPreferences
)
const priceType = getPriceType(
booking.cheques,
booking.roomPoints,
booking.vouchers
)
const featuresPackages = booking.packages.filter(
(pkg) =>
pkg.code === RoomPackageCodeEnum.PET_ROOM ||
pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM ||
pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
)
const packages = featuresPackages.map((pkg) => ({
code: pkg.code as RoomPackageCodeEnum,
description: pkg.description,
inventories: [],
itemCode: "",
localPrice: {
currency: pkg.currency,
price: pkg.unitPrice,
totalPrice: pkg.totalPrice,
},
requestedPrice: {
currency: pkg.currency,
price: pkg.unitPrice,
totalPrice: pkg.totalPrice,
},
}))
const room = {
...booking,
bedType: {
description: hotelRoom?.bedType.mainBed.description ?? "",
roomTypeCode: hotelRoom?.bedType.code ?? "",
},
breakfast,
childrenInRoom,
isCancelled: booking.reservationStatus === BookingStatusEnum.Cancelled,
packages,
priceType,
roomName: hotelRoom?.name ?? "",
roomNumber,
terms: booking.rateDefinition.cancellationText,
}
return (
<DialogTrigger>
<Typography variant="Body/Supporting text (caption)/smBold">
<ButtonRAC className={styles.trigger}>
<span>
{intl.formatMessage({ defaultMessage: "View room details" })}
</span>
<MaterialIcon color="CurrentColor" icon="chevron_right" size={20} />
</ButtonRAC>
</Typography>
<BookedRoomSidePeek
hotelRoom={hotelRoom}
room={room}
user={user.data ?? null}
/>
</DialogTrigger>
)
}

View File

@@ -0,0 +1,48 @@
"use client"
import { DialogTrigger } from "react-aria-components"
import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import { RoomSidePeekContent } from "@/components/SidePeeks/RoomSidePeek/RoomSidePeekContent"
import SidePeekSelfControlled from "@/components/TempDesignSystem/SidePeekSelfControlled"
import { getBookedHotelRoom } from "@/utils/booking"
interface RoomDetailsSidePeekProps {
roomTypeCode: string
}
export default function RoomDetailsSidePeek({
roomTypeCode,
}: RoomDetailsSidePeekProps) {
const { roomCategories } = useBookingConfirmationStore((state) => ({
roomCategories: state.roomCategories,
}))
const room = getBookedHotelRoom(roomCategories, roomTypeCode)
const intl = useIntl()
if (!room) {
return null
}
return (
<DialogTrigger>
<Button
variant="Text"
color="Primary"
size="Small"
typography="Body/Supporting text (caption)/smBold"
>
{intl.formatMessage({ defaultMessage: "View room details" })}
<MaterialIcon icon="chevron_right" size={14} color="CurrentColor" />
</Button>
<SidePeekSelfControlled title={room.name}>
<RoomSidePeekContent room={room} />
</SidePeekSelfControlled>
</DialogTrigger>
)
}

View File

@@ -24,7 +24,6 @@ export default function Room({
checkOutTime,
img,
roomName,
roomNumber = 1,
}: RoomProps) {
const intl = useIntl()
const lang = useLang()
@@ -113,7 +112,7 @@ export default function Room({
<Typography variant="Title/Subtitle/md">
<h2>{roomName}</h2>
</Typography>
<RoomDetailsSidePeek booking={booking} roomNumber={roomNumber} />
<RoomDetailsSidePeek roomTypeCode={booking.roomTypeCode} />
</div>
<Typography variant="Body/Paragraph/mdRegular">
<ul className={styles.details}>

View File

@@ -45,6 +45,7 @@
.roomName {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--Spacing-x-half);
grid-column: 1/-1;
}

View File

@@ -1,10 +0,0 @@
.trigger {
align-items: center;
background: none;
border: none;
color: var(--Component-Button-Brand-Secondary-On-fill-Default);
cursor: pointer;
display: flex;
gap: var(--Space-x1);
padding: var(--Space-x025) 0;
}

View File

@@ -58,7 +58,6 @@ export default async function Rooms({
checkOutTime={checkOutTime}
refId={reservation.refId}
roomIndex={idx + 1}
roomNumber={idx + 2}
/>
</div>
))}

View File

@@ -0,0 +1,138 @@
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import ImageGallery from "@/components/ImageGallery"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import { getBedIconName } from "../bedIcon"
import { FacilityIcon } from "../facilityIcon"
import styles from "./roomSidePeekContent.module.css"
import type { Room } from "@/types/hotel"
interface RoomSidePeekContentProps {
room: Room
}
export function RoomSidePeekContent({ room }: RoomSidePeekContentProps) {
const intl = useIntl()
const roomSize = room.roomSize
const totalOccupancy = room.totalOccupancy
const roomDescription = room.descriptions.medium
const galleryImages = mapApiImagesToGalleryImages(room.images)
return (
<div className={styles.wrapper}>
<div className={styles.mainContent}>
{totalOccupancy && (
<Caption color="uiTextMediumContrast">
{intl.formatMessage(
{
defaultMessage:
"Max. {max, plural, one {{range} guest} other {{range} guests}}",
},
{
max: totalOccupancy.max,
range: totalOccupancy.range,
}
)}
</Caption>
)}
{roomSize && (
<Caption color="uiTextMediumContrast">
{roomSize.min === roomSize.max
? intl.formatMessage(
{
defaultMessage: "{roomSize} m²",
},
{
roomSize: roomSize.min,
}
)
: intl.formatMessage(
{
defaultMessage: "{roomSizeMin}{roomSizeMax} m²",
},
{
roomSizeMin: roomSize.min,
roomSizeMax: roomSize.max,
}
)}
</Caption>
)}
<div className={styles.imageContainer}>
<ImageGallery images={galleryImages} title={room.name} height={280} />
</div>
<Body color="uiTextHighContrast">{roomDescription}</Body>
</div>
<div className={styles.listContainer}>
<Subtitle type="two" color="uiTextHighContrast">
{intl.formatMessage({
defaultMessage: "Room amenities",
})}
</Subtitle>
<ul className={styles.facilityList}>
{room.roomFacilities
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((facility) => {
return (
<li key={facility.name}>
<FacilityIcon
name={facility.icon}
size={24}
color="Icon/Default"
/>
<Body asChild color="uiTextMediumContrast">
<span>
{facility.availableInAllRooms
? facility.name
: intl.formatMessage(
{
defaultMessage:
"{facility} (available in some rooms)",
},
{
facility: facility.name,
}
)}
</span>
</Body>
</li>
)
})}
</ul>
</div>
<div className={styles.listContainer}>
<Subtitle type="two" color="uiTextHighContrast">
{intl.formatMessage({
defaultMessage: "Bed options",
})}
</Subtitle>
<Body color="grey">
{intl.formatMessage({
defaultMessage: "Based on availability",
})}
</Body>
<ul className={styles.bedOptions}>
{room.roomTypes.map((roomType) => {
const bedIcon = getBedIconName(roomType.mainBed.type)
return (
<li key={roomType.code}>
<MaterialIcon icon={bedIcon} color="Icon/Feedback/Neutral" />
<Body color="uiTextMediumContrast" asChild>
<span>{roomType.mainBed.description}</span>
</Body>
</li>
)
})}
</ul>
</div>
</div>
)
}

View File

@@ -1,18 +1,6 @@
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import ImageGallery from "@/components/ImageGallery"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import { getBedIconName } from "./bedIcon"
import { FacilityIcon } from "./facilityIcon"
import styles from "./roomSidePeek.module.css"
import { RoomSidePeekContent } from "./RoomSidePeekContent"
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
import type { RoomSidePeekProps } from "@/types/components/sidePeeks/roomSidePeek"
@@ -22,129 +10,13 @@ export default function RoomSidePeek({
activeSidePeek,
close,
}: RoomSidePeekProps) {
const intl = useIntl()
const roomSize = room.roomSize
const totalOccupancy = room.totalOccupancy
const roomDescription = room.descriptions.medium
const galleryImages = mapApiImagesToGalleryImages(room.images)
return (
<SidePeek
title={room.name}
isOpen={activeSidePeek === SidePeekEnum.roomDetails}
handleClose={close}
>
<div className={styles.wrapper}>
<div className={styles.mainContent}>
{totalOccupancy && (
<Caption color="uiTextMediumContrast">
{intl.formatMessage(
{
defaultMessage:
"Max. {max, plural, one {{range} guest} other {{range} guests}}",
},
{
max: totalOccupancy.max,
range: totalOccupancy.range,
}
)}
</Caption>
)}
{roomSize && (
<Caption color="uiTextMediumContrast">
{roomSize.min === roomSize.max
? intl.formatMessage(
{
defaultMessage: "{roomSize} m²",
},
{
roomSize: roomSize.min,
}
)
: intl.formatMessage(
{
defaultMessage: "{roomSizeMin}{roomSizeMax} m²",
},
{
roomSizeMin: roomSize.min,
roomSizeMax: roomSize.max,
}
)}
</Caption>
)}
<div className={styles.imageContainer}>
<ImageGallery
images={galleryImages}
title={room.name}
height={280}
/>
</div>
<Body color="uiTextHighContrast">{roomDescription}</Body>
</div>
<div className={styles.listContainer}>
<Subtitle type="two" color="uiTextHighContrast">
{intl.formatMessage({
defaultMessage: "Room amenities",
})}
</Subtitle>
<ul className={styles.facilityList}>
{room.roomFacilities
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((facility) => {
return (
<li key={facility.name}>
<FacilityIcon
name={facility.icon}
size={24}
color="Icon/Default"
/>
<Body asChild color="uiTextMediumContrast">
<span>
{facility.availableInAllRooms
? facility.name
: intl.formatMessage(
{
defaultMessage:
"{facility} (available in some rooms)",
},
{
facility: facility.name,
}
)}
</span>
</Body>
</li>
)
})}
</ul>
</div>
<div className={styles.listContainer}>
<Subtitle type="two" color="uiTextHighContrast">
{intl.formatMessage({
defaultMessage: "Bed options",
})}
</Subtitle>
<Body color="grey">
{intl.formatMessage({
defaultMessage: "Based on availability",
})}
</Body>
<ul className={styles.bedOptions}>
{room.roomTypes.map((roomType) => {
const bedIcon = getBedIconName(roomType.mainBed.type)
return (
<li key={roomType.code}>
<MaterialIcon icon={bedIcon} color="Icon/Feedback/Neutral" />
<Body color="uiTextMediumContrast" asChild>
<span>{roomType.mainBed.description}</span>
</Body>
</li>
)
})}
</ul>
</div>
</div>
<RoomSidePeekContent room={room} />
</SidePeek>
)
}

View File

@@ -3,7 +3,6 @@ export interface LinkedReservationProps {
checkOutTime: string
refId: string
roomIndex: number
roomNumber: number
}
export interface RetryProps {

View File

@@ -6,5 +6,4 @@ export interface RoomProps {
checkOutTime: string
img: NonNullable<BookingConfirmation["room"]>["images"][number]
roomName: NonNullable<BookingConfirmation["room"]>["name"]
roomNumber?: number
}