diff --git a/components/HotelReservation/SelectRate/RoomList/FlexibilityOption/index.tsx b/components/HotelReservation/SelectRate/RoomList/FlexibilityOption/index.tsx index a79227d0a..39ef000b6 100644 --- a/components/HotelReservation/SelectRate/RoomList/FlexibilityOption/index.tsx +++ b/components/HotelReservation/SelectRate/RoomList/FlexibilityOption/index.tsx @@ -4,6 +4,8 @@ import { useSearchParams } from "next/navigation" import { useEffect, useRef } from "react" import { useIntl } from "react-intl" +import { useRateSelectionStore } from "@/stores/rate-selection" + import { CheckIcon, InfoCircleIcon } from "@/components/Icons" import Modal from "@/components/Modal" import Button from "@/components/TempDesignSystem/Button" @@ -24,16 +26,11 @@ export default function FlexibilityOption({ priceInformation, roomTypeCode, petRoomPackage, - handleSelectRate, roomListIndex, }: FlexibilityOptionProps) { const intl = useIntl() const inputElementRef = useRef(null) - const handleSelectRateRef = useRef(handleSelectRate) - - useEffect(() => { - handleSelectRateRef.current = handleSelectRate - }, [handleSelectRate]) + const { selectRate, selectedRates } = useRateSelectionStore() const searchParams = useSearchParams() @@ -57,24 +54,30 @@ export default function FlexibilityOption({ return } - handleSelectRateRef.current((prev) => { - // If the user already has made a new selection we respect that and don't do anything else - if (prev) { - return prev - } + // Check if there's already a selection for this room index + const existingSelection = selectedRates[roomListIndex] + if (existingSelection) return - if (inputElementRef.current) { - inputElementRef.current.checked = true - } - - return { - publicRateCode: product.productType.public.rateCode, - roomTypeCode: roomTypeCode, - name: name, - paymentTerm: paymentTerm, - } + selectRate(roomListIndex, { + publicRateCode: product.productType.public.rateCode, + roomTypeCode: roomTypeCode, + name: name, + paymentTerm: paymentTerm, }) - }, [searchParams, roomListIndex, product, roomTypeCode, name, paymentTerm]) + + if (inputElementRef.current) { + inputElementRef.current.checked = true + } + }, [ + searchParams, + roomListIndex, + product, + roomTypeCode, + name, + paymentTerm, + selectedRates, + selectRate, + ]) if (!product) { return ( @@ -98,22 +101,20 @@ export default function FlexibilityOption({ const { public: publicPrice, member: memberPrice } = product.productType const onClick: React.MouseEventHandler = (e) => { - handleSelectRateRef.current((prev) => { - if ( - prev && - prev.publicRateCode === publicPrice.rateCode && - prev.roomTypeCode === roomTypeCode - ) { - if (e.currentTarget?.checked) e.currentTarget.checked = false - return undefined - } else - return { - publicRateCode: publicPrice.rateCode, - roomTypeCode: roomTypeCode, - name: name, - paymentTerm: paymentTerm, - } - }) + if ( + selectedRates[roomListIndex]?.publicRateCode === publicPrice.rateCode && + selectedRates[roomListIndex]?.roomTypeCode === roomTypeCode + ) { + if (e.currentTarget?.checked) e.currentTarget.checked = false + selectRate(roomListIndex, undefined) + } else { + selectRate(roomListIndex, { + publicRateCode: publicPrice.rateCode, + roomTypeCode: roomTypeCode, + name: name, + paymentTerm: paymentTerm, + }) + } } return ( diff --git a/components/HotelReservation/SelectRate/RoomList/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomList/RoomCard/index.tsx index 0ddf3cfb1..4236ad1d2 100644 --- a/components/HotelReservation/SelectRate/RoomList/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomList/RoomCard/index.tsx @@ -3,6 +3,8 @@ import { createElement, useCallback } from "react" import { useIntl } from "react-intl" +import { useRateSelectionStore } from "@/stores/rate-selection" + import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek" import { getIconForFeatureCode } from "@/components/HotelReservation/utils" import { ErrorCircleIcon } from "@/components/Icons" @@ -29,11 +31,14 @@ export default function RoomCard({ roomCategories, selectedPackages, packages, - handleSelectRate, roomListIndex, }: RoomCardProps) { const intl = useIntl() + const selectedRate = useRateSelectionStore( + (state) => state.selectedRates[roomListIndex] + ) + const rates = { saveRate: rateDefinitions.find( (rate) => rate.cancellationRule === "NotCancellable" @@ -215,13 +220,12 @@ export default function RoomCard({ ) : ( Object.entries(rates).map(([key, rate]) => ( diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 9ec77e330..279a03c2c 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -1,10 +1,11 @@ "use client" import { usePathname, useRouter, useSearchParams } from "next/navigation" -import { useCallback, useEffect, useMemo, useState } from "react" +import { useCallback, useEffect, useMemo } from "react" import { useIntl } from "react-intl" -import Caption from "@/components/TempDesignSystem/Text/Caption" +import { useRateSelectionStore } from "@/stores/rate-selection" + import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import { useRateSummary } from "@/hooks/selectRate/useRateSummary" import { useRoomFiltering } from "@/hooks/selectRate/useRoomFiltering" @@ -23,14 +24,9 @@ import { RoomPackageCodeEnum, } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { SelectRateProps } from "@/types/components/hotelReservation/selectRate/roomSelection" -import type { - Rate, - RateCode, -} from "@/types/components/hotelReservation/selectRate/selectRate" +import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" import type { RoomConfiguration } from "@/server/routers/hotels/output" -type SelectedRates = (RateCode | undefined)[] - export default function Rooms({ roomsAvailability, roomCategories = [], @@ -46,19 +42,22 @@ export default function Rooms({ const arrivalDate = searchParams.get("fromDate") const departureDate = searchParams.get("toDate") + const { modifyRate, selectedRates, setSelectedRates } = + useRateSelectionStore() + const searchedRoomsAndGuests = useMemo( () => parseRoomParams(searchParams), [searchParams] ) - const [selectedRates, setSelectedRates] = useState( - new Array(searchedRoomsAndGuests.length).fill(undefined) - ) - const isMultipleRooms = searchedRoomsAndGuests.length > 1 const intl = useIntl() + useEffect(() => { + setSelectedRates(new Array(searchedRoomsAndGuests.length).fill(undefined)) + }, [setSelectedRates, searchedRoomsAndGuests.length]) + const visibleRooms: RoomConfiguration[] = useMemo(() => { const deduped = filterDuplicateRoomTypesByLowestPrice( roomsAvailability.roomConfigurations @@ -173,16 +172,11 @@ export default function Rooms({ router.push(`select-bed?${queryParams}`) } - const setSelectedRateForRoom = useCallback( - (index: number) => (value: React.SetStateAction) => { - setSelectedRates((prev) => { - const newRates = [...prev] - newRates[index] = - typeof value === "function" ? value(prev[index]) : value - return newRates - }) + const handleModify = useCallback( + (index: number) => () => { + modifyRate(index) }, - [] + [modifyRate] ) const handleFilterForRoom = useCallback( @@ -193,6 +187,25 @@ export default function Rooms({ [handleFilter] ) + useEffect(() => { + setTimeout(() => { + 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", + }) + } + }, 0) + }, [selectedRates]) + return (
{isMultipleRooms ? ( @@ -232,6 +245,7 @@ export default function Rooms({ room={room} selectedRate={rateSummary[index]} roomCategories={roomCategories} + handleModify={handleModify(index)} />
@@ -240,7 +254,6 @@ export default function Rooms({ roomCategories={roomCategories} availablePackages={availablePackages} selectedPackages={selectedPackagesByRoom[index]} - setSelectedRate={setSelectedRateForRoom(index)} hotelType={hotelType} handleFilter={handleFilterForRoom(index)} defaultPackages={defaultPackages} @@ -256,7 +269,6 @@ export default function Rooms({ roomCategories={roomCategories} availablePackages={availablePackages} selectedPackages={selectedPackagesByRoom[0]} - setSelectedRate={setSelectedRateForRoom(0)} hotelType={hotelType} handleFilter={handleFilterForRoom(0)} defaultPackages={defaultPackages} diff --git a/components/HotelReservation/SelectRate/Rooms/rooms.module.css b/components/HotelReservation/SelectRate/Rooms/rooms.module.css index cfd0bc2c1..358254657 100644 --- a/components/HotelReservation/SelectRate/Rooms/rooms.module.css +++ b/components/HotelReservation/SelectRate/Rooms/rooms.module.css @@ -10,10 +10,9 @@ .roomContainer { display: flex; flex-direction: column; - gap: var(--Spacing-x2); border: 1px solid var(--Base-Border-Subtle); border-radius: var(--Corner-radius-Large); - padding: var(--Spacing-x2) var(--Spacing-x2) 0 var(--Spacing-x2); + padding: var(--Spacing-x2); } .roomSelectionPanel { @@ -24,6 +23,7 @@ opacity 0.3s ease, grid-template-rows 0.5s ease; height: 0; + gap: var(--Spacing-x2); } .roomSelectionPanel > * { @@ -48,9 +48,11 @@ grid-template-rows: 1fr; opacity: 1; height: auto; - padding-bottom: var(--Spacing-x2); } +.roomSelectionPanelContainer[data-selected="true"] .roomSelectionPanel { + display: none; +} .roomSelectionPanelContainer[data-active-panel="true"] .roomSelectionPanel { grid-template-rows: 1fr; opacity: 1; diff --git a/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx b/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx index fc0b395cc..e76f45eab 100644 --- a/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx +++ b/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx @@ -1,7 +1,9 @@ "use client" import { useIntl } from "react-intl" +import { EditIcon } from "@/components/Icons" import Image from "@/components/Image" +import Button from "@/components/TempDesignSystem/Button" import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" @@ -16,11 +18,13 @@ export default function SelectedRoomPanel({ room, selectedRate, roomCategories, + handleModify, }: { roomIndex: number room: RoomParam selectedRate: Rate | null roomCategories: RoomData[] + handleModify: () => void }) { const intl = useIntl() const images = roomCategories.find((roomCategory) => @@ -67,12 +71,25 @@ export default function SelectedRoomPanel({ })}
- {selectedRate?.roomType +
+ {images && ( +
+ {selectedRate?.roomType +
+ )} +
+ +
+
) } diff --git a/components/HotelReservation/SelectRate/SelectedRoomPanel/selectedRoomPanel.module.css b/components/HotelReservation/SelectRate/SelectedRoomPanel/selectedRoomPanel.module.css index 634069add..8c4adce07 100644 --- a/components/HotelReservation/SelectRate/SelectedRoomPanel/selectedRoomPanel.module.css +++ b/components/HotelReservation/SelectRate/SelectedRoomPanel/selectedRoomPanel.module.css @@ -2,4 +2,35 @@ display: flex; flex-direction: row; justify-content: space-between; + position: relative; +} + +.modifyButtonContainer { + position: absolute; + right: var(--Spacing-x2); + bottom: var(--Spacing-x2); +} + +.imageContainer { + width: 240px; + height: 160px; + position: relative; +} + +@media (max-width: 768px) { + .imageContainer { + width: 120px; + height: 80px; + } + .imageAndModifyButtonContainer { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: var(--Spacing-x1); + } + .modifyButtonContainer { + position: relative; + bottom: 0; + right: 0; + } } diff --git a/stores/rate-selection.ts b/stores/rate-selection.ts new file mode 100644 index 000000000..cca028a37 --- /dev/null +++ b/stores/rate-selection.ts @@ -0,0 +1,27 @@ +import { create } from "zustand" + +import type { RateCode } from "@/types/components/hotelReservation/selectRate/selectRate" + +interface RateSelectionState { + selectedRates: (RateCode | undefined)[] + setSelectedRates: (rates: (RateCode | undefined)[]) => void + modifyRate: (index: number) => void + selectRate: (index: number, rate: RateCode | undefined) => void +} + +export const useRateSelectionStore = create((set) => ({ + selectedRates: [], + setSelectedRates: (rates) => set({ selectedRates: rates }), + modifyRate: (index) => + set((state) => { + const newRates = [...state.selectedRates] + newRates[index] = undefined + return { selectedRates: newRates } + }), + selectRate: (index, rate) => + set((state) => { + const newRates = [...state.selectedRates] + newRates[index] = rate + return { selectedRates: newRates } + }), +})) diff --git a/types/components/hotelReservation/selectRate/flexibilityOption.ts b/types/components/hotelReservation/selectRate/flexibilityOption.ts index 008498ac9..e7f99ddaf 100644 --- a/types/components/hotelReservation/selectRate/flexibilityOption.ts +++ b/types/components/hotelReservation/selectRate/flexibilityOption.ts @@ -7,7 +7,6 @@ import type { RoomConfiguration, } from "@/server/routers/hotels/output" import type { RoomPackage } from "./roomFilter" -import type { RateCode } from "./selectRate" type ProductPrice = z.output export type RoomPriceSchema = z.output @@ -20,12 +19,11 @@ export type FlexibilityOptionProps = { priceInformation?: Array roomTypeCode: RoomConfiguration["roomTypeCode"] petRoomPackage: RoomPackage | undefined - handleSelectRate: React.Dispatch> roomListIndex: number } export interface PriceListProps { publicPrice?: ProductPrice | Record memberPrice?: ProductPrice | Record - petRoomPackage?: RoomPackage | undefined + petRoomPackage?: RoomPackage } diff --git a/types/components/hotelReservation/selectRate/roomCard.ts b/types/components/hotelReservation/selectRate/roomCard.ts index 35569ea0c..5e5d58eae 100644 --- a/types/components/hotelReservation/selectRate/roomCard.ts +++ b/types/components/hotelReservation/selectRate/roomCard.ts @@ -8,7 +8,6 @@ import type { } from "@/server/routers/hotels/output" import type { RoomPriceSchema } from "./flexibilityOption" import type { RoomPackageCodes, RoomPackageData } from "./roomFilter" -import type { RateCode } from "./selectRate" export type RoomCardProps = { hotelId: string @@ -18,7 +17,6 @@ export type RoomCardProps = { roomCategories: RoomData[] selectedPackages: RoomPackageCodes[] packages: RoomPackageData | undefined - handleSelectRate: React.Dispatch> roomListIndex: number } diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts index b452518ad..75a44a7fc 100644 --- a/types/components/hotelReservation/selectRate/roomSelection.ts +++ b/types/components/hotelReservation/selectRate/roomSelection.ts @@ -7,14 +7,12 @@ import type { RoomPackageCodes, RoomPackageData, } from "./roomFilter" -import type { RateCode } from "./selectRate" export interface RoomListProps { roomsAvailability: RoomsAvailability roomCategories: RoomData[] availablePackages: RoomPackageData | undefined selectedPackages: RoomPackageCodes[] - setRateCode: React.Dispatch> hotelType: string | undefined roomListIndex: number } @@ -32,7 +30,6 @@ export interface RoomSelectionPanelProps { roomCategories: RoomData[] availablePackages: RoomPackage[] selectedPackages: RoomPackageCodes[] - setSelectedRate: React.Dispatch> hotelType: string | undefined handleFilter: ( filter: Record