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
This commit is contained in:
Tobias Johansson
2025-02-14 07:48:30 +00:00
parent f9a03052b1
commit 53b6628b25
10 changed files with 210 additions and 118 deletions

View File

@@ -1,12 +1,14 @@
"use client"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useEffect, useMemo } from "react"
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"
@@ -34,9 +36,9 @@ export default function Rooms({
vat,
}: SelectRateProps) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const intl = useIntl()
const [isPending, startTransition] = useTransition()
const hotelId = searchParams.get("hotel")
const arrivalDate = searchParams.get("fromDate")
@@ -48,6 +50,8 @@ export default function Rooms({
calculateRateSummary,
initializeRates,
setGuestsInRooms,
modifyRateIndex,
closeModifyRate,
} = useRateSelectionStore()
const {
@@ -133,13 +137,6 @@ export default function Rooms({
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) => ({
@@ -163,35 +160,35 @@ export default function Rooms({
})
}, [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()
startTransition(() => {
const rooms = rateSummary.map((rate, index) => ({
roomTypeCode: rate?.roomTypeCode,
rateCode: rate?.public.rateCode,
counterRateCode: rate?.member?.rateCode,
packages: selectedPackagesByRoom[index] || [],
}))
window.history.pushState(null, "", `${pathname}?${queryParams.toString()}`)
router.push(`details?${queryParams}`)
const newSearchParams = convertObjToSearchParams({ rooms }, searchParams)
router.push(`details?${newSearchParams}`)
})
}
useEffect(() => {
requestAnimationFrame(() => {
const SCROLL_OFFSET = 100
const roomElements = document.querySelectorAll(`.${styles.roomContainer}`)
const index = selectedRates.findIndex((rate) => rate === undefined)
const targetIndex = index === -1 ? selectedRates.length - 1 : index - 1
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
@@ -202,40 +199,79 @@ export default function Rooms({
})
}
})
}, [selectedRates])
}, [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 classNames = roomSelectionPanelVariants({
active:
(index === 0 || selectedRates[index - 1] !== undefined) &&
selectedRates[index] === undefined,
selected: selectedRates[index] !== undefined,
})
const roomState = getRoomState(index)
const classNames = roomSelectionPanelVariants(roomState)
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>
{!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

View File

@@ -5,6 +5,14 @@
flex-direction: column;
gap: var(--Spacing-x2);
padding: var(--Spacing-x2) 0;
overflow: hidden;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
z-index: 1;
}
.roomContainer {
@@ -12,7 +20,8 @@
flex-direction: column;
border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Large);
padding: var(--Spacing-x2);
padding: var(--Spacing-x3);
background: var(--Base-Surface-Primary-light-Normal);
}
.roomPanel,
@@ -24,6 +33,7 @@
transition:
opacity 0.3s ease,
grid-template-rows 0.3s ease;
transform-origin: bottom;
}
.roomPanel > * {
@@ -47,6 +57,7 @@
grid-template-rows: 1fr;
opacity: 1;
height: auto;
padding-top: var(--Spacing-x1);
}
.hotelAlert {
@@ -54,3 +65,9 @@
margin: 0 auto;
padding: var(--Spacing-x-one-and-half);
}
@media (max-width: 768px) {
.roomContainer {
padding: var(--Spacing-x2);
}
}