Merged in feat/SW-1308-booking-codes-track-b (pull request #1607)

Feat/SW-1308 booking codes track b

* feat: SW-1308 Booking codes track b

* feat: SW-1308 Booking codes Track B implementation

* feat: SW-1308 Optimized after rebase


Approved-by: Arvid Norlin
This commit is contained in:
Hrishikesh Vaipurkar
2025-03-24 11:23:11 +00:00
parent 5643bcc62a
commit b0674d07f5
66 changed files with 1612 additions and 285 deletions

View File

@@ -53,9 +53,9 @@ export default function Summary({
function getMemberPrice(roomRate: RoomRate) {
return roomRate?.memberRate
? {
currency: roomRate.memberRate.localPrice.currency,
pricePerNight: roomRate.memberRate.localPrice.pricePerNight,
amount: roomRate.memberRate.localPrice.pricePerStay,
currency: roomRate.memberRate.localPrice.currency ?? "",
pricePerNight: roomRate.memberRate.localPrice.pricePerNight ?? 0,
amount: roomRate.memberRate.localPrice.pricePerStay ?? 0,
}
: null
}
@@ -299,7 +299,9 @@ export default function Summary({
value: formatPrice(
intl,
totalPrice.requested.price,
totalPrice.requested.currency
totalPrice.requested.currency,
totalPrice.requested.additionalPrice,
totalPrice.requested.additionalPriceCurrency
),
}
)}

View File

@@ -20,7 +20,11 @@ import {
} from "@/utils/numberFormatting"
import MobileSummary from "./MobileSummary"
import { calculateTotalPrice } from "./utils"
import {
calculateChequePrice,
calculateTotalPrice,
calculateVoucherPrice,
} from "./utils"
import styles from "./rateSummary.module.css"
@@ -137,13 +141,26 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
const isBookingCodeRate = rateSummary.some(
(rate) => rate.public?.rateType !== RateTypeEnum.Regular
)
const showDiscounted = isUserLoggedIn || isBookingCodeRate
const isVoucherRate = rateSummary.some((rate) => rate.voucher)
const isChequeRate = rateSummary.some((rate) => rate.bonusCheque)
const showDiscounted =
isUserLoggedIn || isBookingCodeRate || isVoucherRate || isChequeRate
// In case of reward night (redemption) only single room booking is supported by business rules
const totalPriceToShow: Price =
isRedemption && rateSummary[0].redemption
? PointsPriceSchema.parse(rateSummary[0].redemption)
: calculateTotalPrice(rateSummary, isUserLoggedIn, petRoomPackage)
let totalPriceToShow: Price
if (isVoucherRate) {
totalPriceToShow = calculateVoucherPrice(rateSummary)
} else if (isChequeRate) {
totalPriceToShow = calculateChequePrice(rateSummary)
} else if (rateSummary[0].redemption) {
// In case of reward night (redemption) only single room booking is supported by business rules
totalPriceToShow = PointsPriceSchema.parse(rateSummary[0].redemption)
} else {
totalPriceToShow = calculateTotalPrice(
rateSummary,
isUserLoggedIn,
petRoomPackage
)
}
return (
<form action={`details?${params}`} method="GET" onSubmit={handleSubmit}>
@@ -271,7 +288,9 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
value: formatPrice(
intl,
totalPriceToShow.requested.price,
totalPriceToShow.requested.currency
totalPriceToShow.requested.currency,
totalPriceToShow.requested.additionalPrice,
totalPriceToShow.requested.additionalPriceCurrency
),
}
)}

View File

