feat(SW-718) Refactor select rate to support multiroom

This commit is contained in:
Pontus Dreij
2025-01-21 10:31:15 +01:00
parent 7d716dcf4a
commit edcf146ce1
27 changed files with 202 additions and 131 deletions

View File

@@ -1,6 +1,6 @@
import SkeletonShimmer from "@/components/SkeletonShimmer" import SkeletonShimmer from "@/components/SkeletonShimmer"
import { RoomCardSkeleton } from "../../SelectRate/RoomSelection/RoomCard/RoomCardSkeleton" import { RoomCardSkeleton } from "../../SelectRate/RoomList/RoomCard/RoomCardSkeleton"
import styles from "./SelectHotelMapContainerSkeleton.module.css" import styles from "./SelectHotelMapContainerSkeleton.module.css"

View File

@@ -8,7 +8,7 @@ import { selectHotel } from "@/constants/routes/hotelReservation"
import { useHotelFilterStore } from "@/stores/hotel-filters" import { useHotelFilterStore } from "@/stores/hotel-filters"
import { useHotelsMapStore } from "@/stores/hotels-map" import { useHotelsMapStore } from "@/stores/hotels-map"
import { RoomCardSkeleton } from "@/components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton" import { RoomCardSkeleton } from "@/components/HotelReservation/SelectRate/RoomList/RoomCard/RoomCardSkeleton"
import { CloseIcon, CloseLargeIcon } from "@/components/Icons" import { CloseIcon, CloseLargeIcon } from "@/components/Icons"
import InteractiveMap from "@/components/Maps/InteractiveMap" import InteractiveMap from "@/components/Maps/InteractiveMap"
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton" import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"

View File

@@ -4,7 +4,6 @@ import { createElement, useCallback } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek" import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek"
import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption"
import { getIconForFeatureCode } from "@/components/HotelReservation/utils" import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
import { ErrorCircleIcon } from "@/components/Icons" import { ErrorCircleIcon } from "@/components/Icons"
import ImageGallery from "@/components/ImageGallery" import ImageGallery from "@/components/ImageGallery"
@@ -12,6 +11,7 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import FlexibilityOption from "../FlexibilityOption"
import { cardVariants } from "./cardVariants" import { cardVariants } from "./cardVariants"
import styles from "./roomCard.module.css" import styles from "./roomCard.module.css"

View File

@@ -0,0 +1,38 @@
"use client"
import RoomCard from "./RoomCard"
import styles from "./roomSelection.module.css"
import type { RoomListProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
export default function RoomList({
roomsAvailability,
roomCategories,
availablePackages,
selectedPackages,
setRateCode,
hotelType,
}: RoomListProps) {
const { roomConfigurations, rateDefinitions } = roomsAvailability
return (
<div className={styles.wrapper}>
<ul className={styles.roomList}>
{roomConfigurations.map((roomConfiguration, index) => (
<RoomCard
hotelId={roomsAvailability.hotelId.toString()}
hotelType={hotelType}
rateDefinitions={rateDefinitions}
roomConfiguration={roomConfiguration}
roomCategories={roomCategories}
handleSelectRate={setRateCode}
selectedPackages={selectedPackages}
packages={availablePackages}
key={roomConfiguration.roomTypeCode}
/>
))}
</ul>
</div>
)
}

View File

@@ -1,92 +0,0 @@
"use client"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useMemo } from "react"
import { convertObjToSearchParams } from "@/utils/url"
import RateSummary from "./RateSummary"
import RoomCard from "./RoomCard"
import styles from "./roomSelection.module.css"
import type { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
export default function RoomSelection({
roomsAvailability,
roomCategories,
availablePackages,
selectedPackages,
isUserLoggedIn,
setRateCode,
rateSummary,
hotelType,
}: RoomSelectionProps) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const { roomConfigurations, rateDefinitions } = roomsAvailability
const queryParams = useMemo(() => {
// TODO: handle multiple rooms
const newSearchParams = convertObjToSearchParams(
{
rooms: [
{
roomTypeCode: rateSummary?.roomTypeCode,
rateCode: rateSummary?.public.rateCode,
counterRateCode: rateSummary?.member?.rateCode,
packages: selectedPackages,
},
],
},
searchParams
)
return newSearchParams
}, [searchParams, rateSummary, selectedPackages])
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
window.history.replaceState(
null,
"",
`${pathname}?${queryParams.toString()}`
)
router.push(`select-bed?${queryParams}`)
}
return (
<div className={styles.wrapper}>
<form
method="GET"
action={`select-bed?${searchParams}`}
onSubmit={handleSubmit}
>
<ul className={styles.roomList}>
{roomConfigurations.map((roomConfiguration, index) => (
<RoomCard
hotelId={roomsAvailability.hotelId.toString()}
hotelType={hotelType}
rateDefinitions={rateDefinitions}
roomConfiguration={roomConfiguration}
roomCategories={roomCategories}
handleSelectRate={setRateCode}
selectedPackages={selectedPackages}
packages={availablePackages}
key={roomConfiguration.roomTypeCode}
/>
))}
</ul>
{rateSummary && (
<RateSummary
rateSummary={rateSummary}
isUserLoggedIn={isUserLoggedIn}
packages={availablePackages}
roomsAvailability={roomsAvailability}
/>
)}
</form>
</div>
)
}

