Files
web/packages/design-system/lib/components/Map/InteractiveMap/index.tsx
Anton Gunnarsson 16fbdb7ae0 Merged in fix/refactor-currency-display (pull request #3434)
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
2026-01-15 09:32:17 +00:00

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