Files
web/components/HotelReservation/SelectRate/Rooms/index.tsx
Tobias Johansson 53b6628b25 Merged in feat/SW-965-select-rate-modify-room (pull request #1326)
Feat/SW-965 select rate modify room

* feat(SW-965): added new state for modify room and smaller fixes

* feat(SW-965): update state handling of modifyRateIndex

* fix: adjust scroll animation to handle modifyRateIndex

* fix: room state logic and removed unused css class


Approved-by: Pontus Dreij
Approved-by: Arvid Norlin
2025-02-14 07:48:30 +00:00

326 lines
9.9 KiB
TypeScript

"use client"
import { useRouter, useSearchParams } from "next/navigation"
import { useCallback, useEffect, useMemo, useTransition } from "react"
import { useIntl } from "react-intl"
import { useRateSelectionStore } from "@/stores/select-rate/rate-selection"
import { useRoomFilteringStore } from "@/stores/select-rate/room-filtering"
import { ChevronDownSmallIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
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 { 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"
export default function Rooms({
availablePackages,
hotelType,
isUserLoggedIn,
roomsAvailability,
roomCategories = [],
vat,
}: SelectRateProps) {
const router = useRouter()
const searchParams = useSearchParams()
const intl = useIntl()
const [isPending, startTransition] = useTransition()
const hotelId = searchParams.get("hotel")
const arrivalDate = searchParams.get("fromDate")
const departureDate = searchParams.get("toDate")
const {
selectedRates,
rateSummary,
calculateRateSummary,
initializeRates,
setGuestsInRooms,
modifyRateIndex,
closeModifyRate,
} = useRateSelectionStore()
const {
selectedPackagesByRoom,
visibleRooms,
setVisibleRooms,
setRoomsAvailability,
getFilteredRooms,
} = useRoomFilteringStore()
const bookingWidgetSearchData = useMemo(
() =>
convertSearchParamsToObj<SelectRateSearchParams>(
Object.fromEntries(searchParams)
),
[searchParams]
)
useEffect(() => {
bookingWidgetSearchData.rooms.forEach((room, index) => {
setGuestsInRooms(index, room.adults, room.childrenInRoom)
})
}, [bookingWidgetSearchData.rooms, setGuestsInRooms])
const isMultipleRooms = bookingWidgetSearchData.rooms.length > 1
useEffect(() => {
initializeRates(bookingWidgetSearchData.rooms.length)
}, [initializeRates, bookingWidgetSearchData.rooms.length])
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-friendly 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(() => {
if (roomsAvailability) {
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(() => {
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])
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
startTransition(() => {
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)
router.push(`details?${newSearchParams}`)
})
}
useEffect(() => {
requestAnimationFrame(() => {
const SCROLL_OFFSET = 100
const roomElements = document.querySelectorAll(`.${styles.roomContainer}`)
let targetIndex: number
if (modifyRateIndex !== null) {
targetIndex = modifyRateIndex
} else {
const index = selectedRates.findIndex((rate) => rate === undefined)
targetIndex = index === -1 ? selectedRates.length - 1 : index - 1
}
const selectedRoom = roomElements[targetIndex]
if (selectedRoom) {
const elementPosition = selectedRoom.getBoundingClientRect().top
const offsetPosition = elementPosition + window.scrollY - SCROLL_OFFSET
window.scrollTo({
top: offsetPosition,
behavior: "smooth",
})
}
})
}, [selectedRates, modifyRateIndex])
const getRoomState = useCallback(
(index: number) => {
const isFirstRoom = index === 0
const hasPrevRoomBeenSelected = selectedRates[index - 1] !== undefined
const isCurrentRoomSelected = selectedRates[index] !== undefined
const isModifyRoom = modifyRateIndex === index
if (isModifyRoom && isCurrentRoomSelected) {
return { active: true, selected: false }
}
if (isCurrentRoomSelected) {
return { active: false, selected: true }
}
if (
(isFirstRoom || hasPrevRoomBeenSelected) &&
modifyRateIndex === null
) {
return { active: true, selected: false }
}
return { active: false, selected: false }
},
[modifyRateIndex, selectedRates]
)
return (
<div className={styles.content}>
{isMultipleRooms ? (
bookingWidgetSearchData.rooms.map((room, index) => {
const roomState = getRoomState(index)
const classNames = roomSelectionPanelVariants(roomState)
return (
<div key={index} className={styles.roomContainer}>
{!roomState.selected && (
<header className={styles.header}>
<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>
{modifyRateIndex === index ? (
<Button
variant="icon"
size="medium"
intent="text"
theme="base"
onClick={closeModifyRate}
>
{intl.formatMessage({ id: "Close" })}
<ChevronDownSmallIcon />
</Button>
) : null}
</header>
)}
<div className={classNames}>
<div className={styles.roomPanel}>
<SelectedRoomPanel
roomIndex={index}
room={room}
roomCategories={roomCategories}
/>
</div>
<div className={styles.roomSelectionPanel}>
<RoomSelectionPanel
availablePackages={availablePackages}
defaultPackages={defaultPackages}
hotelType={hotelType}
roomCategories={roomCategories}
roomListIndex={index}
selectedPackages={selectedPackagesByRoom[index]}
/>
</div>
</div>
</div>
)
})
) : (
<RoomSelectionPanel
availablePackages={availablePackages}
defaultPackages={defaultPackages}
hotelType={hotelType}
roomCategories={roomCategories}
roomListIndex={0}
selectedPackages={selectedPackagesByRoom[0]}
/>
)}
{rateSummary && roomsAvailability && (
<form
method="GET"
action={`details?${searchParams}`}
onSubmit={handleSubmit}
>
<RateSummary
isUserLoggedIn={isUserLoggedIn}
packages={availablePackages}
roomsAvailability={roomsAvailability}
booking={bookingWidgetSearchData}
vat={vat}
/>
</form>
)}
</div>
)
}