Removes the Router/Switch/Route/Link/LinkContainer legacy-context
warnings. Keeps the shared history singleton (used outside React by
NotificationsObserver + Utils/location) via unstable_HistoryRouter, and
bridges v6 hooks back to v4-shaped history/match/location props with a
new withRouterCompat HOC so the ~20 class pages stay untouched.
- <Switch> -> <Routes>, component= -> element=
- Authenticated/Public -> guard components (children or <Navigate replace>)
- LocationListener -> useLocation + useEffect function
- regex route /fire/:type(active|archive|alert)/:id -> /fire/:type/:id
- history.listen callback is ({ location }) in history v5
Browser-verified: SPA nav, /subscriptions->/login auth redirect,
deep-link /fire/archive/:id (params), browser back/forward. REST smoke
byte-identical.
204 lines
9.8 KiB
JavaScript
204 lines
9.8 KiB
JavaScript
/* eslint-disable jsx-a11y/no-href */
|
|
/* eslint import/no-absolute-path: [2, { esmodule: false, commonjs: false, amd: false }] */
|
|
|
|
import React, { useEffect } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { unstable_HistoryRouter as HistoryRouter, Routes, Route, useLocation } from 'react-router-dom';
|
|
import { Container } from 'react-bootstrap';
|
|
import { I18nextProvider } from 'react-i18next';
|
|
import { Helmet } from 'react-helmet-async';
|
|
import { Meteor } from 'meteor/meteor';
|
|
import { withTracker } from 'meteor/react-meteor-data';
|
|
// https://github.com/gadicc/meteor-blaze-react-component/
|
|
import Blaze from 'meteor/gadicc:blaze-react-component';
|
|
// i18n
|
|
import i18n, { i18nReady } from '/imports/startup/client/i18n';
|
|
import '/imports/startup/client/meta';
|
|
import '/imports/startup/client/ravenLogger';
|
|
import '/imports/startup/client/geolocation';
|
|
import '/imports/startup/client/piwik-start.js';
|
|
import '/imports/startup/client/reconnect.js';
|
|
import Reconnect from '../../components/Reconnect/Reconnect';
|
|
import Navigation from '../../components/Navigation/Navigation';
|
|
import Authenticated from '../../components/Authenticated/Authenticated';
|
|
import Public from '../../components/Public/Public';
|
|
import withRouterCompat from '../../components/withRouterCompat/withRouterCompat';
|
|
import Index from '../../pages/Index/Index';
|
|
import Subscriptions from '../../pages/Subscriptions/Subscriptions';
|
|
import NewSubscription from '../../pages/NewSubscription/NewSubscription';
|
|
import ViewSubscription from '../../pages/ViewSubscription/ViewSubscription';
|
|
import EditSubscription from '../../pages/EditSubscription/EditSubscription';
|
|
import TestError from '../../pages/TestError/TestError';
|
|
import Signup from '../../pages/Signup/Signup';
|
|
import Auth from '../../pages/Auth/Auth';
|
|
import Login from '../../pages/Login/Login';
|
|
import Logout from '../../pages/Logout/Logout';
|
|
import Status from '../../pages/Status/Status';
|
|
import VerifyEmail from '../../pages/VerifyEmail/VerifyEmail';
|
|
import RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
|
|
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
|
|
import Profile from '../../pages/Profile/Profile';
|
|
import Fires from '../../pages/Fires/Fires';
|
|
import Sandbox from '../../pages/Sandbox/Sandbox';
|
|
import ZonesMap from '../../pages/ZonesMap/ZonesMap';
|
|
import FiresMap from '../../pages/FiresMap/FiresMap';
|
|
import NotFound from '../../pages/NotFound/NotFound';
|
|
import Terms from '../../pages/Terms/Terms';
|
|
import About from '../../pages/About/About';
|
|
import Privacy from '../../pages/Privacy/Privacy';
|
|
import License from '../../pages/License/License';
|
|
import Credits from '../../pages/Credits/Credits';
|
|
import CookieConsent from '../../components/CookieConsent/CookieConsent';
|
|
import Footer from '../../components/Footer/Footer';
|
|
import Feedback from '../../components/Feedback/Feedback';
|
|
import ReSendEmail from '../../components/ReSendEmail/ReSendEmail';
|
|
import ErrorBoundary from '../../components/ErrorBoundary/ErrorBoundary';
|
|
import history from '../../components/History/History';
|
|
import '../../components/NotificationsObserver/NotificationsObserver';
|
|
import './App.scss';
|
|
|
|
// Tracks route changes for Piwik/analytics. v6 replaces the old
|
|
// contextTypes.router.history.listen wiring with the useLocation hook.
|
|
const LocationListener = ({ children }) => {
|
|
const location = useLocation();
|
|
useEffect(() => {
|
|
// https://github.com/GoogleChrome/rendertron#rendering-budget-timeout
|
|
window.renderComplete = true;
|
|
Meteor.Piwik.trackPage(location.pathname);
|
|
}, [location.pathname]);
|
|
return children;
|
|
};
|
|
|
|
LocationListener.propTypes = {
|
|
children: PropTypes.oneOfType([
|
|
PropTypes.arrayOf(PropTypes.node),
|
|
PropTypes.node
|
|
]).isRequired
|
|
};
|
|
|
|
// v6 routes render an `element`, not a `component`, and no longer pass down
|
|
// history/match/location. Wrap each class page once (module scope, so the
|
|
// component identity stays stable across renders) with the v4-compat HOC.
|
|
const IndexR = withRouterCompat(Index);
|
|
const SubscriptionsR = withRouterCompat(Subscriptions);
|
|
const NewSubscriptionR = withRouterCompat(NewSubscription);
|
|
const ViewSubscriptionR = withRouterCompat(ViewSubscription);
|
|
const EditSubscriptionR = withRouterCompat(EditSubscription);
|
|
const ProfileR = withRouterCompat(Profile);
|
|
const StatusR = withRouterCompat(Status);
|
|
const FiresMapR = withRouterCompat(FiresMap);
|
|
const ZonesMapR = withRouterCompat(ZonesMap);
|
|
const FiresR = withRouterCompat(Fires);
|
|
const AuthR = withRouterCompat(Auth);
|
|
const SignupR = withRouterCompat(Signup);
|
|
const LoginR = withRouterCompat(Login);
|
|
const LogoutR = withRouterCompat(Logout);
|
|
const VerifyEmailR = withRouterCompat(VerifyEmail);
|
|
const RecoverPasswordR = withRouterCompat(RecoverPassword);
|
|
const ResetPasswordR = withRouterCompat(ResetPassword);
|
|
|
|
const App = props => (
|
|
/* https://react.i18next.com/components/i18nextprovider.html */
|
|
<div>
|
|
{props.i18nReady.get() &&
|
|
<ErrorBoundary>
|
|
<I18nextProvider i18n={i18n}>
|
|
<ErrorBoundary appName={i18n.t('AppNameFull')} title={i18n.t('general-error-title')} subTitle={i18n.t('general-error-description')}>
|
|
<HistoryRouter history={history}>
|
|
<LocationListener>
|
|
{ !props.loading &&
|
|
<div className="App">
|
|
<Helmet>
|
|
<meta charSet="utf-8" />
|
|
<title>{i18n.t('AppName')}</title>
|
|
<meta name="description" content={`${i18n.t('AppDescrip')}: ${i18n.t('AppDescripLong')}`} />
|
|
<link rel="alternate" href={`https://fires.comunes.org${history.location.pathname}`} hrefLang="en" />
|
|
<link rel="alternate" href={`https://fuegos.comunes.org${history.location.pathname}`} hrefLang="es" />
|
|
</Helmet>
|
|
<Navigation {...props} />
|
|
<ReSendEmail {...props} />
|
|
|
|
<Container>
|
|
<Routes>
|
|
<Route path="/" element={<IndexR {...props} />} />
|
|
<Route path="/subscriptions" element={<Authenticated authenticated={props.authenticated}><SubscriptionsR {...props} /></Authenticated>} />
|
|
<Route path="/subscriptions/new" element={<Authenticated authenticated={props.authenticated}><NewSubscriptionR {...props} /></Authenticated>} />
|
|
<Route path="/subscriptions/:_id" element={<Authenticated authenticated={props.authenticated}><ViewSubscriptionR {...props} /></Authenticated>} />
|
|
<Route path="/subscriptions/:_id/edit" element={<Authenticated authenticated={props.authenticated}><EditSubscriptionR {...props} /></Authenticated>} />
|
|
<Route path="/profile" element={<Authenticated authenticated={props.authenticated}><ProfileR {...props} /></Authenticated>} />
|
|
<Route path="/status" element={<Authenticated authenticated={props.authenticated}><StatusR {...props} /></Authenticated>} />
|
|
<Route path="/fires" element={<FiresMapR industries={false} {...props} />} />
|
|
<Route path="/zones" element={<ZonesMapR {...props} />} />
|
|
|
|
{/* v6 has no regex params; `:type` is validated inside Fires. */}
|
|
<Route path="/fire/:type/:id" element={<FiresR {...props} />} />
|
|
<Route path="/fire/:id" element={<FiresR {...props} />} />
|
|
<Route path="/auth/:token" element={<Public authenticated={props.authenticated}><AuthR {...props} /></Public>} />
|
|
<Route path="/signup" element={<Public authenticated={props.authenticated}><SignupR {...props} /></Public>} />
|
|
<Route path="/login" element={<Public authenticated={props.authenticated}><LoginR {...props} /></Public>} />
|
|
<Route path="/logout" element={<LogoutR {...props} />} />
|
|
<Route path="/sandbox" element={<Sandbox {...props} />} />
|
|
<Route path="/error" element={<TestError {...props} />} />
|
|
|
|
<Route path="/verify-email/:token" element={<VerifyEmailR />} />
|
|
<Route path="/recover-password" element={<RecoverPasswordR />} />
|
|
<Route path="/reset-password/:token" element={<ResetPasswordR />} />
|
|
<Route path="/terms" element={<Terms />} />
|
|
<Route path="/privacy" element={<Privacy />} />
|
|
<Route path="/license" element={<License />} />
|
|
<Route path="/credits" element={<Credits />} />
|
|
<Route path="/about" element={<About />} />
|
|
|
|
<Route path="*" element={<NotFound />} />
|
|
</Routes>
|
|
</Container>
|
|
<Footer />
|
|
<Reconnect {...props} />
|
|
<Feedback {...props} />
|
|
{props.i18nReady.get() && <CookieConsent /> }
|
|
</div>}
|
|
</LocationListener>
|
|
</HistoryRouter>
|
|
</ErrorBoundary>
|
|
</I18nextProvider>
|
|
</ErrorBoundary> }
|
|
</div>
|
|
);
|
|
|
|
App.defaultProps = {
|
|
userId: '',
|
|
emailAddress: ''
|
|
};
|
|
|
|
App.propTypes = {
|
|
loading: PropTypes.bool.isRequired,
|
|
i18nReady: PropTypes.object.isRequired,
|
|
userId: PropTypes.string,
|
|
emailAddress: PropTypes.string,
|
|
emailVerified: PropTypes.bool.isRequired
|
|
};
|
|
|
|
const getUserName = name => ({
|
|
string: name,
|
|
object: `${name.first} ${name.last}`
|
|
}[typeof name]);
|
|
|
|
export default withTracker(() => {
|
|
const loggingIn = Meteor.loggingIn();
|
|
const user = Meteor.user();
|
|
const userId = Meteor.userId();
|
|
const loading = !i18nReady.get();
|
|
const name = user && user.profile && user.profile.name && getUserName(user.profile.name);
|
|
const emailAddress = user && user.emails && user.emails[0].address;
|
|
return {
|
|
loading,
|
|
loggingIn,
|
|
i18nReady,
|
|
authenticated: !loggingIn && !!userId,
|
|
name: name || emailAddress,
|
|
roles: (user && user.roles) || [],
|
|
userId,
|
|
emailAddress,
|
|
emailVerified: user && user.emails ? user && user.emails && user.emails[0].verified : true
|
|
};
|
|
})(App);
|