fix: improve loading on destination overview page

- Only load data from Contentstack
- Use static JSON for destination list
- Some logic improvements to data handling and types
This commit is contained in:
Michael Zetterberg
2025-03-26 11:38:10 +01:00
parent f010a6869a
commit 65f75c11ef
37 changed files with 6619 additions and 185 deletions
@@ -27,21 +27,23 @@ export default async function Destination({
<AccordionItem title={country} subtitle={accordionSubtitle}>
<div className={styles.container}>
<ul className={styles.citiesList}>
{cities.map((city) => (
<li key={city.id}>
{city.url ? (
<Link
href={city.url}
color="baseTextMediumContrast"
textDecoration="underline"
>
{`${city.name} (${city.hotelCount})`}
</Link>
) : (
<Body>{`${city.name} (${city.hotelCount})`}</Body>
)}
</li>
))}
{cities.map((city) =>
city.hotelCount > 0 ? (
<li key={city.id}>
{city.url ? (
<Link
href={city.url}
color="baseTextMediumContrast"
textDecoration="underline"
>
{`${city.name} (${city.hotelCount})`}
</Link>
) : (
<Body>{`${city.name} (${city.hotelCount})`}</Body>
)}
</li>
) : null
)}
</ul>
{countryUrl && (
<Link href={countryUrl} variant="icon" color="burgundy" weight="bold">
@@ -7,9 +7,7 @@ import styles from "./destinationsList.module.css"
import type { DestinationsListProps } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
export default function DestinationsList({
destinations,
}: DestinationsListProps) {
export function DestinationsList({ destinations }: DestinationsListProps) {
const middleIndex = Math.ceil(destinations.length / 2)
const accordionLeft = destinations.slice(0, middleIndex)
const accordionRight = destinations.slice(middleIndex)
@@ -17,27 +15,31 @@ export default function DestinationsList({
return (
<div className={styles.listContainer}>
<Accordion className={styles.accordion}>
{accordionLeft.map((data) => (
<Destination
key={data.country}
country={data.country}
countryUrl={data.countryUrl}
numberOfHotels={data.numberOfHotels}
cities={data.cities}
/>
))}
{accordionLeft.map((data) =>
data.numberOfHotels > 0 ? (
<Destination
key={data.country}
country={data.country}
countryUrl={data.countryUrl}
numberOfHotels={data.numberOfHotels}
cities={data.cities}
/>
) : null
)}
</Accordion>
<Divider color="subtle" className={styles.divider} />
<Accordion className={styles.accordion}>
{accordionRight.map((data) => (
<Destination
key={data.country}
country={data.country}
countryUrl={data.countryUrl}
numberOfHotels={data.numberOfHotels}
cities={data.cities}
/>
))}
{accordionRight.map((data) =>
data.numberOfHotels > 0 ? (
<Destination
key={data.country}
country={data.country}
countryUrl={data.countryUrl}
numberOfHotels={data.numberOfHotels}
cities={data.cities}
/>
) : null
)}
</Accordion>
</div>
)
@@ -1,16 +1,19 @@
import { getDestinationsList } from "@/lib/trpc/memoizedRequests"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import DestinationsList from "./DestinationsList"
import { DestinationsList } from "./DestinationsList"
import styles from "./hotelsSection.module.css"
import type { HotelsSectionProps } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
export default async function HotelsSection({
destinations,
}: HotelsSectionProps) {
export default async function HotelsSection() {
const intl = await getIntl()
const destinations = await getDestinationsList()
if (destinations.length === 0) {
return null
}
return (
<section className={styles.container}>
@@ -6,17 +6,8 @@ import MapContent from "../../Map/MapContent"
import MapProvider from "../../Map/MapProvider"
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils"
import ActiveMapCard from "./ActiveMapCard"
import InputForm from "./InputForm"
import type { MapLocation } from "@/types/components/mapLocation"
interface OverviewMapContainerProps {
defaultLocation: MapLocation
}
export default async function OverviewMapContainer({
defaultLocation,
}: OverviewMapContainerProps) {
export default async function OverviewMapContainer() {
const hotelData = await getAllHotels()
if (!hotelData) {
@@ -28,22 +19,13 @@ export default async function OverviewMapContainer({
const markers = getHotelMapMarkers(hotelData)
const geoJson = mapMarkerDataToGeoJson(markers)
const defaultCoordinates = defaultLocation
? {
lat: defaultLocation.latitude,
lng: defaultLocation.longitude,
}
: null
const defaultZoom = defaultLocation?.default_zoom ?? 3
return (
<MapProvider apiKey={googleMapsApiKey} pageType="overview">
<InputForm />
<DynamicMap
mapId={googleMapId}
markers={markers}
defaultCoordinates={defaultCoordinates}
defaultZoom={defaultZoom}
fitBounds
gestureHandling="cooperative"
>
<MapContent geojson={geoJson} />
@@ -2,11 +2,21 @@
position: relative;
display: grid;
width: 100%;
max-width: var(--max-width);
height: 700px;
height: 610px;
margin: 0 auto;
}
@media screen and (min-width: 768px) {
.mapContainer {
height: 580px;
}
}
@media screen and (min-width: 1367px) {
.mapContainer {
height: 560px;
}
}
.main {
display: grid;
gap: var(--Spacing-x9);
@@ -0,0 +1,13 @@
"use client"
import styles from "./destinationOverviewPage.module.css"
import type { PropsWithChildren } from "react"
export function DestinationOverviewPageError({ children }: PropsWithChildren) {
return (
<main className={styles.main}>
<div className={styles.blocks}>{children}</div>
</main>
)
}
@@ -1,9 +1,7 @@
import {
getDestinationOverviewPage,
getDestinationsList,
} from "@/lib/trpc/memoizedRequests"
import { getDestinationOverviewPage } from "@/lib/trpc/memoizedRequests"
import Blocks from "@/components/Blocks"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import TrackingSDK from "@/components/TrackingSDK"
import HotelsSection from "./HotelsSection"
@@ -12,10 +10,7 @@ import OverviewMapContainer from "./OverviewMapContainer"
import styles from "./destinationOverviewPage.module.css"
export default async function DestinationOverviewPage() {
const [pageData, destinationsData] = await Promise.all([
getDestinationOverviewPage(),
getDestinationsList(),
])
const pageData = await getDestinationOverviewPage()
if (!pageData) {
return null
@@ -26,21 +21,35 @@ export default async function DestinationOverviewPage() {
return (
<>
<div className={styles.mapContainer}>
<OverviewMapContainer
defaultLocation={destinationOverviewPage.location}
/>
<OverviewMapContainer />
</div>
<main className={styles.main}>
<div className={styles.blocks}>
<Blocks blocks={destinationOverviewPage.blocks} />
</div>
</main>
{destinationsData && (
<aside className={styles.hotelsAccordions}>
<HotelsSection destinations={destinationsData} />
</aside>
)}
<aside className={styles.hotelsAccordions}>
<HotelsSection />
</aside>
<TrackingSDK pageData={tracking} />
</>
)
}
export function DestinationOverviewPageLoading() {
return (
<>
<div className={styles.mapContainer}>
<SkeletonShimmer width={"100%"} height={"100%"} />
</div>
<main className={styles.main}>
<div className={styles.blocks}>
<SkeletonShimmer width={"100%"} height={"100%"} />
</div>
</main>
<aside className={styles.hotelsAccordions}>
<SkeletonShimmer width={"100%"} height={"100%"} />
</aside>
</>
)
}
@@ -6,7 +6,7 @@
z-index: 0;
}
.mapWrapper::after {
.mapWrapperWithCloseButton:after {
content: "";
position: absolute;
top: 0;
@@ -3,6 +3,7 @@
import "client-only"
import { Map, type MapProps, useMap } from "@vis.gl/react-google-maps"
import { cx } from "class-variance-authority"
import { type PropsWithChildren, useEffect, useRef } from "react"
import { useIntl } from "react-intl"
@@ -28,8 +29,8 @@ const BACKUP_COORDINATES = {
interface DynamicMapProps {
markers: DestinationMarker[]
mapId: string
defaultCoordinates: google.maps.LatLngLiteral | null
defaultZoom: number
defaultCenter?: google.maps.LatLngLiteral
defaultZoom?: number
fitBounds?: boolean
gestureHandling?: "greedy" | "cooperative" | "auto" | "none"
onClose?: () => void
@@ -38,8 +39,8 @@ interface DynamicMapProps {
export default function DynamicMap({
markers,
mapId,
defaultCoordinates,
defaultZoom,
defaultCenter = BACKUP_COORDINATES,
defaultZoom = 3,
fitBounds = true,
onClose,
gestureHandling = "auto",
@@ -61,18 +62,14 @@ export default function DynamicMap({
}, [activeMarker, pageType])
useEffect(() => {
if (map && fitBounds) {
if (markers.length) {
const bounds = new google.maps.LatLngBounds()
markers.forEach((marker) => {
bounds.extend(marker.coordinates)
})
map.fitBounds(bounds, 100)
} else if (defaultCoordinates) {
map.setCenter(defaultCoordinates)
}
if (map && fitBounds && markers?.length) {
const bounds = new google.maps.LatLngBounds()
markers.forEach((marker) => {
bounds.extend(marker.coordinates)
})
map.fitBounds(bounds, 100)
}
}, [map, markers, defaultCoordinates, fitBounds])
}, [map, fitBounds, markers])
useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && onClose) {
@@ -94,7 +91,7 @@ export default function DynamicMap({
}
const mapOptions: MapProps = {
defaultCenter: defaultCoordinates || BACKUP_COORDINATES, // Default center will be overridden by the bounds
defaultCenter, // Default center will be overridden by the bounds
minZoom: 3,
maxZoom: 18,
defaultZoom,
@@ -105,7 +102,13 @@ export default function DynamicMap({
}
return (
<div className={styles.mapWrapper} ref={ref}>
<div
className={cx(
styles.mapWrapper,
onClose && styles.mapWrapperWithCloseButton
)}
ref={ref}
>
<ErrorBoundary fallback={<h2>Unable to display map</h2>}>
<Map {...mapOptions}>{children}</Map>
</ErrorBoundary>
@@ -71,7 +71,7 @@ export default function Map({
const markers = getHotelMapMarkers(hotels)
const geoJson = mapMarkerDataToGeoJson(markers)
const defaultCoordinates = activeHotel
const defaultCenter = activeHotel
? {
lat: activeHotel.hotel.location.latitude,
lng: activeHotel.hotel.location.longitude,
@@ -81,7 +81,7 @@ export default function Map({
lat: defaultLocation.latitude,
lng: defaultLocation.longitude,
}
: null
: undefined
const defaultZoom = activeHotel
? 15
: (defaultLocation?.default_zoom ?? (pageType === "city" ? 10 : 3))
@@ -171,7 +171,7 @@ export default function Map({
markers={markers}
mapId={mapId}
onClose={handleClose}
defaultCoordinates={defaultCoordinates}
defaultCenter={defaultCenter}
defaultZoom={defaultZoom}
fitBounds={!activeHotel}
gestureHandling="greedy"