feat: SW-1588 Optimized as per review comments
This commit is contained in:
@@ -65,7 +65,7 @@ export default function PriceList({
|
||||
|
||||
return (
|
||||
<dl className={styles.priceList}>
|
||||
{isUserLoggedIn && isMainRoom ? null : (
|
||||
{isUserLoggedIn && isMainRoom && memberLocalPrice ? null : (
|
||||
<div className={styles.priceRow}>
|
||||
<dt>
|
||||
<Caption
|
||||
@@ -101,7 +101,7 @@ export default function PriceList({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{memberLocalPrice && !publicLocalPrice.regularPricePerNight ? (
|
||||
{memberLocalPrice && (
|
||||
<div className={styles.priceRow}>
|
||||
<dt>
|
||||
<Caption type="bold" color={memberLocalPrice ? "red" : "disabled"}>
|
||||
@@ -128,29 +128,24 @@ export default function PriceList({
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
) : (
|
||||
publicLocalPrice.regularPricePerNight && (
|
||||
<div className={styles.priceRow}>
|
||||
<dt>
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{intl.formatMessage({ id: "Regular price" })}
|
||||
</Caption>
|
||||
</dt>
|
||||
<dd>
|
||||
<div className={styles.priceStriked}>
|
||||
<Subtitle type="two" color="uiTextHighContrast">
|
||||
{publicLocalPrice.regularPricePerNight}
|
||||
</Subtitle>
|
||||
<Body color="uiTextHighContrast" textTransform="bold">
|
||||
{publicLocalPrice.currency}
|
||||
<span className={styles.perNight}>
|
||||
/{intl.formatMessage({ id: "night" })}
|
||||
</span>
|
||||
</Body>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{publicLocalPrice.regularPricePerNight && (
|
||||
<div className={styles.priceRow}>
|
||||
<dt></dt>
|
||||
<dd>
|
||||
<div className={styles.priceStriked}>
|
||||
<Subtitle type="two" color="uiTextHighContrast">
|
||||
{publicLocalPrice.regularPricePerNight}
|
||||
</Subtitle>
|
||||
<Body color="uiTextHighContrast" textTransform="bold">
|
||||
{publicLocalPrice.currency}
|
||||
<span className={styles.perNight}>
|
||||
/{intl.formatMessage({ id: "night" })}
|
||||
</span>
|
||||
</Body>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{showRequestedPrice && (
|
||||
<div className={styles.priceRow}>
|
||||
|
||||
@@ -146,56 +146,40 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
const payLater = intl.formatMessage({ id: "Pay later" })
|
||||
const payNow = intl.formatMessage({ id: "Pay now" })
|
||||
|
||||
function getRate(rateCode: string, rateDefinition?: RateDefinition) {
|
||||
const rateObj = {
|
||||
terms: rateDefinition?.generalTerms,
|
||||
rateTitle:
|
||||
rateDefinition?.rateType !== RateTypeEnum.Regular
|
||||
? rateDefinition?.title
|
||||
: undefined,
|
||||
}
|
||||
// Possible undefined rate definition carried from roomsAvailability possibility of null
|
||||
function getRate(rateCode: string) {
|
||||
switch (rateCode) {
|
||||
case "change":
|
||||
return {
|
||||
isFlex: false,
|
||||
notAvailable: false,
|
||||
title: freeBooking,
|
||||
...rateObj,
|
||||
}
|
||||
case "flex":
|
||||
return {
|
||||
isFlex: true,
|
||||
notAvailable: false,
|
||||
title: freeCancelation,
|
||||
...rateObj,
|
||||
}
|
||||
case "save":
|
||||
return {
|
||||
isFlex: false,
|
||||
notAvailable: false,
|
||||
title: nonRefundable,
|
||||
...rateObj,
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown key for rate, should be "change", "flex", "save" or "special", but got ${rateCode}`
|
||||
`Unknown key for rate, should be "change", "flex", "save", but got ${rateCode}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function getRateInfo(product: Product) {
|
||||
const rateDefinition = rateDefinitions?.filter(
|
||||
(rateDefinition) =>
|
||||
rateDefinition.rateCode === product.productType.public.rateCode
|
||||
)[0]
|
||||
if (
|
||||
!product.productType.public.rateCode &&
|
||||
!product.productType.member?.rateCode
|
||||
) {
|
||||
const possibleRate = getRate(
|
||||
product.productType.public.rate,
|
||||
rateDefinition
|
||||
)
|
||||
const possibleRate = getRate(product.productType.public.rate)
|
||||
if (possibleRate) {
|
||||
return {
|
||||
...possibleRate,
|
||||
@@ -206,8 +190,6 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
isFlex: false,
|
||||
notAvailable: true,
|
||||
title: "",
|
||||
terms: undefined,
|
||||
rateTitle: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,15 +207,23 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
)
|
||||
}
|
||||
|
||||
// At least one rate is required to proceed here
|
||||
if (!publicRate && !memberRate) {
|
||||
throw new Error(
|
||||
"We should never make it here without any single available rateCode"
|
||||
)
|
||||
}
|
||||
const specialRate = publicRate || memberRate
|
||||
if (product.productType.public.rateType !== "Regular" && specialRate) {
|
||||
return getRate(specialRate, rateDefinition)
|
||||
|
||||
// Booking code scenario which has various rate types in which only
|
||||
// public rate code is allowed/obtained from the API
|
||||
const isBookingCodeRate =
|
||||
product.productType.public.rateType !== RateTypeEnum.Regular
|
||||
if (isBookingCodeRate) {
|
||||
//@ts-ignore (publicRate || memberRate) types as `string | undefined` instead of just `string`
|
||||
return getRate(publicRate || memberRate)
|
||||
}
|
||||
|
||||
// Regular rates (Save, Change, Flex) requires both public and member rates availability
|
||||
if (!publicRate || !memberRate) {
|
||||
throw new Error(
|
||||
"We should never make it here without both public and member rateCodes"
|
||||
@@ -241,10 +231,29 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
}
|
||||
|
||||
const key = isUserLoggedIn && isMainRoom ? memberRate : publicRate
|
||||
return getRate(key, rateDefinition)
|
||||
return getRate(key)
|
||||
}
|
||||
|
||||
const isSpecialRate =
|
||||
function getPartialRateDefinition(
|
||||
product: Product,
|
||||
rateDefinitions: RateDefinition[]
|
||||
) {
|
||||
return rateDefinitions
|
||||
.filter((rateDefinition) =>
|
||||
isUserLoggedIn && product.productType.member
|
||||
? rateDefinition.rateCode === product.productType.member.rateCode
|
||||
: rateDefinition.rateCode === product.productType.public.rateCode
|
||||
)
|
||||
.flatMap((rateDefinition) => ({
|
||||
terms: rateDefinition.generalTerms,
|
||||
rateTitle:
|
||||
rateDefinition.rateType !== RateTypeEnum.Regular
|
||||
? rateDefinition.title
|
||||
: undefined,
|
||||
}))[0]
|
||||
}
|
||||
|
||||
const isBookingCodeRate =
|
||||
bookingCode &&
|
||||
roomConfiguration.products.every((item) => {
|
||||
return item.productType.public.rateType !== RateTypeEnum.Regular
|
||||
@@ -335,7 +344,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
<span>
|
||||
<Caption color="uiTextHighContrast">{breakfastMessage}</Caption>
|
||||
{bookingCode ? (
|
||||
<span className={!isSpecialRate ? styles.strikedText : ""}>
|
||||
<span className={!isBookingCodeRate ? styles.strikedText : ""}>
|
||||
<PriceTagIcon />
|
||||
{bookingCode}
|
||||
</span>
|
||||
@@ -343,19 +352,20 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
</span>
|
||||
{roomConfiguration.products.map((product) => {
|
||||
const rate = getRateInfo(product)
|
||||
const rateDefinition = getPartialRateDefinition(
|
||||
product,
|
||||
rateDefinitions
|
||||
)
|
||||
const isSelectedRateCode =
|
||||
selectedRate?.product.productType.public.rateCode ===
|
||||
product.productType.public.rateCode ||
|
||||
(selectedRate?.product.productType.member?.rateCode ===
|
||||
product.productType.member?.rateCode &&
|
||||
// to handle undefined === undefined scenarios in booking code rates
|
||||
product.productType.member?.rateCode !== undefined)
|
||||
return (
|
||||
<FlexibilityOption
|
||||
key={
|
||||
product.productType.public.rateCode +
|
||||
"_" +
|
||||
product.productType.public.rate
|
||||
}
|
||||
key={product.productType.public.rateCode}
|
||||
features={roomConfiguration.features}
|
||||
isSelected={
|
||||
isSelectedRateCode &&
|
||||
@@ -368,8 +378,8 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
roomType={roomConfiguration.roomType}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
title={rate.title}
|
||||
priceInformation={rate.terms}
|
||||
rateTitle={rate.rateTitle}
|
||||
priceInformation={rateDefinition.terms}
|
||||
rateTitle={rateDefinition.rateTitle}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHote
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
|
||||
export default function RoomSelectionPanel() {
|
||||
const { rooms } = useRoomContext()
|
||||
@@ -33,67 +34,67 @@ export default function RoomSelectionPanel() {
|
||||
(state) => state.activeCodeFilter
|
||||
)
|
||||
|
||||
let filteredRooms = rooms,
|
||||
isRegularRatesAvailableWithCode: boolean = false,
|
||||
noAvailabilityWithBookingCode: boolean = false
|
||||
if (bookingCode) {
|
||||
isRegularRatesAvailableWithCode = bookingCode
|
||||
? rooms?.some((room) => {
|
||||
return (
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.some(
|
||||
(product) =>
|
||||
product.productType.public.rateType === RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
})
|
||||
: false
|
||||
// With Booking code rates we will always obtain public rate and never a member rate,
|
||||
// so we should ignore it from the logic below.
|
||||
const isRegularRatesAvailableWithCode =
|
||||
bookingCode &&
|
||||
rooms.some(
|
||||
(room) =>
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.some(
|
||||
(product) =>
|
||||
product.productType.public.rateType === RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
|
||||
noAvailabilityWithBookingCode = bookingCode
|
||||
? !rooms?.some((room) => {
|
||||
return (
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.some(
|
||||
(product) =>
|
||||
product.productType.public.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
})
|
||||
: false
|
||||
// Booking codes rate comes with various rate types but Regular is reserved
|
||||
// for non-booking code rates (Save, Change & Flex)
|
||||
const isBookingCodeRatesAvailable =
|
||||
bookingCode &&
|
||||
rooms.some(
|
||||
(room) =>
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.some(
|
||||
(product) =>
|
||||
product.productType.public.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
|
||||
filteredRooms =
|
||||
noAvailabilityWithBookingCode ||
|
||||
!isRegularRatesAvailableWithCode ||
|
||||
activeCodeFilter === BookingCodeFilterEnum.All
|
||||
? rooms
|
||||
: rooms.filter((room) => {
|
||||
return (
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.every(
|
||||
(product) =>
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Discounted &&
|
||||
product.productType.public.rateType !==
|
||||
RateTypeEnum.Regular) ||
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Regular &&
|
||||
product.productType.public.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.productType.public.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
const regularRateRooms = rooms.filter(
|
||||
(room) =>
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.every(
|
||||
(product) =>
|
||||
product.productType.public.rateType === RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
// Show booking code filter when both of the booking code rates or regular rates are available
|
||||
const showBookingCodeFilter =
|
||||
isRegularRatesAvailableWithCode && isBookingCodeRatesAvailable
|
||||
|
||||
return (
|
||||
<>
|
||||
{noAvailableRooms ||
|
||||
(bookingCode &&
|
||||
isRegularRatesAvailableWithCode &&
|
||||
noAvailabilityWithBookingCode) ? (
|
||||
{noAvailableRooms || (bookingCode && !isBookingCodeRatesAvailable) ? (
|
||||
<div className={styles.hotelAlert}>
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({ id: "No availability" })}
|
||||
text={
|
||||
noAvailabilityWithBookingCode
|
||||
bookingCode && !isBookingCodeRatesAvailable
|
||||
? intl.formatMessage(
|
||||
{
|
||||
id: "We found no available rooms using this booking code ({bookingCode}). See available rates below.",
|
||||
@@ -113,19 +114,28 @@ export default function RoomSelectionPanel() {
|
||||
</div>
|
||||
) : null}
|
||||
<RoomTypeFilter />
|
||||
{bookingCode &&
|
||||
isRegularRatesAvailableWithCode &&
|
||||
!noAvailabilityWithBookingCode ? (
|
||||
<BookingCodeFilter />
|
||||
) : null}
|
||||
{showBookingCodeFilter ? <BookingCodeFilter /> : null}
|
||||
<ul className={styles.roomList}>
|
||||
{filteredRooms.map((roomConfiguration) => (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
/>
|
||||
))}
|
||||
{/* Show either Booking code filtered rooms or all the rooms */}
|
||||
{showAllRooms
|
||||
? rooms.map((roomConfiguration) => RoomCardWrap(roomConfiguration))
|
||||
: activeCodeFilter === BookingCodeFilterEnum.Discounted
|
||||
? bookingCodeDiscountedRooms.map((roomConfiguration) =>
|
||||
RoomCardWrap(roomConfiguration)
|
||||
)
|
||||
: regularRateRooms.map((roomConfiguration) =>
|
||||
RoomCardWrap(roomConfiguration)
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function RoomCardWrap(roomConfiguration: RoomConfiguration) {
|
||||
return (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -524,7 +524,6 @@
|
||||
"Reference": "Reference",
|
||||
"Reference #{bookingNr}": "Reference #{bookingNr}",
|
||||
"Reference number": "Reference number",
|
||||
"Regular price": "Regular price",
|
||||
"Relax": "Relax",
|
||||
"Remember code": "Remember code",
|
||||
"Remove card from member profile": "Remove card from member profile",
|
||||
|
||||
@@ -44,7 +44,7 @@ export const roomConfigurationSchema = z
|
||||
return product
|
||||
}
|
||||
|
||||
// Return rate even if single when special rates
|
||||
// Return rate even if single when booking code rate which can be any one of other rate types
|
||||
if (product.productType.public.rateType !== RateTypeEnum.Regular) {
|
||||
return product
|
||||
}
|
||||
@@ -73,7 +73,8 @@ export const roomConfigurationSchema = z
|
||||
/**
|
||||
* When all products miss at least one rateCode (member or public), we change the status to NotAvailable
|
||||
* since we cannot as of now (31 january) guarantee the flow with missing rateCodes.
|
||||
* Exception Special rate (Booking code rates)
|
||||
* This rule applies to regular rates (Save, Change and Flex)
|
||||
* Exception Booking code rate
|
||||
*
|
||||
* TODO: (Maybe) notify somewhere that this happened
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user