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 ? ( + + ) : ( + + )} +
+ ); + })}

- - - - - - - - - - - - - {/* Toggle Switches */} - - - Open Now - this.handleChange(e)} - readOnly - /> - + }; - - ID Required - this.handleChange(e)} - readOnly + return ( + + + {/* // Legend button filters for tap type */} + + + + + + + + + + + + + + + - - Kids Only - this.handleChange(e)} - readOnly - /> - - - + {/* Toggle Switches */} + + + Open Now + handleChange(e)} + readOnly + /> + - - - - - - } - > -

- -
- - ); - } -} + + 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 + + + + + + } + > +
+ +
+ + ); }; -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 ( -
-
foot
-
- ); - } -} - -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 - - } - > - -
- blue 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 - - } - > - - green this.props.legendButton('Private-Shared')} - > -

Private-Shared Tap

-
-
- - These taps are located in private businesses; public access is - not guaranteed - - } - > - - yellow this.props.legendButton('Private')} - > -

Private Tap

-
-
- - These taps are restricted from public use - - } - > - -
- red 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 ( + + + + - + + + + + + { + 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); + } + }} + > + + + + - - - - - - - - { - 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 ) : ( - )} - {/* {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"