diff --git a/.eslintignore b/.eslintignore
index 46cf43f9..e8b07b72 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -6,7 +6,7 @@ cypress/
# Build directory
docker/
build/
-src/serviceWorker
+./src/serviceWorker
# Unused files
Legend.js
diff --git a/.eslintrc.json b/.eslintrc.json
index a816a2be..7d13df8f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -3,7 +3,8 @@
"eslint:recommended",
"plugin:react/recommended",
"prettier",
- "plugin:react/jsx-runtime"
+ "plugin:react/jsx-runtime",
+ "plugin:react-hooks/recommended"
],
"rules": {
"react/prop-types": 0,
diff --git a/jsconfig.json b/jsconfig.json
index e18d3093..e2eb21ca 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -1,13 +1,8 @@
{
- "include": [
- "src/**/*"
- ],
- "compilerOptions": {
- "jsx": "react-jsx",
- "target": "es6",
- "baseUrl": "./src"
- },
- "exclude": [
- "node_modules"
- ]
-}
\ No newline at end of file
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "target": "es6",
+ "baseUrl": "./src"
+ }
+}
diff --git a/package.json b/package.json
index 4330d37b..af6be421 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,6 @@
"react": "^17.0.2",
"react-bootstrap": "^2.3.0",
"react-burger-menu": "^3.0.6",
- "react-device-detect": "^2.2.2",
"react-div-100vh": "^0.7.0",
"react-dom": "^17.0.0",
"react-ga4": "^2.0.0",
diff --git a/public/index.html b/public/index.html
index 950c9a83..e26ff3d8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -6,7 +6,7 @@
diff --git a/src/actions/actions.js b/src/actions/actions.js
index 430784fb..71fb6f3b 100644
--- a/src/actions/actions.js
+++ b/src/actions/actions.js
@@ -1,5 +1,6 @@
+import { createAction, createAsyncThunk } from '@reduxjs/toolkit';
import { initializeApp } from 'firebase/app';
-import { getDatabase, onValue, ref } from 'firebase/database';
+import { getDatabase, get, ref } from 'firebase/database';
import { resourcesConfig } from '../firebase/firebaseConfig';
import { testData } from '../firebase/functionalTest';
@@ -27,25 +28,18 @@ export const resetFilterFunction = () => ({
type: RESET_FILTER_FUNCTION
});
-export const GET_RESOURCES_SUCCESS = 'GET_RESOURCES_SUCCESS';
-export const getResourcesSuccess = allResources => ({
- type: GET_RESOURCES_SUCCESS,
- allResources
-});
+export const getResources = createAsyncThunk(
+ 'fetch-resources',
+ async (_, { dispatch }) => {
+ const app = initializeApp(resourcesConfig);
+ const database = getDatabase(app);
-export const getResources = () => dispatch => {
- const app = initializeApp(resourcesConfig);
- const database = getDatabase(app);
- return process.env.REACT_APP_CYPRESS_TEST
- ? dispatch(getResourcesSuccess(testData))
- : onValue(
- ref(database, '/'),
- snapshot => {
- dispatch(getResourcesSuccess(Object.values(snapshot.val())));
- },
- { onlyOnce: true }
- );
-};
+ if (process.env.REACT_APP_CYPRESS_TEST) return testData;
+ const snapshot = await get(ref(database, '/'));
+ const results = snapshot.val();
+ return Object.values(results) || [];
+ }
+);
// Handles the case where a new resource is added from the submission form
export const PUSH_NEW_RESOURCE = 'PUSH_NEW_RESOURCE';
@@ -66,29 +60,8 @@ export const setMapCenter = coords => ({
coords
});
-// export const TOGGLE_SEARCH_BAR = 'TOGGLE_SEARCH_BAR';
-// export const toggleSearchBar = isShown => ({
-// type: TOGGLE_SEARCH_BAR,
-// isShown
-// });
-
-// export const TOGGLE_FILTER_MODAL = 'TOGGLE_FILTER_MODAL';
-// export const toggleFilterModal = isShown => ({
-// type: TOGGLE_FILTER_MODAL,
-// isShown
-// });
-
-export const TOGGLE_INFO_WINDOW = 'TOGGLE_INFO_WINDOW';
-export const toggleInfoWindow = isShown => ({
- type: TOGGLE_INFO_WINDOW,
- isShown
-});
-
-export const TOGGLE_INFO_WINDOW_CLASS = 'TOGGLE_INFO_WINDOW_CLASS';
-export const toggleInfoWindowClass = isShown => ({
- type: TOGGLE_INFO_WINDOW_CLASS,
- isShown
-});
+export const toggleInfoWindow = createAction('TOGGLE_INFO_WINDOW');
+export const toggleInfoWindowClass = createAction('TOGGLE_INFO_WINDOW_CLASS');
export const TOGGLE_INFO_EXPANDED = 'TOGGLE_INFO_EXPANDED';
export const toggleInfoExpanded = isExpanded => ({
@@ -112,11 +85,7 @@ export const setSelectedPlace = selectedPlace => ({
selectedPlace
});
-export const SET_TOOLBAR_MODAL = 'SET_TOOLBAR_MODAL';
-export const setToolbarModal = toolbarModal => ({
- type: SET_TOOLBAR_MODAL,
- mode: toolbarModal
-});
+export const setToolbarModal = createAction('SET_TOOLBAR_MODAL');
export const TOOLBAR_MODAL_NONE = 'TOOLBAR_MODAL_NONE';
export const TOOLBAR_MODAL_RESOURCE = 'TOOLBAR_MODAL_RESOURCE';
diff --git a/src/components/AddResourceModal/AddBathroom/AddBathroom.jsx b/src/components/AddResourceModal/AddBathroom/AddBathroom.jsx
index 2530ec7c..a77766bc 100644
--- a/src/components/AddResourceModal/AddBathroom/AddBathroom.jsx
+++ b/src/components/AddResourceModal/AddBathroom/AddBathroom.jsx
@@ -1,16 +1,16 @@
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { geocode, setDefaults, RequestType } from 'react-geocode';
import styles from '../AddResourceModal.module.scss';
import { useForm } from 'react-hook-form';
import { Box, CardContent, Grid, Typography, IconButton } from '@mui/material';
-import { isMobile } from 'react-device-detect';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import PageOne from './PageOne';
import PageTwo from './PageTwo';
+import useIsMobile from 'hooks/useIsMobile';
function AddBathroom({
prev,
@@ -35,6 +35,7 @@ function AddBathroom({
checkboxChangeHandler,
textFieldChangeHandler
}) {
+ const isMobile = useIsMobile();
const userLocation = useSelector(state => state.filterMarkers.userLocation);
useEffect(() => {
diff --git a/src/components/AddResourceModal/AddBathroom/PageOne.jsx b/src/components/AddResourceModal/AddBathroom/PageOne.jsx
index c839c09a..2cd83c77 100644
--- a/src/components/AddResourceModal/AddBathroom/PageOne.jsx
+++ b/src/components/AddResourceModal/AddBathroom/PageOne.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import ImageUploader from 'react-images-upload';
import PlacesAutocomplete from 'react-places-autocomplete';
-import { geocode, setDefaults, RequestType } from 'react-geocode';
+import { geocode, RequestType } from 'react-geocode';
import styles from '../AddResourceModal.module.scss';
import { Controller } from 'react-hook-form';
import {
@@ -14,7 +14,8 @@ import {
} from '@mui/material';
import MyLocationIcon from '@mui/icons-material/MyLocation';
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
+import noop from 'utils/noop';
const ENTRY_TYPE = [
{ entryType: 'Open access', explanation: 'Public site, open to all' },
@@ -39,6 +40,8 @@ const PageOne = ({
setValue,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
+
return (
<>
{isMobile && (
@@ -142,7 +145,7 @@ const PageOne = ({
textFieldChangeHandler(addr);
onChange(addr);
})
- .catch(console.error);
+ .catch(noop);
}
}}
style={{ backgroundColor: 'white' }}
diff --git a/src/components/AddResourceModal/AddBathroom/PageTwo.jsx b/src/components/AddResourceModal/AddBathroom/PageTwo.jsx
index e4d5403c..aa1761fb 100644
--- a/src/components/AddResourceModal/AddBathroom/PageTwo.jsx
+++ b/src/components/AddResourceModal/AddBathroom/PageTwo.jsx
@@ -11,8 +11,7 @@ import {
TextField,
ListItem
} from '@mui/material';
-
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
const PageTwo = ({
onDrop,
@@ -28,6 +27,8 @@ const PageTwo = ({
checkboxChangeHandler,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
+
const BATHROOM_HELPFUL_INFO = [
{
label: 'Wheelchair accessible',
diff --git a/src/components/AddResourceModal/AddFood/AddFood.jsx b/src/components/AddResourceModal/AddFood/AddFood.jsx
index 65e50cd5..06abad14 100644
--- a/src/components/AddResourceModal/AddFood/AddFood.jsx
+++ b/src/components/AddResourceModal/AddFood/AddFood.jsx
@@ -4,13 +4,13 @@ import { geocode, setDefaults, RequestType } from 'react-geocode';
import styles from '../AddResourceModal.module.scss';
import { useForm, Controller } from 'react-hook-form';
import { Card, CardContent, Grid, IconButton, Typography } from '@mui/material';
-import { isMobile } from 'react-device-detect';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import PageOne from './PageOne';
import PageTwo from './PageTwo';
+import useIsMobile from 'hooks/useIsMobile';
function AddFood({
prev,
@@ -41,6 +41,7 @@ function AddFood({
checkboxChangeHandler,
textFieldChangeHandler
}) {
+ const isMobile = useIsMobile();
const getVariableName = variable => Object.keys(variable)[0];
const userLocation = useSelector(state => state.filterMarkers.userLocation);
diff --git a/src/components/AddResourceModal/AddFood/PageOne.jsx b/src/components/AddResourceModal/AddFood/PageOne.jsx
index d0353b07..b3608d3d 100644
--- a/src/components/AddResourceModal/AddFood/PageOne.jsx
+++ b/src/components/AddResourceModal/AddFood/PageOne.jsx
@@ -20,8 +20,8 @@ import {
} from '@mui/material';
import MyLocationIcon from '@mui/icons-material/MyLocation';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
-
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
+import noop from 'utils/noop';
const PageOne = ({
// state values and handlers for the textfields
@@ -50,6 +50,8 @@ const PageOne = ({
checkboxChangeHandler,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
+
const FOOD_TYPE = [
{
id: '0',
@@ -213,7 +215,7 @@ const PageOne = ({
textFieldChangeHandler(addr);
onChange(addr);
})
- .catch(console.error);
+ .catch(noop);
}
}}
style={{ backgroundColor: 'white' }}
diff --git a/src/components/AddResourceModal/AddFood/PageTwo.jsx b/src/components/AddResourceModal/AddFood/PageTwo.jsx
index d1152da8..0752c905 100644
--- a/src/components/AddResourceModal/AddFood/PageTwo.jsx
+++ b/src/components/AddResourceModal/AddFood/PageTwo.jsx
@@ -12,7 +12,7 @@ import {
ListItem
} from '@mui/material';
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
const PageTwo = ({
onDrop,
@@ -27,6 +27,8 @@ const PageTwo = ({
checkboxChangeHandler,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
+
const FOOD_HELPFUL_INFO = [
{
label: 'Wheelchair accessible',
diff --git a/src/components/AddResourceModal/AddForaging/AddForaging.jsx b/src/components/AddResourceModal/AddForaging/AddForaging.jsx
index d4401d53..2bbebe39 100644
--- a/src/components/AddResourceModal/AddForaging/AddForaging.jsx
+++ b/src/components/AddResourceModal/AddForaging/AddForaging.jsx
@@ -9,8 +9,7 @@ import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import PageOne from './PageOne';
import PageTwo from './PageTwo';
-
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
function AddForaging({
prev,
@@ -41,6 +40,7 @@ function AddForaging({
checkboxChangeHandler,
textFieldChangeHandler
}) {
+ const isMobile = useIsMobile();
const userLocation = useSelector(state => state.filterMarkers.userLocation);
useEffect(() => {
diff --git a/src/components/AddResourceModal/AddForaging/PageOne.jsx b/src/components/AddResourceModal/AddForaging/PageOne.jsx
index 178b16fd..242ebc6a 100644
--- a/src/components/AddResourceModal/AddForaging/PageOne.jsx
+++ b/src/components/AddResourceModal/AddForaging/PageOne.jsx
@@ -20,8 +20,8 @@ import {
} from '@mui/material';
import MyLocationIcon from '@mui/icons-material/MyLocation';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
-
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
+import noop from 'utils/noop';
const ENTRY_TYPE = [
{ entryType: 'Open access', explanation: 'Public site, open to all' },
@@ -54,6 +54,8 @@ const PageOne = ({
checkboxChangeHandler,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
+
const FORAGE_TYPE = [
{
forageType: 'Nut',
@@ -191,7 +193,7 @@ const PageOne = ({
textFieldChangeHandler(addr);
onChange(addr);
})
- .catch(console.error);
+ .catch(noop);
}
}}
style={{ backgroundColor: 'white' }}
diff --git a/src/components/AddResourceModal/AddForaging/PageTwo.jsx b/src/components/AddResourceModal/AddForaging/PageTwo.jsx
index 817f9ffc..d5c442cd 100644
--- a/src/components/AddResourceModal/AddForaging/PageTwo.jsx
+++ b/src/components/AddResourceModal/AddForaging/PageTwo.jsx
@@ -11,8 +11,7 @@ import {
TextField,
ListItem
} from '@mui/material';
-
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
const PageTwo = ({
onDrop,
@@ -27,6 +26,8 @@ const PageTwo = ({
checkboxChangeHandler,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
+
const FORAGING_HELPFUL_INFO = [
{
label: 'Medicinal',
diff --git a/src/components/AddResourceModal/AddResourceModalV2.jsx b/src/components/AddResourceModal/AddResourceModalV2.jsx
index 6a013263..12d00449 100644
--- a/src/components/AddResourceModal/AddResourceModalV2.jsx
+++ b/src/components/AddResourceModal/AddResourceModalV2.jsx
@@ -26,6 +26,7 @@ import {
FORAGE_RESOURCE_TYPE,
BATHROOM_RESOURCE_TYPE
} from '../../types/ResourceEntry';
+import noop from 'utils/noop';
export default function AddResourceModalV2(props) {
const initialState = {
@@ -122,11 +123,7 @@ export default function AddResourceModalV2(props) {
longitude: lng
}));
})
- .catch(error => {
- if (error.message !== 'ZERO_RESULTS') {
- console.error('Error occurred during geocoding:', error);
- }
- });
+ .catch(noop);
}, 500); // 500ms debounce delay
const textFieldChangeHandler = eventOrString => {
@@ -212,7 +209,7 @@ export default function AddResourceModalV2(props) {
return data.getURL;
});
})
- .catch(console.error);
+ .catch(noop);
};
const onSubmit = (resourceType, e) => {
diff --git a/src/components/AddResourceModal/AddWaterTap/AddWaterTap.jsx b/src/components/AddResourceModal/AddWaterTap/AddWaterTap.jsx
index a3e34f5f..a6eb548e 100644
--- a/src/components/AddResourceModal/AddWaterTap/AddWaterTap.jsx
+++ b/src/components/AddResourceModal/AddWaterTap/AddWaterTap.jsx
@@ -1,6 +1,6 @@
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { useSelector } from 'react-redux';
-import { geocode, setDefaults, RequestType } from 'react-geocode';
+import { setDefaults } from 'react-geocode';
import styles from '../AddResourceModal.module.scss';
import { useForm } from 'react-hook-form';
import { Box, CardContent, Grid, Typography, IconButton } from '@mui/material';
@@ -9,7 +9,7 @@ import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import PageOne from './PageOne';
import PageTwo from './PageTwo';
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
function AddWaterTap({
prev,
@@ -44,6 +44,7 @@ function AddWaterTap({
checkboxChangeHandler,
textFieldChangeHandler
}) {
+ const isMobile = useIsMobile();
const userLocation = useSelector(state => state.filterMarkers.userLocation);
useEffect(() => {
diff --git a/src/components/AddResourceModal/AddWaterTap/PageOne.jsx b/src/components/AddResourceModal/AddWaterTap/PageOne.jsx
index 74f203d3..ff3d44b6 100644
--- a/src/components/AddResourceModal/AddWaterTap/PageOne.jsx
+++ b/src/components/AddResourceModal/AddWaterTap/PageOne.jsx
@@ -21,7 +21,8 @@ import {
import MyLocationIcon from '@mui/icons-material/MyLocation';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
+import noop from 'utils/noop';
const ENTRY_TYPE = [
{ entryType: 'Open access', explanation: 'Public site, open to all' },
@@ -57,6 +58,8 @@ const PageOne = ({
checkboxChangeHandler,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
+
const DISPENSER_TYPE = [
{
label: 'Drinking fountain',
@@ -203,7 +206,7 @@ const PageOne = ({
textFieldChangeHandler(addr);
onChange(addr);
})
- .catch(console.error);
+ .catch(noop);
}
}}
style={{ backgroundColor: 'white' }}
diff --git a/src/components/AddResourceModal/AddWaterTap/PageTwo.jsx b/src/components/AddResourceModal/AddWaterTap/PageTwo.jsx
index 4bd529b7..c7d37f0b 100644
--- a/src/components/AddResourceModal/AddWaterTap/PageTwo.jsx
+++ b/src/components/AddResourceModal/AddWaterTap/PageTwo.jsx
@@ -1,25 +1,16 @@
-import React from 'react';
import ImageUploader from 'react-images-upload';
-import styles from '../AddResourceModal.module.scss';
-import { deleteApp } from 'firebase/app';
-import { connectToFirebase } from '../utils';
+
import { Controller } from 'react-hook-form';
import {
- Box,
Button,
- CardContent,
- FormGroup,
Grid,
- Link,
- MenuItem,
- Stack,
Typography,
Checkbox,
TextField,
ListItem
} from '@mui/material';
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
const PageTwo = ({
onDrop,
@@ -35,6 +26,7 @@ const PageTwo = ({
checkboxChangeHandler,
textFieldChangeHandler
}) => {
+ const isMobile = useIsMobile();
const WATER_HELPFUL_INFO = [
{
label: 'Wheelchair accessible',
diff --git a/src/components/AddResourceModal/ChooseResource.jsx b/src/components/AddResourceModal/ChooseResource.jsx
index 7a1d0cdf..4c4100b0 100644
--- a/src/components/AddResourceModal/ChooseResource.jsx
+++ b/src/components/AddResourceModal/ChooseResource.jsx
@@ -1,15 +1,15 @@
-import React from 'react';
import styles from './AddResourceModal.module.scss';
import Button from '@mui/material/Button';
-import { useDispatch, useSelector } from 'react-redux';
+import useIsMobile from 'hooks/useIsMobile';
+import { useDispatch } from 'react-redux';
import { ReactComponent as WaterIconCR } from '../icons/WaterIconChooseResource.svg';
import { ReactComponent as FoodIconCR } from '../icons/FoodIconChooseResource.svg';
import { ReactComponent as ForagingIconCR } from '../icons/ForagingIconChooseResource.svg';
import { ReactComponent as ToiletIconCR } from '../icons/ToiletIconChooseResource.svg';
-import { isMobile } from 'react-device-detect';
function ChooseResource({ setFormStep }) {
+ const isMobile = useIsMobile();
const dispatch = useDispatch();
return (
diff --git a/src/components/AddResourceModal/ModalWrapper.jsx b/src/components/AddResourceModal/ModalWrapper.jsx
index 0807ce4a..544cbc13 100644
--- a/src/components/AddResourceModal/ModalWrapper.jsx
+++ b/src/components/AddResourceModal/ModalWrapper.jsx
@@ -1,13 +1,14 @@
import React from 'react';
-import { isMobile } from 'react-device-detect';
import Paper from '@mui/material/Paper';
import Dialog from '@mui/material/Dialog';
import styles from './AddResourceModal.module.scss';
-/*
- Higher Order Component that returns a Dialog for mobile and a non modal Dialog for Desktop
+import useIsMobile from 'hooks/useIsMobile';
+/*
+ Higher Order Component that returns a Dialog for mobile and a non modal Dialog for Desktop
*/
const ModalWrapper = props => {
+ const isMobile = useIsMobile();
return (
<>
{isMobile ? (
diff --git a/src/components/AddResourceModal/SharedFormFields.jsx b/src/components/AddResourceModal/SharedFormFields.jsx
index 18ac6275..be00013b 100644
--- a/src/components/AddResourceModal/SharedFormFields.jsx
+++ b/src/components/AddResourceModal/SharedFormFields.jsx
@@ -158,15 +158,6 @@ function SharedFormFields({
/>
-
- {/*
- Name
-
- */}
>
);
}
diff --git a/src/components/ChooseResource/ChooseResource.js b/src/components/ChooseResource/ChooseResource.js
index 891cebfe..7ab95fd8 100644
--- a/src/components/ChooseResource/ChooseResource.js
+++ b/src/components/ChooseResource/ChooseResource.js
@@ -1,13 +1,15 @@
-import React from 'react';
-import { isMobile } from 'react-device-detect';
+import useIsMobile from 'hooks/useIsMobile';
import DesktopChooseResource from './DesktopChooseResource';
import MobileChooseResource from './MobileChooseResource';
const ChooseResource = () => {
- return (
- <>{isMobile ? : }>
- );
+
+ const isMobile = useIsMobile();
+
+ return (
+ <>{isMobile ? : }>
+ );
};
export default ChooseResource;
diff --git a/src/components/ChooseResource/DesktopChooseResource.js b/src/components/ChooseResource/DesktopChooseResource.js
index 217af854..fd60a712 100644
--- a/src/components/ChooseResource/DesktopChooseResource.js
+++ b/src/components/ChooseResource/DesktopChooseResource.js
@@ -1,5 +1,5 @@
import { Box, Button, Collapse, Paper } from '@mui/material';
-import React, { useRef } from 'react';
+import { useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
TOOLBAR_MODAL_RESOURCE,
diff --git a/src/components/ChooseResource/MobileChooseResource.js b/src/components/ChooseResource/MobileChooseResource.js
index 5d6efa49..b73896ea 100644
--- a/src/components/ChooseResource/MobileChooseResource.js
+++ b/src/components/ChooseResource/MobileChooseResource.js
@@ -2,7 +2,6 @@ import Box from '@mui/material/Box';
import Dialog from '@mui/material/Dialog';
import List from '@mui/material/List';
import Slide from '@mui/material/Slide';
-import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ReactComponent as FoodIcon } from '../icons/FoodIconV2.svg';
import { ReactComponent as ForagingIcon } from '../icons/ForagingIconV2.svg';
diff --git a/src/components/ClosestTap/ClosestTap.js b/src/components/ClosestTap/ClosestTap.js
deleted file mode 100644
index 21bfcfb1..00000000
--- a/src/components/ClosestTap/ClosestTap.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import { Component } from 'react';
-import { Card } from 'react-bootstrap';
-import { connect } from 'react-redux';
-
-// Actual Magic: https://stackoverflow.com/a/41337005
-// Distance calculates the distance between two lat/lon pairs
-function distance(lat1, lon1, lat2, lon2) {
- var p = 0.017453292519943295;
- var a =
- 0.5 -
- Math.cos((lat2 - lat1) * p) / 2 +
- (Math.cos(lat1 * p) *
- Math.cos(lat2 * p) *
- (1 - Math.cos((lon2 - lon1) * p))) /
- 2;
- return 12742 * Math.asin(Math.sqrt(a));
-}
-
-// Takes an array of objects with lat and lon properties as well as a single object with lat and lon
-// properties and finds the closest point (by shortest distance).
-function getClosest(data, v) {
- var distances = data.map(function (p) {
- return {
- lat: p['lat'],
- lon: p['lon'],
- organization: p['organization'],
- address: p['address'],
- distance: distance(v['lat'], v['lon'], p['lat'], p['lon'])
- };
- });
- var minDistance = Math.min(...distances.map(d => d.distance));
-
- var closestTap = {
- organization: '',
- address: '',
- lat: '',
- lon: ''
- };
-
- for (var i = 0; i < distances.length; i++) {
- if (distances[i].distance === minDistance) {
- closestTap.lat = distances[i].lat;
- closestTap.lon = distances[i].lon;
- closestTap.organization = distances[i].organization;
- closestTap.address = distances[i].address;
- }
- }
- return closestTap;
-}
-
-export class ClosestTap extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- text: 'Click for nearest tap!'
- };
-
- // this.change = this.change.bind(this);
- }
-
- setClosest() {
- const closest = getClosest(this.props.allTaps, {
- lat: this.props.userLocation.lat,
- lon: this.props.userLocation.lng
- });
- }
-
- // change(){
- // if(this.props.lat === "" || this.props.lon === ""){
- // this.setState({
- // text:
- // The closest tap feature is unavailable. We require permission
- // to access your location to provide it.
- //
- // })
- // }
- // else{
- // this.setState({
- // text:
- // The closest tap is: {this.props.org}
- // Located at:
- //
- // {this.props.address}
- //
- //
- // });
- // }
- // }
-
- render() {
- return (
-
-
-
- {this.state.text}
-
-
-
- );
- }
-}
-
-const mapStateToProps = state => ({
- userLocation: state.filterMarkers.userLocation,
- allTaps: state.filterMarkers.allTaps
-});
-
-export default connect(mapStateToProps)(ClosestTap);
diff --git a/src/components/ClosestTap/ClosestTap.stories.jsx b/src/components/ClosestTap/ClosestTap.stories.jsx
deleted file mode 100644
index 46986db5..00000000
--- a/src/components/ClosestTap/ClosestTap.stories.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-
-import { ClosestTap } from './ClosestTap'
-
-// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
-export default {
- title: 'Base/ClosestTap',
- component: ClosestTap,
- // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
- argTypes: {
- backgroundColor: { control: 'color' },
- },
-};
-
-// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const Template = (args) => ;
-
-export const Primary = Template.bind({});
-// More on args: https://storybook.js.org/docs/react/writing-stories/args
-Primary.args = {
- text: "Hello World!",
-};
diff --git a/src/components/ClosestTap/ClosestTap.test.js b/src/components/ClosestTap/ClosestTap.test.js
deleted file mode 100644
index a6894538..00000000
--- a/src/components/ClosestTap/ClosestTap.test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { ClosestTap } from './ClosestTap';
-
-it('renders without crashing', () => {
- const div = document.createElement('div');
- ReactDOM.render( , div);
- ReactDOM.unmountComponentAtNode(div);
-});
diff --git a/src/components/Filter/Filter.js b/src/components/Filter/Filter.js
index 3ca9cad2..758ed994 100644
--- a/src/components/Filter/Filter.js
+++ b/src/components/Filter/Filter.js
@@ -1,26 +1,30 @@
import { Box, Button, Collapse, Paper } from '@mui/material';
import React, { useState } from 'react';
-import { isMobile } from 'react-device-detect';
import { useDispatch, useSelector } from 'react-redux';
-import { TOOLBAR_MODAL_FILTER } from '../../actions/actions';
+import {
+ setToolbarModal,
+ toggleInfoWindow,
+ TOOLBAR_MODAL_FILTER,
+ TOOLBAR_MODAL_NONE
+} from '../../actions/actions';
import styles from './Filter.module.scss';
import CloseIcon from '@mui/icons-material/Close';
import IconButton from '@mui/material/IconButton';
+import useIsMobile from 'hooks/useIsMobile';
-const FilterTags = props => (
+const FilterTags = ({ tags, activeTags, resourceType, index, handleTag }) => (
- {props.tags.map((tag, key) => (
+ {tags.map((tag, key) => (
{
- props.handleTag(0, props.resourceType, props.index, key);
- props.forceUpdate();
+ handleTag(0, resourceType, index, key);
}}
data-cy={`filter-option-${tag}`}
>
@@ -30,20 +34,25 @@ const FilterTags = props => (
);
-const FilterTagsExclusive = props => (
+const FilterTagsExclusive = ({
+ tags,
+ activeTags,
+ resourceType,
+ index,
+ handleTag
+}) => (
- {props.tags.map((tag, key) => (
+ {tags.map((tag, key) => (
{
- props.handleTag(1, props.resourceType, props.index, key);
- props.forceUpdate();
+ handleTag(1, resourceType, index, key);
}}
data-cy={`filter-option-${tag}`}
>
@@ -53,13 +62,15 @@ const FilterTagsExclusive = props => (
);
-function useForceUpdate() {
- const [value, setValue] = useState(0);
- return () => setValue(value => value + 1);
-}
-
-export default function Filter(props) {
- const forceUpdate = useForceUpdate();
+export default function Filter({
+ filters,
+ resourceType,
+ handleTag,
+ activeTags,
+ clearAll,
+ applyTags
+}) {
+ const isMobile = useIsMobile();
const dispatch = useDispatch();
const toolbarModal = useSelector(state => state.filterMarkers.toolbarModal);
return (
@@ -80,11 +91,21 @@ export default function Filter(props) {
timeout="auto"
>
- {props.filters[props.resourceType].title}
+ {filters[resourceType].title}
{
- this.toggleInfoWindow(false);
+ dispatch(
+ toggleInfoWindow({
+ isShown: false,
+ infoWindowClass: isMobile
+ ? 'info-window-out'
+ : 'info-window-out-desktop'
+ })
+ );
+ dispatch(
+ setToolbarModal({ toolbarModal: TOOLBAR_MODAL_NONE })
+ );
}}
sx={{
position: 'absolute',
@@ -106,34 +127,30 @@ export default function Filter(props) {
- {props.filters[props.resourceType].categories.map(
- (category, index) => {
- return (
-
- {category.header}
- {category.type == 0 ? (
-
- ) : (
-
- )}
-
- );
- }
- )}
+ {filters[resourceType].categories.map((category, index) => {
+ return (
+
+ {category.header}
+ {category.type == 0 ? (
+
+ ) : (
+
+ )}
+
+ );
+ })}
{
+ const dispatch = useDispatch();
+ const isMobile = useIsMobile();
+ const idRequired = useSelector(
+ state => state.filterMarkers.foodFilters.idRequired
+ );
+ const kidOnly = useSelector(state => state.filterMarkers.foodFilters.kidOnly);
+ const openNow = useSelector(state => state.filterMarkers.foodFilters.openNow);
+ const accessTypesHidden = useSelector(
+ state => state.filterMarkers.foodFilters.accessTypesHidden
+ );
+
+ const handleChange = event => {
if (event.target.id === 'idRequired') {
- this.props.setToggleStateFood('idRequired', !this.props.idRequired);
+ dispatch(setToggleStateFood('idRequired', !idRequired));
} else if (event.target.id === 'kidOnly') {
- this.props.setToggleStateFood('kidOnly', !this.props.kidOnly);
+ dispatch(setToggleStateFood('kidOnly', !kidOnly));
} else if (event.target.id === 'openNow') {
- this.props.setToggleStateFood('openNow', !this.props.openNow);
+ dispatch(setToggleStateFood('openNow', !openNow));
}
- }
-
- render() {
- return (
-
-
- {/* // Legend button filters for tap type */}
-
-
-
-
- this.props.setFilteredFoodTypes('Food Site')
- }
- >
- OTHER
-
-
-
-
- this.props.setFilteredFoodTypes('School')}
- >
- SCHOOL
-
-
-
-
-
- this.props.setFilteredFoodTypes('Charter School')
- }
- >
- RECREATION
-
-
-
-
-
- this.props.setFilteredFoodTypes('PHA Community Center')
- }
- >
- CONGREGATION
-
-
-
-
-
- {/* Toggle Switches */}
-
-
- Open Now
- this.handleChange(e)}
- readOnly
- />
-
+ };
-
- ID Required
- this.handleChange(e)}
- readOnly
+ return (
+
+
+ {/* // Legend button filters for tap type */}
+
+
+
+ dispatch(setFilteredFoodTypes('Food Site'))}
+ >
+ OTHER
+
-
+
+
+
+ dispatch(setFilteredFoodTypes('School'))}
+ >
+ SCHOOL
+
+
+
+
+
+ dispatch(setFilteredFoodTypes('Charter School'))
+ }
+ >
+ RECREATION
+
+
+
+
+
+ dispatch(setFilteredFoodTypes('PHA Community Center'))
+ }
+ >
+ CONGREGATION
+
+
+
+
-
- Kids Only
- this.handleChange(e)}
- readOnly
- />
-
-
-
+ {/* Toggle Switches */}
+
+
+ Open Now
+ handleChange(e)}
+ readOnly
+ />
+
-
- this.props.resetFilterFunction()}
- >
- Reset
-
-
-
-
- }
- >
-
-
-
-
- );
- }
-}
+
+ ID Required
+ handleChange(e)}
+ readOnly
+ />
+
-const mapStateToProps = state => ({
- idRequired: state.filterMarkers.foodFilters.idRequired,
- kidOnly: state.filterMarkers.foodFilters.kidOnly,
- openNow: state.filterMarkers.foodFilters.openNow,
- accessTypesHidden: state.filterMarkers.foodFilters.accessTypesHidden,
- showingInfoWindow: state.filterMarkers.showingInfoWindow
-});
+
+ Kids Only
+ handleChange(e)}
+ readOnly
+ />
+
+
+
-const mapDispatchToProps = {
- setFilteredFoodTypes,
- setToggleStateFood,
- resetFilterFunction
+
+ dispatch(resetFilterFunction())}
+ >
+ Reset
+
+
+
+
+ }
+ >
+
+
+
+
+ );
};
-export default connect(mapStateToProps, mapDispatchToProps)(FoodFilter);
+export default FoodFilter;
diff --git a/src/components/Foot/Foot.js b/src/components/Foot/Foot.js
deleted file mode 100644
index 709ebe76..00000000
--- a/src/components/Foot/Foot.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import React, { Component } from 'react';
-import ClosestTap from '../ClosestTap/ClosestTap';
-export class Foot extends Component {
- render() {
- return (
-
-
-
- );
- }
-}
-
-export default Foot;
diff --git a/src/components/Foot/Foot.test.js b/src/components/Foot/Foot.test.js
deleted file mode 100644
index 141e73de..00000000
--- a/src/components/Foot/Foot.test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { Foot } from './Foot';
-
-it('renders without crashing', () => {
- const div = document.createElement('div');
- ReactDOM.render( , div);
- ReactDOM.unmountComponentAtNode(div);
-});
diff --git a/src/components/Head/Head.js b/src/components/Head/Head.js
index 3e14760b..2b70e610 100644
--- a/src/components/Head/Head.js
+++ b/src/components/Head/Head.js
@@ -1,33 +1,13 @@
-import React from 'react';
-import {
- ListItemIcon,
- styled,
-} from '@mui/material';
-import { isMobile } from 'react-device-detect';
import About from '../Pages/About.js';
import Contact from '../Pages/Contact.js';
import Join from '../Pages/Join.js';
import MobileHead from '../MobileHead/MobileHead';
import { HeaderProvider } from '../../contexts/HeaderContext'; // Import the HeaderContext component
-import { DesktopHead } from '../DesktopHead/DesktopHead'
-
-
-
-const NavIcon = styled(ListItemIcon)(({ theme }) => ({
- width: '30px',
- height: '30px',
- '& svg': {
- width: '30px',
- height: 'auto'
- }
-}));
+import { DesktopHead } from '../DesktopHead/DesktopHead';
+import useIsMobile from 'hooks/useIsMobile.js';
export default function Head(props) {
-
- const pagePaths = /(\/mission)|(\/share)|(\/project)|(\/contribute)/;
- const isNotMapPage = () => {
- return window.location.pathname.match(pagePaths);
- };
+ const isMobile = useIsMobile();
let page = null;
switch (HeaderProvider.shownPage) {
@@ -44,17 +24,10 @@ export default function Head(props) {
break;
}
-
return (
<>
-
-
-
- {isMobile ? ( ) : ( )}
+ {isMobile ? : }
>
);
diff --git a/src/components/Legend/Legend.css b/src/components/Legend/Legend.css
deleted file mode 100644
index 5e8b3ade..00000000
--- a/src/components/Legend/Legend.css
+++ /dev/null
@@ -1,13 +0,0 @@
-.filterCard {
- width: 170px;
-}
-
-.tapIcon {
- position: relative;
- top: -10px;
- margin: 5px;
-}
-
-.tapName {
- font-size: 12px;
-}
diff --git a/src/components/Legend/Legend.js b/src/components/Legend/Legend.js
deleted file mode 100644
index 6501804f..00000000
--- a/src/components/Legend/Legend.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import React from 'react';
-import { Card, Container, Row, OverlayTrigger, Tooltip } from 'react-bootstrap';
-import './Legend.css';
-
-export function Legend() {
- return (
-
-
- Legend
-
-
- These taps are maintained by the City or publicly-oriented
- enterprise
-
- }
- >
-
-
-
this.props.legendButton('Public')}
- >
-
- Public Tap
-
-
-
- Taps located in private enterprises that have either explicitly
- granted public access or function as a de-facto public space
-
- }
- >
-
- this.props.legendButton('Private-Shared')}
- >
- Private-Shared Tap
-
-
-
- These taps are located in private businesses; public access is
- not guaranteed
-
- }
- >
-
- this.props.legendButton('Private')}
- >
- Private Tap
-
-
-
- These taps are restricted from public use
-
- }
- >
-
-
-
this.props.legendButton('Restricted')}
- >
-
- Restricted Tap
-
-
-
-
-
- );
-}
-export default Legend;
diff --git a/src/components/MapPage/MapPage.js b/src/components/MapPage/MapPage.js
index 2f708d86..db0714f6 100644
--- a/src/components/MapPage/MapPage.js
+++ b/src/components/MapPage/MapPage.js
@@ -1,57 +1,14 @@
-import React, { Component } from 'react';
+import { useState } from 'react';
import ReactGoogleMaps from '../ReactGoogleMaps/ReactGoogleMaps';
import styles from './MapPage.module.scss';
-
-export class MapPage extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- ada: false,
- filtered: false,
- tapsDisplayed: ['Public', 'Private', 'Private-Shared', 'Restricted']
- };
-
- this.legendButton = this.legendButton.bind(this);
- this.toggleSwitch = this.toggleSwitch.bind(this);
- }
-
- //Toggles whether color is in the tapsDisplayed array or not
- legendButton(color) {
- const taps = [...this.state.tapsDisplayed];
-
- if (taps.includes(color)) {
- //removes color form tapsDisplayed array
- const filteredTaps = taps.filter(item => item !== color);
- this.setState({ tapsDisplayed: filteredTaps });
- } else {
- //adds color to tapsDisplayed array
- this.setState({ tapsDisplayed: [...this.state.tapsDisplayed, color] });
- }
- }
-
- toggleSwitch(type, position) {
- switch (type) {
- case 'ada':
- return this.setState({ ada: position });
- case 'filtered':
- return this.setState({ filtered: position });
- default:
- return this.state;
- }
- }
-
- render() {
- return (
-
-
-
- );
- }
-}
+import useIsMobile from 'hooks/useIsMobile';
+
+export const MapPage = () => {
+ return (
+
+
+
+ );
+};
export default MapPage;
diff --git a/src/components/MobileHead/MobileHead.jsx b/src/components/MobileHead/MobileHead.jsx
index 36cc991d..0e811592 100644
--- a/src/components/MobileHead/MobileHead.jsx
+++ b/src/components/MobileHead/MobileHead.jsx
@@ -1,11 +1,5 @@
import React from 'react';
-import {
- Button,
- Collapse,
- IconButton,
- Box,
- Paper
-} from '@mui/material';
+import { Button, Collapse, IconButton, Box, Paper } from '@mui/material';
import CloseIcon from '../../components/icons/CloseIcon';
import DropLink from '../../components/Buttons/DropLink';
import { HeaderContext } from '../../contexts/HeaderContext';
@@ -16,175 +10,177 @@ import { ReactComponent as PhlaskNoTextIcon } from '../../components/icons/Phlas
import { ReactComponent as UsersIcon } from '../../components/icons/UsersIcon.svg';
import { ReactComponent as IDIcon } from '../../components/icons/ModalIDRequired.svg';
import {
- setToolbarModal,
- TOOLBAR_MODAL_CONTRIBUTE,
- TOOLBAR_MODAL_FILTER,
- TOOLBAR_MODAL_NONE,
- TOOLBAR_MODAL_RESOURCE,
- TOOLBAR_MODAL_SEARCH,
+ setToolbarModal,
+ TOOLBAR_MODAL_FILTER,
+ TOOLBAR_MODAL_NONE,
+ TOOLBAR_MODAL_SEARCH
} from '../../actions/actions';
-import { connect } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
+
+function MobileHead() {
+ const dispatch = useDispatch();
+ const headerContext = React.useContext(HeaderContext);
+ const {
+ setShowMapControls,
+ shownPage,
+ menuClicked,
+ toggleMenuExpand,
+ menuExpand,
+ pageExpand,
+ verticalAnimFinished1,
+ verticalAnimFinished2,
+ setVerticalAnimFinished1,
+ setVerticalAnimFinished2
+ } = headerContext;
-function MobileHead(props) {
- const headerContext = React.useContext(HeaderContext);
- const {
- setShowMapControls,
- shownPage,
- menuClicked,
- toggleMenuExpand,
- menuExpand,
- pageExpand,
- verticalAnimFinished1,
- verticalAnimFinished2,
- setVerticalAnimFinished1,
- setVerticalAnimFinished2,
- } = headerContext;
- return (
- state.filterMarkers.toolbarModal);
+ return (
+
+
+
+
-
+
+
+
+ setShowMapControls(true)}
+ >
+
+
+
+ {
+ dispatch(
+ setToolbarModal(
+ toolbarModal !== TOOLBAR_MODAL_SEARCH
+ ? TOOLBAR_MODAL_SEARCH
+ : TOOLBAR_MODAL_NONE
+ )
+ );
+ }}
+ >
+
+
+ {
+ if (toolbarModal != TOOLBAR_MODAL_FILTER) {
+ dispatch(
+ setToolbarModal({
+ toolbarModal: TOOLBAR_MODAL_FILTER
+ })
+ );
+ } else {
+ dispatch(
+ setToolbarModal({ toolbarModal: TOOLBAR_MODAL_NONE })
+ );
+ }
}}
+ >
+
+
+
+
+ {
+ if (pageExpand) {
+ setVerticalAnimFinished1(true);
+ }
+ }}
+ >
+
+
+
+
-
-
-
-
-
- setShowMapControls(true)}
- >
-
-
-
- {
- if (props.toolbarModal != TOOLBAR_MODAL_SEARCH) {
- props.setToolbarModal(TOOLBAR_MODAL_SEARCH);
- } else {
- props.setToolbarModal(TOOLBAR_MODAL_NONE);
- }
- }}>
-
-
- {
- if (props.toolbarModal != TOOLBAR_MODAL_FILTER) {
- props.setToolbarModal(TOOLBAR_MODAL_FILTER);
- } else {
- props.setToolbarModal(TOOLBAR_MODAL_NONE);
- }
- }}
- >
-
-
-
-
- {
- if (pageExpand) {
- setVerticalAnimFinished1(true);
- }
- }}
- >
-
-
-
-
- menuClicked('about')}
- startIcon={ }
- >
- About Phlask
-
- menuClicked('join')}
- startIcon={ }
- >
- Join the team
-
- menuClicked('contact')}
- startIcon={ }
- >
- Contact
-
-
-
- {
- if (pageExpand) {
- setVerticalAnimFinished2(true);
- }
- }}
- >
-
-
-
-
-
- {verticalAnimFinished1 && verticalAnimFinished2 && shownPage}
-
-
-
+ menuClicked('about')}
+ startIcon={ }
+ >
+ About Phlask
+
+ menuClicked('join')}
+ startIcon={ }
+ >
+ Join the team
+
+ menuClicked('contact')}
+ startIcon={ }
+ >
+ Contact
+
+
+
+ {
+ if (pageExpand) {
+ setVerticalAnimFinished2(true);
+ }
+ }}
+ >
+
+
- );
+
+
+ {verticalAnimFinished1 && verticalAnimFinished2 && shownPage}
+
+
+
+
+ );
}
-const mapStateToProps = state => ({
- toolbarModal: state.filterMarkers.toolbarModal
-});
-
-const mapDispatchToProps = {
- TOOLBAR_MODAL_CONTRIBUTE,
- TOOLBAR_MODAL_FILTER,
- TOOLBAR_MODAL_RESOURCE,
- TOOLBAR_MODAL_SEARCH,
- TOOLBAR_MODAL_NONE,
- setToolbarModal
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(MobileHead);
+export default MobileHead;
diff --git a/src/components/ReactGoogleMaps/ReactGoogleMaps.js b/src/components/ReactGoogleMaps/ReactGoogleMaps.js
index ac34fc73..7a68f684 100644
--- a/src/components/ReactGoogleMaps/ReactGoogleMaps.js
+++ b/src/components/ReactGoogleMaps/ReactGoogleMaps.js
@@ -1,17 +1,11 @@
import { Fade } from '@mui/material';
import { GoogleApiWrapper, Map, Marker } from 'google-maps-react';
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
+import { useEffect, useState } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
import ReactTouchEvents from 'react-touch-events';
import {
- TOOLBAR_MODAL_CONTRIBUTE,
- TOOLBAR_MODAL_FILTER,
- TOOLBAR_MODAL_NONE,
- TOOLBAR_MODAL_RESOURCE,
TOOLBAR_MODAL_SEARCH,
- setFilterFunction,
setMapCenter,
- setToolbarModal,
setUserLocation,
toggleInfoWindow,
getResources,
@@ -20,10 +14,7 @@ import {
import SearchBar from '../SearchBar/SearchBar';
import SelectedTap from '../SelectedTap/SelectedTap';
import styles from './ReactGoogleMaps.module.scss';
-// import Legend from "./Legend";
-// Temporary Food/Water Toggle
import Stack from '@mui/material/Stack';
-import { isMobile } from 'react-device-detect';
import AddResourceModalV2 from '../AddResourceModal/AddResourceModalV2';
import ChooseResource from '../ChooseResource/ChooseResource';
import TutorialModal from '../TutorialModal/TutorialModal';
@@ -31,6 +22,8 @@ import Filter from '../Filter/Filter';
import Toolbar from '../Toolbar/Toolbar';
import phlaskMarkerIconV2 from '../icons/PhlaskMarkerIconV2';
import selectFilteredResource from '../../selectors/waterSelectors';
+import useIsMobile from 'hooks/useIsMobile';
+import { CITY_HALL_COORDINATES } from 'constants/defaults';
function getCoordinates() {
return new Promise(function (resolve, reject) {
@@ -38,7 +31,7 @@ function getCoordinates() {
});
}
-const LoadingContainer = props =>
Looking for water!
;
+const LoadingContainer = () => Looking for water!
;
const style = {
width: '100%',
@@ -156,272 +149,230 @@ for (const [key, value] of Object.entries(filters)) {
noActiveFilterTags[key] = data;
}
-export class ReactGoogleMaps extends Component {
- constructor(props) {
- super(props);
+export const ReactGoogleMaps = ({ google }) => {
+ const dispatch = useDispatch();
+ const isMobile = useIsMobile();
+ const allResources = useSelector(state => state.filterMarkers.allResources);
+ const filteredResources = useSelector(state => selectFilteredResource(state));
- this.state = {
- isExpanded: false,
- activeMarker: {},
- currlat: this.props.mapCenter.lat, // 39.9528,
- currlon: this.props.mapCenter.lng, //-75.1635,
- closestTap: {},
- taps: [],
- tapsLoaded: false,
- zoom: 16,
- searchedTap: null,
- anchor: false,
- map: null,
- activeFilterTags: JSON.parse(JSON.stringify(noActiveFilterTags)),
- appliedFilterTags: JSON.parse(JSON.stringify(noActiveFilterTags))
- };
- this.toggleDrawer = this.toggleDrawer.bind(this);
- }
+ const mapCenter = useSelector(state => state.filterMarkers.mapCenter);
+ const resourceType = useSelector(state => state.filterMarkers.resourceType);
+ const showingInfoWindow = useSelector(
+ state => state.filterMarkers.showingInfoWindow
+ );
+ const toolbarModal = useSelector(state => state.filterMarkers.toolbarModal);
- componentDidMount() {
- // Fetch and load the resources
- if (!this.props.allResources.length && this.props.getResources) {
- this.props.getResources();
+ const [currentLat, setCurrentLat] = useState(mapCenter.lat);
+ const [currentLon, setCurrentLon] = useState(mapCenter.lng);
+ const [zoom, setZoom] = useState(16);
+ const [searchedTap, setSearchedTap] = useState(null);
+ const [map, setMap] = useState(null);
+ const [activeFilterTags, setActiveFilterTags] = useState(
+ JSON.parse(JSON.stringify(noActiveFilterTags))
+ );
+ const [appliedFilterTags, setAppliedFilterTags] = useState(
+ JSON.parse(JSON.stringify(noActiveFilterTags))
+ );
+
+ useEffect(() => {
+ if (!allResources.length) {
+ dispatch(getResources());
}
+ }, [allResources.length, dispatch]);
- getCoordinates().then(
- position => {
+ useEffect(() => {
+ const setDefaultLocation = () => {
+ setCurrentLat(parseFloat(CITY_HALL_COORDINATES.latitude));
+ setCurrentLon(parseFloat(CITY_HALL_COORDINATES.longitude));
+ };
+ getCoordinates()
+ .then(position => {
if (
- isNaN(position.coords.latitude) ||
- isNaN(position.coords.longitude)
+ Number.isNaN(position.coords.latitude) ||
+ Number.isNaN(position.coords.longitude)
) {
- this.setState({
- currlat: parseFloat('39.952744'),
- currlon: parseFloat('-75.163500')
- });
+ setDefaultLocation();
} else {
- this.props.setMapCenter({
- lat: position.coords.latitude,
- lng: position.coords.longitude
- });
- this.props.setUserLocation({
- lat: position.coords.latitude,
- lng: position.coords.longitude
- });
- this.setState({
- currlat: position.coords.latitude,
- currlon: position.coords.longitude
- });
+ dispatch(
+ setMapCenter({
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ })
+ );
+ dispatch(
+ setUserLocation({
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ })
+ );
+ setCurrentLat(position.coords.latitude);
+ setCurrentLon(position.coords.longitude);
}
- },
- () => {}
- );
- }
+ })
+ .catch(() => {
+ setDefaultLocation();
+ });
+ }, [dispatch]);
//toggle window goes here
- onMarkerClick = (resource, markerProps) => {
- this.props.toggleInfoWindow(true);
- this.props.setSelectedPlace(resource);
+ const onMarkerClick = (resource, markerProps) => {
+ dispatch(
+ toggleInfoWindow({
+ isShown: true,
+ infoWindowClass: isMobile ? 'info-window-in' : 'info-window-in-desktop'
+ })
+ );
+ dispatch(setSelectedPlace(resource));
markerProps.map.panTo({ lat: resource.latitude, lng: resource.longitude });
};
- onReady = (_, map) => {
- this.setState({ map: map });
+ const onReady = (_, map) => {
+ setMap(map);
};
- searchForLocation = location => {
- this.setState({
- currlat: location.lat,
- currlon: location.lng,
- zoom: 16,
- searchedTap: { lat: location.lat, lng: location.lng }
- });
+ const searchForLocation = location => {
+ setCurrentLat(location.lat);
+ setCurrentLon(location.lng);
+ setZoom(16);
+ setSearchedTap({ lat: location.lat, lng: location.lng });
};
- handleTap = e => {
- if (
- e.target instanceof HTMLDivElement &&
- this.props.showingInfoWindow &&
- !isMobile
- ) {
- this.props.toggleInfoWindow(false);
+ const handleTap = e => {
+ if (e.target instanceof HTMLDivElement && showingInfoWindow && !isMobile) {
+ dispatch(
+ toggleInfoWindow({
+ isShown: false,
+ infoWindowClass: isMobile
+ ? 'info-window-in'
+ : 'info-window-in-desktop'
+ })
+ );
}
};
- toggleDrawer = () => event => {
- if (
- event.type === 'keydown' &&
- (event.key === 'Tab' || event.key === 'Shift')
- ) {
- return;
- }
-
- this.setState(prevState => ({
- anchor: !prevState.anchor
- }));
- };
-
- handleTag = (type, filterType, index, key) => {
+ const handleTag = (type, filterType, index, key) => {
if (type == 0) {
- let activeFilterTags_ = this.state.activeFilterTags;
+ let activeFilterTags_ = { ...activeFilterTags };
activeFilterTags_[filterType][index][key] =
!activeFilterTags_[filterType][index][key];
- this.setState({ activeFilterTags: activeFilterTags_ });
+ setActiveFilterTags(activeFilterTags_);
} else if (type == 1) {
- let activeFilterTags_ = this.state.activeFilterTags;
+ let activeFilterTags_ = { ...activeFilterTags };
if (activeFilterTags_[filterType][index] == key) {
activeFilterTags_[filterType][index] = null;
} else {
- activeFilterTags_[filterType][index] = key;
+ activeFilterTags_[filterType][index] = { ...key };
}
- this.setState({
- activeFilterTags: activeFilterTags_
- });
+ setActiveFilterTags(activeFilterTags_);
}
};
- clearAllTags = () => {
- this.setState({
- activeFilterTags: JSON.parse(JSON.stringify(noActiveFilterTags))
- });
+ const clearAllTags = () => {
+ setActiveFilterTags(JSON.parse(JSON.stringify(noActiveFilterTags)));
};
- applyTags = () => {
- this.setState({
- appliedFilterTags: JSON.parse(JSON.stringify(this.state.activeFilterTags))
- });
+ const applyTags = () => {
+ setAppliedFilterTags(JSON.parse(JSON.stringify(activeFilterTags)));
};
- render() {
- return (
-
-
-
-
- {this.props.filteredResources.map((resource, index) => {
- return (
- {
- this.onMarkerClick(resource, markerProps);
- }}
- position={{
- lat: resource.latitude,
- lng: resource.longitude
- }}
- icon={{
- url: phlaskMarkerIconV2(resource.resource_type, 56, 56)
- }}
- // This is used for marker targetting as we are unable to add custom properties with this library.
- // We should eventually replace this so that we can still enable the use of screen readers in the future.
- title={`data-cy-${index}`}
- />
- );
- })}
-
- {this.state.searchedTap != null && (
+ return (
+
+
+
+
+ {filteredResources.map((resource, index) => {
+ return (
{
+ onMarkerClick(resource, markerProps);
+ }}
+ position={{
+ lat: resource.latitude,
+ lng: resource.longitude
+ }}
+ icon={{
+ url: phlaskMarkerIconV2(resource.resource_type, 56, 56)
+ }}
+ // This is used for marker targeting as we are unable to add custom properties with this library.
+ // We should eventually replace this so that we can still enable the use of screen readers in the future.
+ title={`data-cy-${index}`}
/>
- )}
-
-
-
- {isMobile && (
-
-
-
- )}
-
-
- this.searchForLocation(location)}
- />
-
-
-
-
+ )}
+
+
+
+ {isMobile && (
+
+
+
+ )}
+
+
+ searchForLocation(location)}
/>
-
-
+
-
-
- );
- }
-}
-
-const mapStateToProps = state => ({
- filtered: state.filterMarkers.filtered,
- handicap: state.filterMarkers.handicap,
- allResources: state.filterMarkers.allResources,
- filteredResources: selectFilteredResource(state),
- filterFunction: state.filterMarkers.filterFunction,
- mapCenter: state.filterMarkers.mapCenter,
- resourceType: state.filterMarkers.resourceType,
- showingInfoWindow: state.filterMarkers.showingInfoWindow,
- toolbarModal: state.filterMarkers.toolbarModal
- // infoIsExpanded: state.infoIsExpanded
-});
-
-const mapDispatchToProps = {
- setFilterFunction,
- toggleInfoWindow,
- setUserLocation,
- setMapCenter,
- TOOLBAR_MODAL_CONTRIBUTE,
- TOOLBAR_MODAL_FILTER,
- TOOLBAR_MODAL_NONE,
- TOOLBAR_MODAL_RESOURCE,
- TOOLBAR_MODAL_SEARCH,
- setToolbarModal,
- setSelectedPlace,
- getResources
+
+
+
+
+
+
+
+ );
};
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(
- GoogleApiWrapper({
- apiKey: 'AIzaSyABw5Fg78SgvedyHr8tl-tPjcn5iFotB6I',
- LoadingContainer: LoadingContainer,
- version: 'quarterly'
- })(ReactGoogleMaps)
-);
+export default GoogleApiWrapper({
+ apiKey: 'AIzaSyABw5Fg78SgvedyHr8tl-tPjcn5iFotB6I',
+ LoadingContainer: LoadingContainer,
+ version: 'quarterly'
+})(ReactGoogleMaps);
diff --git a/src/components/SearchBar/SearchBar.js b/src/components/SearchBar/SearchBar.js
index 3a329669..3705fd02 100644
--- a/src/components/SearchBar/SearchBar.js
+++ b/src/components/SearchBar/SearchBar.js
@@ -1,66 +1,41 @@
import SearchIcon from '@mui/icons-material/Search';
import Input from '@mui/material/Input';
import InputAdornment from '@mui/material/InputAdornment';
-import React from 'react';
-import { isMobile } from 'react-device-detect';
+import { useRef, useState } from 'react';
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from 'react-places-autocomplete';
-import { connect } from 'react-redux';
-import {
- TOOLBAR_MODAL_CONTRIBUTE,
- TOOLBAR_MODAL_FILTER,
- TOOLBAR_MODAL_NONE,
- TOOLBAR_MODAL_RESOURCE,
- TOOLBAR_MODAL_SEARCH,
- setToolbarModal
-} from '../../actions/actions';
+import { TOOLBAR_MODAL_SEARCH, setToolbarModal } from '../../actions/actions';
import styles from './SearchBar.module.scss';
+import useIsMobile from 'hooks/useIsMobile';
+import noop from 'utils/noop';
+import { useSelector } from 'react-redux/es/exports';
-class SearchBar extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- address: '',
- refSearchBar: React.createRef()
- };
- }
+const SearchBar = ({ search }) => {
+ const refSearchBar = useRef();
+ const [address, setAddress] = useState('');
+ const toolbarModal = useSelector(state => state.filterMarkers.toolbarModal);
+ const isMobile = useIsMobile();
- handleChange = address => {
- this.setState({ address });
- };
-
- handleSelect = address => {
- this.setState({ address });
+ const handleSelect = address => {
+ setAddress(address);
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
- .then(latLng => this.props.search(latLng))
- .catch(error => console.error('Error', error));
+ .then(latLng => search(latLng))
+ .catch(noop);
};
- componentDidUpdate(prevProps) {
- // if (this.props.isSearchShown && !prevProps.isSearchShown) {
- // this.state.refSearchBar.current.focus();
- // }
- }
-
- componentDidMount() {
- // this.setSearchDisplayType();
- }
-
- render() {
- return (
- <>
- {this.props.toolbarModal == TOOLBAR_MODAL_SEARCH && (<>
+ return (
+ <>
+ {toolbarModal == TOOLBAR_MODAL_SEARCH && (
+ <>
{!isMobile ? (
-
+
{({
getInputProps,
@@ -70,7 +45,9 @@ class SearchBar extends React.Component {
}) => (
0 ? styles.hasDropdown : ''
+ loading || suggestions.length > 0
+ ? styles.hasDropdown
+ : ''
}`}
>
{/* type="search" is only HTML5 compliant */}
@@ -80,7 +57,7 @@ class SearchBar extends React.Component {
})}
className={`${styles.searchInput} form-control`}
type="search"
- ref={this.state.refSearchBar}
+ ref={refSearchBar}
startAdornment={
@@ -118,9 +95,9 @@ class SearchBar extends React.Component {
) : (
{({
getInputProps,
@@ -130,17 +107,18 @@ class SearchBar extends React.Component {
}) => (
0 ? styles.hasDropdown : ''
+ loading || suggestions.length > 0
+ ? styles.hasDropdown
+ : ''
}`}
>
- {/* type="search" is only HTML5 compliant */}
@@ -176,24 +154,11 @@ class SearchBar extends React.Component {
)}
- )}>
- )}
- >
- );
- }
-}
-
-const mapStateToProps = state => ({
- toolbarModal: state.filterMarkers.toolbarModal
-});
-
-const mapDispatchToProps = {
- TOOLBAR_MODAL_CONTRIBUTE,
- TOOLBAR_MODAL_FILTER,
- TOOLBAR_MODAL_RESOURCE,
- TOOLBAR_MODAL_SEARCH,
- TOOLBAR_MODAL_NONE,
- setToolbarModal
+ )}
+ >
+ )}
+ >
+ );
};
-export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
+export default SearchBar;
diff --git a/src/components/SelectedTap/SelectedTap.js b/src/components/SelectedTap/SelectedTap.js
index 57bab8b1..64fb44ea 100644
--- a/src/components/SelectedTap/SelectedTap.js
+++ b/src/components/SelectedTap/SelectedTap.js
@@ -1,63 +1,63 @@
-/* eslint-disable no-console */
-import React from 'react';
-import { isMobile } from 'react-device-detect';
+import { useCallback, useEffect, useRef, useState } from 'react';
import ReactGA from 'react-ga4';
-import { connect } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import {
toggleInfoExpanded,
toggleInfoWindow,
toggleInfoWindowClass
} from '../../actions/actions';
import SelectedTapHours from '../SelectedTapHours/SelectedTapHours';
-import SelectedTapIcons from '../SelectedTapIcons/SelectedTapIcons';
+
import sampleImg from '../images/phlask-tessellation.png';
import sampleImg2x from '../images/phlask-tessellation@2x.png';
-import phlaskBlue from '../images/phlaskBlue.png';
-import phlaskGreen from '../images/phlaskGreen.png';
import './SelectedTap.css';
-import styles from './SelectedTap.module.scss';
import { Paper, SwipeableDrawer } from '@mui/material';
-import IconButton from '@mui/material/IconButton';
import SelectedTapDetails from '../SelectedTapMobile/SelectedTapDetails';
-import { withStyles } from '@mui/styles';
-import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
-import IosShareIcon from '@mui/icons-material/IosShare';
-import CloseIcon from '@mui/icons-material/Close';
-import { WATER_RESOURCE_TYPE } from '../../types/ResourceEntry';
+import useIsMobile from 'hooks/useIsMobile';
const tempImages = {
tapImg: sampleImg,
tapImg2x: sampleImg2x
};
-class SelectedTap extends React.Component {
- refSelectedTap = React.createRef();
- refContentArea = React.createRef();
-
- state = {
- previewHeight: 0,
- infoExpansionStyle: {},
- isDescriptionShown: false,
- tapNormsAndRules: null,
- animationSpeed: 600,
- testIcons: {
- access: phlaskBlue,
- accessibility: phlaskGreen
- },
- walkingDuration: 0,
- walkingDistance: 0,
- infoCollapseMobile: false
- };
-
- getWalkingDurationAndTimes = () => {
- if (!this.props.selectedPlace) return;
+const SelectedTap = () => {
+ const dispatch = useDispatch();
+ const refSelectedTap = useRef();
+ const isMobile = useIsMobile();
+
+ const [previewHeight, setPreviewHeight] = useState(0);
+ const [infoExpansionStyle, setInfoExpansionStyle] = useState({});
+ const [isDescriptionShown, setIsDescriptionShown] = useState(false);
+
+ const [walkingDuration, setWalkingDuration] = useState(0);
+ const [infoCollapseMobile, setInfoCollapseMobile] = useState(false);
+
+ const showingInfoWindow = useSelector(
+ state => state.filterMarkers.showingInfoWindow
+ );
+ const infoIsExpanded = useSelector(
+ state => state.filterMarkers.infoIsExpanded
+ );
+
+ const selectedPlace = useSelector(state => state.filterMarkers.selectedPlace);
+ const resourceType = useSelector(state => state.filterMarkers.resourceType);
+ const userLocation = useSelector(state => state.filterMarkers.userLocation);
+
+ const getWalkingDurationAndTimes = useCallback(() => {
+ if (
+ !selectedPlace?.latitude ||
+ !selectedPlace?.longitude ||
+ !userLocation?.lat ||
+ !userLocation?.lng
+ )
+ return;
const orsAPIKey =
'5b3ce3597851110001cf6248ac903cdbe0364ca9850aa85cb64d8dfc';
- fetch(`https://api.openrouteservice.org/v2/directions/foot-walking?api_key=${orsAPIKey}&start=${this.props.userLocation.lng},
- ${this.props.userLocation?.lat}&end=${this.props.selectedPlace?.longitude},${this.props.selectedPlace?.latitude}`)
+ fetch(`https://api.openrouteservice.org/v2/directions/foot-walking?api_key=${orsAPIKey}&start=${userLocation?.lng},
+ ${userLocation?.lat}&end=${selectedPlace?.longitude},${selectedPlace?.latitude}`)
.then(response => response.json())
.then(data => {
if (!data.features) return;
@@ -69,227 +69,179 @@ class SelectedTap extends React.Component {
let distance = (
data.features[0].properties.summary.distance * 0.00062
).toFixed(1);
- this.setState({
- walkingDuration: duration,
- walkingDistance: distance
- });
- });
- };
- toggleInfoExpanded(shouldExpand) {
+ setWalkingDuration(duration);
+ });
+ }, [
+ selectedPlace?.latitude,
+ selectedPlace?.longitude,
+ userLocation?.lat,
+ userLocation?.lng
+ ]);
+
+ const handleToggleInfoExpanded = shouldExpand => {
if (!shouldExpand) {
// Start animation before unmounting description
setTimeout(() => {
- this.setState({
- isDescriptionShown: shouldExpand
- });
- }, this.state.animationSpeed);
+ setIsDescriptionShown(shouldExpand);
+ }, 600);
// Expand or Collapse
- this.animateInfoExpansion(shouldExpand);
+ animateInfoExpansion(shouldExpand);
// Close if in preview mode
- if (!this.props.infoIsExpanded) {
- this.toggleInfoWindow(false);
+ if (infoIsExpanded) {
+ handleToggleInfoWindow({
+ isShown: false,
+ infoWindowClass: isMobile
+ ? 'info-window-out'
+ : 'info-window-out-desktop'
+ });
}
} else {
// Set height on first render to animate expansion
- if (Object.keys(this.state.infoExpansionStyle).length < 1) {
- this.setState(
- {
- infoExpansionStyle: {
- height: this.state.previewHeight
- }
- },
- () => {
- this.setState(
- {
- isDescriptionShown: shouldExpand
- },
- () => {
- // Collapse
- this.animateInfoExpansion(shouldExpand);
- }
- );
- }
- );
+ if (Object.keys(infoExpansionStyle).length < 1) {
+ setInfoExpansionStyle({
+ height: previewHeight
+ });
+
+ setIsDescriptionShown(shouldExpand);
+
+ animateInfoExpansion(shouldExpand);
} else {
- this.setState(
- {
- isDescriptionShown: shouldExpand
- },
- () => {
- // Expand
- this.animateInfoExpansion(shouldExpand);
- }
- );
+ setIsDescriptionShown(shouldExpand);
+
+ animateInfoExpansion(shouldExpand);
}
}
- }
-
- setInfoCollapseMobile = collapse => {
- this.setState({ infoCollapseMobile: collapse });
};
- toggleInfoWindow(shouldShow) {
- this.props.toggleInfoWindowClass(shouldShow);
+ const handleToggleInfoWindow = isShown => {
+ let infoWindowClass = 'info-window-';
+ infoWindowClass += isShown ? 'in' : 'out';
+ if (!isMobile) infoWindowClass += '-desktop';
+
+ dispatch(
+ toggleInfoWindowClass({
+ isShown,
+ infoWindowClass
+ })
+ );
// Animate in
- if (shouldShow) {
- this.props.toggleInfoWindow(shouldShow);
+ if (isShown) {
+ dispatch(
+ toggleInfoWindow({
+ isShown,
+ infoWindowClass: isMobile
+ ? 'info-window-out'
+ : 'info-window-out-desktop'
+ })
+ );
}
// Animate Out
else {
- this.props.toggleInfoWindow(false);
- this.setInfoCollapseMobile(false);
+ dispatch(
+ toggleInfoWindow({
+ isShown: false,
+ infoWindowClass: isMobile
+ ? 'info-window-out'
+ : 'info-window-out-desktop'
+ })
+ );
+ setInfoCollapseMobile(false);
}
- }
-
- animateInfoExpansion(shouldExpand) {
- this.setState(
- {
- infoExpansionStyle: {
- height: shouldExpand ? '80%' : this.state.previewHeight
- }
- },
- () => {
- this.props.toggleInfoExpanded(shouldExpand);
- }
- );
- }
+ };
- handleSwipe(direction) {
- if (direction === 'top') {
- this.toggleInfoExpanded(true);
- } else if (direction === 'bottom') {
- this.toggleInfoExpanded(false);
- }
- }
+ const animateInfoExpansion = shouldExpand => {
+ setInfoExpansionStyle({ height: shouldExpand ? '80%' : previewHeight });
+ dispatch(toggleInfoExpanded(shouldExpand));
+ };
- handleGA() {
+ const handleGA = useCallback(() => {
ReactGA.event({
- category: `Tap - ${this.props.resourceType}`,
+ category: `Tap - ${resourceType}`,
action: 'InfoShown',
- label: `${this.props.selectedPlace?.name}, ${this.props.selectedPlace?.address}`
+ label: `${selectedPlace?.name}, ${selectedPlace?.address}`
});
- }
-
- // Handle Times
+ }, [resourceType, selectedPlace?.address, selectedPlace?.name]);
- setCurrentDate() {
- const selectedPlace = this.props.selectedPlace;
+ useEffect(() => {
+ handleGA();
+ getWalkingDurationAndTimes();
+ setPreviewHeight(refSelectedTap.current?.clientHeight ?? 0);
+ }, [handleGA, getWalkingDurationAndTimes]);
- this.setState({
- name: selectedPlace?.name,
- address: selectedPlace?.address,
- tapDescription: selectedPlace?.description
- ? selectedPlace?.description
- : 'Happy PHLasking',
- tapStatement: selectedPlace?.statement,
- tapNormsAndRules: selectedPlace?.norms_rules
- });
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.showingInfoWindow) {
- if (this.props.selectedPlace !== prevProps.selectedPlace) {
- this.setCurrentDate();
- this.getWalkingDurationAndTimes();
- this.handleGA();
- }
+ useEffect(() => {
+ if (showingInfoWindow) {
if (
isMobile &&
- this.state.previewHeight !== this.refSelectedTap.current.clientHeight &&
- !this.state.isDescriptionShown
+ previewHeight !== refSelectedTap.current.clientHeight &&
+ !isDescriptionShown
) {
- this.setState({
- previewHeight: this.refSelectedTap.current.clientHeight
- });
+ setPreviewHeight(refSelectedTap.current.clientHeight);
}
}
- }
-
- componentDidMount() {
- this.setCurrentDate();
- }
-
- render() {
- const { classes } = this.props;
- return (
-
- {isMobile && (
-
- {this.props.selectedPlace && (
- this.toggleInfoWindow(true)}
- onClose={() => this.toggleInfoWindow(false)}
- PaperProps={{ square: false }}
- >
-
-
-
-
- )}
-
- )}
- {!isMobile && this.props.showingInfoWindow && (
-
- {/* Desktop dialog panel */}
-
+ {isMobile && (
+
+ {selectedPlace && (
+ handleToggleInfoWindow(true)}
+ onClose={() => handleToggleInfoWindow(false)}
+ PaperProps={{ square: false }}
>
this.toggleInfoWindow(false)}
+ estWalkTime={walkingDuration}
+ selectedPlace={selectedPlace}
+ infoCollapse={infoCollapseMobile}
+ setInfoCollapse={setInfoCollapseMobile}
+ isMobile
>
-
-
- )}
-
- );
- }
-}
-const mapStateToProps = state => ({
- showingInfoWindow: state.filterMarkers.showingInfoWindow,
- infoIsExpanded: state.filterMarkers.infoIsExpanded,
- infoWindowClass: state.filterMarkers.infoWindowClass,
- selectedPlace: state.filterMarkers.selectedPlace,
- resourceType: state.filterMarkers.resourceType,
- userLocation: state.filterMarkers.userLocation
-});
-const mapDispatchToProps = {
- toggleInfoExpanded,
- toggleInfoWindow,
- toggleInfoWindowClass
+
+ )}
+
+ )}
+ {!isMobile && showingInfoWindow && (
+
+ {/* Desktop dialog panel */}
+
+ handleToggleInfoWindow(false)}
+ >
+
+
+
+
+ )}
+
+ );
};
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(withStyles(styles)(SelectedTap));
+export default SelectedTap;
diff --git a/src/components/SelectedTapHours/SelectedTapHours.js b/src/components/SelectedTapHours/SelectedTapHours.js
index 60c749aa..ff6d3026 100644
--- a/src/components/SelectedTapHours/SelectedTapHours.js
+++ b/src/components/SelectedTapHours/SelectedTapHours.js
@@ -1,9 +1,7 @@
-import React, { useState, useEffect } from 'react';
+import { useState, useEffect } from 'react';
import { hours } from '../../helpers/hours';
-import { isMobile } from 'react-device-detect';
import styles from './SelectedTapHours.module.scss';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
+import useIsMobile from 'hooks/useIsMobile';
const SelectedTapHours = ({ infoIsExpanded, selectedPlace }) => {
const [isHoursExpanded, setIsHoursExpanded] = useState(false);
@@ -73,7 +71,7 @@ const SelectedTapHours = ({ infoIsExpanded, selectedPlace }) => {
setCurrentOrgHours(false);
setIsOpen(null);
}
- }, [selectedPlace]);
+ }, [currentDay, selectedPlace]);
return (
<>
diff --git a/src/components/SelectedTapMobile/SelectedTapDetails.js b/src/components/SelectedTapMobile/SelectedTapDetails.js
index 241a42e3..79ab5886 100644
--- a/src/components/SelectedTapMobile/SelectedTapDetails.js
+++ b/src/components/SelectedTapMobile/SelectedTapDetails.js
@@ -16,8 +16,7 @@ import BathroomIcon from '../icons/CircleBathroomIcon.svg';
import {
WATER_RESOURCE_TYPE,
FOOD_RESOURCE_TYPE,
- FORAGE_RESOURCE_TYPE,
- BATHROOM_RESOURCE_TYPE
+ FORAGE_RESOURCE_TYPE
} from '../../types/ResourceEntry';
function SelectedTapDetails(props) {
diff --git a/src/components/Toolbar/Toolbar.js b/src/components/Toolbar/Toolbar.js
index b4eedcff..2d82474c 100644
--- a/src/components/Toolbar/Toolbar.js
+++ b/src/components/Toolbar/Toolbar.js
@@ -1,5 +1,4 @@
import IconButton from '@mui/material/Button';
-import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
TOOLBAR_MODAL_CONTRIBUTE,
@@ -20,8 +19,6 @@ import {
BATHROOM_RESOURCE_TYPE
} from '../../types/ResourceEntry';
-import { isMobile } from 'react-device-detect';
-
import { ReactComponent as ToiletIcon } from '../icons/CircleBathroomIcon.svg';
import { ReactComponent as FoodIcon } from '../icons/CircleFoodIcon.svg';
import { ReactComponent as ForagingIcon } from '../icons/CircleForagingIcon.svg';
@@ -42,6 +39,7 @@ import Box from '@mui/material/Box';
import ChooseResource from '../ChooseResource/ChooseResource';
import NavigationItem from './NavigationItem';
+import useIsMobile from 'hooks/useIsMobile';
// Actual Magic: https://stackoverflow.com/a/41337005
// Distance calculates the distance between two lat/lon pairs
@@ -64,7 +62,7 @@ function distance(lat1, lon1, lat2, lon2) {
// @param {ResourceEntry[]} data
// @return {ResourceEntry}
function getClosest(data, userLocation) {
- let distances = data.map((resource, index) => {
+ const distances = data.map((resource, index) => {
return {
resource,
distance: distance(
@@ -77,28 +75,20 @@ function getClosest(data, userLocation) {
});
// Return the resource with the minimum distance value
- if (!distances) return null;
+ if (!distances.length) return null;
return distances.reduce(
(min, p) => (p.distance < min.distance ? p : min),
distances[0]
).resource;
}
-function getCoordinates() {
- return new Promise(function (resolve, reject) {
- navigator.geolocation.getCurrentPosition(resolve, reject);
- });
-}
-
-function Toolbar(props) {
-
+function Toolbar({ map }) {
const dispatch = useDispatch();
-
+ const isMobile = useIsMobile();
const resourceType = useSelector(state => state.filterMarkers.resourceType);
const allResources = useSelector(state => state.filterMarkers.allResources);
const userLocation = useSelector(state => state.filterMarkers.userLocation);
const toolbarModal = useSelector(state => state.filterMarkers.toolbarModal);
-
const blackToGrayFilter =
'invert(43%) sepia(20%) saturate(526%) hue-rotate(178deg) brightness(95%) contrast(93%)';
@@ -130,14 +120,17 @@ function Toolbar(props) {
lat: userLocation.lat,
lon: userLocation.lng
});
-
+ if (!closest) return;
dispatch(setSelectedPlace(closest));
- props.map.panTo({
+ map.panTo({
lat: closest.latitude,
lng: closest.longitude
});
- dispatch(toggleInfoWindow(true));
+ toggleInfoWindow({
+ isShown: true,
+ infoWindowClass: isMobile ? 'info-window-in' : 'info-window-in-desktop'
+ });
}
function closestButtonClicked() {
@@ -145,11 +138,8 @@ function Toolbar(props) {
}
function toolbarClicked(modal) {
- if (toolbarModal === modal) {
- dispatch(setToolbarModal(TOOLBAR_MODAL_NONE));
- } else {
- dispatch(setToolbarModal(modal));
- }
+ if (toolbarModal === modal) dispatch(setToolbarModal(TOOLBAR_MODAL_NONE));
+ else dispatch(setToolbarModal(modal));
}
let phlaskButton = null;
diff --git a/src/components/TutorialModal/TutorialModal.js b/src/components/TutorialModal/TutorialModal.js
index 2919e725..ecc2d2de 100644
--- a/src/components/TutorialModal/TutorialModal.js
+++ b/src/components/TutorialModal/TutorialModal.js
@@ -1,6 +1,6 @@
import { faSlidersH } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import { Button, Form, Modal } from 'react-bootstrap';
import useLocalStorage from '../../hooks/useLocalStorage';
import phlaskFilterIcon from '../icons/PhlaskFilterIcon';
@@ -11,7 +11,7 @@ import foodImg from '../images/foodButton.png';
import waterImg from '../images/waterButton.png';
import styles from './TutorialModal.module.scss';
-const TutorialModal = ({ showButton }) => {
+const TutorialModal = () => {
const [showModal, setShowModal] = useState(null);
const [modalStep, setModalStep] = useState(1);
const [showModalPreference, setShowModalPreference] = useLocalStorage(
@@ -21,11 +21,6 @@ const TutorialModal = ({ showButton }) => {
const [showModalCheckbox, setShowModalCheckbox] = useState(true);
const [modalCheckbox, setModalCheckbox] = useState(false);
- function handleShow() {
- setShowModal(true);
- setShowModalCheckbox(false);
- }
-
function handleClose() {
setShowModal(false);
setModalStep(1);
@@ -50,7 +45,7 @@ const TutorialModal = ({ showButton }) => {
useEffect(() => {
setShowModal(showModalPreference);
- }, []);
+ }, [showModalPreference]);
const modalContent = {
1: {
@@ -195,22 +190,12 @@ const TutorialModal = ({ showButton }) => {
Next
) : (
-
+
Close
)}
- {/* {showButton && (
-
-
-
- )} */}
>
);
};
diff --git a/src/constants/defaults.js b/src/constants/defaults.js
new file mode 100644
index 00000000..03c452bb
--- /dev/null
+++ b/src/constants/defaults.js
@@ -0,0 +1,4 @@
+export const CITY_HALL_COORDINATES = {
+ latitude: '39.952744',
+ longitude: '-75.163500'
+};
diff --git a/src/contexts/HeaderContext.js b/src/contexts/HeaderContext.js
index eecf8747..ce7984b4 100644
--- a/src/contexts/HeaderContext.js
+++ b/src/contexts/HeaderContext.js
@@ -1,112 +1,110 @@
import About from 'components/Pages/About';
import Contact from 'components/Pages/Contact';
import Join from 'components/Pages/Join';
-import React, { createContext, useEffect, useState } from 'react';
+import { createContext, useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
const HeaderContext = createContext({});
// Create a HeaderProvider component
const HeaderProvider = ({ children }) => {
- // Define the state for the header context
- const [anchorEl, setAnchorEl] = useState(null);
- const [sidebarOpen, setSidebarOpen] = useState(false);
- const [showMapControls, setShowMapControls] = useState(false);
- const [menuExpand, setMenuExpand] = useState(false);
- const [pageExpand, setPageExpand] = useState(false);
- const [verticalAnimFinished1, setVerticalAnimFinished1] = useState(false);
- const [verticalAnimFinished2, setVerticalAnimFinished2] = useState(false);
- const [shownPage, setShownPage] = useState(null);
- const isSearchShown = useSelector(state => state.filterMarkers.isSearchShown);
- const isFilterShown = useSelector(state => state.filterMarkers.isFilterShown);
+ // Define the state for the header context
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+ const [showMapControls, setShowMapControls] = useState(false);
+ const [menuExpand, setMenuExpand] = useState(false);
+ const [pageExpand, setPageExpand] = useState(false);
+ const [verticalAnimFinished1, setVerticalAnimFinished1] = useState(false);
+ const [verticalAnimFinished2, setVerticalAnimFinished2] = useState(false);
+ const [shownPage, setShownPage] = useState(null);
+ const isSearchShown = useSelector(state => state.filterMarkers.isSearchShown);
+ const isFilterShown = useSelector(state => state.filterMarkers.isFilterShown);
- // Define any functions or values you want to expose to consumers of the context
- const open = Boolean(anchorEl);
- const toggleMenuExpand = event => {
- if (menuExpand) {
- setVerticalAnimFinished1(false);
- setVerticalAnimFinished2(false);
- setPageExpand(false);
- setShownPage(null);
- }
- setMenuExpand(!menuExpand);
- };
+ // Define any functions or values you want to expose to consumers of the context
+ const open = Boolean(anchorEl);
+ const toggleMenuExpand = event => {
+ if (menuExpand) {
+ setVerticalAnimFinished1(false);
+ setVerticalAnimFinished2(false);
+ setPageExpand(false);
+ setShownPage(null);
+ }
+ setMenuExpand(!menuExpand);
+ };
- const showSidebar = () => {
- setSidebarOpen(true);
- };
+ const showSidebar = () => {
+ setSidebarOpen(true);
+ };
- const menuClicked = page => {
- if (page == shownPage) {
- setVerticalAnimFinished1(false);
- setVerticalAnimFinished2(false);
- setPageExpand(false);
- setShownPage(null);
- } else {
- setPageExpand(true);
- switch (page) {
- case 'about':
- page = ;
- break;
- case 'join':
- page = ;
- break;
- case 'contact':
- page = ;
- break;
- default:
- break;
- }
- setShownPage(page);
- }
- };
+ const menuClicked = page => {
+ if (page == shownPage) {
+ setVerticalAnimFinished1(false);
+ setVerticalAnimFinished2(false);
+ setPageExpand(false);
+ setShownPage(null);
+ } else {
+ setPageExpand(true);
+ switch (page) {
+ case 'about':
+ page = ;
+ break;
+ case 'join':
+ page = ;
+ break;
+ case 'contact':
+ page = ;
+ break;
+ default:
+ break;
+ }
+ setShownPage(page);
+ }
+ };
+ const isNotMapPage = useCallback(() => {
const pagePaths = /(\/mission)|(\/share)|(\/project)|(\/contribute)/;
- const isNotMapPage = () => {
- return window.location.pathname.match(pagePaths);
- };
+ return window.location.pathname.match(pagePaths);
+ }, []);
- //On render, check if on map page to show or hide map controls
- useEffect(() => {
- if (isNotMapPage()) {
- setShowMapControls(false);
- } else {
- setShowMapControls(true);
- }
- }, [isNotMapPage, setShowMapControls]);
+ //On render, check if on map page to show or hide map controls
+ useEffect(() => {
+ if (isNotMapPage()) {
+ setShowMapControls(false);
+ } else {
+ setShowMapControls(true);
+ }
+ }, [isNotMapPage, setShowMapControls]);
- const stateVal = {
- anchorEl,
- setAnchorEl,
- sidebarOpen,
- setSidebarOpen,
- showMapControls,
- setShowMapControls,
- menuExpand,
- setMenuExpand,
- pageExpand,
- setPageExpand,
- verticalAnimFinished1,
- setVerticalAnimFinished1,
- verticalAnimFinished2,
- setVerticalAnimFinished2,
- shownPage,
- setShownPage,
- isSearchShown,
- isFilterShown,
- open,
- toggleMenuExpand,
- showSidebar,
- menuClicked,
- isNotMapPage,
- };
+ const stateVal = {
+ anchorEl,
+ setAnchorEl,
+ sidebarOpen,
+ setSidebarOpen,
+ showMapControls,
+ setShowMapControls,
+ menuExpand,
+ setMenuExpand,
+ pageExpand,
+ setPageExpand,
+ verticalAnimFinished1,
+ setVerticalAnimFinished1,
+ verticalAnimFinished2,
+ setVerticalAnimFinished2,
+ shownPage,
+ setShownPage,
+ isSearchShown,
+ isFilterShown,
+ open,
+ toggleMenuExpand,
+ showSidebar,
+ menuClicked,
+ isNotMapPage
+ };
- // Return the HeaderContext.Provider with the headerState and any other values/functions
- return (
-
- {children}
-
- );
+ // Return the HeaderContext.Provider with the headerState and any other values/functions
+ return (
+ {children}
+ );
};
-export { HeaderContext, HeaderProvider };
\ No newline at end of file
+export { HeaderContext, HeaderProvider };
diff --git a/src/hooks/useIsMobile.js b/src/hooks/useIsMobile.js
new file mode 100644
index 00000000..fb7efa74
--- /dev/null
+++ b/src/hooks/useIsMobile.js
@@ -0,0 +1,11 @@
+import useMediaQuery from '@mui/material/useMediaQuery';
+
+const useIsMobile = () => {
+ const isMobile = useMediaQuery(theme =>
+ theme.breakpoints.down(theme.breakpoints.values.sm)
+ );
+
+ return isMobile;
+};
+
+export default useIsMobile;
diff --git a/src/reducers/filterMarkers.js b/src/reducers/filterMarkers.js
index ff16d980..b7059d01 100644
--- a/src/reducers/filterMarkers.js
+++ b/src/reducers/filterMarkers.js
@@ -1,20 +1,20 @@
-import { isMobile } from 'react-device-detect';
+import { CITY_HALL_COORDINATES } from 'constants/defaults';
import * as actions from '../actions/actions';
import { WATER_RESOURCE_TYPE } from '../types/ResourceEntry';
const initialState = {
mapCenter: {
- lat: parseFloat('39.952744'),
- lng: parseFloat('-75.163500')
+ lat: parseFloat(CITY_HALL_COORDINATES.latitude),
+ lng: parseFloat(CITY_HALL_COORDINATES.longitude)
},
// Change to reflect user's current location
userLocation: {
- lat: parseFloat('39.952744'),
- lng: parseFloat('-75.163500')
+ lat: parseFloat(CITY_HALL_COORDINATES.latitude),
+ lng: parseFloat(CITY_HALL_COORDINATES.longitude)
},
showingInfoWindow: false,
infoIsExpanded: false,
- infoWindowClass: isMobile ? 'info-window-out' : 'info-window-out-desktop',
+ infoWindowClass: 'info-window-out-desktop',
tapFilters: {
filtered: false,
handicap: false,
@@ -87,8 +87,8 @@ export default (state = initialState, act) => {
case actions.SET_MAP_CENTER:
return { ...state, mapCenter: act.coords };
- case actions.GET_RESOURCES_SUCCESS:
- return { ...state, allResources: act.allResources };
+ case actions.getResources.fulfilled.type:
+ return { ...state, allResources: act.payload };
case actions.PUSH_NEW_RESOURCE:
return {
@@ -110,27 +110,16 @@ export default (state = initialState, act) => {
showingInfoWindow: true
};
- case actions.TOGGLE_INFO_WINDOW:
- return act.isShown
- ? {
- ...state,
- showingInfoWindow: act.isShown,
- infoWindowClass: isMobile
- ? 'info-window-in'
- : 'info-window-in-desktop'
- }
- : { ...state, showingInfoWindow: act.isShown };
-
- case actions.TOGGLE_INFO_WINDOW_CLASS:
+ case actions.toggleInfoWindow.type:
+ return {
+ ...state,
+ showingInfoWindow: act.payload.isShown,
+ infoWindowClass: act.payload.infoWindowClass
+ };
+ case actions.toggleInfoWindowClass.type:
return {
...state,
- infoWindowClass: isMobile
- ? act.isShown
- ? 'info-window-in'
- : 'info-window-out'
- : act.isShown
- ? 'info-window-in-desktop'
- : 'info-window-out-desktop'
+ infoWindowClass: act.payload.infoWindowClass
};
case actions.TOGGLE_INFO_EXPANDED:
@@ -193,8 +182,8 @@ export default (state = initialState, act) => {
};
}
- case actions.SET_TOOLBAR_MODAL:
- return { ...state, toolbarModal: act.mode };
+ case actions.setToolbarModal.type:
+ return { ...state, toolbarModal: act.payload };
case actions.SET_RESOURCE_TYPE:
return { ...state, resourceType: act.resourceType };
diff --git a/src/selectors/waterSelectors.js b/src/selectors/waterSelectors.js
index b5cf023f..92cd8ca7 100644
--- a/src/selectors/waterSelectors.js
+++ b/src/selectors/waterSelectors.js
@@ -10,8 +10,8 @@ const getResourceType = state => state.filterMarkers.resourceType;
* This creates a selector for all resources filtered by the requested filters.
*/
const selectFilteredResource = createSelector(
- [getTapFilters, getAllResources, getResourceType],
- (tapFilters, allResources, resourceType) => {
+ [getAllResources, getResourceType],
+ (allResources, resourceType) => {
// First, filter based on resource
return allResources.filter(resource => {
return resource.resource_type === resourceType;
diff --git a/src/utils/noop.js b/src/utils/noop.js
new file mode 100644
index 00000000..938cfa5c
--- /dev/null
+++ b/src/utils/noop.js
@@ -0,0 +1,3 @@
+function noop() {}
+
+export default noop;
diff --git a/yarn.lock b/yarn.lock
index f32029e9..e97a6391 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12070,13 +12070,6 @@ react-dev-utils@^12.0.1:
strip-ansi "^6.0.1"
text-table "^0.2.0"
-react-device-detect@^2.2.2:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca"
- integrity sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==
- dependencies:
- ua-parser-js "^1.0.33"
-
react-div-100vh@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/react-div-100vh/-/react-div-100vh-0.7.0.tgz#b3bec03a833fa40e406f36ed2e23a35a59d1068f"
@@ -14046,11 +14039,6 @@ ua-parser-js@^0.7.30:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz#e464e66dac2d33a7a1251d7d7a99d6157ec27832"
integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==
-ua-parser-js@^1.0.33:
- version "1.0.37"
- resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f"
- integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==
-
ufo@^1.4.0:
version "1.5.3"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344"