feat: SW-1588 Implemented booking code select-rate
This commit is contained in:
@@ -24,6 +24,8 @@ import {
|
||||
HotelCardListingTypeEnum,
|
||||
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
export default function HotelCardListing({
|
||||
hotelData,
|
||||
@@ -55,10 +57,11 @@ export default function HotelCardListing({
|
||||
? sortedHotels.filter(
|
||||
(hotel) =>
|
||||
!hotel.price ||
|
||||
activeCodeFilter === "all" ||
|
||||
(activeCodeFilter === "discounted" &&
|
||||
hotel.price?.public?.rateType?.toLowerCase() !== "regular") ||
|
||||
activeCodeFilter === hotel.price?.public?.rateType?.toLowerCase()
|
||||
activeCodeFilter === BookingCodeFilterEnum.All ||
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Discounted &&
|
||||
hotel.price?.public?.rateType !== RateTypeEnum.Regular) ||
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Regular &&
|
||||
hotel.price?.public?.rateType === RateTypeEnum.Regular)
|
||||
)
|
||||
: sortedHotels
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import styles from "./bookingCodeFilter.module.css"
|
||||
|
||||
import type { Key } from "react"
|
||||
|
||||
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
||||
|
||||
export default function BookingCodeFilter() {
|
||||
const intl = useIntl()
|
||||
const activeCodeFilter = useBookingCodeFilterStore(
|
||||
@@ -21,20 +23,20 @@ export default function BookingCodeFilter() {
|
||||
const bookingCodeFilterItems = [
|
||||
{
|
||||
label: intl.formatMessage({ id: "Discounted rooms" }),
|
||||
value: "discounted",
|
||||
value: BookingCodeFilterEnum.Discounted,
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: "Full price rooms" }),
|
||||
value: "regular",
|
||||
value: BookingCodeFilterEnum.Regular,
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: "See all" }),
|
||||
value: "all",
|
||||
value: BookingCodeFilterEnum.All,
|
||||
},
|
||||
]
|
||||
|
||||
function updateFilter(selectedFilter: Key) {
|
||||
setFilter(selectedFilter as string)
|
||||
setFilter(selectedFilter as BookingCodeFilterEnum)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,6 +28,8 @@ import styles from "./selectHotelMapContent.module.css"
|
||||
|
||||
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||
import type { SelectHotelMapProps } from "@/types/components/hotelReservation/selectHotel/map"
|
||||
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
const SKELETON_LOAD_DELAY = 750
|
||||
|
||||
@@ -83,10 +85,11 @@ export default function SelectHotelContent({
|
||||
? hotelPins.filter(
|
||||
(hotel) =>
|
||||
!hotel.publicPrice ||
|
||||
activeCodeFilter === "all" ||
|
||||
(activeCodeFilter === "discounted" &&
|
||||
hotel.rateType?.toLowerCase() !== "regular") ||
|
||||
activeCodeFilter === hotel.rateType?.toLowerCase()
|
||||
activeCodeFilter === BookingCodeFilterEnum.All ||
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Discounted &&
|
||||
hotel.rateType !== RateTypeEnum.Regular) ||
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Regular &&
|
||||
hotel.rateType === RateTypeEnum.Regular)
|
||||
)
|
||||
: hotelPins
|
||||
return updatedHotelsList.filter((hotel) =>
|
||||
|
||||
@@ -101,32 +101,57 @@ export default function PriceList({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.priceRow}>
|
||||
<dt>
|
||||
<Caption type="bold" color={memberLocalPrice ? "red" : "disabled"}>
|
||||
{intl.formatMessage({ id: "Member price" })}
|
||||
</Caption>
|
||||
</dt>
|
||||
<dd>
|
||||
{memberLocalPrice ? (
|
||||
<div className={styles.price}>
|
||||
<Subtitle type="two" color="red">
|
||||
{totalMemberLocalPricePerNight}
|
||||
</Subtitle>
|
||||
<Body color="red" textTransform="bold">
|
||||
{memberLocalPrice.currency}
|
||||
<span className={styles.perNight}>
|
||||
/{intl.formatMessage({ id: "night" })}
|
||||
</span>
|
||||
{memberLocalPrice && !publicLocalPrice.regularPricePerNight ? (
|
||||
<div className={styles.priceRow}>
|
||||
<dt>
|
||||
<Caption type="bold" color={memberLocalPrice ? "red" : "disabled"}>
|
||||
{intl.formatMessage({ id: "Member price" })}
|
||||
</Caption>
|
||||
</dt>
|
||||
<dd>
|
||||
{memberLocalPrice ? (
|
||||
<div className={styles.price}>
|
||||
<Subtitle type="two" color="red">
|
||||
{totalMemberLocalPricePerNight}
|
||||
</Subtitle>
|
||||
<Body color="red" textTransform="bold">
|
||||
{memberLocalPrice.currency}
|
||||
<span className={styles.perNight}>
|
||||
/{intl.formatMessage({ id: "night" })}
|
||||
</span>
|
||||
</Body>
|
||||
</div>
|
||||
) : (
|
||||
<Body textTransform="bold" color="disabled">
|
||||
-
|
||||
</Body>
|
||||
</div>
|
||||
) : (
|
||||
<Body textTransform="bold" color="disabled">
|
||||
-
|
||||
</Body>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</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>
|
||||
)
|
||||
)}
|
||||
{showRequestedPrice && (
|
||||
<div className={styles.priceRow}>
|
||||
<dt>
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.priceStriked {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-half);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.perNight {
|
||||
font-weight: 400;
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
|
||||
@@ -100,4 +100,5 @@ input[type="radio"]:checked + .card .checkIcon {
|
||||
.termsIcon {
|
||||
padding-right: var(--Spacing-x1);
|
||||
flex-shrink: 0;
|
||||
flex-basis: 32px;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ 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 } from "@/components/Icons"
|
||||
import { ErrorCircleIcon, PriceTagIcon } from "@/components/Icons"
|
||||
import ImageGallery from "@/components/ImageGallery"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
@@ -28,7 +28,9 @@ import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHote
|
||||
import type { RoomCardProps } from "@/types/components/hotelReservation/selectRate/roomCard"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { HotelTypeEnum } from "@/types/enums/hotelType"
|
||||
import type { Product } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
import type { Product, RateDefinition } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
|
||||
function getBreakfastMessage(
|
||||
publicBreakfastIncluded: boolean,
|
||||
@@ -72,6 +74,9 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
const lessThanFiveRoomsLeft =
|
||||
roomConfiguration.roomsLeft > 0 && roomConfiguration.roomsLeft < 5
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const bookingCode = searchParams.get("bookingCode")
|
||||
|
||||
const {
|
||||
hotelId,
|
||||
hotelType,
|
||||
@@ -138,39 +143,52 @@ 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 getRate(rateCode: string, rateDefinition?: RateDefinition) {
|
||||
switch (rateCode) {
|
||||
case "change":
|
||||
return {
|
||||
isFlex: false,
|
||||
notAvailable: false,
|
||||
title: freeBooking,
|
||||
terms: rateDefinition?.generalTerms,
|
||||
}
|
||||
case "flex":
|
||||
return {
|
||||
isFlex: true,
|
||||
notAvailable: false,
|
||||
title: freeCancelation,
|
||||
terms: rateDefinition?.generalTerms,
|
||||
}
|
||||
case "save":
|
||||
return {
|
||||
isFlex: false,
|
||||
notAvailable: false,
|
||||
title: nonRefundable,
|
||||
terms: rateDefinition?.generalTerms,
|
||||
}
|
||||
case "special":
|
||||
return {
|
||||
isFlex: rateDefinition?.cancellationRule === "CancellableBefore6PM",
|
||||
notAvailable: false,
|
||||
title: rateDefinition?.title ?? "",
|
||||
terms: rateDefinition?.generalTerms,
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown key for rate, should be "change", "flex" or "save", but got ${rateCode}`
|
||||
`Unknown key for rate, should be "change", "flex", "save" or "special", 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)
|
||||
const possibleRate = getRate(product.productType.public.rate, rateDefinition)
|
||||
if (possibleRate) {
|
||||
return {
|
||||
...possibleRate,
|
||||
@@ -181,6 +199,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
isFlex: false,
|
||||
notAvailable: true,
|
||||
title: "",
|
||||
terms: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,14 +217,31 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if (!publicRate || !memberRate) {
|
||||
throw new Error("We should never make it where without rateCodes")
|
||||
throw new Error(
|
||||
"We should never make it here without both public and member rateCodes"
|
||||
)
|
||||
}
|
||||
|
||||
const key = isUserLoggedIn && isMainRoom ? memberRate : publicRate
|
||||
return getRate(key)
|
||||
return getRate(key, rateDefinition)
|
||||
}
|
||||
|
||||
const isSpecialRate =
|
||||
bookingCode &&
|
||||
roomConfiguration.products.every((item) => {
|
||||
return item.productType.public.rateType !== RateTypeEnum.Regular
|
||||
})
|
||||
|
||||
return (
|
||||
<li className={classNames}>
|
||||
<div className={styles.imageContainer}>
|
||||
@@ -289,6 +325,12 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
) : (
|
||||
<>
|
||||
<Caption color="uiTextHighContrast">{breakfastMessage}</Caption>
|
||||
{bookingCode ? (
|
||||
<span className={!isSpecialRate ? styles.strikedText : ""}>
|
||||
<PriceTagIcon />
|
||||
{bookingCode}
|
||||
</span>
|
||||
) : null}
|
||||
{roomConfiguration.products.map((product) => {
|
||||
const rate = getRateInfo(product)
|
||||
const isSelectedRateCode =
|
||||
@@ -311,6 +353,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
roomType={roomConfiguration.roomType}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
title={rate.title}
|
||||
priceInformation={rate.terms}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -97,4 +97,7 @@ div[data-multiroom="true"] .imageContainer {
|
||||
gap: var(--Spacing-x1);
|
||||
margin: 0;
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
}
|
||||
.strikedText {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
|
||||
import { alternativeHotels } from "@/constants/routes/hotelReservation"
|
||||
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import BookingCodeFilter from "@/components/HotelReservation/SelectHotel/BookingCodeFilter"
|
||||
import { useRoomContext } from "@/contexts/Room"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
@@ -14,14 +16,48 @@ import styles from "./roomSelectionPanel.module.css"
|
||||
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
import { useBookingCodeFilterStore } from "@/stores/bookingCode-filter"
|
||||
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
||||
|
||||
export default function RoomSelectionPanel() {
|
||||
const { rooms } = useRoomContext()
|
||||
const searchParams = useSearchParams()
|
||||
const bookingCode = searchParams.get("bookingCode")
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const noAvailableRooms = rooms.every(
|
||||
(roomConfig) => roomConfig.status === AvailabilityEnum.NotAvailable
|
||||
)
|
||||
const activeCodeFilter = useBookingCodeFilterStore((state) => state.activeCodeFilter)
|
||||
|
||||
let filteredRooms = rooms, isRegularRatesAvailableWithCode: boolean = false
|
||||
if (bookingCode) {
|
||||
isRegularRatesAvailableWithCode =
|
||||
!!bookingCode ?
|
||||
rooms?.some((room) => {
|
||||
return (
|
||||
room.status === "Available" &&
|
||||
room.products.some(
|
||||
(product) =>
|
||||
product.productType.public.rateType === RateTypeEnum.Regular
|
||||
)
|
||||
)
|
||||
})
|
||||
: false
|
||||
|
||||
filteredRooms = !isRegularRatesAvailableWithCode || activeCodeFilter === BookingCodeFilterEnum.All
|
||||
? rooms : rooms.filter((room) => {
|
||||
return room.products.every(
|
||||
(product) =>
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Discounted &&
|
||||
product.productType.public.rateType !== RateTypeEnum.Regular) ||
|
||||
(activeCodeFilter === BookingCodeFilterEnum.Regular &&
|
||||
product.productType.public.rateType === RateTypeEnum.Regular)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{noAvailableRooms ? (
|
||||
@@ -41,8 +77,9 @@ export default function RoomSelectionPanel() {
|
||||
</div>
|
||||
) : null}
|
||||
<RoomTypeFilter />
|
||||
{bookingCode && isRegularRatesAvailableWithCode ? <BookingCodeFilter /> : null}
|
||||
<ul className={styles.roomList}>
|
||||
{rooms.map((roomConfiguration) => (
|
||||
{filteredRooms.map((roomConfiguration) => (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
|
||||
@@ -22,6 +22,7 @@ export function RoomsContainer({
|
||||
hotelId,
|
||||
hotelData,
|
||||
toDate,
|
||||
bookingCode,
|
||||
}: RoomsContainerProps) {
|
||||
const { data: session } = useSession()
|
||||
const isUserLoggedIn = isValidClientSession(session)
|
||||
@@ -39,7 +40,8 @@ export function RoomsContainer({
|
||||
fromDateString,
|
||||
toDateString,
|
||||
lang,
|
||||
childArray
|
||||
childArray,
|
||||
bookingCode
|
||||
)
|
||||
|
||||
const { data: packages, isPending: isLoadingPackages } = useHotelPackages(
|
||||
|
||||
@@ -33,7 +33,7 @@ export default async function SelectRatePage({
|
||||
if (!searchDetails?.hotel) {
|
||||
return notFound()
|
||||
}
|
||||
const { hotel, adultsInRoom, childrenInRoom, selectHotelParams } =
|
||||
const { hotel, adultsInRoom, childrenInRoom, selectHotelParams, bookingCode } =
|
||||
searchDetails
|
||||
|
||||
const { fromDate, toDate } = getValidDates(
|
||||
|
||||
@@ -2,6 +2,7 @@ import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
@@ -38,14 +39,17 @@ export function getRates(
|
||||
) {
|
||||
return {
|
||||
change: rateDefinitions.filter(
|
||||
(rate) => rate.cancellationRule === "Changeable"
|
||||
(rate) => rate.cancellationRule === "Changeable" && rate.rateType === RateTypeEnum.Regular
|
||||
),
|
||||
flex: rateDefinitions.filter(
|
||||
(rate) => rate.cancellationRule === "CancellableBefore6PM"
|
||||
(rate) => rate.cancellationRule === "CancellableBefore6PM" && rate.rateType === RateTypeEnum.Regular
|
||||
),
|
||||
save: rateDefinitions.filter(
|
||||
(rate) => rate.cancellationRule === "NotCancellable"
|
||||
(rate) => rate.cancellationRule === "NotCancellable" && rate.rateType === RateTypeEnum.Regular
|
||||
),
|
||||
special: rateDefinitions.filter(
|
||||
(rate) => rate.rateType !== RateTypeEnum.Regular
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +59,8 @@ export function useRoomsAvailability(
|
||||
fromDateString: string,
|
||||
toDateString: string,
|
||||
lang: Lang,
|
||||
childArray?: Child[]
|
||||
childArray?: Child[],
|
||||
bookingCode?: string,
|
||||
) {
|
||||
const returnValue =
|
||||
trpc.hotel.availability.roomsCombinedAvailability.useQuery({
|
||||
@@ -65,6 +70,7 @@ export function useRoomsAvailability(
|
||||
uniqueAdultsCount,
|
||||
childArray,
|
||||
lang,
|
||||
bookingCode,
|
||||
})
|
||||
|
||||
const combinedAvailability = returnValue.data?.length
|
||||
|
||||
Reference in New Issue
Block a user