Merged in feat/SW-1418-destination-page-map-data (pull request #1347)
feat/SW-1418 destination page map data * feat(SW-1418): implement dynamic map on overview page * feat(SW-1418): update folder structure * feat(SW-1418): use getHotelsByHotelIds Approved-by: Erik Tiekstra Approved-by: Matilda Landström
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { headers } from "next/headers"
|
||||
import { notFound,redirect } from "next/navigation"
|
||||
import { notFound, redirect } from "next/navigation"
|
||||
|
||||
import { overview } from "@/constants/routes/myPages"
|
||||
import { isSignupPage } from "@/constants/routes/signup"
|
||||
@@ -7,16 +7,16 @@ import { env } from "@/env/server"
|
||||
import { getHotelPage } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
import DestinationOverviewPage from "@/components/ContentType/DestinationOverviewPage"
|
||||
import DestinationCityPage from "@/components/ContentType/DestinationPage/DestinationCityPage"
|
||||
import DestinationCountryPage from "@/components/ContentType/DestinationPage/DestinationCountryPage"
|
||||
import DestinationOverviewPage from "@/components/ContentType/DestinationPage/DestinationOverviewPage"
|
||||
import HotelPage from "@/components/ContentType/HotelPage"
|
||||
import HotelSubpage from "@/components/ContentType/HotelSubpage"
|
||||
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
|
||||
import StartPage from "@/components/ContentType/StartPage"
|
||||
import CollectionPage from "@/components/ContentType/StaticPages/CollectionPage"
|
||||
import ContentPage from "@/components/ContentType/StaticPages/ContentPage"
|
||||
import { getLang,setLang } from "@/i18n/serverContext"
|
||||
import { getLang, setLang } from "@/i18n/serverContext"
|
||||
import { isValidSession } from "@/utils/session"
|
||||
|
||||
import type {
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
"use client"
|
||||
import { Map, type MapProps, useMap } from "@vis.gl/react-google-maps"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { MinusIcon, PlusIcon } from "@/components/Icons"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
|
||||
import styles from "./overviewMap.module.css"
|
||||
|
||||
import type { OverviewMapProps } from "@/types/components/destinationOverviewPage/overviewMap/overviewMap"
|
||||
|
||||
export default function OverviewMap({ mapId }: OverviewMapProps) {
|
||||
const map = useMap()
|
||||
const intl = useIntl()
|
||||
|
||||
// Placeholder value, currently these coordinates are Stockholm
|
||||
const defaultCenter = {
|
||||
lat: 59.3293,
|
||||
lng: 18.0686,
|
||||
}
|
||||
|
||||
const mapOptions: MapProps = {
|
||||
defaultZoom: 4,
|
||||
minZoom: 3,
|
||||
defaultCenter: defaultCenter,
|
||||
disableDefaultUI: true,
|
||||
clickableIcons: false,
|
||||
mapId: mapId,
|
||||
}
|
||||
|
||||
function zoomIn() {
|
||||
const currentZoom = map && map.getZoom()
|
||||
if (currentZoom) {
|
||||
map.setZoom(currentZoom + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
const currentZoom = map && map.getZoom()
|
||||
if (currentZoom) {
|
||||
map.setZoom(currentZoom - 1)
|
||||
}
|
||||
}
|
||||
|
||||
const zoomControls = (
|
||||
<div className={styles.buttonContainer}>
|
||||
<Button theme="base" intent="inverted" onClick={zoomOut}>
|
||||
<MinusIcon width={12} height={12} color="black" />
|
||||
</Button>
|
||||
<Button theme="base" intent="inverted" onClick={zoomIn}>
|
||||
<PlusIcon width={12} height={12} color="black" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={styles.mapContainer}>
|
||||
<Map {...mapOptions}></Map>
|
||||
{zoomControls}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
.mapContainer {
|
||||
position: relative;
|
||||
min-width: var(--max-width-page);
|
||||
min-height: 700px; /* Placeholder value */
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
gap: var(--Spacing-x1);
|
||||
right: 25px;
|
||||
bottom: 25px;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
"use client"
|
||||
import { APIProvider } from "@vis.gl/react-google-maps"
|
||||
|
||||
import InputForm from "./InputForm"
|
||||
import OverviewMap from "./OverviewMap"
|
||||
|
||||
import type { OverviewMapContainerProps } from "@/types/components/destinationOverviewPage/overviewMap/overviewMapContainer"
|
||||
|
||||
export default function OverviewMapContainer({
|
||||
apiKey,
|
||||
mapId,
|
||||
}: OverviewMapContainerProps) {
|
||||
return (
|
||||
<APIProvider apiKey={apiKey}>
|
||||
<InputForm />
|
||||
<OverviewMap mapId={mapId} />
|
||||
</APIProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { env } from "@/env/server"
|
||||
import { getAllHotels } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import DynamicMap from "../../Map/DynamicMap"
|
||||
import MapContent from "../../Map/MapContent"
|
||||
import MapProvider from "../../Map/MapProvider"
|
||||
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils"
|
||||
import InputForm from "./InputForm"
|
||||
|
||||
export default async function OverviewMapContainer() {
|
||||
const hotelData = await getAllHotels()
|
||||
|
||||
if (!hotelData) {
|
||||
return null
|
||||
}
|
||||
|
||||
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
||||
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
||||
|
||||
const markers = getHotelMapMarkers(hotelData)
|
||||
const geoJson = mapMarkerDataToGeoJson(markers)
|
||||
|
||||
return (
|
||||
<MapProvider apiKey={googleMapsApiKey}>
|
||||
<InputForm />
|
||||
<DynamicMap mapId={googleMapId} markers={markers}>
|
||||
<MapContent geojson={geoJson} />
|
||||
</DynamicMap>
|
||||
</MapProvider>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
display: grid;
|
||||
width: 100%;
|
||||
max-width: var(--max-width);
|
||||
height: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
import {
|
||||
getDestinationOverviewPage,
|
||||
getDestinationsList,
|
||||
@@ -26,16 +25,11 @@ export default async function DestinationOverviewPage() {
|
||||
|
||||
const { tracking, destinationOverviewPage } = pageData
|
||||
|
||||
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
||||
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
||||
|
||||
return (
|
||||
<>
|
||||
{googleMapsApiKey ? (
|
||||
<div className={styles.mapContainer}>
|
||||
<OverviewMapContainer apiKey={googleMapsApiKey} mapId={googleMapId} />
|
||||
</div>
|
||||
) : null}
|
||||
<div className={styles.mapContainer}>
|
||||
<OverviewMapContainer />
|
||||
</div>
|
||||
<main className={styles.main}>
|
||||
<div className={styles.blocks}>
|
||||
<Blocks blocks={destinationOverviewPage.blocks} />
|
||||
@@ -36,6 +36,7 @@
|
||||
.zoomButtons {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
|
||||
@@ -80,17 +80,19 @@ export default function DynamicMap({
|
||||
{children}
|
||||
</Map>
|
||||
<div className={styles.ctaButtons}>
|
||||
<Button
|
||||
theme="base"
|
||||
intent="inverted"
|
||||
variant="icon"
|
||||
size="small"
|
||||
className={styles.closeButton}
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseLargeIcon color="burgundy" />
|
||||
<span>{intl.formatMessage({ id: "Close the map" })}</span>
|
||||
</Button>
|
||||
{onClose && (
|
||||
<Button
|
||||
theme="base"
|
||||
intent="inverted"
|
||||
variant="icon"
|
||||
size="small"
|
||||
className={styles.closeButton}
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseLargeIcon color="burgundy" />
|
||||
<span>{intl.formatMessage({ id: "Close the map" })}</span>
|
||||
</Button>
|
||||
)}
|
||||
<div className={styles.zoomButtons}>
|
||||
<Button
|
||||
theme="base"
|
||||
|
||||
@@ -16,11 +16,10 @@ import { debounce } from "@/utils/debounce"
|
||||
import DynamicMap from "./DynamicMap"
|
||||
import MapContent from "./MapContent"
|
||||
import MapProvider from "./MapProvider"
|
||||
import { mapMarkerDataToGeoJson } from "./utils"
|
||||
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "./utils"
|
||||
|
||||
import styles from "./map.module.css"
|
||||
|
||||
import type { DestinationMarker } from "@/types/components/maps/destinationMarkers"
|
||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||
|
||||
interface MapProps {
|
||||
@@ -44,20 +43,7 @@ export default function Map({
|
||||
const [mapHeight, setMapHeight] = useState("0px")
|
||||
const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0)
|
||||
|
||||
const markers = hotels
|
||||
.map(({ hotel }) => ({
|
||||
id: hotel.id,
|
||||
type: hotel.hotelType || "regular",
|
||||
name: hotel.name,
|
||||
coordinates: hotel.location
|
||||
? {
|
||||
lat: hotel.location.latitude,
|
||||
lng: hotel.location.longitude,
|
||||
}
|
||||
: null,
|
||||
}))
|
||||
.filter((item): item is DestinationMarker => !!item.coordinates)
|
||||
|
||||
const markers = getHotelMapMarkers(hotels)
|
||||
const geoJson = mapMarkerDataToGeoJson(markers)
|
||||
|
||||
// Calculate the height of the map based on the viewport height from the start-point (below the header and booking widget)
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
MarkerFeature,
|
||||
MarkerGeojson,
|
||||
} from "@/types/components/maps/destinationMarkers"
|
||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||
|
||||
export function mapMarkerDataToGeoJson(markers: DestinationMarker[]) {
|
||||
const features = markers.map<MarkerFeature>(
|
||||
@@ -26,3 +27,21 @@ export function mapMarkerDataToGeoJson(markers: DestinationMarker[]) {
|
||||
|
||||
return geoJson
|
||||
}
|
||||
|
||||
export function getHotelMapMarkers(hotels: HotelDataWithUrl[]) {
|
||||
const markers = hotels
|
||||
.map(({ hotel }) => ({
|
||||
id: hotel.id,
|
||||
type: hotel.hotelType || "regular",
|
||||
name: hotel.name,
|
||||
coordinates: hotel.location
|
||||
? {
|
||||
lat: hotel.location.latitude,
|
||||
lng: hotel.location.longitude,
|
||||
}
|
||||
: null,
|
||||
}))
|
||||
.filter((item): item is DestinationMarker => !!item.coordinates)
|
||||
|
||||
return markers
|
||||
}
|
||||
|
||||
@@ -221,6 +221,9 @@ export const getHotelsByCityIdentifier = cache(
|
||||
})
|
||||
}
|
||||
)
|
||||
export const getAllHotels = cache(async function getMemoizedAllHotels() {
|
||||
return serverClient().hotel.hotels.getAllHotels.get()
|
||||
})
|
||||
export const getDestinationCityPage = cache(
|
||||
async function getMemoizedDestinationCityPage() {
|
||||
return serverClient().contentstack.destinationCityPage.get()
|
||||
|
||||
@@ -1172,6 +1172,52 @@ export const hotelQueryRouter = router({
|
||||
return hotels.filter((hotel): hotel is HotelDataWithUrl => !!hotel)
|
||||
}),
|
||||
}),
|
||||
getAllHotels: router({
|
||||
get: serviceProcedure.query(async function ({ ctx }) {
|
||||
const apiLang = toApiLang(ctx.lang)
|
||||
const params = new URLSearchParams({
|
||||
language: apiLang,
|
||||
})
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
// needs to clear default option as only
|
||||
// cache or next.revalidate is permitted
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: env.CACHE_TIME_HOTELS,
|
||||
},
|
||||
}
|
||||
const countries = await getCountries(options, params, ctx.lang)
|
||||
if (!countries) {
|
||||
return null
|
||||
}
|
||||
const countryNames = countries.data.map((country) => country.name)
|
||||
const hotelData: HotelDataWithUrl[] = (
|
||||
await Promise.all(
|
||||
countryNames.map(async (country) => {
|
||||
const countryParams = new URLSearchParams({
|
||||
country: country,
|
||||
})
|
||||
const hotelIds = await getHotelIdsByCountry(
|
||||
country,
|
||||
options,
|
||||
countryParams
|
||||
)
|
||||
|
||||
const hotels = await getHotelsByHotelIds(
|
||||
hotelIds,
|
||||
ctx.lang,
|
||||
ctx.serviceToken
|
||||
)
|
||||
return hotels
|
||||
})
|
||||
)
|
||||
).flat()
|
||||
return hotelData
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
nearbyHotelIds: serviceProcedure
|
||||
.input(nearbyHotelIdsInput)
|
||||
|
||||
@@ -348,7 +348,7 @@ export async function getHotelIdsByCityId(
|
||||
}
|
||||
|
||||
export async function getHotelIdsByCountry(
|
||||
country: Country,
|
||||
country: string,
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams
|
||||
) {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export type OverviewMapProps = {
|
||||
mapId: string
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export type OverviewMapContainerProps = {
|
||||
apiKey: string
|
||||
mapId: string
|
||||
}
|
||||
Reference in New Issue
Block a user