Files
web/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Room/index.tsx
Hrishikesh Vaipurkar a5790ee454 Merged in chore/SW-2878-extract-booking-confirmation-pag (pull request #2779)
Chore/SW-2878 extract booking confirmation pag

* chore(SW-2878): Moved booking confirmation page to booking-flow package

* chore(SW-2878): Fixed promo styles as per design

* chore(SW-2878): Kept tiny duplicate function to avoid export from booking-flow package


Approved-by: Anton Gunnarsson
2025-09-10 07:50:48 +00:00

343 lines
11 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.
import { cx } from "class-variance-authority"
import { useIntl } from "react-intl"
import { getFeatureDescription } from "@scandic-hotels/booking-flow/utils/getRoomFeatureDescription"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Button } from "@scandic-hotels/design-system/Button"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
import Breakfast from "./Breakfast"
import styles from "./room.module.css"
import type { Room as RoomType } from "@/types/stores/enter-details"
interface RoomProps {
room: RoomType
roomNumber: number
roomCount: number
isUserLoggedIn: boolean
nightsCount: number
defaultCurrency: CurrencyEnum
}
export default function Room({
room,
roomNumber,
roomCount,
isUserLoggedIn,
nightsCount,
defaultCurrency,
}: RoomProps) {
const intl = useIntl()
const adults = room.adults
const childrenInRoom = room.childrenInRoom
const childrenBeds = childrenInRoom?.reduce(
(acc, value) => {
const bedType = Number(value.bed)
if (bedType === ChildBedMapEnum.IN_ADULTS_BED) {
return acc
}
const count = acc.get(bedType) ?? 0
acc.set(bedType, count + 1)
return acc
},
new Map<ChildBedMapEnum, number>([
[ChildBedMapEnum.IN_CRIB, 0],
[ChildBedMapEnum.IN_EXTRA_BED, 0],
])
)
const childBedCrib = childrenBeds?.get(ChildBedMapEnum.IN_CRIB)
const childBedExtraBed = childrenBeds?.get(ChildBedMapEnum.IN_EXTRA_BED)
const isFirstRoomMember = roomNumber === 1 && isUserLoggedIn
const isOrWillBecomeMember = !!(
room.guest.join ||
room.guest.membershipNo ||
isFirstRoomMember
)
const showMemberPrice = !!(
isOrWillBecomeMember &&
"member" in room.roomRate &&
room.roomRate.member
)
const isSpecialRate =
"corporateCheque" in room.roomRate ||
"redemption" in room.roomRate ||
"voucher" in room.roomRate ||
room.roomRate.bookingCode ||
room.roomRate.rateDefinition.isCampaignRate
const showDiscounted = isSpecialRate || showMemberPrice
const adultsMsg = intl.formatMessage(
{
defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
},
{ totalAdults: adults }
)
const guestsParts = [adultsMsg]
if (childrenInRoom?.length) {
const childrenMsg = intl.formatMessage(
{
defaultMessage:
"{totalChildren, plural, one {# child} other {# children}}",
},
{ totalChildren: childrenInRoom.length }
)
guestsParts.push(childrenMsg)
}
let rateDetails = room.rateDetails
if (room.memberRateDetails) {
if (showMemberPrice) {
rateDetails = room.memberRateDetails
}
}
const guests = guestsParts.join(", ")
const zeroPrice = formatPrice(intl, 0, defaultCurrency)
let price = formatPrice(
intl,
room.roomPrice.perStay.local.price,
room.roomPrice.perStay.local.currency,
room.roomPrice.perStay.local.additionalPrice,
room.roomPrice.perStay.local.additionalPriceCurrency
)
let currency: string = room.roomPrice.perStay.local.currency
const isVoucher = "voucher" in room.roomRate
if (isVoucher) {
currency = CurrencyEnum.Voucher
price = formatPrice(
intl,
room.roomPrice.perStay.local.price,
currency,
room.roomPrice.perStay.local.additionalPrice,
room.roomPrice.perStay.local.additionalPriceCurrency
)
}
return (
<>
<div className={styles.room} data-testid={`summary-room-${roomNumber}`}>
<div>
{roomCount > 1 ? (
<Typography variant="Body/Supporting text (caption)/smBold">
<p className={styles.roomTitle}>
{intl.formatMessage(
{
defaultMessage: "Room {roomIndex}",
},
{
roomIndex: roomNumber,
}
)}
</p>
</Typography>
) : null}
<div className={styles.entry}>
<div>
<Typography variant="Body/Paragraph/mdBold">
<p>{room.roomType}</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<div className={styles.additionalInformation}>
<p>{guests}</p>
<p>{room.cancellationText}</p>
</div>
</Typography>
</div>
<Typography variant="Body/Paragraph/mdRegular">
<div className={styles.prices}>
<p
className={cx(styles.price, {
[styles.discounted]: showDiscounted,
})}
>
{price}
</p>
{showDiscounted && room.roomPrice.perStay.local.regularPrice ? (
<s className={styles.strikeThroughRate}>
{formatPrice(
intl,
room.roomPrice.perStay.local.regularPrice,
currency
)}
</s>
) : null}
</div>
</Typography>
</div>
{rateDetails?.length ? (
<div className={styles.ctaWrapper}>
<Modal
trigger={
<Button
className={styles.termsButton}
variant="Text"
typography="Body/Supporting text (caption)/smBold"
wrapping={false}
>
{intl.formatMessage({
defaultMessage: "Rate details",
})}
<MaterialIcon
icon="chevron_right"
size={20}
color="CurrentColor"
/>
</Button>
}
title={room.cancellationText}
>
<div className={styles.terms}>
{rateDetails.map((info) => (
<Typography key={info} variant="Body/Paragraph/mdRegular">
<p className={styles.termsText}>
<MaterialIcon
icon="check"
color="Icon/Feedback/Success"
size={20}
className={styles.termsIcon}
/>
{info}
</p>
</Typography>
))}
</div>
</Modal>
</div>
) : null}
</div>
{room.roomFeatures
? room.roomFeatures.map((feature) => (
<Typography key={feature.code} variant="Body/Paragraph/mdRegular">
<div className={styles.entry}>
<p>
{getFeatureDescription(
feature.code,
feature.description,
intl
)}
</p>
<div className={styles.prices}>
<span className={styles.price}>
{formatPrice(
intl,
feature.localPrice.price,
feature.localPrice.currency
)}
</span>
</div>
</div>
</Typography>
))
: null}
{room.bedType ? (
<Typography variant="Body/Paragraph/mdRegular">
<div className={styles.entry}>
<div>
<p>{room.bedType.description}</p>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
defaultMessage: "Subject to availability",
})}
</p>
</Typography>
</div>
<div className={styles.prices}>
<span className={styles.price}>{zeroPrice}</span>
</div>
</div>
</Typography>
) : null}
{childBedCrib ? (
<Typography variant="Body/Paragraph/mdRegular">
<div className={styles.entry}>
<div>
<p>
{intl.formatMessage(
{
defaultMessage: "Crib (child) × {count}",
},
{ count: childBedCrib }
)}
</p>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
defaultMessage: "Subject to availability",
})}
</p>
</Typography>
</div>
<div className={styles.prices}>
<span className={styles.price}>
{formatPrice(intl, 0, currency)}
</span>
</div>
</div>
</Typography>
) : null}
{childBedExtraBed ? (
<Typography variant="Body/Paragraph/mdRegular">
<div className={styles.entry}>
<div>
<p>
{intl.formatMessage(
{
defaultMessage: "Extra bed (child) × {count}",
},
{
count: childBedExtraBed,
}
)}
</p>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
defaultMessage: "Subject to availability",
})}
</p>
</Typography>
</div>
<div className={styles.prices}>
<span className={styles.price}>
{formatPrice(intl, 0, currency)}
</span>
</div>
</div>
</Typography>
) : null}
<Breakfast
adults={room.adults}
breakfast={room.breakfast}
breakfastIncluded={room.breakfastIncluded}
guests={guests}
nights={nightsCount}
/>
</div>
<Divider color="Border/Divider/Subtle" />
</>
)
}