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