Files
web/packages/booking-flow/lib/components/BookingConfirmation/Receipt/Room/index.tsx
Hrishikesh Vaipurkar 4c0daf8062 Merged in feat/BOOK-479-scandic-go-hotels-rm-brf- (pull request #3143)
feat(BOOK-479): Updated breakfast UI for ScandicGo hotels

Approved-by: Erik Tiekstra
2025-11-18 13:03:46 +00:00

304 lines
10 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.
"use client"
import { cx } from "class-variance-authority"
import { useIntl } from "react-intl"
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
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 { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum"
import { useBookingConfirmationStore } from "../../../../stores/booking-confirmation"
import { getRoomFeatureDescription } from "../../../../utils/getRoomFeatureDescription"
import { SummaryBreakfast } from "../../../SummaryBreakfast"
import RoomSkeletonLoader from "./RoomSkeletonLoader"
import styles from "./room.module.css"
import type { Room } from "../../../../types/stores/booking-confirmation"
type BookingConfirmationReceiptRoomProps = {
room: Room
roomNumber: number
roomCount: number
showBookingCodeChip?: boolean
}
export function ReceiptRoom({
room,
roomNumber,
roomCount,
showBookingCodeChip = false,
}: BookingConfirmationReceiptRoomProps) {
const intl = useIntl()
const { currencyCode, isVatCurrency, bookingCode, hotelOffersBreakfast } =
useBookingConfirmationStore((state) => ({
currencyCode: state.currencyCode,
isVatCurrency: state.isVatCurrency,
bookingCode: state.bookingCode,
hotelOffersBreakfast: state.hotelOffersBreakfast,
}))
if (!room) {
return <RoomSkeletonLoader />
}
const childBedCrib = room.childBedPreferences.find(
(c) => c.bedType === ChildBedTypeEnum.Crib
)
const childBedExtraBed = room.childBedPreferences.find(
(c) => c.bedType === ChildBedTypeEnum.ExtraBed
)
const adultsMsg = intl.formatMessage(
{
id: "common.numberOfAdults",
defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
},
{ totalAdults: room.adults }
)
const guestsParts = [adultsMsg]
if (room.childrenAges?.length) {
const childrenMsg = intl.formatMessage(
{
id: "common.numberOfChildren",
defaultMessage:
"{totalChildren, plural, one {# child} other {# children}}",
},
{ totalChildren: room.childrenAges.length }
)
guestsParts.push(childrenMsg)
}
const guests = guestsParts.join(", ")
const showDiscounted =
room.rateDefinition.isMemberRate || room.rateDefinition.isCampaignRate
const breakfastPrice = room.breakfast
? room.breakfast?.totalPrice
: room.breakfast
const breakfastCurrency = room.breakfast ? room.breakfast?.currency : null
return (
<>
<div className={styles.room}>
<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.name}</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<div className={styles.additionalInformation}>
<p>{guestsParts.join(", ")}</p>
<p>{room.rateDefinition.cancellationText}</p>
</div>
</Typography>
</div>
<Typography variant="Body/Paragraph/mdRegular">
<div className={styles.prices}>
<p
className={cx(styles.price, {
[styles.discounted]: showDiscounted,
})}
>
{room.formattedRoomCost}
</p>
{/* TODO: add original price, we're currently not receiving this value from API */}
</div>
</Typography>
</div>
{room.rateDefinition.generalTerms ? (
<div className={styles.ctaWrapper}>
<Modal
trigger={
<Button
className={styles.termsButton}
variant="Text"
typography="Body/Supporting text (caption)/smBold"
wrapping={false}
>
{intl.formatMessage({
id: "bookingConfirmation.receipt.reservationPolicy",
defaultMessage: "Reservation policy",
})}
<MaterialIcon
icon="chevron_right"
size={20}
color="CurrentColor"
/>
</Button>
}
title={
(isVatCurrency
? room.rateDefinition.cancellationText
: room.rateDefinition.title) || ""
}
subtitle={
room.rateDefinition.cancellationRule ===
CancellationRuleEnum.CancellableBefore6PM
? intl.formatMessage({
id: "booking.payLater",
defaultMessage: "Pay later",
})
: intl.formatMessage({
id: "booking.payNow",
defaultMessage: "Pay now",
})
}
>
<div className={styles.terms}>
{room.rateDefinition.generalTerms?.map((info) => (
<Typography
key={info}
className={styles.termsText}
variant="Body/Paragraph/mdRegular"
>
<span>
<MaterialIcon
icon="check"
color="Icon/Feedback/Success"
size={20}
className={styles.termsIcon}
/>
{info}
</span>
</Typography>
))}
</div>
</Modal>
</div>
) : null}
</div>
{room.roomFeatures
? room.roomFeatures.map((feature) => (
<div className={styles.entry} key={feature.code}>
<div>
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.uiTextHighContrast}>
{getRoomFeatureDescription(
feature.code,
feature.description,
intl
)}
</p>
</Typography>
</div>
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.uiTextHighContrast}>
{formatPrice(intl, feature.totalPrice, feature.currency)}
</p>
</Typography>
</div>
))
: null}
<div className={styles.entry}>
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.uiTextHighContrast}>{room.bedDescription}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.uiTextHighContrast}>
{formatPrice(intl, 0, currencyCode)}
</p>
</Typography>
</div>
{childBedCrib ? (
<Typography variant="Body/Paragraph/mdRegular">
<div className={styles.entry}>
<div>
<p>
{intl.formatMessage(
{
id: "booking.numberOfCribs",
defaultMessage: "Crib (child) × {count}",
},
{ count: childBedCrib.quantity }
)}
</p>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.uiTextHighContrast}>
{intl.formatMessage({
id: "booking.subjectToAvailability",
defaultMessage: "Subject to availability",
})}
</p>
</Typography>
</div>
<div className={styles.prices}>
<span className={styles.price}>
{formatPrice(intl, 0, currencyCode)}
</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.quantity,
}
)}
</p>
</div>
<div className={styles.prices}>
<span className={styles.price}>
{formatPrice(intl, 0, currencyCode)}
</span>
</div>
</div>
</Typography>
) : null}
<SummaryBreakfast
breakfastPrice={breakfastPrice}
breakfastCurrency={breakfastCurrency}
breakfastIncluded={room.breakfastIncluded}
guests={guests}
hotelOffersBreakfast={hotelOffersBreakfast}
/>
{showBookingCodeChip && (
<BookingCodeChip
isCampaign={room.rateDefinition.isCampaignRate}
bookingCode={bookingCode}
alignCenter
/>
)}
</div>
<Divider color="Border/Divider/Subtle" />
</>
)
}