Files
web/packages/booking-flow/lib/components/EnterDetails/Summary/UI/Room/index.tsx
Bianca Widstam 200cf3f617 Merged in fix/BOOK-584-red-price-strikethrough (pull request #3298)
fix(BOOK-584): show discounted if specialrate or member

* fix(BOOK-584): show discounted if specialrate or member


Approved-by: Anton Gunnarsson
2025-12-08 14:23:44 +00:00

366 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.
import { cx } from "class-variance-authority"
import { useIntl } from "react-intl"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { BookingCodeChip } from "@scandic-hotels/design-system/BookingCodeChip"
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 { getRoomFeatureDescription } from "../../../../../utils/getRoomFeatureDescription"
import { SummaryBreakfast } from "../../../../SummaryBreakfast"
import { isDiscounted } from "../utils"
import styles from "./room.module.css"
import type { Room as RoomType } from "../../../../../stores/enter-details/types.ts"
interface RoomProps {
room: RoomType
roomNumber: number
roomCount: number
isUserLoggedIn: boolean
nightsCount: number
defaultCurrency: CurrencyEnum
showBookingCodeChip?: boolean
hotelOffersBreakfast?: boolean
}
export default function Room({
room,
roomNumber,
roomCount,
isUserLoggedIn,
nightsCount,
defaultCurrency,
showBookingCodeChip = false,
hotelOffersBreakfast = true,
}: 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 showDiscounted = isFirstRoomMember || isDiscounted(room)
const adultsMsg = intl.formatMessage(
{
id: "common.numberOfAdults",
defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
},
{ totalAdults: adults }
)
const guestsParts = [adultsMsg]
if (childrenInRoom?.length) {
const childrenMsg = intl.formatMessage(
{
id: "common.numberOfChildren",
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
)
}
const breakfastPrice = room.breakfast
? room.breakfast?.localPrice.price * adults * nightsCount
: room.breakfast
const breakfastCurrency = room.breakfast
? room.breakfast?.localPrice.currency
: null
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(
{
id: "booking.roomIndex",
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({
id: "booking.rateDetails",
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>
{getRoomFeatureDescription(
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({
id: "booking.subjectToAvailability",
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(
{
id: "booking.numberOfCribs",
defaultMessage: "Crib (child) × {count}",
},
{ count: childBedCrib }
)}
</p>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
id: "booking.subjectToAvailability",
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(
{
id: "booking.extraBedsCount",
defaultMessage: "Extra bed (child) × {count}",
},
{
count: childBedExtraBed,
}
)}
</p>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
id: "booking.subjectToAvailability",
defaultMessage: "Subject to availability",
})}
</p>
</Typography>
</div>
<div className={styles.prices}>
<span className={styles.price}>
{formatPrice(intl, 0, currency)}
</span>
</div>
</div>
</Typography>
) : null}
<SummaryBreakfast
breakfastIncluded={room.breakfastIncluded}
breakfastPrice={breakfastPrice}
breakfastCurrency={breakfastCurrency}
guests={guests}
hotelOffersBreakfast={hotelOffersBreakfast}
/>
</div>
{showBookingCodeChip && (
<BookingCodeChip
isCampaign={room.roomRate.rateDefinition.isCampaignRate}
bookingCode={room.roomRate.bookingCode}
alignCenter
/>
)}
<Divider color="Border/Divider/Subtle" />
</>
)
}