Files
web/packages/booking-flow/lib/components/EnterDetails/Summary/UI/Room/index.tsx
Joakim Jäderberg aafad9781f Merged in feat/lokalise-rebuild (pull request #2993)
Feat/lokalise rebuild

* chore(lokalise): update translation ids

* chore(lokalise): easier to switch between projects

* chore(lokalise): update translation ids

* .

* .

* .

* .

* .

* .

* chore(lokalise): update translation ids

* chore(lokalise): update translation ids

* .

* .

* .

* chore(lokalise): update translation ids

* chore(lokalise): update translation ids

* .

* .

* chore(lokalise): update translation ids

* chore(lokalise): update translation ids

* chore(lokalise): new translations

* merge

* switch to errors for missing id's

* merge

* sync translations


Approved-by: Linus Flood
2025-10-22 11:00:03 +00:00

352 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 { 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 { getRoomFeatureDescription } from "../../../../../utils/getRoomFeatureDescription"
import Breakfast from "./Breakfast"
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
}
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(
{
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
)
}
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}
<Breakfast
adults={room.adults}
breakfast={room.breakfast}
breakfastIncluded={room.breakfastIncluded}
guests={guests}
nights={nightsCount}
/>
</div>
<Divider color="Border/Divider/Subtle" />
</>
)
}