From cae28bdc55fe0270057b685e6db42033bcdaa575 Mon Sep 17 00:00:00 2001 From: Lee Allen Date: Mon, 19 Feb 2018 20:00:09 -0500 Subject: [PATCH] Upgrade react router v3 to react router dom --- .../components/RegistrationConfirmed.jsx | 2 +- package.json | 4 +- src/reducers/index.js | 4 +- src/reducers/persist.js | 17 +++++ src/reducers/persist.spec.js | 30 ++++++++ src/routes/AuthorizedRoute.jsx | 64 +++++++++++++++++ src/routes/index.jsx | 38 +++++----- src/selectors/isUserAuthorized.js | 15 ++++ src/selectors/isUserAuthorized.spec.js | 40 +++++++++++ src/utils/.keep | 0 yarn.lock | 70 ++++++++++++++----- 11 files changed, 243 insertions(+), 41 deletions(-) create mode 100644 src/reducers/persist.js create mode 100644 src/reducers/persist.spec.js create mode 100644 src/routes/AuthorizedRoute.jsx create mode 100644 src/selectors/isUserAuthorized.js create mode 100644 src/selectors/isUserAuthorized.spec.js create mode 100644 src/utils/.keep 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"