diff --git a/src/components/map/ConnectedGeolocation.js b/src/components/map/ConnectedGeolocation.js index 9e71bf7b..bf1926cd 100644 --- a/src/components/map/ConnectedGeolocation.js +++ b/src/components/map/ConnectedGeolocation.js @@ -5,7 +5,12 @@ import { useGeolocation } from 'react-use' import { geolocationChange } from '../../redux/mapSlice' export const ConnectedGeolocation = () => { - const geolocation = useGeolocation() + const geolocation = useGeolocation({ + // Ask the device to use the most accurate means of geolocation + // We want to help with precise annotation of locations + // and to provide a good zoomed-in view + enableHighAccuracy: true, + }) const dispatch = useDispatch() useEffect(() => { diff --git a/src/components/map/Map.js b/src/components/map/Map.js index 866962b8..0c4825ec 100644 --- a/src/components/map/Map.js +++ b/src/components/map/Map.js @@ -203,7 +203,7 @@ const Map = ({ onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps)} yesIWantToUseGoogleMapApiInternals > - {geolocation && !geolocation.loading && ( + {geolocation && !geolocation.loading && !geolocation.error && ( - $active && !disabled ? 'blue' : 'tertiaryText' +const getTrackLocationColor = ({ userDeniedLocation, $active }) => + $active && !userDeniedLocation ? 'blue' : 'tertiaryText' -const TrackLocationIcon = ({ disabled, $loading, ...props }) => { - if (disabled) { - return // TODO: replace this with a specific "disabled geolocation" icon, like Google Maps has +const TrackLocationIcon = ({ userDeniedLocation, $loading, ...props }) => { + if (userDeniedLocation) { + return } else if ($loading) { return } else { @@ -39,9 +39,8 @@ const TrackLocationPrependButton = styled.button.attrs((props) => ({ padding-left: 3px; padding-right: 8px; - &:enabled { - cursor: pointer; - } + cursor: ${({ userDeniedLocation }) => + userDeniedLocation ? 'help' : 'pointer'}; svg { color: ${({ theme, ...props }) => theme[getTrackLocationColor(props)]}; @@ -59,6 +58,8 @@ const TrackLocationIconButton = styled(IconButton).attrs((props) => ({ svg { padding: 10px; } + cursor: ${({ userDeniedLocation }) => + userDeniedLocation ? 'help' : 'pointer'}; position: absolute; bottom: 84px; @@ -72,6 +73,9 @@ const TrackLocationButton = ({ isIcon }) => { const isTrackingLocation = useSelector( (state) => state.map.isTrackingLocation, ) + const userDeniedLocation = useSelector( + (state) => state.map.userDeniedLocation, + ) const TrackLocationBtn = isIcon ? TrackLocationIconButton @@ -79,11 +83,18 @@ const TrackLocationButton = ({ isIcon }) => { return ( { - dispatch(startTrackingLocation()) + onClick={(event) => { + if (userDeniedLocation) { + toast.info( + 'Permission to use your location was denied. To enable geolocation, please allow location sharing in your browser settings and refresh the page.' + ) + } else { + dispatch(startTrackingLocation()) + } + event.stopPropagation() }} /> ) diff --git a/src/redux/mapSlice.js b/src/redux/mapSlice.js index 80731709..a33b2c84 100644 --- a/src/redux/mapSlice.js +++ b/src/redux/mapSlice.js @@ -1,6 +1,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { fitBounds } from 'google-map-react' import { eqBy, prop, unionWith } from 'ramda' +import { toast } from 'react-toastify' import { VISIBLE_CLUSTER_ZOOM_LIMIT } from '../constants/map' import { getClusters, getLocations } from '../utils/api' @@ -8,7 +9,6 @@ import { parseUrl } from '../utils/getInitialUrl' import { searchView } from './searchView' import { selectParams } from './selectParams' import { updateSelection } from './updateSelection' - /** * Initial view state of the map. * @constant {Object} @@ -61,6 +61,7 @@ export const mapSlice = createSlice({ geolocation: null, isTrackingLocation: false, justStartedTrackingLocation: false, + userDeniedLocation: false, locationRequested: false, streetView: false, location: null, @@ -111,7 +112,24 @@ export const mapSlice = createSlice({ if (action.payload.loading) { // Loading } else if (action.payload.error) { - // TODO: send a toast that geolocation isn't working + if (action.payload.error.code === 1) { + // code 1 of GeolocationPositionError means user denied location request + // browsers will block subsequent requests so disable the setting + state.userDeniedLocation = true + } else { + // Treat code 2, internal error, as fatal + // Toast the message and suggest a retry + // + // The last value is code 3, timeout, is unreachable as we use the default of no timeout + // @see src/components/map/ConnectedGeolocation.js + if (!state.geolocation.error) { + toast.error( + `Geolocation failed: ${action.payload.error.message}. Please refresh the page and retry`, + ) + } else { + // We already toasted + } + } state.isTrackingLocation = false state.justStartedTrackingLocation = false } else if (state.isTrackingLocation) {