View File

@@ -0,0 +1,33 @@
import RoomFilter from "../RoomFilter"
import RoomList from "../RoomList"
import type { RoomSelectionPanelProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
export function RoomSelectionPanel({
rooms,
roomCategories,
availablePackages,
selectedPackages,
setSelectedRate,
hotelType,
handleFilter,
defaultPackages,
}: RoomSelectionPanelProps) {
return (
<>
<RoomFilter
numberOfRooms={rooms.roomConfigurations.length}
onFilter={handleFilter}
filterOptions={defaultPackages}
/>
<RoomList
roomsAvailability={rooms}
roomCategories={roomCategories}
availablePackages={availablePackages}
selectedPackages={selectedPackages}
setRateCode={setSelectedRate}
hotelType={hotelType}
/>
</>
)
}

View File

@@ -1,4 +1,4 @@
import { RoomCardSkeleton } from "../RoomSelection/RoomCard/RoomCardSkeleton" import { RoomCardSkeleton } from "../RoomList/RoomCard/RoomCardSkeleton"
import styles from "./RoomsContainerSkeleton.module.css" import styles from "./RoomsContainerSkeleton.module.css"

View File

@@ -1,14 +1,15 @@
"use client" "use client"
import { useSearchParams } from "next/navigation" import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useCallback, useEffect, useMemo, useState } from "react" import { useCallback, useEffect, useMemo, useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { trackLowestRoomPrice } from "@/utils/tracking" import { trackLowestRoomPrice } from "@/utils/tracking"
import { convertObjToSearchParams } from "@/utils/url"
import RoomFilter from "../RoomFilter" import RateSummary from "../RateSummary"
import RoomSelection from "../RoomSelection" import { RoomSelectionPanel } from "../RoomSelectionPanel"
import { filterDuplicateRoomTypesByLowestPrice, parseRoomParams } from "./utils" import { filterDuplicateRoomTypesByLowestPrice, parseRoomParams } from "./utils"
import styles from "./rooms.module.css" import styles from "./rooms.module.css"
@@ -32,6 +33,8 @@ export default function Rooms({
hotelType, hotelType,
isUserLoggedIn, isUserLoggedIn,
}: SelectRateProps) { }: SelectRateProps) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const hotelId = searchParams.get("hotel") const hotelId = searchParams.get("hotel")
@@ -43,6 +46,8 @@ export default function Rooms({
[searchParams] [searchParams]
) )
const isMultipleRooms = searchedRoomsAndGuests.length > 1
const intl = useIntl() const intl = useIntl()
const visibleRooms: RoomConfiguration[] = useMemo(() => { const visibleRooms: RoomConfiguration[] = useMemo(() => {
@@ -218,33 +223,94 @@ export default function Rooms({
}) })
}, [arrivalDate, departureDate, hotelId, rooms.roomConfigurations]) }, [arrivalDate, departureDate, hotelId, rooms.roomConfigurations])
const queryParams = useMemo(() => {
// TODO: handle multiple rooms
const newSearchParams = convertObjToSearchParams(
{
rooms: [
{
roomTypeCode: rateSummary?.roomTypeCode,
rateCode: rateSummary?.public.rateCode,
counterRateCode: rateSummary?.member?.rateCode,
packages: selectedPackages,
},
],
},
searchParams
)
return newSearchParams
}, [searchParams, rateSummary, selectedPackages])
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
window.history.replaceState(
null,
"",
`${pathname}?${queryParams.toString()}`
)
router.push(`select-bed?${queryParams}`)
}
return ( return (
<> <div className={styles.content}>
{searchedRoomsAndGuests.map((room, index) => ( {isMultipleRooms ? (
<div key={index} className={styles.content}> searchedRoomsAndGuests.map((room, index) => (
<Subtitle> <div key={index}>
{`Room ${index + 1}, ${room.adults} adults`} <Subtitle>
{room.children && {intl.formatMessage(
room.children.length > 0 && {
`, ${room.children.length} children`} id: room.children?.length
</Subtitle> ? "Room {roomIndex}, {adults} adults, {children} children"
<RoomFilter : "Room {roomIndex}, {adults} adults",
numberOfRooms={rooms.roomConfigurations.length} },
onFilter={handleFilter} {
filterOptions={defaultPackages} roomIndex: index + 1,
/> adults: room.adults,
<RoomSelection children: room.children?.length,
roomsAvailability={rooms} }
roomCategories={roomCategories} )}
availablePackages={availablePackages} </Subtitle>
selectedPackages={selectedPackages} <RoomSelectionPanel
setRateCode={setSelectedRate} rooms={rooms}
roomCategories={roomCategories}
availablePackages={availablePackages}
selectedPackages={selectedPackages}
setSelectedRate={setSelectedRate}
hotelType={hotelType}
handleFilter={handleFilter}
defaultPackages={defaultPackages}
/>
</div>
))
) : (
<RoomSelectionPanel
rooms={rooms}
roomCategories={roomCategories}
availablePackages={availablePackages}
selectedPackages={selectedPackages}
setSelectedRate={setSelectedRate}
hotelType={hotelType}
handleFilter={handleFilter}
defaultPackages={defaultPackages}
/>
)}
{rateSummary && (
<form
method="GET"
action={`select-bed?${searchParams}`}
onSubmit={handleSubmit}
>
<RateSummary
rateSummary={rateSummary} rateSummary={rateSummary}
hotelType={hotelType}
isUserLoggedIn={isUserLoggedIn} isUserLoggedIn={isUserLoggedIn}
packages={availablePackages}
roomsAvailability={roomsAvailability}
/> />
</div> </form>
))} )}
</> </div>
) )
} }

