Merged in feat/SW-1557-implement-booking-code-select (pull request #1304)

feat: SW-1577 Implemented booking code city search

* feat: SW-1577 Implemented booking code city search

* feat: SW-1557 Strict comparison

* feat: SW-1557 Review comments fix


Approved-by: Michael Zetterberg
Approved-by: Pontus Dreij
This commit is contained in:
Hrishikesh Vaipurkar
2025-02-13 09:24:47 +00:00
parent d46a85a529
commit eabe45b73c
35 changed files with 627 additions and 276 deletions
@@ -0,0 +1,15 @@
.bookingCodeFilter {
display: flex;
justify-content: flex-end;
width: 100%;
}
.bookingCodeFilterSelect {
min-width: 200px;
}
@media screen and (max-width: 767px) {
.bookingCodeFilter {
margin-bottom: var(--Spacing-x3);
}
}
@@ -0,0 +1,56 @@
"use client"
import { useIntl } from "react-intl"
import { useBookingCodeFilterStore } from "@/stores/bookingCode-filter"
import { PriceTagIcon } from "@/components/Icons"
import Select from "@/components/TempDesignSystem/Select"
import styles from "./bookingCodeFilter.module.css"
import type { Key } from "react"
export default function BookingCodeFilter() {
const intl = useIntl()
const activeCodeFilter = useBookingCodeFilterStore(
(state) => state.activeCodeFilter
)
const setFilter = useBookingCodeFilterStore((state) => state.setFilter)
const bookingCodeFilterItems = [
{
label: intl.formatMessage({ id: "Discounted rooms" }),
value: "discounted",
},
{
label: intl.formatMessage({ id: "Full price rooms" }),
value: "regular",
},
{
label: intl.formatMessage({ id: "See all" }),
value: "all",
},
]
function updateFilter(selectedFilter: Key) {
setFilter(selectedFilter as string)
}
return (
<>
<div className={styles.bookingCodeFilter}>
<Select
aria-label="Booking Code Filter"
className={styles.bookingCodeFilterSelect}
name="bookingCodeFilter"
onSelect={updateFilter}
label=""
items={bookingCodeFilterItems}
defaultSelectedKey={activeCodeFilter}
optionsIcon={<PriceTagIcon />}
/>
</div>
</>
)
}
@@ -55,6 +55,7 @@ export default function HotelSorter({ discreet }: HotelSorterProps) {
items={sortItems}
defaultSelectedKey={searchParams.get("sort") ?? DEFAULT_SORT}
label={intl.formatMessage({ id: "Sort by" })}
aria-label={intl.formatMessage({ id: "Sort by" })}
name="sort"
showRadioButton
discreet={discreet}
@@ -8,6 +8,7 @@ import { getCityCoordinates } from "@/lib/trpc/memoizedRequests"
import {
fetchAlternativeHotels,
fetchAvailableHotels,
fetchBookingCodeAvailableHotels,
getFiltersFromHotels,
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
import { getHotelSearchDetails } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils"
@@ -19,10 +20,7 @@ import { getHotelPins } from "../../HotelCardDialogListing/utils"
import SelectHotelMap from "."
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type {
HotelData,
NullableHotelData,
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { SelectHotelMapContainerProps } from "@/types/components/hotelReservation/selectHotel/map"
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import {
@@ -60,6 +58,7 @@ export async function SelectHotelMapContainer({
childrenInRoom,
childrenInRoomString,
hotel: isAlternativeFor,
bookingCode,
} = searchDetails
if (!city) return notFound()
@@ -71,17 +70,29 @@ export async function SelectHotelMapContainer({
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
bookingCode,
})
)
: safeTry(
fetchAvailableHotels({
cityId: city.id,
roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
})
)
: bookingCode
? safeTry(
fetchBookingCodeAvailableHotels({
cityId: city.id,
roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
bookingCode,
})
)
: safeTry(
fetchAvailableHotels({
cityId: city.id,
roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
})
)
const [hotels] = await fetchAvailableHotelsPromise
@@ -142,6 +153,7 @@ export async function SelectHotelMapContainer({
hotels={validHotels}
filterList={filterList}
cityCoordinates={cityCoordinates}
bookingCode={bookingCode ?? ""}
/>
<Suspense fallback={null}>
<TrackingSDK
@@ -5,6 +5,7 @@ import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
import { selectHotel } from "@/constants/routes/hotelReservation"
import { useBookingCodeFilterStore } from "@/stores/bookingCode-filter"
import { useHotelFilterStore } from "@/stores/hotel-filters"
import { useHotelsMapStore } from "@/stores/hotels-map"
@@ -18,6 +19,7 @@ import useLang from "@/hooks/useLang"
import { useScrollToTop } from "@/hooks/useScrollToTop"
import { debounce } from "@/utils/debounce"
import BookingCodeFilter from "../../BookingCodeFilter"
import FilterAndSortModal from "../../FilterAndSortModal"
import HotelListing from "../HotelListing"
import { getVisibleHotels } from "./utils"
@@ -35,6 +37,7 @@ export default function SelectHotelContent({
mapId,
hotels,
filterList,
bookingCode,
}: Omit<SelectHotelMapProps, "apiKey">) {
const lang = useLang()
const intl = useIntl()
@@ -53,6 +56,9 @@ export default function SelectHotelContent({
elementRef: listingContainerRef,
refScrollable: true,
})
const activeCodeFilter = useBookingCodeFilterStore(
(state) => state.activeCodeFilter
)
const coordinates = useMemo(
() =>
@@ -72,15 +78,23 @@ export default function SelectHotelContent({
}
}, [activeHotelCard, activeHotelPin])
const filteredHotelPins = useMemo(
() =>
hotelPins.filter((hotel) =>
activeFilters.every((filterId) =>
hotel.facilityIds.includes(Number(filterId))
const filteredHotelPins = useMemo(() => {
const updatedHotelsList = bookingCode
? hotelPins.filter(
(hotel) =>
!hotel.publicPrice ||
activeCodeFilter === "all" ||
(activeCodeFilter === "discounted" &&
hotel.rateType?.toLowerCase() !== "regular") ||
activeCodeFilter === hotel.rateType?.toLowerCase()
)
),
[activeFilters, hotelPins]
)
: hotelPins
return updatedHotelsList.filter((hotel) =>
activeFilters.every((filterId) =>
hotel.facilityIds.includes(Number(filterId))
)
)
}, [activeFilters, hotelPins, bookingCode, activeCodeFilter])
const getHotelCards = useCallback(() => {
const visibleHotels = getVisibleHotels(hotels, filteredHotelPins, map)
@@ -143,6 +157,7 @@ export default function SelectHotelContent({
filters={filterList}
setShowSkeleton={setShowSkeleton}
/>
{bookingCode ? <BookingCodeFilter /> : null}
</div>
{showSkeleton ? (
@@ -13,6 +13,7 @@ export default function SelectHotelMap({
hotels,
filterList,
cityCoordinates,
bookingCode,
}: SelectHotelMapProps) {
return (
<APIProvider apiKey={apiKey}>
@@ -22,6 +23,7 @@ export default function SelectHotelMap({
mapId={mapId}
hotels={hotels}
filterList={filterList}
bookingCode={bookingCode}
/>
</APIProvider>
)
@@ -12,6 +12,7 @@ import {
import {
fetchAlternativeHotels,
fetchAvailableHotels,
fetchBookingCodeAvailableHotels,
getFiltersFromHotels,
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
import { getHotelSearchDetails } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils"
@@ -27,6 +28,7 @@ import { safeTry } from "@/utils/safeTry"
import { convertObjToSearchParams } from "@/utils/url"
import HotelCardListing from "../HotelCardListing"
import BookingCodeFilter from "./BookingCodeFilter"
import HotelCount from "./HotelCount"
import HotelFilter from "./HotelFilter"
import HotelSorter from "./HotelSorter"
@@ -74,6 +76,7 @@ export default async function SelectHotel({
childrenInRoomString,
childrenInRoom,
hotel: isAlternativeFor,
bookingCode,
} = searchDetails
if (!city) return notFound()
@@ -85,17 +88,29 @@ export default async function SelectHotel({
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
bookingCode,
})
)
: safeTry(
fetchAvailableHotels({
cityId: city.id,
roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
})
)
: bookingCode
? safeTry(
fetchBookingCodeAvailableHotels({
cityId: city.id,
roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
bookingCode,
})
)
: safeTry(
fetchAvailableHotels({
cityId: city.id,
roomStayStartDate: selectHotelParams.fromDate,
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
})
)
const [hotels] = await hotelsPromise
@@ -204,6 +219,7 @@ export default async function SelectHotel({
</div>
</header>
<main className={styles.main}>
{bookingCode ? <BookingCodeFilter /> : null}
<div className={styles.sideBar}>
{hotels && hotels.length > 0 ? ( // TODO: Temp fix until API returns hotels that are not available
<Link
@@ -75,6 +75,9 @@
@media (min-width: 768px) {
.main {
padding: var(--Spacing-x5) 0;
flex-direction: row;
gap: var(--Spacing-x5);
flex-wrap: wrap;
}
.headerContent {
@@ -125,10 +128,6 @@
border-radius: var(--Corner-radius-Medium);
border: 1px solid var(--Base-Border-Subtle);
}
.main {
flex-direction: row;
gap: var(--Spacing-x5);
}
.buttonContainer {
display: none;
}