@@ -4,6 +4,7 @@ import {
RoomPackageCodeEnum,
} from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
import { CurrencyEnum } from "@/types/enums/currency"
export const calculateTotalPrice = (
selectedRateSummary: Rate[],
@@ -68,3 +69,83 @@ export const calculateTotalPrice = (
}
)
}
export const calculateVoucherPrice = (selectedRateSummary: Rate[]) => {
return selectedRateSummary.reduce<Price>(
(total, room) => {
const rate = room.voucher
if (!rate) {
return total
}
return <Price>{
local: {
currency: total.local.currency,
price: total.local.price + rate.numberOfVouchers,
},
requested: undefined,
}
},
{
local: {
currency: CurrencyEnum.Voucher,
price: 0,
},
requested: undefined,
}
)
}
export const calculateChequePrice = (selectedRateSummary: Rate[]) => {
return selectedRateSummary.reduce<Price>(
(total, room) => {
const rate = room.bonusCheque
if (!rate) {
return total
}
const price = total.local.price + rate.localPrice.numberOfBonusCheques
const additionalPrice =
rate.localPrice.numberOfBonusCheques &&
(total.local.additionalPrice ?? 0) +
(rate.localPrice.additionalPricePerStay ?? 0)
const additionalPriceCurrency = (rate.localPrice.numberOfBonusCheques &&
rate.localPrice.currency)!
const requestedPrice = rate.requestedPrice?.numberOfBonusCheques
? (total.requested?.price ?? 0) +
rate.requestedPrice?.numberOfBonusCheques
: total.requested?.price
const requestedAdditionalPrice =
rate.requestedPrice?.additionalPricePerStay &&
(total.requested?.additionalPrice ?? 0) +
(rate.requestedPrice?.additionalPricePerStay ?? 0)
return <Price>{
local: {
currency: CurrencyEnum.CC,
price,
additionalPrice,
additionalPriceCurrency,
},
requested: rate.requestedPrice
? {
currency: CurrencyEnum.CC,
price: requestedPrice,
additionalPrice: requestedAdditionalPrice,
additionalPriceCurrency: rate.requestedPrice?.currency,
}
: undefined,
}
},
{
local: {
currency: CurrencyEnum.CC,
price: 0,
},
requested: undefined,
}
)
}

View File

