diff --git a/modules/authentication/components/RegistrationConfirmed.jsx b/modules/authentication/components/RegistrationConfirmed.jsx
index 9db7f9e..7781ac1 100644
--- a/modules/authentication/components/RegistrationConfirmed.jsx
+++ b/modules/authentication/components/RegistrationConfirmed.jsx
@@ -5,7 +5,7 @@ import React, {
import PropTypes from 'prop-types';
import {
Link
-} from 'react-router';
+} from 'react-router-dom';
import routes from '../../../src/constants/routes';
import services from '../services';
diff --git a/package.json b/package.json
index 9ff63e0..99f7e60 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"dependencies": {
"axios": "^0.17.1",
"es6-promise": "^4.2.4",
+ "history": "^4.7.2",
"immutable": "^3.8.2",
"keymirror": "^0.1.1",
"normalize.css": "^5.0.0",
@@ -37,13 +38,14 @@
"react-dom": "^15.4.1",
"react-immutable-proptypes": "^2.1.0",
"react-redux": "^5.0.1",
- "react-router": "^3.0.0",
+ "react-router-dom": "^4.2.2",
"redux": "^3.6.0",
"redux-devtools-extension": "^2.13.0",
"redux-immutable": "^4.0.0",
"redux-persist-cookie-storage": "^0.3.0",
"redux-persist-immutable": "^4.3.1",
"redux-thunk": "^2.1.0",
+ "reselect": "^3.0.1",
"sanitize.css": "^4.1.0"
},
"devDependencies": {
diff --git a/src/reducers/index.js b/src/reducers/index.js
index be6d237..0ee8e71 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -1,8 +1,10 @@
import { combineReducers } from 'redux-immutable';
import authentication from '../../modules/authentication/reducer';
import helloWorld from './helloWorldReducer';
+import persist from './persist';
export default combineReducers({
authentication,
- helloWorld
+ helloWorld,
+ persist
});
diff --git a/src/reducers/persist.js b/src/reducers/persist.js
new file mode 100644
index 0000000..f824cba
--- /dev/null
+++ b/src/reducers/persist.js
@@ -0,0 +1,17 @@
+import Immutable from 'immutable';
+import { REHYDRATE } from 'redux-persist-immutable/constants';
+
+const INITIAL_STATE = Immutable.fromJS({
+ rehydrated: false
+});
+
+function reducer(state = INITIAL_STATE, action) {
+ switch (action.type) {
+ case REHYDRATE:
+ return state.set('rehydrated', true);
+ default:
+ return state;
+ }
+}
+
+export default reducer;
diff --git a/src/reducers/persist.spec.js b/src/reducers/persist.spec.js
new file mode 100644
index 0000000..7651f79
--- /dev/null
+++ b/src/reducers/persist.spec.js
@@ -0,0 +1,30 @@
+import Immutable from 'immutable';
+import { REHYDRATE } from 'redux-persist-immutable/constants';
+
+import persistReducer from './persist';
+
+describe('reducers/persist', function() {
+ const INITIAL_STATE = Immutable.fromJS({
+ rehydrated: false
+ });
+
+ describe(REHYDRATE, function() {
+ let nextState;
+
+ beforeEach(function() {
+ nextState = persistReducer(INITIAL_STATE, {
+ type: REHYDRATE
+ });
+ });
+
+ it('sets rehydration to true', function() {
+ expect(nextState.get('rehydrated')).to.be.true;
+ });
+
+ it('returns a default initial state', function() {
+ nextState = persistReducer(undefined, {});// eslint-disable-line no-undefined
+
+ expect(nextState).to.deep.equal(INITIAL_STATE);
+ });
+ });
+});
diff --git a/src/routes/AuthorizedRoute.jsx b/src/routes/AuthorizedRoute.jsx
new file mode 100644
index 0000000..dfc9100
--- /dev/null
+++ b/src/routes/AuthorizedRoute.jsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ Redirect,
+ Route
+} from 'react-router-dom';
+import { connect } from 'react-redux';
+
+import isUserAuthorized from '../selectors/isUserAuthorized';
+import routes from '../constants/routes';
+
+const propTypes = {
+ component: PropTypes.func.isRequired,
+ exact: PropTypes.bool,
+ hydrated: PropTypes.bool.isRequired,
+ isApproved: PropTypes.bool,
+ isAuthorized: PropTypes.bool.isRequired,
+ location: PropTypes.object.isRequired,
+ path: PropTypes.string.isRequired
+};
+
+function AuthorizedRoute(props) {
+ const {
+ component: Component,
+ exact,
+ hydrated,
+ isAuthorized,
+ location,
+ path
+ } = props;
+
+ if (!hydrated) {
+ return
Loading...
;
+ }
+
+ return (
+ {
+ return isAuthorized ?
+ :
+ ;
+ }}
+ />
+ );
+}
+
+AuthorizedRoute.propTypes = propTypes;
+
+function mapStateToProps(state) {
+ return {
+ hydrated: state.getIn(['persist', 'rehydrated']),
+ isAuthorized: isUserAuthorized(state)
+ };
+}
+
+export default connect(mapStateToProps)(AuthorizedRoute);
diff --git a/src/routes/index.jsx b/src/routes/index.jsx
index bee480f..057859e 100644
--- a/src/routes/index.jsx
+++ b/src/routes/index.jsx
@@ -1,41 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
- browserHistory as history,
- IndexRoute,
Route,
- Router
-} from 'react-router';
+ Router,
+ Switch
+} from 'react-router-dom';
+import { createBrowserHistory } from 'history';
import AppContainer from '../containers/AppContainer';
import ForgotPassword from '../../modules/authentication/containers/ForgotPassword';
import Register from '../../modules/authentication/containers/Register';
-import RegistrationConfirmed from '../../modules/authentication/containers/RegistrationConfirmed';
+// import RegistrationConfirmed from '../../modules/authentication/containers/RegistrationConfirmed';
import ResetPassword from '../../modules/authentication/containers/ResetPassword';
import SignIn from '../../modules/authentication/containers/SignIn';
-import authenticationService from '../../modules/authentication/services';
+// import authenticationService from '../../modules/authentication/services';
import routes from '../constants/routes';
+import AuthorizedRoute from './AuthorizedRoute';
+
+const history = createBrowserHistory();
const propTypes = {
store: PropTypes.object.isRequired
};
-function Routes({ store }) {
+function Routes() {
return (
-
-
-
-
-
- {
- return authenticationService.prehydrateStore(store)(null, null, callback);
- }}
- />
-
-
+
+
+
+
+
+
+
);
}
diff --git a/src/selectors/isUserAuthorized.js b/src/selectors/isUserAuthorized.js
new file mode 100644
index 0000000..07d3ea7
--- /dev/null
+++ b/src/selectors/isUserAuthorized.js
@@ -0,0 +1,15 @@
+import { createSelector } from 'reselect';
+import Immutable from 'immutable';
+import { VALID_TOKEN_INFO_FIELDS } from '../../modules/authentication/constants';
+
+function getTokenInfoKeys(state) {
+ return state.getIn(['authentication', 'tokenInfo'], Immutable.Map());
+}
+
+function isUserAuthorized(tokenInfo) {
+ return VALID_TOKEN_INFO_FIELDS.filter((field) => {
+ return tokenInfo.get(field, null);
+ }).length === VALID_TOKEN_INFO_FIELDS.length;
+}
+
+export default createSelector(getTokenInfoKeys, isUserAuthorized);
diff --git a/src/selectors/isUserAuthorized.spec.js b/src/selectors/isUserAuthorized.spec.js
new file mode 100644
index 0000000..71c09fc
--- /dev/null
+++ b/src/selectors/isUserAuthorized.spec.js
@@ -0,0 +1,40 @@
+import Immutable from 'immutable';
+import isUserAuthorized from './isUserAuthorized';
+import { VALID_TOKEN_INFO_FIELDS } from '../../modules/authentication/constants';
+
+describe('selectors/isUserAuthorized', function() {
+ it('returns true if the user has values in the valid token info fields', function() {
+ const validInfoFields = VALID_TOKEN_INFO_FIELDS.reduce((fields, field) => {
+ fields[field] = faker.lorem.word();
+ return fields;
+ }, {});
+
+ const state = Immutable.fromJS({
+ authentication: {
+ tokenInfo: validInfoFields
+ }
+ });
+
+ const isAuthorized = isUserAuthorized(state);
+
+ expect(isAuthorized).to.be.true;
+ });
+
+ it('returns false if any of the fields are missing a value', function() {
+ const randomIndex = Math.floor(Math.random() * VALID_TOKEN_INFO_FIELDS.length);
+ const validInfoFields = VALID_TOKEN_INFO_FIELDS.reduce((fields, field, index) => {
+ fields[field] = index !== randomIndex ? faker.lorem.word() : null;
+ return fields;
+ }, {});
+
+ const state = Immutable.fromJS({
+ authentication: {
+ tokenInfo: validInfoFields
+ }
+ });
+
+ const isAuthorized = isUserAuthorized(state);
+
+ expect(isAuthorized).to.be.false;
+ });
+});
diff --git a/src/utils/.keep b/src/utils/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/yarn.lock b/yarn.lock
index 5440c1b..7f1046f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3096,23 +3096,28 @@ he@1.1.x:
version "1.1.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.0.tgz#29319d49beec13a9b1f3c4f9b2a6dde4859bb2a7"
-history@^3.0.0:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/history/-/history-3.2.1.tgz#71c7497f4e6090363d19a6713bb52a1bfcdd99aa"
+history@^4.7.2:
+ version "4.7.2"
+ resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
dependencies:
invariant "^2.2.1"
loose-envify "^1.2.0"
- query-string "^4.2.2"
+ resolve-pathname "^2.2.0"
+ value-equal "^0.4.0"
warning "^3.0.0"
hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
-hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
+hoist-non-react-statics@^1.0.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
+hoist-non-react-statics@^2.3.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
+
home-or-tmp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -3346,12 +3351,18 @@ interpret@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c"
-invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.0.0, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
loose-envify "^1.0.0"
+invariant@^2.2.1:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688"
+ dependencies:
+ loose-envify "^1.0.0"
+
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
@@ -4034,13 +4045,13 @@ longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.0.tgz#6b26248c42f6d4fa4b0d8542f78edfcde35642a8"
dependencies:
js-tokens "^2.0.0"
-loose-envify@^1.3.1:
+loose-envify@^1.2.0, loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
dependencies:
@@ -5536,7 +5547,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
-prop-types@^15.6.0:
+prop-types@^15.5.4, prop-types@^15.6.0:
version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
dependencies:
@@ -5589,7 +5600,7 @@ qs@~6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442"
-query-string@^4.1.0, query-string@^4.2.2:
+query-string@^4.1.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.2.3.tgz#9f27273d207a25a8ee4c7b8c74dcd45d556db822"
dependencies:
@@ -5682,14 +5693,27 @@ react-redux@^5.0.1:
lodash-es "^4.2.0"
loose-envify "^1.1.0"
-react-router@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.0.0.tgz#3f313e4dbaf57048c48dd0a8c3cac24d93667dff"
+react-router-dom@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d"
dependencies:
- history "^3.0.0"
- hoist-non-react-statics "^1.2.0"
- invariant "^2.2.1"
- loose-envify "^1.2.0"
+ history "^4.7.2"
+ invariant "^2.2.2"
+ loose-envify "^1.3.1"
+ prop-types "^15.5.4"
+ react-router "^4.2.0"
+ warning "^3.0.0"
+
+react-router@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986"
+ dependencies:
+ history "^4.7.2"
+ hoist-non-react-statics "^2.3.0"
+ invariant "^2.2.2"
+ loose-envify "^1.3.1"
+ path-to-regexp "^1.7.0"
+ prop-types "^15.5.4"
warning "^3.0.0"
react@^15.4.1:
@@ -6016,6 +6040,10 @@ requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+reselect@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
+
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -6034,6 +6062,10 @@ resolve-from@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
+resolve-pathname@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879"
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -7030,6 +7062,10 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
+value-equal@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
+
vary@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140"