View File

@@ -381,6 +381,8 @@
"Room & Terms": "Værelse & Vilkår", "Room & Terms": "Værelse & Vilkår",
"Room charge": "Værelsesafgift", "Room charge": "Værelsesafgift",
"Room facilities": "Værelsesfaciliteter", "Room facilities": "Værelsesfaciliteter",
"Room {roomIndex}, {adults} adults": "Værelse {roomIndex}, {adults, plural, one {# voksen} other {# voksne}}",
"Room {roomIndex}, {adults} adults, {children} children": "Værelse {roomIndex}, {adults, plural, one {# voksen} other {# voksne}}, {children, plural, one {# barn} other {# børn}}",
"Rooms": "Værelser", "Rooms": "Værelser",
"Rooms & Guests": "Værelser & gæster", "Rooms & Guests": "Værelser & gæster",
"Sat-Sun Always open": "Lør-Søn Altid åben", "Sat-Sun Always open": "Lør-Søn Altid åben",

View File

@@ -376,10 +376,11 @@
"Restaurant & Bar": "Restaurant & Bar", "Restaurant & Bar": "Restaurant & Bar",
"Restaurants & Bars": "Restaurants & Bars", "Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Neues Passwort erneut eingeben", "Retype new password": "Neues Passwort erneut eingeben",
"Room": "Zimmer",
"Room & Terms": "Zimmer & Bedingungen", "Room & Terms": "Zimmer & Bedingungen",
"Room charge": "Zimmerpreis", "Room charge": "Zimmerpreis",
"Room facilities": "Zimmerausstattung", "Room facilities": "Zimmerausstattung",
"Room {roomIndex}, {adults} adults": "Zimmer {roomIndex}, {adults, plural, one {# Erwachsene} other {# Erwachsene}}",
"Room {roomIndex}, {adults} adults, {children} children": "Zimmer {roomIndex}, {adults, plural, one {# Erwachsene} other {# Erwachsene}}, {children, plural, one {# Kind} other {# Kinder}}",
"Rooms": "Räume", "Rooms": "Räume",
"Rooms & Guests": "Zimmer & Gäste", "Rooms & Guests": "Zimmer & Gäste",
"Sat-Sun Always open": "Sa-So Immer geöffnet", "Sat-Sun Always open": "Sa-So Immer geöffnet",

