feat(SW-1111) Added Suspense on map

This commit is contained in:
Pontus Dreij
2024-12-09 11:05:44 +01:00
parent c610b4f532
commit b14a1a7097
9 changed files with 190 additions and 60 deletions

View File

@@ -7,6 +7,7 @@ CMS_API_KEY="test"
CMS_PREVIEW_TOKEN="test"
CMS_PREVIEW_URL="test"
CMS_URL="test"
CMS_BRANCH="development"
CURITY_CLIENT_ID_SERVICE="test"
CURITY_CLIENT_SECRET_SERVICE="test"
CURITY_CLIENT_ID_USER="test"

View File

@@ -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>
}

View File

@@ -1,10 +1,10 @@
import { notFound } from "next/navigation"
import { Suspense } from "react"
import { env } from "@/env/server"
import { getCityCoordinates, getLocations } from "@/lib/trpc/memoizedRequests"
import { getLocations } from "@/lib/trpc/memoizedRequests"
import { getHotelPins } from "@/components/HotelReservation/HotelCardDialogListing/utils"
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
import {
generateChildrenString,
getHotelReservationQueryParams,
@@ -12,9 +12,8 @@ import {
import { MapContainer } from "@/components/MapContainer"
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 { LangParams, PageArgs } from "@/types/params"
@@ -34,46 +33,26 @@ export default async function SelectHotelMapPage({
)
if (!city) return notFound()
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
const selectHotelParams = new URLSearchParams(searchParams)
const selectHotelParamsObject =
getHotelReservationQueryParams(selectHotelParams)
const adults = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms
const children = selectHotelParamsObject.room[0].child
const adultsInRoom = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms
const childrenInRoom = selectHotelParamsObject.room[0].child
? generateChildrenString(selectHotelParamsObject.room[0].child)
: 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 (
<MapContainer>
<SelectHotelMap
apiKey={googleMapsApiKey}
hotelPins={hotelPins}
mapId={googleMapId}
hotels={validHotels}
filterList={filterList}
cityCoordinates={cityCoordinates}
/>
</MapContainer>
<div className={styles.main}>
<Suspense key={city.id} fallback={<SelectHotelMapContainerSkeleton />}>
<MapContainer>
<SelectHotelMapContainer
city={city}
searchParams={searchParams}
adultsInRoom={adultsInRoom}
childrenInRoom={childrenInRoom}
/>
</MapContainer>
</Suspense>
</div>
)
}

View File

@@ -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}
/>
)
}

View File

@@ -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;
}
}

View File

@@ -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>
)
}

View File

@@ -1,4 +1,5 @@
"use client"
import { APIProvider } from "@vis.gl/react-google-maps"
import { useSearchParams } from "next/navigation"
import { useEffect, useMemo, useRef, useState } from "react"
@@ -95,6 +96,8 @@ export default function SelectHotelMap({
[activeFilters, hotelPins]
)
console.log("hotelPins", hotelPins)
const closeButton = (
<Button
intent="inverted"

View File

@@ -26,6 +26,7 @@ export default function Rooms({
user,
availablePackages,
}: SelectRateProps) {
console.log("ROOMS PAGE")
const visibleRooms: RoomConfiguration[] =
filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations)
const [selectedRate, setSelectedRate] = useState<RateCode | undefined>(

View File

@@ -21,6 +21,38 @@ export function MapContainer({ children }: { children: React.ReactNode }) {
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,
// 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.