295 lines
9.1 KiB
TypeScript
295 lines
9.1 KiB
TypeScript
"use client"
|
|
|
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|
import { useEffect, useMemo } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { useRateSelectionStore } from "@/stores/select-rate/rate-selection"
|
|
import { useRoomFilteringStore } from "@/stores/select-rate/room-filtering"
|
|
|
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
import { trackLowestRoomPrice } from "@/utils/tracking"
|
|
import { convertObjToSearchParams, convertSearchParamsToObj } from "@/utils/url"
|
|
|
|
import RateSummary from "../RateSummary"
|
|
import { RoomSelectionPanel } from "../RoomSelectionPanel"
|
|
import SelectedRoomPanel from "../SelectedRoomPanel"
|
|
import { filterDuplicateRoomTypesByLowestPrice } from "./utils"
|
|
import { roomSelectionPanelVariants } from "./variants"
|
|
|
|
import styles from "./rooms.module.css"
|
|
|
|
import {
|
|
type DefaultFilterOptions,
|
|
RoomPackageCodeEnum,
|
|
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
|
import type { SelectRateProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
|
|
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
|
|
|
|
export default function Rooms({
|
|
availablePackages,
|
|
hotelType,
|
|
isUserLoggedIn,
|
|
roomsAvailability,
|
|
roomCategories = [],
|
|
}: SelectRateProps) {
|
|
const router = useRouter()
|
|
const pathname = usePathname()
|
|
const searchParams = useSearchParams()
|
|
|
|
const hotelId = searchParams.get("hotel")
|
|
const arrivalDate = searchParams.get("fromDate")
|
|
const departureDate = searchParams.get("toDate")
|
|
|
|
const { selectedRates, rateSummary, calculateRateSummary, initializeRates } =
|
|
useRateSelectionStore()
|
|
|
|
const {
|
|
selectedPackagesByRoom,
|
|
setVisibleRooms,
|
|
setRoomsAvailability,
|
|
getFilteredRooms,
|
|
} = useRoomFilteringStore()
|
|
|
|
const bookingWidgetSearchData = useMemo(
|
|
() =>
|
|
convertSearchParamsToObj<SelectRateSearchParams>(
|
|
Object.fromEntries(searchParams)
|
|
),
|
|
[searchParams]
|
|
)
|
|
|
|
const isMultipleRooms = bookingWidgetSearchData.rooms.length > 1
|
|
|
|
const intl = useIntl()
|
|
|
|
useEffect(() => {
|
|
initializeRates(bookingWidgetSearchData.rooms.length)
|
|
}, [initializeRates, bookingWidgetSearchData.rooms.length])
|
|
|
|
const visibleRooms: RoomConfiguration[] = useMemo(() => {
|
|
const deduped = filterDuplicateRoomTypesByLowestPrice(
|
|
roomsAvailability.roomConfigurations
|
|
)
|
|
|
|
const separated = deduped.reduce<{
|
|
available: RoomConfiguration[]
|
|
notAvailable: RoomConfiguration[]
|
|
}>(
|
|
(acc, curr) => {
|
|
if (curr.status === "NotAvailable") {
|
|
return { ...acc, notAvailable: [...acc.notAvailable, curr] }
|
|
}
|
|
return { ...acc, available: [...acc.available, curr] }
|
|
},
|
|
{ available: [], notAvailable: [] }
|
|
)
|
|
|
|
return [...separated.available, ...separated.notAvailable]
|
|
}, [roomsAvailability.roomConfigurations])
|
|
|
|
const defaultPackages: DefaultFilterOptions[] = useMemo(
|
|
() => [
|
|
{
|
|
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
|
description: intl.formatMessage({ id: "Accessible Room" }),
|
|
itemCode: availablePackages.find(
|
|
(pkg) => pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
|
|
)?.itemCode,
|
|
},
|
|
{
|
|
code: RoomPackageCodeEnum.ALLERGY_ROOM,
|
|
description: intl.formatMessage({ id: "Allergy Room" }),
|
|
itemCode: availablePackages.find(
|
|
(pkg) => pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM
|
|
)?.itemCode,
|
|
},
|
|
{
|
|
code: RoomPackageCodeEnum.PET_ROOM,
|
|
description: intl.formatMessage({ id: "Pet Room" }),
|
|
itemCode: availablePackages.find(
|
|
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
|
)?.itemCode,
|
|
},
|
|
],
|
|
[availablePackages, intl]
|
|
)
|
|
|
|
useEffect(() => {
|
|
setRoomsAvailability(roomsAvailability)
|
|
setVisibleRooms()
|
|
}, [roomsAvailability, setRoomsAvailability, setVisibleRooms])
|
|
|
|
useEffect(() => {
|
|
if (
|
|
selectedRates.length > 0 &&
|
|
selectedRates.some((rate) => rate !== undefined)
|
|
) {
|
|
calculateRateSummary({
|
|
getFilteredRooms,
|
|
availablePackages,
|
|
roomCategories,
|
|
selectedPackagesByRoom,
|
|
})
|
|
}
|
|
}, [
|
|
selectedRates,
|
|
getFilteredRooms,
|
|
availablePackages,
|
|
roomCategories,
|
|
selectedPackagesByRoom,
|
|
calculateRateSummary,
|
|
])
|
|
|
|
useEffect(() => {
|
|
if (!rateSummary?.some((rate) => rate === null)) return
|
|
|
|
const hasAnySelection = selectedRates.some((rate) => rate !== undefined)
|
|
if (!hasAnySelection) return
|
|
}, [rateSummary, selectedRates])
|
|
|
|
useEffect(() => {
|
|
const pricesWithCurrencies = visibleRooms.flatMap((room) =>
|
|
room.products.map((product) => ({
|
|
price: product.productType.public.localPrice.pricePerNight,
|
|
currency: product.productType.public.localPrice.currency,
|
|
}))
|
|
)
|
|
const lowestPrice = pricesWithCurrencies.reduce(
|
|
(minPrice, { price }) => Math.min(minPrice, price),
|
|
Infinity
|
|
)
|
|
|
|
const currency = pricesWithCurrencies[0]?.currency
|
|
|
|
trackLowestRoomPrice({
|
|
hotelId,
|
|
arrivalDate,
|
|
departureDate,
|
|
lowestPrice: lowestPrice,
|
|
currency: currency,
|
|
})
|
|
}, [arrivalDate, departureDate, hotelId, visibleRooms])
|
|
|
|
const queryParams = useMemo(() => {
|
|
const rooms = rateSummary.map((rate, index) => ({
|
|
roomTypeCode: rate?.roomTypeCode,
|
|
rateCode: rate?.public.rateCode,
|
|
counterRateCode: rate?.member?.rateCode,
|
|
packages: selectedPackagesByRoom[index] || [],
|
|
}))
|
|
|
|
const newSearchParams = convertObjToSearchParams({ rooms }, searchParams)
|
|
|
|
return newSearchParams
|
|
}, [searchParams, rateSummary, selectedPackagesByRoom])
|
|
|
|
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
e.preventDefault()
|
|
|
|
window.history.pushState(null, "", `${pathname}?${queryParams.toString()}`)
|
|
router.push(`select-bed?${queryParams}`)
|
|
}
|
|
|
|
useEffect(() => {
|
|
requestAnimationFrame(() => {
|
|
const SCROLL_OFFSET = 100
|
|
const roomElements = document.querySelectorAll(`.${styles.roomContainer}`)
|
|
const index = selectedRates.findIndex((rate) => rate === undefined)
|
|
const selectedRoom = roomElements[index - 1]
|
|
|
|
if (selectedRoom) {
|
|
const elementPosition = selectedRoom.getBoundingClientRect().top
|
|
const offsetPosition = elementPosition + window.scrollY - SCROLL_OFFSET
|
|
|
|
window.scrollTo({
|
|
top: offsetPosition,
|
|
behavior: "smooth",
|
|
})
|
|
}
|
|
})
|
|
}, [selectedRates])
|
|
|
|
return (
|
|
<div className={styles.content}>
|
|
{isMultipleRooms ? (
|
|
bookingWidgetSearchData.rooms.map((room, index) => {
|
|
const classNames = roomSelectionPanelVariants({
|
|
active:
|
|
(index === 0 || selectedRates[index - 1] !== undefined) &&
|
|
selectedRates[index] === undefined,
|
|
selected: selectedRates[index] !== undefined,
|
|
})
|
|
return (
|
|
<div key={index} className={styles.roomContainer}>
|
|
{selectedRates[index] === undefined && (
|
|
<Subtitle>
|
|
{intl.formatMessage(
|
|
{ id: "Room {roomIndex}" },
|
|
{ roomIndex: index + 1 }
|
|
)}
|
|
,{" "}
|
|
{intl.formatMessage(
|
|
{
|
|
id: room.childrenInRoom?.length
|
|
? "{adults} adults, {children} children"
|
|
: "{adults} adults",
|
|
},
|
|
{
|
|
adults: room.adults,
|
|
children: room.childrenInRoom?.length,
|
|
}
|
|
)}
|
|
</Subtitle>
|
|
)}
|
|
<div className={classNames}>
|
|
<div className={styles.roomPanel}>
|
|
<SelectedRoomPanel
|
|
roomIndex={index}
|
|
room={room}
|
|
roomCategories={roomCategories}
|
|
/>
|
|
</div>
|
|
<div className={styles.roomSelectionPanel}>
|
|
<RoomSelectionPanel
|
|
roomCategories={roomCategories}
|
|
availablePackages={availablePackages}
|
|
selectedPackages={selectedPackagesByRoom[index]}
|
|
hotelType={hotelType}
|
|
defaultPackages={defaultPackages}
|
|
roomListIndex={index}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})
|
|
) : (
|
|
<RoomSelectionPanel
|
|
roomCategories={roomCategories}
|
|
availablePackages={availablePackages}
|
|
selectedPackages={selectedPackagesByRoom[0]}
|
|
hotelType={hotelType}
|
|
defaultPackages={defaultPackages}
|
|
roomListIndex={0}
|
|
/>
|
|
)}
|
|
|
|
{rateSummary && (
|
|
<form
|
|
method="GET"
|
|
action={`select-bed?${searchParams}`}
|
|
onSubmit={handleSubmit}
|
|
>
|
|
<RateSummary
|
|
isUserLoggedIn={isUserLoggedIn}
|
|
packages={availablePackages}
|
|
roomsAvailability={roomsAvailability}
|
|
/>
|
|
</form>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|