View File

@@ -417,10 +417,11 @@
"Restaurant & Bar": "Restaurant & Bar", "Restaurant & Bar": "Restaurant & Bar",
"Restaurants & Bars": "Restaurants & Bars", "Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Retype new password", "Retype new password": "Retype new password",
"Room": "Room",
"Room & Terms": "Room & Terms", "Room & Terms": "Room & Terms",
"Room charge": "Room charge", "Room charge": "Room charge",
"Room facilities": "Room facilities", "Room facilities": "Room facilities",
"Room {roomIndex}, {adults} adults": "Room {roomIndex}, {adults, plural, one {# adult} other {# adults}}",
"Room {roomIndex}, {adults} adults, {children} children": "Room {roomIndex}, {adults, plural, one {# adult} other {# adults}}, {children, plural, one {# child} other {# children}}",
"Rooms": "Rooms", "Rooms": "Rooms",
"Rooms & Guests": "Rooms & Guests", "Rooms & Guests": "Rooms & Guests",
"Sat-Sun Always open": "Sat-Sun Always open", "Sat-Sun Always open": "Sat-Sun Always open",

View File

@@ -381,6 +381,8 @@
"Room & Terms": "Huone & Ehdot", "Room & Terms": "Huone & Ehdot",
"Room charge": "Huonemaksu", "Room charge": "Huonemaksu",
"Room facilities": "Huoneen varustelu", "Room facilities": "Huoneen varustelu",
"Room {roomIndex}, {adults} adults": "Huone {roomIndex}, {adults, plural, one {# vieras} other {# vieraita}}",
"Room {roomIndex}, {adults} adults, {children} children": "Huone {roomIndex}, {adults, plural, one {# vieras} other {# vieraita}}, {children, plural, one {# lapsi} other {# lapsia}}",
"Rooms": "Huoneet", "Rooms": "Huoneet",
"Rooms & Guests": "Huoneet & Vieraat", "Rooms & Guests": "Huoneet & Vieraat",
"Rooms & Guestss": "Huoneet & Vieraat", "Rooms & Guestss": "Huoneet & Vieraat",

View File

