Merged in fix/allow-single-rateCode (pull request #1438)
fix: allow rates that only have either of member or public to be selectable * fix: allow rates that only have either of member or public to be selectable Approved-by: Michael Zetterberg
This commit is contained in:
committed by
Linus Flood
parent
3f01266a75
commit
c3e3fa62ec
@@ -25,6 +25,7 @@ import styles from "./summary.module.css"
|
||||
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
||||
import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
||||
import type { SelectRateSummaryProps } from "@/types/components/hotelReservation/summary"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
export default function Summary({
|
||||
booking,
|
||||
@@ -56,6 +57,11 @@ export default function Summary({
|
||||
|
||||
const memberPrice = getMemberPrice(rooms[0].roomRate)
|
||||
|
||||
const containsBookingCodeRate = rooms.find(
|
||||
(room) => room.roomRate.publicRate?.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
const showDiscounted = containsBookingCodeRate || isMember
|
||||
|
||||
return (
|
||||
<section className={styles.summary}>
|
||||
<header className={styles.header}>
|
||||
@@ -103,6 +109,9 @@ export default function Summary({
|
||||
|
||||
const memberPrice = getMemberPrice(room.roomRate)
|
||||
const showMemberPrice = !!(isMember && memberPrice && roomNumber === 1)
|
||||
const isBookingCodeRate =
|
||||
room.roomRate.publicRate?.rateType !== RateTypeEnum.Regular
|
||||
const showDiscounted = isBookingCodeRate || showMemberPrice
|
||||
|
||||
const adultsMsg = intl.formatMessage(
|
||||
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||
@@ -134,7 +143,7 @@ export default function Summary({
|
||||
) : null}
|
||||
<div className={styles.entry}>
|
||||
<Body color="uiTextHighContrast">{room.roomType}</Body>
|
||||
<Body color={showMemberPrice ? "red" : "uiTextHighContrast"}>
|
||||
<Body color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
||||
{formatPrice(
|
||||
intl,
|
||||
room.roomPrice.perStay.local.price,
|
||||
@@ -249,7 +258,11 @@ export default function Summary({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Body textTransform="bold" data-testid="total-price">
|
||||
<Body
|
||||
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
||||
textTransform="bold"
|
||||
data-testid="total-price"
|
||||
>
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPrice.local.price,
|
||||
|
||||
@@ -14,6 +14,7 @@ import Summary from "./Summary"
|
||||
import styles from "./mobileSummary.module.css"
|
||||
|
||||
import type { MobileSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
export default function MobileSummary({
|
||||
isAllRoomsSelected,
|
||||
@@ -71,19 +72,24 @@ export default function MobileSummary({
|
||||
roomPrice: {
|
||||
perNight: {
|
||||
local: {
|
||||
price: room.public.localPrice.pricePerNight,
|
||||
currency: room.public.localPrice.currency,
|
||||
price: (room.public?.localPrice.pricePerNight ||
|
||||
room.member?.localPrice.pricePerNight)!,
|
||||
currency: (room.public?.localPrice.currency ||
|
||||
room.member?.localPrice.currency)!,
|
||||
},
|
||||
requested: undefined,
|
||||
},
|
||||
perStay: {
|
||||
local: {
|
||||
price: room.public.localPrice.pricePerStay,
|
||||
currency: room.public.localPrice.currency,
|
||||
price: (room.public?.localPrice.pricePerStay ||
|
||||
room.member?.localPrice.pricePerStay)!,
|
||||
currency: (room.public?.localPrice.currency ||
|
||||
room.member?.localPrice.currency)!,
|
||||
},
|
||||
requested: undefined,
|
||||
},
|
||||
currency: room.public.localPrice.currency,
|
||||
currency: (room.public?.localPrice.currency ||
|
||||
room.member?.localPrice.currency)!,
|
||||
},
|
||||
roomRate: {
|
||||
...room.public,
|
||||
@@ -91,13 +97,23 @@ export default function MobileSummary({
|
||||
publicRate: room.public,
|
||||
},
|
||||
rateDetails: rateDefinitions.find(
|
||||
(rate) => rate.rateCode === room.public.rateCode
|
||||
(rate) =>
|
||||
rate.rateCode === room.public?.rateCode ||
|
||||
rate.rateCode === room.member?.rateCode
|
||||
)?.generalTerms,
|
||||
cancellationText:
|
||||
rateDefinitions.find((rate) => rate.rateCode === room.public.rateCode)
|
||||
?.cancellationText ?? "",
|
||||
rateDefinitions.find(
|
||||
(rate) =>
|
||||
rate.rateCode === room.public?.rateCode ||
|
||||
rate.rateCode === room.member?.rateCode
|
||||
)?.cancellationText ?? "",
|
||||
}))
|
||||
|
||||
const containsBookingCodeRate = rateSummary.find(
|
||||
(rate) => rate.public?.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
const showDiscounted = containsBookingCodeRate || isUserLoggedIn
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper} data-open={isSummaryOpen}>
|
||||
<div className={styles.content}>
|
||||
@@ -122,7 +138,7 @@ export default function MobileSummary({
|
||||
className={styles.priceDetailsButton}
|
||||
>
|
||||
<Caption>{intl.formatMessage({ id: "Total price" })}</Caption>
|
||||
<Subtitle>
|
||||
<Subtitle color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPriceToShow.local.price,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useIntl } from "react-intl"
|
||||
import { dt } from "@/lib/dt"
|
||||
import { useRatesStore } from "@/stores/select-rate"
|
||||
|
||||
import { getRates } from "@/components/HotelReservation/SelectRate/utils"
|
||||
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
|
||||
import SignupPromoMobile from "@/components/HotelReservation/SignupPromo/Mobile"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
@@ -23,6 +22,8 @@ import styles from "./rateSummary.module.css"
|
||||
|
||||
import type { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
const {
|
||||
@@ -86,19 +87,13 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
const hasMemberRates = rateSummary.some((room) => room.member)
|
||||
const showMemberDiscountBanner = hasMemberRates && !isUserLoggedIn
|
||||
|
||||
const rates = getRates(roomsAvailability.rateDefinitions)
|
||||
|
||||
const freeCancelation = intl.formatMessage({ id: "Free cancellation" })
|
||||
const nonRefundable = intl.formatMessage({ id: "Non-refundable" })
|
||||
const freeBooking = intl.formatMessage({ id: "Free rebooking" })
|
||||
const payLater = intl.formatMessage({ id: "Pay later" })
|
||||
const payNow = intl.formatMessage({ id: "Pay now" })
|
||||
|
||||
function getRateDetails(rateCode: string) {
|
||||
const rate = Object.keys(rates).find((k) =>
|
||||
rates[k as keyof typeof rates].find((a) => a.rateCode === rateCode)
|
||||
)
|
||||
|
||||
function getRateDetails(rate: Rate["rate"]) {
|
||||
switch (rate) {
|
||||
case "change":
|
||||
return `${freeBooking}, ${payNow}`
|
||||
@@ -122,6 +117,11 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isBookingCodeRate = rateSummary.some(
|
||||
(rate) => rate.public?.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
const showDiscounted = isUserLoggedIn || isBookingCodeRate
|
||||
|
||||
const totalPriceToShow = calculateTotalPrice(
|
||||
rateSummary,
|
||||
isUserLoggedIn,
|
||||
@@ -134,7 +134,6 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
<div className={styles.content}>
|
||||
<div className={styles.summaryText}>
|
||||
{rateSummary.map((room, index) => {
|
||||
const isMainRoom = index + 1 === 1
|
||||
return (
|
||||
<div key={index} className={styles.roomSummary}>
|
||||
{rateSummary.length > 1 ? (
|
||||
@@ -147,11 +146,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
</Subtitle>
|
||||
<Body color="uiTextMediumContrast">{room.roomType}</Body>
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{getRateDetails(
|
||||
isUserLoggedIn && room.member && isMainRoom
|
||||
? room.member?.rateCode
|
||||
: room.public.rateCode
|
||||
)}
|
||||
{getRateDetails(room.rate)}
|
||||
</Caption>
|
||||
</>
|
||||
) : (
|
||||
@@ -160,11 +155,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
{room.roomType}
|
||||
</Subtitle>
|
||||
<Body color="uiTextMediumContrast">
|
||||
{getRateDetails(
|
||||
isUserLoggedIn && room.member && isMainRoom
|
||||
? room.member?.rateCode
|
||||
: room.public.rateCode
|
||||
)}
|
||||
{getRateDetails(room.rate)}
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
@@ -206,9 +197,8 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
: 0
|
||||
return total + memberPrice + petRoomPrice
|
||||
}, 0),
|
||||
currency:
|
||||
rateSummary[0].member?.localPrice.currency ??
|
||||
rateSummary[0].public.localPrice.currency,
|
||||
currency: (rateSummary[0].member?.localPrice.currency ??
|
||||
rateSummary[0].public?.localPrice.currency)!,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -225,7 +215,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
<div className={styles.summaryPrice}>
|
||||
<div className={styles.summaryPriceTextDesktop}>
|
||||
<Subtitle
|
||||
color={isUserLoggedIn ? "red" : "uiTextHighContrast"}
|
||||
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
||||
textAlign="right"
|
||||
>
|
||||
{formatPrice(
|
||||
@@ -253,7 +243,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage({ id: "Total price" })}
|
||||
</Caption>
|
||||
<Subtitle color={isUserLoggedIn ? "red" : "uiTextHighContrast"}>
|
||||
<Subtitle color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPriceToShow.local.price,
|
||||
|
||||
@@ -12,10 +12,15 @@ export const calculateTotalPrice = (
|
||||
) => {
|
||||
return selectedRateSummary.reduce<Price>(
|
||||
(total, room, idx) => {
|
||||
const priceToUse =
|
||||
const rate =
|
||||
isUserLoggedIn && room.member && idx + 1 === 1
|
||||
? room.member
|
||||
: room.public
|
||||
|
||||
if (!rate) {
|
||||
return total
|
||||
}
|
||||
|
||||
const isPetRoom = room.features.find(
|
||||
(feature) => feature.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
@@ -31,18 +36,16 @@ export const calculateTotalPrice = (
|
||||
|
||||
return {
|
||||
local: {
|
||||
currency: priceToUse.localPrice.currency,
|
||||
currency: rate.localPrice.currency,
|
||||
price:
|
||||
total.local.price +
|
||||
priceToUse.localPrice.pricePerStay +
|
||||
petRoomPrice,
|
||||
total.local.price + rate.localPrice.pricePerStay + petRoomPrice,
|
||||
},
|
||||
requested: priceToUse.requestedPrice
|
||||
requested: rate.requestedPrice
|
||||
? {
|
||||
currency: priceToUse.requestedPrice.currency,
|
||||
currency: rate.requestedPrice.currency,
|
||||
price:
|
||||
(total.requested?.price ?? 0) +
|
||||
priceToUse.requestedPrice.pricePerStay +
|
||||
rate.requestedPrice.pricePerStay +
|
||||
petRoomPrice,
|
||||
}
|
||||
: undefined,
|
||||
@@ -50,7 +53,8 @@ export const calculateTotalPrice = (
|
||||
},
|
||||
{
|
||||
local: {
|
||||
currency: selectedRateSummary[0].public.localPrice.currency,
|
||||
currency: (selectedRateSummary[0].public?.localPrice.currency ||
|
||||
selectedRateSummary[0].member?.localPrice.currency)!,
|
||||
price: 0,
|
||||
},
|
||||
requested: undefined,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useIntl } from "react-intl"
|
||||
|
||||
import { useRatesStore } from "@/stores/select-rate"
|
||||
|
||||
import { getRates } from "@/components/HotelReservation/SelectRate/utils"
|
||||
import { EditIcon } from "@/components/Icons"
|
||||
import Image from "@/components/Image"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
@@ -15,9 +14,11 @@ import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||
|
||||
import styles from "./selectedRoomPanel.module.css"
|
||||
|
||||
import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
|
||||
export default function SelectedRoomPanel() {
|
||||
const intl = useIntl()
|
||||
const { isUserLoggedIn, rateDefinitions, roomCategories } = useRatesStore(
|
||||
const { isUserLoggedIn, roomCategories } = useRatesStore(
|
||||
(state) => ({
|
||||
isUserLoggedIn: state.isUserLoggedIn,
|
||||
rateDefinitions: state.roomsAvailability?.rateDefinitions,
|
||||
@@ -37,23 +38,13 @@ export default function SelectedRoomPanel() {
|
||||
)
|
||||
)?.images
|
||||
|
||||
if (!rateDefinitions) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rates = getRates(rateDefinitions)
|
||||
|
||||
const freeCancelation = intl.formatMessage({ id: "Free cancellation" })
|
||||
const nonRefundable = intl.formatMessage({ id: "Non-refundable" })
|
||||
const freeBooking = intl.formatMessage({ id: "Free rebooking" })
|
||||
const payLater = intl.formatMessage({ id: "Pay later" })
|
||||
const payNow = intl.formatMessage({ id: "Pay now" })
|
||||
|
||||
function getRateDetails(rateCode: string) {
|
||||
const rate = Object.keys(rates).find((k) =>
|
||||
rates[k as keyof typeof rates].find((a) => a.rateCode === rateCode)
|
||||
)
|
||||
|
||||
function getRateTitle(rate: Rate["rate"]) {
|
||||
switch (rate) {
|
||||
case "change":
|
||||
return `${freeBooking}, ${payNow}`
|
||||
@@ -65,10 +56,14 @@ export default function SelectedRoomPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
const rate =
|
||||
isUserLoggedIn && isMainRoom && selectedRate?.product.productType.member
|
||||
? selectedRate?.product.productType.member
|
||||
: selectedRate?.product.productType.public
|
||||
if (!selectedRate) {
|
||||
return null
|
||||
}
|
||||
|
||||
const selectedProduct =
|
||||
isUserLoggedIn && isMainRoom && selectedRate.product?.member
|
||||
? selectedRate.product?.member
|
||||
: selectedRate.product?.public
|
||||
|
||||
return (
|
||||
<div className={styles.selectedRoomPanel}>
|
||||
@@ -80,20 +75,21 @@ export default function SelectedRoomPanel() {
|
||||
)}
|
||||
</Caption>
|
||||
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
|
||||
{selectedRate?.roomType}
|
||||
{selectedRate.roomType}
|
||||
</Subtitle>
|
||||
<Body color="uiTextMediumContrast">
|
||||
{rate?.rateCode ? getRateDetails(rate.rateCode) : null}
|
||||
{getRateTitle(selectedRate.product.rate)}
|
||||
</Body>
|
||||
<Body color="uiTextHighContrast">
|
||||
{rate?.localPrice.pricePerNight} {rate?.localPrice.currency}/
|
||||
{selectedProduct?.localPrice.pricePerNight}{" "}
|
||||
{selectedProduct?.localPrice.currency}/
|
||||
{intl.formatMessage({ id: "night" })}
|
||||
</Body>
|
||||
</div>
|
||||
<div className={styles.imageContainer}>
|
||||
{images?.[0]?.imageSizes?.tiny ? (
|
||||
<Image
|
||||
alt={selectedRate?.roomType ?? images[0].metaData?.altText ?? ""}
|
||||
alt={selectedRate.roomType ?? images[0].metaData?.altText ?? ""}
|
||||
className={styles.img}
|
||||
height={300}
|
||||
src={images[0].imageSizes.tiny}
|
||||
|
||||
@@ -64,8 +64,9 @@ export default function PriceList({
|
||||
|
||||
// When it is Promotion rate either booking code rate or public
|
||||
// Show striked Regular rate which is overtaken by the Promotional rate when member rate is not available
|
||||
const showOvertakingPrice =
|
||||
const showOvertakingPrice = !!(
|
||||
!memberLocalPrice && publicLocalPrice.regularPricePerNight
|
||||
)
|
||||
|
||||
const priceLabelColor =
|
||||
rateTitle && !memberLocalPrice ? "red" : "uiTextHighContrast"
|
||||
|
||||
@@ -16,10 +16,10 @@ import PriceTable from "./PriceList"
|
||||
import styles from "./flexibilityOption.module.css"
|
||||
|
||||
import type { FlexibilityOptionProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
export default function FlexibilityOption({
|
||||
features,
|
||||
isSelected,
|
||||
paymentTerm,
|
||||
priceInformation,
|
||||
petRoomPackage,
|
||||
@@ -35,6 +35,7 @@ export default function FlexibilityOption({
|
||||
actions: { selectRate },
|
||||
isMainRoom,
|
||||
roomNr,
|
||||
selectedRate,
|
||||
} = useRoomContext()
|
||||
|
||||
function handleSelect() {
|
||||
@@ -48,7 +49,11 @@ export default function FlexibilityOption({
|
||||
}
|
||||
}
|
||||
|
||||
if (!product) {
|
||||
const isBookingCodeRate = product?.public?.rateType !== RateTypeEnum.Regular
|
||||
if (
|
||||
!product ||
|
||||
(isMainRoom && isUserLoggedIn && !product.member && !isBookingCodeRate)
|
||||
) {
|
||||
return (
|
||||
<div className={styles.noPricesCard}>
|
||||
<div className={styles.header}>
|
||||
@@ -67,9 +72,26 @@ export default function FlexibilityOption({
|
||||
)
|
||||
}
|
||||
|
||||
const { public: publicPrice, member: memberPrice } = product.productType
|
||||
const rate =
|
||||
isUserLoggedIn && isMainRoom && memberPrice ? memberPrice : publicPrice
|
||||
const productMember = product.member
|
||||
const selectedRateMember = selectedRate?.product.member
|
||||
const bothMemberExist = productMember && selectedRateMember
|
||||
const selectedRateIsMember =
|
||||
bothMemberExist && productMember.rateCode === selectedRateMember.rateCode
|
||||
const productPublic = product.public
|
||||
const selectedRatePublic = selectedRate?.product.public
|
||||
const bothPublicExist = productPublic && selectedRatePublic
|
||||
const selectedRateIsPublic =
|
||||
bothPublicExist && productPublic.rateCode === selectedRatePublic.rateCode
|
||||
const isSelected = !!(
|
||||
(selectedRateIsMember || selectedRateIsPublic) &&
|
||||
selectedRate?.roomTypeCode === roomTypeCode
|
||||
)
|
||||
|
||||
const rate = (
|
||||
isUserLoggedIn && isMainRoom && product.member && !isBookingCodeRate
|
||||
? product.member
|
||||
: product.public
|
||||
)!
|
||||
|
||||
return (
|
||||
<label>
|
||||
@@ -125,9 +147,9 @@ export default function FlexibilityOption({
|
||||
</div>
|
||||
</div>
|
||||
<PriceTable
|
||||
memberPrice={memberPrice}
|
||||
memberPrice={product.member}
|
||||
petRoomPackage={petRoomPackage}
|
||||
publicPrice={publicPrice}
|
||||
publicPrice={product.public}
|
||||
rateTitle={rateTitle}
|
||||
/>
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useIntl } from "react-intl"
|
||||
import { useRatesStore } from "@/stores/select-rate"
|
||||
|
||||
import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek"
|
||||
import { getRates } from "@/components/HotelReservation/SelectRate/utils"
|
||||
import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
|
||||
import { ErrorCircleIcon, PriceTagIcon } from "@/components/Icons"
|
||||
import ImageGallery from "@/components/ImageGallery"
|
||||
@@ -91,7 +90,11 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
rateDefinitions: state.roomsAvailability?.rateDefinitions,
|
||||
roomCategories: state.roomCategories,
|
||||
}))
|
||||
const { isMainRoom, roomNr, selectedPackage, selectedRate } = useRoomContext()
|
||||
const { isMainRoom, roomNr, selectedPackage } = useRoomContext()
|
||||
|
||||
if (!rateDefinitions) {
|
||||
return null
|
||||
}
|
||||
|
||||
const classNames = cardVariants({
|
||||
availability:
|
||||
@@ -119,12 +122,6 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
roomNr
|
||||
)
|
||||
|
||||
if (!rateDefinitions) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rates = getRates(rateDefinitions)
|
||||
|
||||
const petRoomPackageSelected =
|
||||
(selectedPackage === RoomPackageCodeEnum.PET_ROOM && petRoomPackage) ||
|
||||
undefined
|
||||
@@ -144,93 +141,19 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
const payLater = intl.formatMessage({ id: "Pay later" })
|
||||
const payNow = intl.formatMessage({ id: "Pay now" })
|
||||
|
||||
function getRate(rateCode: string) {
|
||||
function getRateTitle(rateCode: Product["rate"]) {
|
||||
switch (rateCode) {
|
||||
case "change":
|
||||
return {
|
||||
isFlex: false,
|
||||
notAvailable: false,
|
||||
title: freeBooking,
|
||||
}
|
||||
return freeBooking
|
||||
case "flex":
|
||||
return {
|
||||
isFlex: true,
|
||||
notAvailable: false,
|
||||
title: freeCancelation,
|
||||
}
|
||||
return freeCancelation
|
||||
case "save":
|
||||
return {
|
||||
isFlex: false,
|
||||
notAvailable: false,
|
||||
title: nonRefundable,
|
||||
}
|
||||
return nonRefundable
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown key for rate, should be "change", "flex", "save", but got ${rateCode}`
|
||||
)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
function getRateInfo(product: Product) {
|
||||
if (
|
||||
!product.productType.public.rateCode &&
|
||||
!product.productType.member?.rateCode
|
||||
) {
|
||||
const possibleRate = getRate(product.productType.public.rate)
|
||||
if (possibleRate) {
|
||||
return {
|
||||
...possibleRate,
|
||||
notAvailable: true,
|
||||
}
|
||||
}
|
||||
return {
|
||||
isFlex: false,
|
||||
notAvailable: true,
|
||||
title: "",
|
||||
}
|
||||
}
|
||||
|
||||
const publicRate = Object.keys(rates).find((k) =>
|
||||
rates[k as keyof typeof rates].find(
|
||||
(a) => a.rateCode === product.productType.public.rateCode
|
||||
)
|
||||
)
|
||||
let memberRate
|
||||
if (product.productType.member) {
|
||||
memberRate = Object.keys(rates).find((k) =>
|
||||
rates[k as keyof typeof rates].find(
|
||||
(a) => a.rateCode === product.productType.member!.rateCode
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// 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"
|
||||
)
|
||||
}
|
||||
|
||||
// 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-expect-error: <(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"
|
||||
)
|
||||
}
|
||||
|
||||
const key = isUserLoggedIn && isMainRoom ? memberRate : publicRate
|
||||
return getRate(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get terms and rate title from the rate definitions when booking code rate
|
||||
* or public promotion is in play. Returns undefined when product is not available
|
||||
@@ -244,16 +167,16 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
rateDefinitions: RateDefinition[]
|
||||
) {
|
||||
return rateDefinitions.find((rateDefinition) =>
|
||||
isUserLoggedIn && product.productType.member
|
||||
? rateDefinition.rateCode === product.productType.member.rateCode
|
||||
: rateDefinition.rateCode === product.productType.public.rateCode
|
||||
isUserLoggedIn && product.member && isMainRoom
|
||||
? rateDefinition.rateCode === product.member?.rateCode
|
||||
: rateDefinition.rateCode === product.public?.rateCode
|
||||
)
|
||||
}
|
||||
|
||||
const isBookingCodeRate =
|
||||
bookingCode &&
|
||||
roomConfiguration.products.every((item) => {
|
||||
return item.productType.public.rateType !== RateTypeEnum.Regular
|
||||
return item.public?.rateType !== RateTypeEnum.Regular
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -294,9 +217,9 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{occupancy.max === occupancy.min
|
||||
? intl.formatMessage(
|
||||
{ id: "guests.plural" },
|
||||
{ guests: occupancy.max }
|
||||
)
|
||||
{ id: "guests.plural" },
|
||||
{ guests: occupancy.max }
|
||||
)
|
||||
: intl.formatMessage({ id: "guests.span" }, occupancy)}
|
||||
</Caption>
|
||||
)}
|
||||
@@ -349,33 +272,25 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
) : null}
|
||||
</span>
|
||||
{roomConfiguration.products.map((product) => {
|
||||
const rate = getRateInfo(product)
|
||||
const rateTitle = getRateTitle(product.rate)
|
||||
const isAvailable =
|
||||
product.public ||
|
||||
(product.member && isUserLoggedIn && isMainRoom)
|
||||
const rateDefinition = getRateDefinition(product, rateDefinitions)
|
||||
const isSelectedRateCode =
|
||||
selectedRate?.product.productType.public.rateCode ===
|
||||
product.productType.public.rateCode ||
|
||||
(selectedRate?.product.productType.member?.rateCode ===
|
||||
product.productType.member?.rateCode &&
|
||||
// handle undefined === undefined scenarios in booking code rates
|
||||
product.productType.member?.rateCode !== undefined)
|
||||
return (
|
||||
<FlexibilityOption
|
||||
key={rate.title}
|
||||
key={product.rate}
|
||||
features={roomConfiguration.features}
|
||||
isSelected={
|
||||
isSelectedRateCode &&
|
||||
selectedRate?.roomTypeCode ===
|
||||
roomConfiguration.roomTypeCode
|
||||
}
|
||||
paymentTerm={rate.isFlex ? payLater : payNow}
|
||||
paymentTerm={product.isFlex ? payLater : payNow}
|
||||
petRoomPackage={petRoomPackageSelected}
|
||||
product={rate.notAvailable ? undefined : product}
|
||||
priceInformation={rateDefinition?.generalTerms}
|
||||
product={isAvailable ? product : undefined}
|
||||
roomType={roomConfiguration.roomType}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
title={rate.title}
|
||||
priceInformation={rateDefinition?.generalTerms}
|
||||
title={rateTitle}
|
||||
rateTitle={
|
||||
product.productType.public.rateType !== RateTypeEnum.Regular
|
||||
product.public &&
|
||||
product.public?.rateType !== RateTypeEnum.Regular
|
||||
? rateDefinition?.title
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"use client"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { alternativeHotels } from "@/constants/routes/hotelReservation"
|
||||
import { useBookingCodeFilterStore } from "@/stores/bookingCode-filter"
|
||||
import { useRatesStore } from "@/stores/select-rate"
|
||||
|
||||
import BookingCodeFilter from "@/components/HotelReservation/SelectHotel/BookingCodeFilter"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
@@ -22,6 +24,9 @@ import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
export default function RoomSelectionPanel() {
|
||||
const { rooms } = useRoomContext()
|
||||
const isSingleRoomAndHasSelection = useRatesStore(
|
||||
(state) => state.booking.rooms.length === 1 && !!state.rateSummary.length
|
||||
)
|
||||
const searchParams = useSearchParams()
|
||||
const bookingCode = searchParams.get("bookingCode")
|
||||
const intl = useIntl()
|
||||
@@ -41,8 +46,7 @@ export default function RoomSelectionPanel() {
|
||||
(room) =>
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.some(
|
||||
(product) =>
|
||||
product.productType.public.rateType === RateTypeEnum.Regular
|
||||
(product) => product.public?.rateType === RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
|
||||
@@ -56,8 +60,7 @@ export default function RoomSelectionPanel() {
|
||||
(room) =>
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.some(
|
||||
(product) =>
|
||||
product.productType.public.rateType !== RateTypeEnum.Regular
|
||||
(product) => product.public?.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
|
||||
@@ -71,22 +74,44 @@ export default function RoomSelectionPanel() {
|
||||
(room) =>
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.every(
|
||||
(product) =>
|
||||
product.productType.public.rateType !== RateTypeEnum.Regular
|
||||
(product) => product.public?.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
const regularRateRooms = rooms.filter(
|
||||
(room) =>
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.products.every(
|
||||
(product) =>
|
||||
product.productType.public.rateType === RateTypeEnum.Regular
|
||||
(product) => product.public?.rateType === RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
// Show booking code filter when both of the booking code rates or regular rates are available
|
||||
const showBookingCodeFilter =
|
||||
isRegularRatesAvailableWithCode && isBookingCodeRatesAvailable
|
||||
|
||||
useEffect(() => {
|
||||
if (isSingleRoomAndHasSelection) {
|
||||
// Required to prevent the history.pushState on the first selection
|
||||
// to scroll user back to top
|
||||
requestAnimationFrame(() => {
|
||||
const SCROLL_OFFSET = 100
|
||||
const selectedInputRoomCard = document.querySelector(
|
||||
`.${styles.roomList} li:has(input[type=radio]:checked)`
|
||||
)
|
||||
if (selectedInputRoomCard) {
|
||||
const elementPosition =
|
||||
selectedInputRoomCard.getBoundingClientRect().top
|
||||
const offsetPosition =
|
||||
elementPosition + window.scrollY - SCROLL_OFFSET
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: "instant",
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [isSingleRoomAndHasSelection])
|
||||
|
||||
return (
|
||||
<>
|
||||
{noAvailableRooms || (bookingCode && !isBookingCodeRatesAvailable) ? (
|
||||
@@ -117,27 +142,27 @@ 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) => (
|
||||
{/* 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}
|
||||
/>
|
||||
))
|
||||
: activeCodeFilter === BookingCodeFilterEnum.Discounted
|
||||
? bookingCodeDiscountedRooms.map((roomConfiguration) => (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
/>
|
||||
))
|
||||
: regularRateRooms.map((roomConfiguration) => (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
/>
|
||||
))}
|
||||
: regularRateRooms.map((roomConfiguration) => (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -30,10 +30,14 @@ export default function Rooms() {
|
||||
|
||||
useEffect(() => {
|
||||
const pricesWithCurrencies = visibleRooms.flatMap((room) =>
|
||||
room.products.map((product) => ({
|
||||
price: product.productType.public.localPrice.pricePerNight,
|
||||
currency: product.productType.public.localPrice.currency,
|
||||
}))
|
||||
room.products
|
||||
.filter((product) => product.member || product.public)
|
||||
.map((product) => ({
|
||||
currency: (product.public?.localPrice.currency ||
|
||||
product.member?.localPrice.currency)!,
|
||||
price: (product.public?.localPrice.pricePerNight ||
|
||||
product.member?.localPrice.pricePerNight)!,
|
||||
}))
|
||||
)
|
||||
const lowestPrice = pricesWithCurrencies.reduce(
|
||||
(minPrice, { price }) => Math.min(minPrice, price),
|
||||
|
||||
Reference in New Issue
Block a user