Merged in SW-3270-move-interactive-map-to-design-system-or-booking-flow (pull request #2681)
SW-3270 move interactive map to design system or booking flow * wip * wip * merge * wip * add support for locales in design-system * add story for HotelCard * setup alias * . * remove tracking from design-system for hotelcard * pass isUserLoggedIn * export design-system-new-deprecated.css from design-system * Add HotelMarkerByType to Storybook * Add interactive map to Storybook * fix reactintl in vitest * rename env variables * . * fix background colors * add storybook stories for <Link /> * merge * fix tracking for when clicking 'See rooms' in InteractiveMap * Merge branch 'master' of bitbucket.org:scandic-swap/web into SW-3270-move-interactive-map-to-design-system-or-booking-flow * remove deprecated comment Approved-by: Anton Gunnarsson
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
'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 '@scandic-hotels/design-system/IconButton'
|
||||
import { MaterialIcon } from '@scandic-hotels/design-system/Icons/MaterialIcon'
|
||||
|
||||
import {
|
||||
DEFAULT_ZOOM,
|
||||
MAP_RESTRICTIONS,
|
||||
MAX_ZOOM,
|
||||
MIN_ZOOM,
|
||||
} from '../mapConstants'
|
||||
|
||||
import { useZoomControls } from './useZoomControls'
|
||||
|
||||
import { HotelListingMapContent } from './HotelListingMapContent'
|
||||
import PoiMapMarkers from './PoiMapMarkers'
|
||||
|
||||
import styles from './interactiveMap.module.css'
|
||||
|
||||
import type { PointOfInterest } from '@scandic-hotels/trpc/types/hotel'
|
||||
import type { MarkerInfo } from '@scandic-hotels/trpc/types/marker'
|
||||
import { HotelPin } from '../types'
|
||||
import { Lang } from '@scandic-hotels/common/constants/language'
|
||||
|
||||
export type InteractiveMapProps = {
|
||||
lang: Lang
|
||||
coordinates: {
|
||||
lat: number
|
||||
lng: number
|
||||
}
|
||||
activePoi?: PointOfInterest['name'] | 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: PointOfInterest['name'] | 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()
|
||||
|
||||
const mapOptions: MapProps = {
|
||||
defaultZoom: DEFAULT_ZOOM,
|
||||
minZoom: MIN_ZOOM,
|
||||
maxZoom: 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
|
||||
theme="Inverted"
|
||||
style="Elevated"
|
||||
className={styles.zoomButton}
|
||||
onClick={zoomOut}
|
||||
aria-label={intl.formatMessage({
|
||||
defaultMessage: 'Zoom out',
|
||||
})}
|
||||
isDisabled={isMinZoom}
|
||||
>
|
||||
<MaterialIcon icon="remove" color="CurrentColor" />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
theme="Inverted"
|
||||
style="Elevated"
|
||||
className={styles.zoomButton}
|
||||
onClick={zoomIn}
|
||||
aria-label={intl.formatMessage({
|
||||
defaultMessage: 'Zoom in',
|
||||
})}
|
||||
isDisabled={isMaxZoom}
|
||||
>
|
||||
<MaterialIcon icon="add" color="CurrentColor" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user