Files
web/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx
Simon.Emanuelsson 85acd3453d Merged in feat/SW-1719-strikethrough-rates (pull request #2266)
Feat/SW-1719 strikethrough rates

* feat(SW-1719): Strikethrough rate if logged in on regular rate cards

* feat(SW-1719): Strikethrough rate if logged in on rate summary

* feat(SW-1719): Strikethrough rate if logged in on mobile rate summary

* feat(SW-1719): Strikethrough rate if logged in on enter details

* feat(SW-1719): Strikethrough rate support for multiple rooms

* feat(SW-1719): booking receipt fixes on confirmation page

* feat(SW-1719): improve initial total price calculation

* feat: harmonize enter details total price to use one and the same function


Approved-by: Michael Zetterberg
2025-06-13 12:01:16 +00:00

265 lines
8.6 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 { 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 { Typography } from "@scandic-hotels/design-system/Typography"
import { CancellationRuleEnum, ChildBedTypeEnum } from "@/constants/booking"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import Modal from "@/components/Modal"
import { formatPrice } from "@/utils/numberFormatting"
import Breakfast from "./Breakfast"
import RoomSkeletonLoader from "./RoomSkeletonLoader"
import styles from "./room.module.css"
import type { BookingConfirmationReceiptRoomProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt"
export default function ReceiptRoom({
room,
roomNumber,
roomCount,
}: BookingConfirmationReceiptRoomProps) {
const intl = useIntl()
const { currencyCode, isVatCurrency } = useBookingConfirmationStore(
(state) => ({
currencyCode: state.currencyCode,
isVatCurrency: state.isVatCurrency,
})
)
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(
{
defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
},
{ totalAdults: room.adults }
)
const guestsParts = [adultsMsg]
if (room.childrenAges?.length) {
const childrenMsg = intl.formatMessage(
{
defaultMessage:
"{totalChildren, plural, one {# child} other {# children}}",
},
{ totalChildren: room.childrenAges.length }
)
guestsParts.push(childrenMsg)
}
const guests = guestsParts.join(", ")
const showDiscounted = room.rateDefinition.isMemberRate
return (
<>
<div className={styles.room}>
<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.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({
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({
defaultMessage: "Pay later",
})
: intl.formatMessage({
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}>
{feature.description}
</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(
{
defaultMessage: "Crib (child) × {count}",
},
{ count: childBedCrib.quantity }
)}
</p>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.uiTextHighContrast}>
{intl.formatMessage({
defaultMessage: "Based on 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(
{
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}
<Breakfast
breakfast={room.breakfast}
breakfastIncluded={room.breakfastIncluded}
guests={guests}
/>
</div>
<Divider color="Border/Divider/Subtle" />
</>
)
}