@@ -380,6 +380,8 @@
"Room & Terms": "Rom & Vilkår", "Room & Terms": "Rom & Vilkår",
"Room charge": "Pris for rom", "Room charge": "Pris for rom",
"Room facilities": "Romfasiliteter", "Room facilities": "Romfasiliteter",
"Room {roomIndex}, {adults} adults": "Rom {roomIndex}, {adults, plural, one {# voksen} other {# voksne}}",
"Room {roomIndex}, {adults} adults, {children} children": "Rom {roomIndex}, {adults, plural, one {# voksen} other {# voksne}}, {children, plural, one {# barn} other {# børn}}",
"Rooms": "Rom", "Rooms": "Rom",
"Rooms & Guests": "Rom og gjester", "Rooms & Guests": "Rom og gjester",
"Sat-Sun Always open": "Lør-Søn Alltid åpen", "Sat-Sun Always open": "Lør-Søn Alltid åpen",

View File

@@ -380,6 +380,8 @@
"Room & Terms": "Rum & Villkor", "Room & Terms": "Rum & Villkor",
"Room charge": "Rumspris", "Room charge": "Rumspris",
"Room facilities": "Rumfaciliteter", "Room facilities": "Rumfaciliteter",
"Room {roomIndex}, {adults} adults": "Rum {roomIndex}, {adults, plural, one {# vuxen} other {# vuxna}}",
"Room {roomIndex}, {adults} adults, {children} children": "Rum {roomIndex}, {adults, plural, one {# vuxen} other {# vuxna}}, {children, plural, one {# barn} other {# barn}}",
"Rooms": "Rum", "Rooms": "Rum",
"Rooms & Guests": "Rum och gäster", "Rooms & Guests": "Rum och gäster",
"Sat-Sun Always open": "Lör-Sön Alltid öppet", "Sat-Sun Always open": "Lör-Sön Alltid öppet",

View File

@@ -1,18 +1,21 @@
import type { RoomData } from "@/types/hotel" import type { RoomData } from "@/types/hotel"
import type { SafeUser } from "@/types/user"
import type { RoomsAvailability } from "@/server/routers/hotels/output" import type { RoomsAvailability } from "@/server/routers/hotels/output"
import type { RoomPackageCodes, RoomPackageData } from "./roomFilter" import type {
import type { Rate, RateCode } from "./selectRate" DefaultFilterOptions,
RoomPackage,
RoomPackageCodeEnum,
RoomPackageCodes,
RoomPackageData,
} from "./roomFilter"
import type { RateCode } from "./selectRate"
export interface RoomSelectionProps { export interface RoomListProps {
roomsAvailability: RoomsAvailability roomsAvailability: RoomsAvailability
roomCategories: RoomData[] roomCategories: RoomData[]
availablePackages: RoomPackageData | undefined availablePackages: RoomPackageData | undefined
selectedPackages: RoomPackageCodes[] selectedPackages: RoomPackageCodes[]
setRateCode: React.Dispatch<React.SetStateAction<RateCode | undefined>> setRateCode: React.Dispatch<React.SetStateAction<RateCode | undefined>>
rateSummary: Rate | null
hotelType: string | undefined hotelType: string | undefined
isUserLoggedIn: boolean
} }
export interface SelectRateProps { export interface SelectRateProps {
@@ -22,3 +25,16 @@ export interface SelectRateProps {
hotelType: string | undefined hotelType: string | undefined
isUserLoggedIn: boolean isUserLoggedIn: boolean
} }
export interface RoomSelectionPanelProps {
rooms: RoomsAvailability
roomCategories: RoomData[]
availablePackages: RoomPackage[]
selectedPackages: RoomPackageCodes[]
setSelectedRate: React.Dispatch<React.SetStateAction<RateCode | undefined>>
hotelType: string | undefined
handleFilter: (
filter: Record<RoomPackageCodeEnum, boolean | undefined>
) => void
defaultPackages: DefaultFilterOptions[]
}