fix(SW-3616): Handle EuroBonus point type everywhere * Add tests to formatPrice * formatPrice * More work replacing config with api points type * More work replacing config with api points type * More fixing with currency * maybe actually fixed it * Fix MyStay * Clean up * Fix comments * Merge branch 'master' into fix/refactor-currency-display * Fix calculateTotalPrice for EB points + SF points + cash Approved-by: Joakim Jäderberg
167 lines
4.4 KiB
TypeScript
167 lines
4.4 KiB
TypeScript
"use client"
|
|
|
|
import { Map, type MapProps, useMap } from "@vis.gl/react-google-maps"
|
|
import { useEffect, useState } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { IconButton } from "../../IconButton"
|
|
|
|
import { HOTEL_PAGE, MAP_RESTRICTIONS } from "../mapConstants"
|
|
|
|
import { useZoomControls } from "@scandic-hotels/common/hooks/map/useZoomControls"
|
|
|
|
import { HotelListingMapContent } from "./HotelListingMapContent"
|
|
import PoiMapMarkers from "./PoiMapMarkers"
|
|
|
|
import styles from "./interactiveMap.module.css"
|
|
|
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
|
import { HotelPin, MarkerInfo, PointOfInterest } from "../types"
|
|
|
|
export type InteractiveMapProps = {
|
|
lang: Lang
|
|
coordinates: {
|
|
lat: number
|
|
lng: number
|
|
}
|
|
activePoi?: string | null
|
|
hotelPins?: HotelPin[]
|
|
pointsOfInterest?: PointOfInterest[]
|
|
markerInfo?: MarkerInfo
|
|
mapId: string
|
|
closeButton: React.ReactNode
|
|
fitBounds?: boolean
|
|
hoveredHotelPin?: string | null
|
|
activeHotelPin?: string | null
|
|
|
|
isUserLoggedIn: boolean
|
|
onTilesLoaded?: () => void
|
|
onActivePoiChange?: (poi: string | null) => void
|
|
|
|
onClickHotel?: (hotelId: string) => void
|
|
|
|
/**
|
|
* Called when a hotel pin is hovered.
|
|
* @param args when null, it means the hover has ended
|
|
* @returns
|
|
*/
|
|
onHoverHotelPin?: (
|
|
args: { hotelName: string; hotelId: string } | null
|
|
) => void
|
|
|
|
/**
|
|
* Called when a hotel pin is activated.
|
|
* @param args when null, it means nothing is active
|
|
* @returns
|
|
*/
|
|
onSetActiveHotelPin?: (
|
|
args: { hotelName: string; hotelId: string } | null
|
|
) => void
|
|
}
|
|
|
|
export function InteractiveMap({
|
|
lang,
|
|
coordinates,
|
|
pointsOfInterest,
|
|
activePoi,
|
|
hotelPins,
|
|
mapId,
|
|
closeButton,
|
|
markerInfo,
|
|
fitBounds = true,
|
|
hoveredHotelPin,
|
|
activeHotelPin,
|
|
isUserLoggedIn,
|
|
onClickHotel,
|
|
onHoverHotelPin,
|
|
onSetActiveHotelPin,
|
|
onTilesLoaded,
|
|
onActivePoiChange,
|
|
}: InteractiveMapProps) {
|
|
const intl = useIntl()
|
|
const map = useMap()
|
|
const [hasInitializedBounds, setHasInitializedBounds] = useState(false)
|
|
const { zoomIn, zoomOut, isMaxZoom, isMinZoom } = useZoomControls(HOTEL_PAGE)
|
|
|
|
const mapOptions: MapProps = {
|
|
defaultZoom: HOTEL_PAGE.DEFAULT_ZOOM,
|
|
minZoom: HOTEL_PAGE.MIN_ZOOM,
|
|
maxZoom: HOTEL_PAGE.MAX_ZOOM,
|
|
defaultCenter: coordinates,
|
|
disableDefaultUI: true,
|
|
clickableIcons: false,
|
|
mapId,
|
|
gestureHandling: "greedy",
|
|
restriction: MAP_RESTRICTIONS,
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (map && hotelPins?.length && !hasInitializedBounds) {
|
|
if (fitBounds) {
|
|
const bounds = new google.maps.LatLngBounds()
|
|
hotelPins.forEach((marker) => {
|
|
bounds.extend(marker.coordinates)
|
|
})
|
|
map.fitBounds(bounds, 100)
|
|
}
|
|
setHasInitializedBounds(true)
|
|
}
|
|
}, [map, fitBounds, hotelPins, hasInitializedBounds])
|
|
|
|
return (
|
|
<div className={styles.mapContainer}>
|
|
<Map {...mapOptions} onTilesLoaded={onTilesLoaded}>
|
|
{hotelPins && (
|
|
<HotelListingMapContent
|
|
lang={lang}
|
|
isUserLoggedIn={isUserLoggedIn}
|
|
hotelPins={hotelPins}
|
|
setActiveHotel={onSetActiveHotelPin}
|
|
setHoveredHotel={onHoverHotelPin}
|
|
activeHotel={activeHotelPin}
|
|
hoveredHotel={hoveredHotelPin}
|
|
onClickHotel={onClickHotel}
|
|
/>
|
|
)}
|
|
{pointsOfInterest && markerInfo && (
|
|
<PoiMapMarkers
|
|
coordinates={coordinates}
|
|
pointsOfInterest={pointsOfInterest}
|
|
onActivePoiChange={onActivePoiChange}
|
|
activePoi={activePoi}
|
|
markerInfo={markerInfo}
|
|
/>
|
|
)}
|
|
</Map>
|
|
<div className={styles.ctaButtons}>
|
|
{closeButton}
|
|
<div className={styles.zoomButtons}>
|
|
<IconButton
|
|
variant="Elevated"
|
|
className={styles.zoomButton}
|
|
onClick={zoomOut}
|
|
aria-label={intl.formatMessage({
|
|
id: "map.zoomOut",
|
|
defaultMessage: "Zoom out",
|
|
})}
|
|
isDisabled={isMinZoom}
|
|
iconName="remove"
|
|
/>
|
|
|
|
<IconButton
|
|
variant="Elevated"
|
|
className={styles.zoomButton}
|
|
onClick={zoomIn}
|
|
aria-label={intl.formatMessage({
|
|
id: "map.zoomIn",
|
|
defaultMessage: "Zoom in",
|
|
})}
|
|
isDisabled={isMaxZoom}
|
|
iconName="add"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|