Geovation/photos

View on GitHub
src/App.js

Summary

Maintainability
F
1 wk
Test Coverage
import React, { useState, useContext, useEffect, useRef, Fragment } from "react";
import { Route, Switch, withRouter } from "react-router-dom";
import { connect } from "react-redux";

import * as localforage from "localforage";
import _ from "lodash";

import RootRef from "@material-ui/core/RootRef";
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import Snackbar from "@material-ui/core/Snackbar";
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogTitle from "@material-ui/core/DialogTitle";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import CloseIcon from "@material-ui/icons/Close";
import MuiAlert from "@material-ui/lab/Alert";
import CachedIcon from "@material-ui/icons/Cached";

import { dbFirebase, authFirebase } from "features/firebase";

import SwipeTutorialPage from "./components/common/SwipeTutorialPage";
import PhotoPage from "./components/PhotoPage";
import ProfilePage from "./components/ProfilePage";
import Map from "./components/MapPage/Map";
import ModeratorPage from "./components/ModeratorPage";
import OwnPhotosPage from "./components/OwnPhotosPage";
import LoginFirebase from "./components/LoginFirebase";
import Login from "./components/Login";
import AboutPage from "./components/AboutPage";
import LeaderboardPage from "./components/Leaderboard";
import WriteFeedbackPage from "./components/WriteFeedbackPage";
import DrawerContainer from "./components/DrawerContainer";
import TermsDialog from "./components/TermsDialog";
import EmailVerifiedDialog from "./components/EmailVerifiedDialog";
import DisplayPhoto from "./components/MapPage/DisplayPhoto";
import config from "custom/config";

import { gtagPageView, gtagEvent } from "./gtag.js";
import "./App.scss";
import FeedbackReportsSubrouter from "./components/FeedbackReports/FeedbackReportsSubrouter";
import MapLocation from "./types/MapLocation";

import tutorialSteps from "./custom/tutorialSteps";
import welcomeSteps from "./custom/welcomeSteps";
import { GeolocationContext } from "store/GeolocationContext";

import placeholderImage from "custom/assets/images/logo.svg";

const styles = (theme) => ({
  dialogClose: {
    position: "absolute",
    top: theme.spacing(1),
    right: theme.spacing(1),
  },
});

