feat(SW-1111) Added Suspense on map
This commit is contained in:
@@ -7,6 +7,7 @@ CMS_API_KEY="test"
|
|||||||
CMS_PREVIEW_TOKEN="test"
|
CMS_PREVIEW_TOKEN="test"
|
||||||
CMS_PREVIEW_URL="test"
|
CMS_PREVIEW_URL="test"
|
||||||
CMS_URL="test"
|
CMS_URL="test"
|
||||||
|
CMS_BRANCH="development"
|
||||||
CURITY_CLIENT_ID_SERVICE="test"
|
CURITY_CLIENT_ID_SERVICE="test"
|
||||||
CURITY_CLIENT_SECRET_SERVICE="test"
|
CURITY_CLIENT_SECRET_SERVICE="test"
|
||||||
CURITY_CLIENT_ID_USER="test"
|
CURITY_CLIENT_ID_USER="test"
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEffect } from "react"
|
|
||||||
|
|
||||||
import styles from "../layout.module.css"
|
|
||||||
|
|
||||||
import { LangParams, LayoutArgs } from "@/types/params"
|
|
||||||
|
|
||||||
export default function HotelReservationLayout({
|
|
||||||
children,
|
|
||||||
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
|
||||||
useEffect(() => {
|
|
||||||
document.body.style.overflow = "hidden"
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.body.style.overflow = ""
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
return <div className={styles.layout}>{children}</div>
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
||||||
import { getCityCoordinates, getLocations } from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import { getHotelPins } from "@/components/HotelReservation/HotelCardDialogListing/utils"
|
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
|
||||||
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
|
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
|
||||||
import {
|
import {
|
||||||
generateChildrenString,
|
generateChildrenString,
|
||||||
getHotelReservationQueryParams,
|
getHotelReservationQueryParams,
|
||||||
@@ -12,9 +12,8 @@ import {
|
|||||||
import { MapContainer } from "@/components/MapContainer"
|
import { MapContainer } from "@/components/MapContainer"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import { fetchAvailableHotels, getFiltersFromHotels } from "../utils"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
|
||||||
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
@@ -34,46 +33,26 @@ export default async function SelectHotelMapPage({
|
|||||||
)
|
)
|
||||||
if (!city) return notFound()
|
if (!city) return notFound()
|
||||||
|
|
||||||
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
|
||||||
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
|
||||||
|
|
||||||
const selectHotelParams = new URLSearchParams(searchParams)
|
const selectHotelParams = new URLSearchParams(searchParams)
|
||||||
const selectHotelParamsObject =
|
const selectHotelParamsObject =
|
||||||
getHotelReservationQueryParams(selectHotelParams)
|
getHotelReservationQueryParams(selectHotelParams)
|
||||||
const adults = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms
|
const adultsInRoom = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms
|
||||||
const children = selectHotelParamsObject.room[0].child
|
const childrenInRoom = selectHotelParamsObject.room[0].child
|
||||||
? generateChildrenString(selectHotelParamsObject.room[0].child)
|
? generateChildrenString(selectHotelParamsObject.room[0].child)
|
||||||
: undefined // TODO: Handle multiple rooms
|
: undefined // TODO: Handle multiple rooms
|
||||||
|
|
||||||
const hotels = await fetchAvailableHotels({
|
|
||||||
cityId: city.id,
|
|
||||||
roomStayStartDate: searchParams.fromDate,
|
|
||||||
roomStayEndDate: searchParams.toDate,
|
|
||||||
adults,
|
|
||||||
children,
|
|
||||||
})
|
|
||||||
|
|
||||||
const validHotels = hotels.filter(
|
|
||||||
(hotel): hotel is HotelData => hotel !== null
|
|
||||||
)
|
|
||||||
|
|
||||||
const hotelPins = getHotelPins(validHotels)
|
|
||||||
const filterList = getFiltersFromHotels(validHotels)
|
|
||||||
const cityCoordinates = await getCityCoordinates({
|
|
||||||
city: city.name,
|
|
||||||
hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress },
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapContainer>
|
<div className={styles.main}>
|
||||||
<SelectHotelMap
|
<Suspense key={city.id} fallback={<SelectHotelMapContainerSkeleton />}>
|
||||||
apiKey={googleMapsApiKey}
|
<MapContainer>
|
||||||
hotelPins={hotelPins}
|
<SelectHotelMapContainer
|
||||||
mapId={googleMapId}
|
city={city}
|
||||||
hotels={validHotels}
|
searchParams={searchParams}
|
||||||
filterList={filterList}
|
adultsInRoom={adultsInRoom}
|
||||||
cityCoordinates={cityCoordinates}
|
childrenInRoom={childrenInRoom}
|
||||||
/>
|
/>
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { env } from "@/env/server"
|
||||||
|
import { getCityCoordinates } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchAvailableHotels,
|
||||||
|
getFiltersFromHotels,
|
||||||
|
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
|
||||||
|
import { safeTry } from "@/utils/safeTry"
|
||||||
|
|
||||||
|
import { getHotelPins } from "../../HotelCardDialogListing/utils"
|
||||||
|
import SelectHotelMap from "."
|
||||||
|
|
||||||
|
import type {
|
||||||
|
HotelData,
|
||||||
|
NullableHotelData,
|
||||||
|
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||||
|
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
||||||
|
import type { Location } from "@/types/trpc/routers/hotel/locations"
|
||||||
|
|
||||||
|
function isHotelData(hotel: NullableHotelData): hotel is HotelData {
|
||||||
|
return hotel !== null && hotel !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
city: Location
|
||||||
|
searchParams: SelectHotelSearchParams
|
||||||
|
adultsInRoom: number
|
||||||
|
childrenInRoom: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function SelectHotelMapContainer({
|
||||||
|
city,
|
||||||
|
searchParams,
|
||||||
|
adultsInRoom,
|
||||||
|
childrenInRoom,
|
||||||
|
}: Props) {
|
||||||
|
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
||||||
|
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
||||||
|
|
||||||
|
const fetchAvailableHotelsPromise = safeTry(
|
||||||
|
fetchAvailableHotels({
|
||||||
|
cityId: city.id,
|
||||||
|
roomStayStartDate: searchParams.fromDate,
|
||||||
|
roomStayEndDate: searchParams.toDate,
|
||||||
|
adults: adultsInRoom,
|
||||||
|
children: childrenInRoom,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const [hotels, hotelsError] = await fetchAvailableHotelsPromise
|
||||||
|
|
||||||
|
if (hotelsError) {
|
||||||
|
// TODO: show proper error component
|
||||||
|
console.error("[SelectHotelMapContainer] unable to fetch hotels")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const validHotels = hotels?.filter(isHotelData) || []
|
||||||
|
|
||||||
|
const hotelPins = getHotelPins(validHotels)
|
||||||
|
const filterList = getFiltersFromHotels(validHotels)
|
||||||
|
const cityCoordinates = await getCityCoordinates({
|
||||||
|
city: city.name,
|
||||||
|
hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress },
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectHotelMap
|
||||||
|
apiKey={googleMapsApiKey}
|
||||||
|
hotelPins={hotelPins}
|
||||||
|
mapId={googleMapId}
|
||||||
|
hotels={validHotels}
|
||||||
|
filterList={filterList}
|
||||||
|
cityCoordinates={cityCoordinates}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.container {
|
||||||
|
max-width: var(--max-width);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listingContainer {
|
||||||
|
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||||
|
padding: var(--Spacing-x3) var(--Spacing-x4);
|
||||||
|
overflow-y: auto;
|
||||||
|
max-width: 505px;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeletonContainer {
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
padding-top: var(--Spacing-x6);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeletonItem {
|
||||||
|
width: 440px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.skeletonContainer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { RoomCardSkeleton } from "../../SelectRate/RoomSelection/RoomCard/RoomCardSkeleton"
|
||||||
|
|
||||||
|
import styles from "./SelectHotelMapContainerSkeleton.module.css"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
count?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function SelectHotelMapContainerSkeleton({ count = 2 }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.listingContainer}>
|
||||||
|
<div className={styles.skeletonContainer}>
|
||||||
|
{Array.from({ length: count }).map((_, index) => (
|
||||||
|
<div key={index} className={styles.skeletonItem}>
|
||||||
|
<RoomCardSkeleton />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { APIProvider } from "@vis.gl/react-google-maps"
|
import { APIProvider } from "@vis.gl/react-google-maps"
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
import { useEffect, useMemo, useRef, useState } from "react"
|
import { useEffect, useMemo, useRef, useState } from "react"
|
||||||
@@ -95,6 +96,8 @@ export default function SelectHotelMap({
|
|||||||
[activeFilters, hotelPins]
|
[activeFilters, hotelPins]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log("hotelPins", hotelPins)
|
||||||
|
|
||||||
const closeButton = (
|
const closeButton = (
|
||||||
<Button
|
<Button
|
||||||
intent="inverted"
|
intent="inverted"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export default function Rooms({
|
|||||||
user,
|
user,
|
||||||
availablePackages,
|
availablePackages,
|
||||||
}: SelectRateProps) {
|
}: SelectRateProps) {
|
||||||
|
console.log("ROOMS PAGE")
|
||||||
const visibleRooms: RoomConfiguration[] =
|
const visibleRooms: RoomConfiguration[] =
|
||||||
filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations)
|
filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations)
|
||||||
const [selectedRate, setSelectedRate] = useState<RateCode | undefined>(
|
const [selectedRate, setSelectedRate] = useState<RateCode | undefined>(
|
||||||
|
|||||||
@@ -21,6 +21,38 @@ export function MapContainer({ children }: { children: React.ReactNode }) {
|
|||||||
setMapTop(`${topPosition + scrollY}px`)
|
setMapTop(`${topPosition + scrollY}px`)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const originalOverflowY = document.body.style.overflowY
|
||||||
|
|
||||||
|
// Function to enforce overflowY to hidden
|
||||||
|
const enforceOverflowHidden = () => {
|
||||||
|
if (document.body.style.overflowY !== "hidden") {
|
||||||
|
document.body.style.overflowY = "hidden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set overflowY to hidden initially
|
||||||
|
enforceOverflowHidden()
|
||||||
|
|
||||||
|
// Create a MutationObserver to watch for changes to the style attribute
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
enforceOverflowHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Observe changes to the style attribute of the body
|
||||||
|
observer.observe(document.body, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["style"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Disconnect the observer on cleanup
|
||||||
|
observer.disconnect()
|
||||||
|
// Restore the original overflowY style
|
||||||
|
document.body.style.overflowY = originalOverflowY
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Making sure the map is always opened at the top of the page,
|
// Making sure the map is always opened at the top of the page,
|
||||||
// just below the header and booking widget as these should stay visible.
|
// just below the header and booking widget as these should stay visible.
|
||||||
// When closing, the page should scroll back to the position it was before opening the map.
|
// When closing, the page should scroll back to the position it was before opening the map.
|
||||||
|
|||||||
Reference in New Issue
Block a user