@@ -14,6 +14,7 @@ import { calculatePricesPerNight } from "./utils"
import styles from "./priceList.module.css"
import type { PriceListProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
import { RateTypeEnum } from "@/types/enums/rateType"
export default function PriceList({
publicPrice = {},
@@ -34,9 +35,11 @@ export default function PriceList({
const petRoomRequestedPrice = petRoomPackage?.requestedPrice
const showRequestedPrice =
publicRequestedPrice &&
memberRequestedPrice &&
publicRequestedPrice.currency !== publicLocalPrice.currency
(publicRequestedPrice &&
memberRequestedPrice &&
publicRequestedPrice.currency !== publicLocalPrice.currency) ||
(publicPrice.rateType !== RateTypeEnum.Regular && publicRequestedPrice)
const searchParams = useSearchParams()
const fromDate = searchParams.get("fromDate")
const toDate = searchParams.get("toDate")
@@ -76,16 +79,18 @@ export default function PriceList({
{isUserLoggedIn && isMainRoom && memberLocalPrice ? null : (
<div className={styles.priceRow}>
<dt>
{rateName ? null : (
{
<Caption
type="bold"
color={
totalPublicLocalPricePerNight ? priceLabelColor : "disabled"
}
>
{intl.formatMessage({ id: "Standard price" })}
{rateName
? rateName
: intl.formatMessage({ id: "Standard price" })}
</Caption>
)}
}
</dt>
<dd>
{publicLocalPrice ? (
@@ -163,19 +168,27 @@ export default function PriceList({
</dt>
<dd>
<Caption color="uiTextMediumContrast">
{isUserLoggedIn
? intl.formatMessage(
{ id: "{memberPrice} {currency}" },
{
memberPrice: totalMemberRequestedPricePerNight,
currency: publicRequestedPrice.currency,
}
)
{totalMemberRequestedPricePerNight
? isUserLoggedIn
? intl.formatMessage(
{ id: "{memberPrice} {currency}" },
{
memberPrice: totalMemberRequestedPricePerNight,
currency: publicRequestedPrice.currency,
}
)
: intl.formatMessage(
{ id: "{publicPrice}/{memberPrice} {currency}" },
{
publicPrice: totalPublicRequestedPricePerNight,
memberPrice: totalMemberRequestedPricePerNight,
currency: publicRequestedPrice.currency,
}
)
: intl.formatMessage(
{ id: "{publicPrice}/{memberPrice} {currency}" },
{ id: "{price} {currency}" },
{
publicPrice: totalPublicRequestedPricePerNight,
memberPrice: totalMemberRequestedPricePerNight,
price: publicRequestedPrice.pricePerNight,
currency: publicRequestedPrice.currency,
}
)}

View File

@@ -20,6 +20,7 @@
.price {
display: flex;
gap: var(--Spacing-x-half);
align-items: baseline;
}
.priceStriked {

View File

@@ -104,11 +104,6 @@ export default function FlexibilityOption({
value={rate.rateCode}
/>
<div className={styles.card}>
{rateName ? (
<div className={styles.header}>
<Caption>{rateName}</Caption>
</div>
) : null}
<div className={styles.header}>
<Modal
trigger={

View File

@@ -0,0 +1,15 @@
.priceList {
margin: 0;
}
.priceRow {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.price {
display: flex;
gap: var(--Spacing-x-half);
align-items: baseline;
}

View File

@@ -0,0 +1,79 @@
import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./chequePrice.module.css"
import { CurrencyEnum } from "@/types/enums/currency"
import type { Product } from "@/types/trpc/routers/hotel/roomAvailability"
export default function ChequePrice({
chequePrice,
rateTitle,
}: {
chequePrice: NonNullable<Product["bonusCheque"]>
rateTitle: string
}) {
const intl = useIntl()
return (
<dl className={styles.priceList}>
<div className={styles.priceRow}>
<dt>
<Caption type="bold" color="red">
{rateTitle}
</Caption>
</dt>
<dd>
<div className={styles.price}>
<Subtitle type="two" color="red">
{chequePrice.localPrice.numberOfBonusCheques}
</Subtitle>
<Body color="red">{CurrencyEnum.CC}</Body>
{chequePrice.localPrice.additionalPricePerStay ? (
<>
<Body color="red">{" + "}</Body>
<Subtitle type="two" color="red">
{chequePrice.localPrice.additionalPricePerStay}
</Subtitle>
<Body color="red">{chequePrice.localPrice.currency}</Body>
</>
) : null}
</div>
</dd>
</div>
{chequePrice.requestedPrice?.additionalPricePerStay ? (
<div className={styles.priceRow}>
<dt>
<Caption type="bold">
{intl.formatMessage({ id: "Approx." })}
</Caption>
</dt>
<dd>
<div className={styles.price}>
<Caption>
{intl.formatMessage(
{ id: "{price} {currency}" },
{
price: chequePrice.requestedPrice.numberOfBonusCheques,
currency: CurrencyEnum.CC,
}
)}
{" + "}
{intl.formatMessage(
{ id: "{price} {currency}" },
{
price: chequePrice.requestedPrice.additionalPricePerStay,
currency: chequePrice.requestedPrice.currency,
}
)}
</Caption>
</div>
</dd>
</div>
) : null}
</dl>
)
}

View File

@@ -0,0 +1,73 @@
.card {
border-radius: var(--Corner-radius-Large);
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
background-color: var(--Base-Surface-Secondary-light-Normal);
border: 1px solid var(--Base-Surface-Secondary-light-Normal);
position: relative;
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
height: 100%;
}
.card:hover {
cursor: pointer;
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
.checkIcon {
width: 24px;
height: 24px;
border-radius: 100px;
background-color: var(--UI-Input-Controls-Fill-Selected);
border: 2px solid var(--Base-Border-Inverted);
justify-content: center;
align-items: center;
display: none;
}
input[type="radio"].radio {
opacity: 0;
position: fixed;
width: 0;
}
input[type="radio"]:checked + .card {
border: 1px solid var(--Primary-Dark-On-Surface-Divider);
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
input[type="radio"]:checked + .card .checkIcon {
display: flex;
position: absolute;
top: -10px;
right: -10px;
}
.header {
display: flex;
gap: var(--Spacing-x-half);
align-items: flex-start;
}
.priceType {
display: flex;
gap: var(--Spacing-x-half);
flex-wrap: wrap;
}
.terms {
padding-top: var(--Spacing-x3);
}
.termsText:nth-child(n) {
display: flex;
align-items: center;
padding-bottom: var(--Spacing-x1);
}
.termsIcon {
padding-right: var(--Spacing-x1);
flex-shrink: 0;
flex-basis: 32px;
}

View File

@@ -0,0 +1,118 @@
"use client"
import { useIntl } from "react-intl"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import ChequePrice from "./ChequePrice"
import styles from "./flexibilityOptionCheque.module.css"
import type { FlexibilityOptionChequeProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
export default function FlexibilityOptionCheque({
features,
paymentTerm,
priceInformation,
product,
roomType,
roomTypeCode,
title,
rateName,
}: FlexibilityOptionChequeProps) {
const intl = useIntl()
const {
actions: { selectRateCheque },
roomNr,
selectedRate,
} = useRoomContext()
if (!product.bonusCheque) {
return null
}
function handleSelect() {
selectRateCheque({
features,
product,
roomType,
roomTypeCode,
})
}
const voucherRate = product.bonusCheque
const isSelected = !!(
selectedRate?.product.bonusCheque &&
selectedRate?.product.bonusCheque.rateCode === voucherRate?.rateCode &&
selectedRate?.roomTypeCode === roomTypeCode
)
const rate = product.bonusCheque
const chequeRateName =
rateName ?? intl.formatMessage({ id: "Corporate Cheque" })
return (
<label>
<input
checked={isSelected}
className={styles.radio}
name={`rateCode-${roomNr}-${rate.rateCode}`}
onChange={handleSelect}
type="radio"
value={rate.rateCode}
/>
<div className={styles.card}>
<div className={styles.header}>
<Modal
trigger={
<Button intent="text">
<InfoCircleIcon
width={16}
height={16}
color="uiTextMediumContrast"
/>
</Button>
}
title={chequeRateName}
subtitle={`${title} (${paymentTerm})`}
>
<div className={styles.terms}>
{priceInformation?.map((info) => (
<Body
key={info}
color="uiTextHighContrast"
className={styles.termsText}
>
<CheckIcon
color="uiSemanticSuccess"
width={20}
height={20}
className={styles.termsIcon}
></CheckIcon>
{info}
</Body>
))}
</div>
</Modal>
<div className={styles.priceType}>
<Caption color="uiTextHighContrast">{title}</Caption>
<Caption color="uiTextPlaceholder">({paymentTerm})</Caption>
</div>
</div>
<ChequePrice
chequePrice={product.bonusCheque}
rateTitle={chequeRateName}
/>
<div className={styles.checkIcon}>
<CheckIcon color="white" height="16" width="16" />
</div>
</div>
</label>
)
}

View File

@@ -0,0 +1,36 @@
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./voucherPrice.module.css"
import { CurrencyEnum } from "@/types/enums/currency"
import type { Product } from "@/types/trpc/routers/hotel/roomAvailability"
export default function VoucherPrice({
voucherPrice,
rateTitle,
}: {
voucherPrice: NonNullable<Product["voucher"]>
rateTitle: string
}) {
return (
<dl className={styles.priceList}>
<div className={styles.priceRow}>
<dt>
<Caption type="bold" color="red">
{rateTitle}
</Caption>
</dt>
<dd>
<div className={styles.price}>
<Subtitle type="two" color="red">
{voucherPrice.numberOfVouchers}
</Subtitle>
<Body color="red">{CurrencyEnum.Voucher}</Body>
</div>
</dd>
</div>
</dl>
)
}

View File

@@ -0,0 +1,15 @@
.priceList {
margin: 0;
}
.priceRow {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.price {
display: flex;
gap: var(--Spacing-x-half);
align-items: baseline;
}

View File

@@ -0,0 +1,73 @@
.card {
border-radius: var(--Corner-radius-Large);
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
background-color: var(--Base-Surface-Secondary-light-Normal);
border: 1px solid var(--Base-Surface-Secondary-light-Normal);
position: relative;
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
height: 100%;
}
.card:hover {
cursor: pointer;
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
.checkIcon {
width: 24px;
height: 24px;
border-radius: 100px;
background-color: var(--UI-Input-Controls-Fill-Selected);
border: 2px solid var(--Base-Border-Inverted);
justify-content: center;
align-items: center;
display: none;
}
input[type="radio"].radio {
opacity: 0;
position: fixed;
width: 0;
}
input[type="radio"]:checked + .card {
border: 1px solid var(--Primary-Dark-On-Surface-Divider);
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
input[type="radio"]:checked + .card .checkIcon {
display: flex;
position: absolute;
top: -10px;
right: -10px;
}
.header {
display: flex;
gap: var(--Spacing-x-half);
align-items: flex-start;
}
.priceType {
display: flex;
gap: var(--Spacing-x-half);
flex-wrap: wrap;
}
.terms {
padding-top: var(--Spacing-x3);
}
.termsText:nth-child(n) {
display: flex;
align-items: center;
padding-bottom: var(--Spacing-x1);
}
.termsIcon {
padding-right: var(--Spacing-x1);
flex-shrink: 0;
flex-basis: 32px;
}

View File

@@ -0,0 +1,117 @@
"use client"
import { useIntl } from "react-intl"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import VoucherPrice from "./VoucherPrice"
import styles from "./flexibilityOptionVoucher.module.css"
import type { FlexibilityOptionVoucherProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
export default function FlexibilityOptionVoucher({
features,
paymentTerm,
priceInformation,
product,
roomType,
roomTypeCode,
title,
rateName,
}: FlexibilityOptionVoucherProps) {
const intl = useIntl()
const {
actions: { selectRateVoucher },
roomNr,
selectedRate,
} = useRoomContext()
if (!product.voucher) {
return null
}
function handleSelect() {
selectRateVoucher({
features,
product,
roomType,
roomTypeCode,
})
}
const voucherRate = product.voucher
const isSelected = !!(
selectedRate?.product.voucher &&
selectedRate?.product.voucher.rateCode === voucherRate?.rateCode &&
selectedRate?.roomTypeCode === roomTypeCode
)
const rate = product.voucher
const voucherRateName = rateName ?? intl.formatMessage({ id: "Voucher" })
return (
<label>
<input
checked={isSelected}
className={styles.radio}
name={`rateCode-${roomNr}-${rate.rateCode}`}
onChange={handleSelect}
type="radio"
value={rate.rateCode}
/>
<div className={styles.card}>
<div className={styles.header}>
<Modal
trigger={
<Button intent="text">
<InfoCircleIcon
width={16}
height={16}
color="uiTextMediumContrast"
/>
</Button>
}
title={voucherRateName}
subtitle={`${title} (${paymentTerm})`}
>
<div className={styles.terms}>
{priceInformation?.map((info) => (
<Body
key={info}
color="uiTextHighContrast"
className={styles.termsText}
>
<CheckIcon
color="uiSemanticSuccess"
width={20}
height={20}
className={styles.termsIcon}
></CheckIcon>
{info}
</Body>
))}
</div>
</Modal>
<div className={styles.priceType}>
<Caption color="uiTextHighContrast">{title}</Caption>
<Caption color="uiTextPlaceholder">({paymentTerm})</Caption>
</div>
</div>
<VoucherPrice
voucherPrice={product.voucher}
rateTitle={voucherRateName}
/>
<div className={styles.checkIcon}>
<CheckIcon color="white" height="16" width="16" />
</div>
</div>
</label>
)
}

View File

@@ -20,6 +20,8 @@ import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import { cardVariants } from "./cardVariants"
import FlexibilityOption from "./FlexibilityOption"
import FlexibilityOptionPoints from "./FlexibilityOptionPoints"
import FlexibilityOptionCheque from "./FlexibilityOptionCheque"
import FlexibilityOptionVoucher from "./FlexibilityOptionVoucher"
import RoomSize from "./RoomSize"
import styles from "./roomCard.module.css"
@@ -171,6 +173,10 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
rateCode = product.member.rateCode
} else if (product.public?.rateCode) {
rateCode = product.public.rateCode
} else if (product.voucher) {
rateCode = product.voucher.rateCode
} else if (product.bonusCheque) {
rateCode = product.bonusCheque.rateCode
} else if (product.redemptions?.length) {
// In case of redemption there will be same rate terms and title
// irrespective of ratecodes
@@ -292,7 +298,9 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
const isAvailable =
product.public ||
(product.member && isUserLoggedIn && isMainRoom) ||
product.redemptions?.length
product.redemptions?.length ||
product.bonusCheque ||
product.voucher
const rateDefinition = getRateDefinition(
product,
roomAvailability.rateDefinitions
@@ -307,14 +315,35 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
roomTypeCode: roomConfiguration.roomTypeCode,
title: rateTitle,
rateName:
isBookingCodeRate || isRedemption
isBookingCodeRate || isRedemption ||
product.voucher ||
product.bonusCheque
? rateDefinition?.title
: undefined,
}
return isRedemption ? (
<FlexibilityOptionPoints key={product.rate} {...props} />
) : (
<FlexibilityOption key={product.rate} {...props} />
return (<>
{isRedemption &&
<FlexibilityOptionPoints key={product.rate} {...props} />}
{product.voucher ? (
<FlexibilityOptionVoucher
key={product.rate}
{...props}
rateName={rateDefinition?.title}
product={product}
/>
) : null}
{product.bonusCheque ? (
<FlexibilityOptionCheque
key={product.rate}
{...props}
rateName={rateDefinition?.title}
product={product}
/>
) : null}
{product.public || product.member ? (
<FlexibilityOption key={product.rate} {...props} />
) : null}
</>
)
})}
</>

View File

@@ -38,52 +38,60 @@ export default function RoomSelectionPanel() {
(state) => state.activeCodeFilter
)
// Regular Rates (Save, Change and Flex) always should send both public and member rates
// so we can check public rates for availability
const isRegularRatesAvailableWithCode =
bookingCode &&
rooms.some(
const isVoucherOrCorpChequeRate = rooms.find((room) =>
room.products.some((product) => product.voucher || product.bonusCheque)
)
let isRegularRatesAvailableWithCode = false,
isBookingCodeRatesAvailable = false
let visibleRooms = rooms
if (bookingCode && !isVoucherOrCorpChequeRate) {
// Regular Rates (Save, Change and Flex) always should send both public and member rates
// so we can check public rates for availability
isRegularRatesAvailableWithCode = rooms.some(
(room) =>
room.status === AvailabilityEnum.Available &&
room.products.some(
(product) => product.public?.rateType === RateTypeEnum.Regular
(product) =>
product.public?.rateType === RateTypeEnum.Regular ||
product.member?.rateType === RateTypeEnum.Regular
)
)
// Booking codes rate comes with various rate types but Regular is reserved
// for non-booking code rates (Save, Change & Flex)
// With Booking code rates we will always obtain public rate and maybe a member rate
// so we check for public rate and ignore member rate
const isBookingCodeRatesAvailable =
bookingCode &&
rooms.some(
// Booking codes rate comes with various rate types but Regular is reserved
// for non-booking code rates (Save, Change & Flex)
// With Booking code rates we will always obtain public rate and maybe a member rate
// so we check for public rate and ignore member rate
isBookingCodeRatesAvailable = rooms.some(
(room) =>
room.status === AvailabilityEnum.Available &&
room.products.some(
(product) => product.public?.rateType !== RateTypeEnum.Regular
(product) =>
product.public?.rateType !== RateTypeEnum.Regular ||
product.member?.rateType !== RateTypeEnum.Regular
)
)
// Show all rooms if either booking code rates or regular rates are not available
// or filter selection is All rooms
const showAllRooms =
!isBookingCodeRatesAvailable ||
!isRegularRatesAvailableWithCode ||
activeCodeFilter === BookingCodeFilterEnum.All
const bookingCodeDiscountedRooms = rooms.filter(
(room) =>
room.status === AvailabilityEnum.Available &&
room.products.every(
(product) => product.public?.rateType !== RateTypeEnum.Regular
if (activeCodeFilter === BookingCodeFilterEnum.Discounted) {
visibleRooms = rooms.filter(
(room) =>
room.status === AvailabilityEnum.Available &&
room.products.every(
(product) => product.public?.rateType !== RateTypeEnum.Regular
)
)
)
const regularRateRooms = rooms.filter(
(room) =>
room.status === AvailabilityEnum.Available &&
room.products.every(
(product) => product.public?.rateType === RateTypeEnum.Regular
} else if (activeCodeFilter === BookingCodeFilterEnum.Regular) {
visibleRooms = rooms.filter(
(room) =>
room.status === AvailabilityEnum.Available &&
room.products.every(
(product) =>
product.public?.rateType === RateTypeEnum.Regular ||
product.member?.rateType === RateTypeEnum.Regular
)
)
)
}
}
// Show booking code filter when both of the booking code rates or regular rates are available
const showBookingCodeFilter =
isRegularRatesAvailableWithCode && isBookingCodeRatesAvailable
@@ -114,7 +122,10 @@ export default function RoomSelectionPanel() {
return (
<>
{noAvailableRooms || (bookingCode && !isBookingCodeRatesAvailable) ? (
{noAvailableRooms ||
(bookingCode &&
!isBookingCodeRatesAvailable &&
!isVoucherOrCorpChequeRate) ? (
<div className={styles.hotelAlert}>
<Alert
type={AlertTypeEnum.Info}
@@ -142,27 +153,12 @@ export default function RoomSelectionPanel() {
<RoomTypeFilter />
{showBookingCodeFilter ? <BookingCodeFilter /> : null}
<ul className={styles.roomList}>
{/* Show either Booking code filtered rooms or all the rooms */}
{showAllRooms
? rooms.map((roomConfiguration) => (
<RoomCard
key={roomConfiguration.roomTypeCode}
roomConfiguration={roomConfiguration}
/>
))
: activeCodeFilter === BookingCodeFilterEnum.Discounted
? bookingCodeDiscountedRooms.map((roomConfiguration) => (
<RoomCard
key={roomConfiguration.roomTypeCode}
roomConfiguration={roomConfiguration}
/>
))
: regularRateRooms.map((roomConfiguration) => (
<RoomCard
key={roomConfiguration.roomTypeCode}
roomConfiguration={roomConfiguration}
/>
))}
{visibleRooms.map((roomConfiguration) => (
<RoomCard
key={roomConfiguration.roomTypeCode}
roomConfiguration={roomConfiguration}
/>
))}
</ul>
</>
)