const App = (props) => {
  const { classes, dispatch, history, location, geojson, online, user } = props;
  const fields = Object.values(config.PHOTO_FIELDS);

  const [file, setFile] = useState(null);
  const [loginLogoutDialogOpen, setLoginLogoutDialogOpen] = useState(false);
  const [leftDrawerOpen, setLeftDrawerOpen] = useState(false);
  const [welcomeShown, setWelcomeShown] = useState(
    !!localStorage.getItem("welcomeShown")
  );
  const [termsAccepted, setTermsAccepted] = useState(
    !!localStorage.getItem("termsAccepted")
  );
  const [dialogOpen, setDialogOpen] = useState(false);
  const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
  const [usersLeaderboard, setUsersLeaderboard] = useState([]);
  const [confirmDialogHandleOk, setConfirmDialogHandleOk] = useState(null);
  const [selectedFeature, setSelectedFeature] = useState(undefined); // undefined = not selectd, null = feature not found
  const [photoAccessedByUrl, setPhotoAccessedByUrl] = useState(false);
  const [photosToModerate, setPhotosToModerate] = useState({});
  const [mapLocation, setMapLocation] = useState(new MapLocation());
  const [dbStats, setDbStats] = useState();
  const [stats, setStats] = useState();
  const [firebaseConfig, setFirebaseConfig] = useState();
  const [dialogTitle, setDialogTitle] = useState("");
  const [dialogContentText, setDialogContentText] = useState("");
  const [confirmDialogTitle, setConfirmDialogTitle] = useState("");
  const [ignoreUpdate, setIgnoreUpdate] = useState(false);
  const [alert, setAlert] = useState({});

  const geolocationContext = useContext(GeolocationContext);

  let unregisterAuthObserver = useRef();
  let unregisterConfigObserver = useRef();
  let unregisterConnectionObserver = useRef();
  let unregisterPublishedPhotosRT = useRef();
  let unregisterPhotosToModerate = useRef();
  let unregisterOwnPhotos = useRef();
  let domRefInput = useRef();
  let userChecked = useRef(false);

  const VISIBILITY_REGEX = new RegExp(
    "(^/@|^/$|^" +
      config.PAGES.displayPhoto.path +
      "/|^" +
      config.PAGES.embeddable.path +
      ")",
    "g"
  );

  const openPhotoPage = (file) => {
    setFile(file);

    history.push(config.PAGES.photos.path);
  };

  const handleDialogClose = () => setDialogOpen(false);

  const fetchPhotoIfUndefined = async (photoId) => {
    // it means that we landed on the app with a photoId in the url
    if (photoId && !selectedFeature) {
      return dbFirebase
        .getPhotoByID(photoId)
        .then((selectedFeature) => setSelectedFeature(selectedFeature))
        .catch((e) => setSelectedFeature(null));
    }
  };

  const extractPathnameParams = () => {
    // extracts photoID
    const regexPhotoIDMatch = location.pathname.match(
      new RegExp(`${config.PAGES.displayPhoto.path}\\/(\\w+)`)
    );

    const photoId = regexPhotoIDMatch && regexPhotoIDMatch[1];

    // extracts mapLocation
    const regexMapLocationMatch = location.pathname.match(
      new RegExp("@(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*),(\\d*\\.?\\d*)z")
    );

    const mapLocation =
      (regexMapLocationMatch &&
        new MapLocation({
          latitude: regexMapLocationMatch[1],
          longitude: regexMapLocationMatch[2],
          zoom: regexMapLocationMatch[3],
        })) ||
      new MapLocation();
    if (!regexMapLocationMatch) {
      mapLocation.zoom = config.ZOOM;
    }

    return { photoId, mapLocation };
  };

  const unexpectedErrorFn = (error) => {
    console.error(error);
    alert(`Let our dev know about this error: ${error}`);
    window.location.reload();
  };

  const prevLocationRef = useRef();
  useEffect(() => {
    // didMount
    prevLocationRef.current = location;

    setStats(config.getStats(geojson, dbStats));

    let { photoId, mapLocation } = extractPathnameParams();
    setMapLocation(mapLocation);
    unregisterConnectionObserver.current = dbFirebase.onConnectionStateChanged(
      (online) => dispatch({ type: "SET_ONLINE", payload: { online } })
    );

    dbFirebase.fetchStats().then((dbStats) => {
      console.log(dbStats);
      setUsersLeaderboard(dbStats.users);
      setDbStats(dbStats);

      return dbStats;
    });

    // when photoId is defined (when acceding the app with photoid query string), need to get the photo info.
    fetchPhotoIfUndefined(photoId).then(() => {
      // If the selectedFeature is not null, it means that we were able to retrieve a photo from the URL and so we landed
      // into the photoId.
      setPhotoAccessedByUrl(!!selectedFeature);

      gtagPageView(location.pathname);
    });

    // Get the photos from the cache first.
    (async () => {
      const featuresDict = (await localforage.getItem("featuresDict")) || {};
      if (!_.isEmpty(featuresDict)) {
        dispatch({ type: "SET_FEATURES", payload: { featuresDict } });
      } else {
        fetchPhotos();
      }
    })();

    registerPublishedPhotosRT();

    if (!welcomeShown) {
      history.push(config.PAGES.welcome.path);
    }

    unregisterAuthObserver.current = authFirebase.onAuthStateChanged(
      (firebaseUser) =>
        dispatch({ type: "SET_USER", payload: { user: firebaseUser } })
    );

    unregisterConfigObserver.current = dbFirebase.configObserver(
      (config) => setFirebaseConfig(config),
      console.error
    );
    // willUnmount
    return async () => {
      unregisterAuthObserver.current();
      unregisterConnectionObserver.current();
      unregisterConfigObserver.current();
      unregisterPhotosToModerate.current &&
        unregisterPhotosToModerate.current();
      unregisterOwnPhotos.current && unregisterOwnPhotos.current();
      unregisterPublishedPhotosRT.current &&
        unregisterPublishedPhotosRT.current();
      await dbFirebase.disconnect();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    console.log(user);
    // lets start fresh if the user logged out
    if (userChecked.current && !user) {
      gtagEvent("Signed out", "User");

      history.push(config.PAGES.map.path);
      window.location.reload();
    } else {
      userChecked.current = true;
    }
  }, [user, history]);

  useEffect(() => {
    setStats(config.getStats(geojson, dbStats));
  }, [dbStats, geojson]);

  useEffect(() => {
    // didUpdate
    if (prevLocationRef.current !== location) {
      prevLocationRef.current = location;
      gtagPageView(location.pathname);

      // if it updates, then it is guaranteed that we didn't landed into the photo
      setPhotoAccessedByUrl(false);
      fetchPhotoIfUndefined(_.get(selectedFeature, "properties.id"));
    }

    // listen to new photos to be moderated
    if (_.get(user, "isModerator") && !unregisterPhotosToModerate.current) {
      unregisterPhotosToModerate.current = dbFirebase.photosToModerateRT(
        config.MODERATING_PHOTOS,
        (photo) => updatePhotoToModerate(photo),
        (photo) => removePhotoToModerate(photo)
      );
    }

    // The user has logged in
    if (user && !unregisterOwnPhotos.current) {
      unregisterOwnPhotos.current = dbFirebase.ownPhotosRT(
        addFeature,
        modifyFeature,
        removeFeature,
        unexpectedErrorFn
      );

      dbFirebase.processScheduledUploads();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [geojson, location, user]);

  useEffect(() => {
    const actions = (
      <Fragment>
        <IconButton
          color="primary"
          size="small"
          onClick={() => window.location.reload()}
        >
          <CachedIcon />
        </IconButton>
        <IconButton
          color="primary"
          size="small"
          onClick={() => setIgnoreUpdate(true)}
        >
          <CloseIcon />
        </IconButton>
      </Fragment>
    );

    setAlert({
      key: "newVersion",
      open: props.newVersionAvailable && !ignoreUpdate,
      actions,
      message: "New verison available !",
      autoHideDuration: 10 * 1000,
    });
  }, [props.newVersionAvailable, ignoreUpdate]);

  const modifyFeature = (photo) => {
    console.debug(`modifying ${photo.id}`);
    dispatch({ type: "UPDATE_FEATURE", payload: { photo } });
  };

  const addFeature = (photo) => {
    console.debug(`adding ${photo.id} --v`);
    modifyFeature(photo);
  };

  const removeFeature = (photo) => {
    console.debug(`removing ${photo.id}`);
    dispatch({ type: "DELETE_FEATURE", payload: { photo } });
  };

  const registerPublishedPhotosRT = async () => {
    if (unregisterPublishedPhotosRT.current) {
      await unregisterPublishedPhotosRT.current();
    }

    // The following line should speedup things. It reads all the photos until before trigger the RT listener
    await fetchPhotos(false, calculateLastUpdate());

    unregisterPublishedPhotosRT.current = dbFirebase.publishedPhotosRT(
      addFeature,
      modifyFeature,
      removeFeature,
      unexpectedErrorFn,
      calculateLastUpdate()
    );
  };

  const calculateLastUpdate = () => {
    let lastUpdated = new Date(null);
    if (geojson) {
      const latestPhoto = _.maxBy(geojson.features, (photo) => {
        return photo.properties.updated;
      });
      lastUpdated = _.get(latestPhoto, "properties.updated");
    }
    return lastUpdated;
  };

  const fetchPhotos = async (fromAPI = true, lastUpdate = new Date(null)) => {
    return dbFirebase
      .fetchPhotos(fromAPI, lastUpdate)
      .then((photos) => _.forEach(photos, (photo) => addFeature(photo)))
      .catch(console.error);
  };

  const removePhotoToModerate = (photo) => {
    console.debug(
      `removing the element ${photo.id} from the collection photosToModerate in the view`
    );
    setPhotosToModerate((photosToModerate) =>
      _.filter(photosToModerate, (p) => p.id !== photo.id)
    );
  };

  const updatePhotoToModerate = (photo) => {
    console.debug(
      `updating the element ${photo.id} from the collection photosToModerate in the view`
    );

    setPhotosToModerate((photosToModerate) => {
      const newDict = { ...photosToModerate };
      newDict[photo.id] = photo;
      return newDict;
    });
  };

  const handleClickLoginLogout = () => {
    let loginLogoutDialogOpen = true;

    if (user) {
      authFirebase.signOut();
      loginLogoutDialogOpen = false;
    }

    setLoginLogoutDialogOpen(loginLogoutDialogOpen);
  };

  const handleLoginClose = () => setLoginLogoutDialogOpen(false);

  const handleCameraClick = () => {
    if (config.SECURITY.UPLOAD_REQUIRES_LOGIN && !user) {
      setDialogOpen(true);
      setDialogTitle("Please login to add a photo");
      setDialogContentText(
        "Before adding photos, you must be logged into your account."
      );
    } else {
      console.log("Clicking on photo");
      domRefInput.current.click();
    }
  };

  const handleUploadClick = async ({ location, imgSrc, fieldsValues } = {}) => {
    history.goBack();
    setAlert({
      key: "photoScheduled",
      open: true,
      message: "Photo upload scheduled :)",
    });
    const { promise, cancel } = await dbFirebase.scheduleUpload({
      location,
      imgSrc,
      fieldsValues,
    });
    console.debug("I could cancel with ", cancel);
    await promise;
    setAlert({ key: "photoUploaded", open: true, message: "Photo uploaded !" });
  };

  const openFile = (e) => {
    if (e.target.files[0]) {
      openPhotoPage(e.target.files[0]);
    }
  };

  const handleWelcomePageClose = () => {
    setWelcomeShown(true);
    localStorage.setItem("welcomeShown", true);
    history.goBack();
  };

  const handleTermsPageClose = (e) => {
    localStorage.setItem("termsAccepted", true);
    setTermsAccepted(true);
  };

  const toggleLeftDrawer = (isItOpen) => () => {
    gtagEvent(isItOpen ? "Opened" : "Closed", "Menu");
    setLeftDrawerOpen(isItOpen);
  };

  const handleLoginPhotoAdd = () => {
    setLoginLogoutDialogOpen(true);
    setDialogOpen(false);
  };

  const handleNextClick = async () => {
    const user = await authFirebase.reloadUser();
    if (user.emailVerified) {
      dispatch({
        type: "SET_USER",
        payload: {
          user: { ...user, emailVerified: user.emailVerified },
        },
      });

      let message = {
        title: "Confirmation",
        body: "Thank you for verifying your email.",
      };
      return message;
    } else {
      let message = {
        title: "Warning",
        body: "Email not verified yet. Please click the link in the email we sent you.",
      };
      return message;
    }
  };

  const handleConfirmDialogClose = () => {
    setConfirmDialogOpen(false);
  };

  const handleRejectClick = (photo) => {
    setConfirmDialogOpen(true);
    setConfirmDialogTitle("Are you sure you want to unpublish the photo ?");
    setConfirmDialogHandleOk(() => rejectPhoto(photo));
  };

  const handleApproveClick = (photo) => {
    setConfirmDialogOpen(true);
    setConfirmDialogTitle("Are you sure you want to publish the photo ?");
    setConfirmDialogHandleOk(() => approvePhoto(photo));
  };

  const approveRejectPhoto = async (isApproved, photo) => {
    // close dialogs
    handleConfirmDialogClose();

    // publish/unpublish photo in firestore
    try {
      if (isApproved) {
        await dbFirebase.approvePhoto(photo.id, user ? user.id : null);
      } else {
        await dbFirebase.rejectPhoto(photo.id, user ? user.id : null);
      }

      const _selectedFeature = selectedFeature;

      photo.published = isApproved;

      if (_.get(selectedFeature, "properties.id") === photo.id) {
        _selectedFeature.properties.published = isApproved;
        setSelectedFeature(_selectedFeature);
      }
    } catch (e) {
      console.error(e);
      setConfirmDialogOpen(true);
      setConfirmDialogTitle(
        `The photo state has not changed. Please try again, id:${photo.id}`
      );
      setConfirmDialogHandleOk(handleConfirmDialogClose);
    }
  };

  const approvePhoto = (photo) => approveRejectPhoto(true, photo);

  const rejectPhoto = (photo) => approveRejectPhoto(false, photo);

  const handleMapLocationChange = (newMapLocation) => {
    if (!history.location.pathname.match(VISIBILITY_REGEX)) {
      return;
    }

    const currentMapLocation = extractPathnameParams().mapLocation;

    // change url coords if the coords are different and if we are in the map
    if (
      currentMapLocation == null ||
      !currentMapLocation.isEqual(newMapLocation)
    ) {
      const currentUrl = history.location;
      const prefix = currentUrl.pathname.split("@")[0];
      const newUrl = `${prefix}@${newMapLocation.urlFormated()}`;

      history.replace(newUrl);
      setMapLocation(newMapLocation);
    }
  };

  const handleLocationClick = () => {
    gtagEvent("Location FAB clicked", "Map");
    setMapLocation(geolocationContext.geolocation);
  };

  const handlePhotoPageClose = () => {
    const PAGES = config.PAGES;
    const photoPath = location.pathname;
    const coords = photoPath.split("@")[1];
    const mapPath = location.pathname.startsWith(PAGES.embeddable.path)
      ? PAGES.embeddable.path
      : PAGES.map.path;
    if (photoAccessedByUrl) {
      const mapUrl = mapPath + (coords ? `@${coords}` : "");
      history.replace(mapUrl);
      history.push(photoPath);
    }

    history.goBack();
  };

  const handlePhotoClick = (feature) => {
    setSelectedFeature(feature);

    let pathname = `${config.PAGES.displayPhoto.path}/${feature.properties.id}`;
    const currentPath = history.location.pathname;

    const coordsUrl =
      currentPath.split("@")[1] ||
      new MapLocation(
        feature.geometry.coordinates[1],
        feature.geometry.coordinates[0],
        config.ZOOM_FLYTO
      ).urlFormated();
    pathname =
      currentPath === config.PAGES.embeddable.path
        ? currentPath + pathname
        : pathname;

    // if it is in map, change the url
    if (history.location.pathname.match(VISIBILITY_REGEX)) {
      history.replace(`${currentPath.split("@")[0]}@${coordsUrl}`);
    }

    history.push(`${pathname}@${coordsUrl}`);
  };

  const reloadPhotos = () => {
    // delete photos.
    dispatch({ type: "SET_FEATURES", payload: { featuresDict: {} } });

    // fetch all the photos from firestore instead than from the CDN
    fetchPhotos(false);
  };

  // from the own photos from the dict
  const getOwnPhotos = () => {
    let ownPhotos = {};
    if (user) {
      const allPhotos = _.get(props, "geojson.features");
      ownPhotos = _.filter(
        allPhotos,
        (photo) => _.get(photo, "properties.owner_id") === user.id
      ).reduce((accumulator, currentValue) => {
        accumulator[currentValue.properties.id] = currentValue;
        return accumulator;
      }, {});
    }
    return ownPhotos;
  };

  const Alert = (props) => {
    return <MuiAlert elevation={6} variant="filled" {...props} />;
  };

  const handleAlertClose = (event, reason) => {
    if (reason === "clickaway") {
      return;
    }

    setAlert({ open: false });
  };

  console.log(firebaseConfig);
  return (
    <div className="geovation-app">
      {!termsAccepted &&
        !history.location.pathname.startsWith(config.PAGES.embeddable.path) && (
          <TermsDialog handleClose={handleTermsPageClose} />
        )}

      <EmailVerifiedDialog
        open={!!(user && !user.emailVerified)}
        handleNextClick={handleNextClick}
      />

      <main className="content">
        <Switch>
          {config.CUSTOM_PAGES.map(
            (CustomPage, index) =>
              !!CustomPage.page && (
                <Route
                  key={index}
                  path={CustomPage.path}
                  render={(props) => (
                    // eslint-disable-next-line react/jsx-pascal-case
                    <CustomPage.page {...props} handleClose={history.goBack} />
                  )}
                />
              )
          )}

          <Route
            path={config.PAGES.about.path}
            render={(props) => (
              <AboutPage
                {...props}
                handleClose={history.goBack}
                reloadPhotos={reloadPhotos}
              />
            )}
          />

          <Route
            path={config.PAGES.tutorial.path}
            render={(props) => (
              <SwipeTutorialPage
                {...props}
                steps={tutorialSteps}
                label={config.PAGES.tutorial.label}
                handleClose={history.goBack}
                hasLogo={true}
              />
            )}
          />

          <Route
            path={config.PAGES.welcome.path}
            render={(props) => (
              <SwipeTutorialPage
                {...props}
                steps={welcomeSteps}
                label={config.PAGES.welcome.label}
                handleClose={handleWelcomePageClose}
              />
            )}
          />

          <Route
            path={config.PAGES.leaderboard.path}
            render={(props) => (
              <LeaderboardPage
                {...props}
                usersLeaderboard={usersLeaderboard}
                handleClose={history.goBack}
              />
            )}
          />

          {user && user.isModerator && (
            <Route
              path={config.PAGES.moderator.path}
              render={(props) => (
                <ModeratorPage
                  {...props}
                  photos={photosToModerate}
                  handleClose={history.goBack}
                  handleRejectClick={handleRejectClick}
                  handleApproveClick={handleApproveClick}
                />
              )}
            />
          )}

          {user && (
            <Route
              path={config.PAGES.ownPhotos.path}
              render={(props) => (
                <OwnPhotosPage
                  {...props}
                  photos={getOwnPhotos()}
                  handleClose={history.goBack}
                  handlePhotoClick={handlePhotoClick}
                />
              )}
            />
          )}

          {user && user.isModerator && (
            <Route
              path={config.PAGES.feedbackReports.path}
              render={(props) => (
                <FeedbackReportsSubrouter
                  {...props}
                  handleClose={history.goBack}
                />
              )}
            />
          )}

          <Route
            path={config.PAGES.photos.path}
            render={(props) => (
              <PhotoPage
                {...props}
                file={file}
                fields={fields}
                handleClose={history.goBack}
                handleRetakeClick={handleCameraClick}
                handleUploadClick={handleUploadClick}
              />
            )}
          />

          {user && (
            <Route
              path={config.PAGES.account.path}
              render={(props) => (
                <ProfilePage
                  {...props}
                  handleClose={history.goBack}
                  handlePhotoClick={handlePhotoClick}
                />
              )}
            />
          )}

          <Route
            path={config.PAGES.writeFeedback.path}
            render={(props) => (
              <WriteFeedbackPage {...props} handleClose={history.goBack} />
            )}
          />

          <Route
            path={[
              `${config.PAGES.displayPhoto.path}/:id`,
              `${config.PAGES.embeddable.path}${config.PAGES.displayPhoto.path}/:id`,
            ]}
            render={(props) => (
              <DisplayPhoto
                {...props}
                placeholderImage={placeholderImage}
                handleRejectClick={handleRejectClick}
                handleApproveClick={handleApproveClick}
                handleClose={handlePhotoPageClose}
                feature={selectedFeature}
              />
            )}
          />
        </Switch>

        <Map
          history={history}
          visible={history.location.pathname.match(VISIBILITY_REGEX)}
          embeddable={history.location.pathname.match(
            new RegExp(config.PAGES.embeddable.path, "g")
          )}
          handleCameraClick={handleCameraClick}
          toggleLeftDrawer={toggleLeftDrawer}
          handlePhotoClick={handlePhotoClick}
          mapLocation={mapLocation}
          handleMapLocationChange={(newMapLocation) =>
            handleMapLocationChange(newMapLocation)
          }
          handleLocationClick={handleLocationClick}
        />
      </main>

      <Snackbar
        open={alert.open}
        key={alert.key}
        anchorOrigin={{ vertical: "top", horizontal: "center" }}
        autoHideDuration={alert.autoHideDuration || 2000}
        onClose={alert.onClose || handleAlertClose}
      >
        <Alert
          severity={alert.severity || "success"}
          onClose={alert.onClose || handleAlertClose}
          action={alert.actions}
        >
          {alert.message || "Hello"}
        </Alert>
      </Snackbar>

      <Snackbar open={!geojson} message="Loading photos..." />
      <Snackbar
        open={welcomeShown && !online}
        message="Connecting to our servers..."
      />

      <RootRef rootRef={domRefInput}>
        <input
          className="hidden"
          type="file"
          accept="image/*"
          id={"fileInput"}
          onChange={openFile}
          onClick={(e) => (e.target.value = null)}
        />
      </RootRef>

      <Login
        open={loginLogoutDialogOpen && !user}
        handleClose={handleLoginClose}
        loginComponent={LoginFirebase}
      />

      <DrawerContainer
        handleClickLoginLogout={handleClickLoginLogout}
        leftDrawerOpen={leftDrawerOpen}
        toggleLeftDrawer={toggleLeftDrawer}
        stats={stats}
      />

      <Dialog open={dialogOpen} onClose={handleDialogClose}>
        <DialogTitle disableTypography>
          <Typography variant="h6">{dialogTitle}</Typography>
          <IconButton
            className={classes.dialogClose}
            aria-label="close"
            onClick={handleDialogClose}
          >
            <CloseIcon />
          </IconButton>
        </DialogTitle>
        <DialogContent>
          <DialogContentText>{dialogContentText}</DialogContentText>
        </DialogContent>

        <DialogActions>
          {/* clicking ok should either open a login box or there should be a text field in the box to enter your email address */}
          <Button onClick={handleLoginPhotoAdd} color="secondary">
            Login
          </Button>
        </DialogActions>
      </Dialog>

      <Dialog open={confirmDialogOpen} onClose={handleConfirmDialogClose}>
        <DialogTitle>{confirmDialogTitle}</DialogTitle>
        <DialogActions>
          <Button onClick={handleConfirmDialogClose} color="secondary">
            Cancel
          </Button>
          <Button onClick={confirmDialogHandleOk} color="secondary">
            Ok
          </Button>
        </DialogActions>
      </Dialog>
    </div>
  );
};

const mapStateToProps = (state) => ({
  user: state.user,
  online: state.online,
  geojson: state.geojson,
});

export default connect(mapStateToProps)(
  withRouter(withStyles(styles, { withTheme: true })(App))
);