From dff0778da5ec6e4fbd8943c70f49ebd35978ead5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Sep 2020 17:40:13 -0400 Subject: [PATCH 01/20] fix(auth0-react): Make login/logout work with auth0-react. --- lib/components/app/responsive-webapp.js | 2 +- lib/components/user/nav-login-button-auth0.js | 15 ++++--- package.json | 1 + yarn.lock | 44 ++++++++++++++++++- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 7dff8032d..a86766ee0 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -1,3 +1,4 @@ +import { Auth0Provider } from '@auth0/auth0-react' import { ConnectedRouter } from 'connected-react-router' import { createHashHistory } from 'history' import isEqual from 'lodash.isequal' @@ -6,7 +7,6 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' import { connect } from 'react-redux' import { Route, Switch, withRouter } from 'react-router' -import { Auth0Provider } from 'use-auth0-hooks' import PrintLayout from './print-layout' import * as authActions from '../../actions/auth' diff --git a/lib/components/user/nav-login-button-auth0.js b/lib/components/user/nav-login-button-auth0.js index 415d30b98..1b83387c3 100644 --- a/lib/components/user/nav-login-button-auth0.js +++ b/lib/components/user/nav-login-button-auth0.js @@ -1,5 +1,5 @@ +import { useAuth0 } from '@auth0/auth0-react' import React from 'react' -import { useAuth } from 'use-auth0-hooks' import { URL_ROOT } from '../../util/constants' import NavLoginButton from './nav-login-button' @@ -13,15 +13,18 @@ const NavLoginButtonAuth0 = ({ links, style }) => { - const { isAuthenticated, login, logout, user } = useAuth() + const { isAuthenticated, loginWithRedirect, logout, user } = useAuth0() // On login, preserve the current trip query if any. - // TODO: check that URLs are whitelisted. All trip query URLs in /#/ are. + // After login, redirect to a whitelisted URL in the Auth0 dashboard. const afterLoginPath = '/#/signedin' - const handleLogin = () => login({ - redirect_uri: `${URL_ROOT}${afterLoginPath}`, + const handleLogin = () => loginWithRedirect({ + redirectUri: `${URL_ROOT}${afterLoginPath}`, appState: {urlHash: window.location.hash} // The part of href from #/?, e.g. #/?ui_activeSearch=... }) + const handleLogout = () => logout({ + returnTo: URL_ROOT + }) // On logout, it is better to "clear" the screen, so // return to redirectUri set in (no specific event handler). @@ -32,7 +35,7 @@ const NavLoginButtonAuth0 = ({ id={id} links={links} onSignInClick={handleLogin} - onSignOutClick={logout} + onSignOutClick={handleLogout} profile={isAuthenticated ? user : null} style={style} /> diff --git a/package.json b/package.json index e816419cf..874fcf3bb 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ }, "homepage": "https://github.com/opentripplanner/otp-react-redux#readme", "dependencies": { + "@auth0/auth0-react": "^1.1.0", "@opentripplanner/base-map": "^1.0.1", "@opentripplanner/core-utils": "^1.2.1", "@opentripplanner/endpoints-overlay": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index 78bbf2c4e..92623bed3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,26 @@ # yarn lockfile v1 +"@auth0/auth0-react@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-1.1.0.tgz#deeb5f1872f7c0d813cdb9115dfdf0a70a34e1c2" + integrity sha512-FqRzdSkraM2GFOl8NhdlpocALxCd9oNRxiPpIV0Lm5ophHUQRXaE8raTGjnPhHleJGJMw9XoEJ3ye9mjbkvweQ== + dependencies: + "@auth0/auth0-spa-js" "^1.12.1" + +"@auth0/auth0-spa-js@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.12.1.tgz#791cdda722afa25f4c8879b93b8d7ab54a26e4c1" + integrity sha512-YdXtA1T2wK4iNG79VlGS/CPfNNezS1nqZURerj71jKSf8ICVKmvJgUNXFEZEwNNU/KFdCCPCHd5wMeWLDyEILw== + dependencies: + abortcontroller-polyfill "^1.5.0" + browser-tabs-lock "^1.2.9" + core-js "^3.6.5" + es-cookie "^1.3.2" + fast-text-encoding "^1.0.3" + promise-polyfill "^8.1.3" + unfetch "^4.1.0" + "@auth0/auth0-spa-js@^1.2.4": version "1.7.0" resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.7.0.tgz#4d19a7271bb031da4f696a2209d4bfe03dc857dd" @@ -1949,6 +1969,11 @@ abortcontroller-polyfill@^1.4.0: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4" integrity sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA== +abortcontroller-polyfill@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.5.0.tgz#2c562f530869abbcf88d949a2b60d1d402e87a7c" + integrity sha512-O6Xk757Jb4o0LMzMOMdWvxpHWrQzruYBaUruFaIOfAQRnWFxfdXYobw12jrVHGtoXk6WiiyYzc0QWN9aL62HQA== + accounting@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/accounting/-/accounting-0.4.1.tgz#87dd4103eff7f4460f1e186f5c677ed6cf566883" @@ -3574,6 +3599,13 @@ browser-tabs-lock@^1.2.8: resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.8.tgz#5642cad86df41111065a44c3ba744ce19b2af72f" integrity sha512-Xrj33YUTltPDoGrD1KnaAn5ZuxnnlJFcIW9srVTPHbMNPd9MlcnBCWaGV0STlvGKu8Ok0ad5qxyx5sIwFTr/Ig== +browser-tabs-lock@^1.2.9: + version "1.2.11" + resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.11.tgz#59d72c6653b827685b54fca1c0e6eb4aee08ef52" + integrity sha512-R0xXMzQ8CU0v52zSFn3EDGIfsjteMFJ6oIhhZK6+Vhz5NYzSxCP2epLD9PN7e2HprQl2QTReFptwp6c9tdOF0g== + dependencies: + lodash ">=4.17.19" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -4727,7 +4759,7 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^3.1.4, core-js@^3.6.4: +core-js@^3.1.4, core-js@^3.6.4, core-js@^3.6.5: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== @@ -6490,6 +6522,11 @@ fast-text-encoding@^1.0.1: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz#ff1ad5677bde049e0f8656aa6083a7ef2c5836e2" integrity sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw== +fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + fastparse@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" @@ -9560,6 +9597,11 @@ lodash@4.17.14: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== +lodash@>=4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" From 411b0bb3eec4c44734df9759cb261752b77a0753 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Sep 2020 18:23:33 -0400 Subject: [PATCH 02/20] refactor: Adapt rest of code to auth0-react. --- lib/actions/auth.js | 2 +- lib/actions/user.js | 4 +-- lib/components/user/saved-trip-list.js | 4 +-- lib/components/user/saved-trip-screen.js | 4 +-- lib/components/user/user-account-screen.js | 8 +++--- .../user/with-logged-in-user-support.js | 28 +++++++++++-------- 6 files changed, 28 insertions(+), 22 deletions(-) diff --git a/lib/actions/auth.js b/lib/actions/auth.js index cf5478e0f..85c924625 100644 --- a/lib/actions/auth.js +++ b/lib/actions/auth.js @@ -30,7 +30,7 @@ export function showLoginError (err) { /** * This function is called by the Auth0Provider component, with the described parameter(s), * after the user signs in. - * @param {Object} appState The state that was stored when calling useAuth().login(). + * @param {Object} appState The state that was stored when calling useAuth0().login(). */ export function processSignIn (appState) { return function (dispatch, getState) { diff --git a/lib/actions/user.js b/lib/actions/user.js index b5c094073..2afa386d1 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -49,13 +49,13 @@ export function fetchUserMonitoredTrips (accessToken) { /** * Fetches user preferences to state.user, or set initial values under state.user if no user has been loaded. */ -export function fetchOrInitializeUser (auth) { +export function fetchOrInitializeUser (auth0, accessToken) { return async function (dispatch, getState) { const { otp } = getState() const { otp_middleware: otpMiddleware = null } = otp.config.persistence if (otpMiddleware) { - const { accessToken, user: authUser } = auth + const { user: authUser } = auth0 const { data: user, status: fetchUserStatus } = await fetchUser(otpMiddleware, accessToken) // Beware! On AWS API gateway, if a user is not found in the middleware diff --git a/lib/components/user/saved-trip-list.js b/lib/components/user/saved-trip-list.js index 9969a5d3c..dce789737 100644 --- a/lib/components/user/saved-trip-list.js +++ b/lib/components/user/saved-trip-list.js @@ -1,7 +1,7 @@ +import { withAuthenticationRequired } from '@auth0/auth0-react' import React, { Component } from 'react' import { Button, ButtonGroup } from 'react-bootstrap' import { connect } from 'react-redux' -import { withLoginRequired } from 'use-auth0-hooks' import * as uiActions from '../../actions/ui' import DesktopNav from '../app/desktop-nav' @@ -80,6 +80,6 @@ const mapDispatchToProps = { } export default withLoggedInUserSupport( - withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripList)), + withAuthenticationRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripList)), true ) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 70932da42..e7650de3d 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -1,6 +1,6 @@ +import { withAuthenticationRequired } from '@auth0/auth0-react' import React, { Component } from 'react' import { connect } from 'react-redux' -import { withLoginRequired } from 'use-auth0-hooks' import * as uiActions from '../../actions/ui' import * as userActions from '../../actions/user' @@ -229,6 +229,6 @@ const mapDispatchToProps = { } export default withLoggedInUserSupport( - withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen)), + withAuthenticationRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen)), true ) diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index 0c77fa85d..9495856c3 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -1,7 +1,7 @@ +import { withAuthenticationRequired } from '@auth0/auth0-react' import clone from 'lodash/cloneDeep' import React, { Component } from 'react' import { connect } from 'react-redux' -import { withLoginRequired } from 'use-auth0-hooks' import { routeTo } from '../../actions/ui' import { createOrUpdateUser } from '../../actions/user' @@ -88,12 +88,12 @@ class UserAccountScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { auth, loggedInUser } = this.props + const { auth0, loggedInUser } = this.props const { userData } = this.state let formContents if (isNewUser(loggedInUser)) { - if (!auth.user.email_verified) { + if (!auth0.user.email_verified) { // Check and prompt for email verification first to avoid extra user wait. formContents = } else { @@ -144,6 +144,6 @@ const mapDispatchToProps = { } export default withLoggedInUserSupport( - withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(UserAccountScreen)), + withAuthenticationRequired(connect(mapStateToProps, mapDispatchToProps)(UserAccountScreen)), true ) diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index 4b60ee4d7..70434dee0 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -1,6 +1,6 @@ +import { withAuth0 } from '@auth0/auth0-react' import React, { Component } from 'react' import { connect } from 'react-redux' -import { withAuth } from 'use-auth0-hooks' import * as userActions from '../../actions/user' import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants' @@ -37,26 +37,32 @@ export default function withLoggedInUserSupport (WrappedComponent, requireLogged * Upon completion (or if no user is logged in or if auth is disabled), it renders children. */ class UserLoaderScreen extends Component { - componentDidUpdate () { - const { auth, fetchOrInitializeUser, loggedInUser } = this.props + async componentDidUpdate () { + const { auth0, fetchOrInitializeUser, loggedInUser } = this.props - // Once accessToken is available, proceed to fetch or initialize loggedInUser. - if (auth && auth.accessToken && !loggedInUser) { - fetchOrInitializeUser(auth) + if (auth0 && auth0.isAuthenticated && !loggedInUser) { + try { + // Get the token + const token = await auth0.getAccessTokenSilently() + // Once accessToken is available, proceed to fetch or initialize loggedInUser. + fetchOrInitializeUser(auth0, token) + } catch (error) { + alert('Error obtaining an authorization token.') + } } } render () { - const { auth, children, loggedInUser, requireLoggedInUser } = this.props + const { auth0, children, loggedInUser, requireLoggedInUser } = this.props - if (auth) { - if (requireLoggedInUser && auth.isAuthenticated && !loggedInUser) { + if (auth0) { + if (requireLoggedInUser && auth0.isAuthenticated && !loggedInUser) { // Display a hint while fetching user data for logged in user (from componentDidMount). // Don't display this if loggedInUser is already available. // TODO: Improve this screen. return } else { - return renderChildrenWithProps(children, { auth }) + return renderChildrenWithProps(children, { auth0 }) } } @@ -76,7 +82,7 @@ const mapDispatchToProps = { fetchOrInitializeUser: userActions.fetchOrInitializeUser } -const UserLoaderScreenWithAuth = withAuth( +const UserLoaderScreenWithAuth = withAuth0( connect(mapStateToProps, mapDispatchToProps)(UserLoaderScreen), { audience: AUTH0_AUDIENCE, From 390f4856502a13e97ea6ead00115fdd81d85e13d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Sep 2020 18:30:10 -0400 Subject: [PATCH 03/20] build(deps): Remove use-auth0-hooks. --- package.json | 1 - yarn.lock | 63 ++++------------------------------------------------ 2 files changed, 4 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 874fcf3bb..13566a6c2 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "redux-logger": "^2.7.4", "redux-thunk": "^2.3.0", "transitive-js": "^0.13.3", - "use-auth0-hooks": "^0.7.0", "velocity-react": "^1.3.3" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 92623bed3..4f40f9d42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,19 +22,6 @@ promise-polyfill "^8.1.3" unfetch "^4.1.0" -"@auth0/auth0-spa-js@^1.2.4": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.7.0.tgz#4d19a7271bb031da4f696a2209d4bfe03dc857dd" - integrity sha512-OAeNUI0bTBdk3cIWJMBLgf4B1DvyPXmbBBDQI7EpGR3pU0/+9d19sDCPC1b9epp6Aa7pnd4OR5u9JIQCc3eSKw== - dependencies: - abortcontroller-polyfill "^1.4.0" - browser-tabs-lock "^1.2.8" - core-js "^3.6.4" - es-cookie "^1.3.2" - fast-text-encoding "^1.0.1" - promise-polyfill "^8.1.3" - unfetch "^4.1.0" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -1295,21 +1282,7 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/core-utils@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-2.1.0.tgz#ab7271a2b560168b21b0dd26c78532f24fd031f5" - integrity sha512-sQ3oiZB7f01kCVuTj5SzqsPe4uchupNbla+onLWH+aO3wH+OLZNuLRZZ/7oFFFnvpcFBakyg4TePPxyyEyJlMA== - dependencies: - "@mapbox/polyline" "^1.1.0" - "@turf/along" "^6.0.1" - bowser "^2.7.0" - lodash.isequal "^4.5.0" - moment "^2.24.0" - moment-timezone "^0.5.27" - prop-types "^15.7.2" - qs "^6.9.1" - -"@opentripplanner/core-utils@^2.1.1": +"@opentripplanner/core-utils@^2.1.0", "@opentripplanner/core-utils@^2.1.1": version "2.1.2" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-2.1.2.tgz#a19d5d788704f0a6c2aece5206a2c8997c251d16" integrity sha512-i+ADDdHhC+oJNYPrk7o9eu3F/2IMMZ5YAOXR2QGBJbS97kQOYeTAN4pVGfWKTm7ClTTIBG9/NDnVyQMzGuYInw== @@ -1964,11 +1937,6 @@ abbrev@1, abbrev@~1.1.1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abortcontroller-polyfill@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4" - integrity sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA== - abortcontroller-polyfill@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.5.0.tgz#2c562f530869abbcf88d949a2b60d1d402e87a7c" @@ -3594,11 +3562,6 @@ browser-resolve@^1.11.0, browser-resolve@^1.11.3, browser-resolve@^1.7.0: dependencies: resolve "1.1.7" -browser-tabs-lock@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.8.tgz#5642cad86df41111065a44c3ba744ce19b2af72f" - integrity sha512-Xrj33YUTltPDoGrD1KnaAn5ZuxnnlJFcIW9srVTPHbMNPd9MlcnBCWaGV0STlvGKu8Ok0ad5qxyx5sIwFTr/Ig== - browser-tabs-lock@^1.2.9: version "1.2.11" resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.11.tgz#59d72c6653b827685b54fca1c0e6eb4aee08ef52" @@ -4759,7 +4722,7 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^3.1.4, core-js@^3.6.4, core-js@^3.6.5: +core-js@^3.1.4, core-js@^3.6.5: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== @@ -6517,11 +6480,6 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-text-encoding@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz#ff1ad5677bde049e0f8656aa6083a7ef2c5836e2" - integrity sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw== - fast-text-encoding@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" @@ -9597,16 +9555,11 @@ lodash@4.17.14: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== -lodash@>=4.17.19: +lodash@>=4.17.19, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== - log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -12904,7 +12857,7 @@ query-string@^4.1.0, query-string@^4.2.3: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^6.8.2, query-string@^6.8.3: +query-string@^6.8.2: version "6.13.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad" integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA== @@ -15932,14 +15885,6 @@ url@^0.11.0, url@~0.11.0: punycode "1.3.2" querystring "0.2.0" -use-auth0-hooks@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/use-auth0-hooks/-/use-auth0-hooks-0.7.0.tgz#879a930ee36b893c91616ffa9b609ed8f685ff57" - integrity sha512-vL+G4jDu0nQduaMwg1+TMdLORpMgMQx72iPq0LGiEw0tXPmLmuIwi/7e7yrGJu+LuwZfD3g1qbI6Pjb+bDV7nw== - dependencies: - "@auth0/auth0-spa-js" "^1.2.4" - query-string "^6.8.3" - use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From 2c962c4ef9cee0d67a6665abd977a98b68458e65 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 21 Sep 2020 12:27:11 -0400 Subject: [PATCH 04/20] fix(login): Support login from protected URLs. Refactor processSignIn to use returnTo. Refactor accordingly. --- lib/actions/auth.js | 36 ++++++++----------- lib/components/app/responsive-webapp.js | 4 +-- lib/components/user/nav-login-button-auth0.js | 8 ++--- lib/components/user/saved-trip-list.js | 8 ++++- lib/components/user/saved-trip-screen.js | 8 ++++- lib/components/user/user-account-screen.js | 8 ++++- lib/util/constants.js | 3 -- 7 files changed, 39 insertions(+), 36 deletions(-) diff --git a/lib/actions/auth.js b/lib/actions/auth.js index 85c924625..039139469 100644 --- a/lib/actions/auth.js +++ b/lib/actions/auth.js @@ -1,4 +1,4 @@ -import { push } from 'connected-react-router' +import { replace, push } from 'connected-react-router' import { setPathBeforeSignIn } from '../actions/user' @@ -30,30 +30,22 @@ export function showLoginError (err) { /** * This function is called by the Auth0Provider component, with the described parameter(s), * after the user signs in. - * @param {Object} appState The state that was stored when calling useAuth0().login(). + * @param {Object} appState The state stored when calling useAuth0().loginWithRedirect + * or a withAuhenticationRequired-enabled component. */ export function processSignIn (appState) { return function (dispatch, getState) { - if (appState && appState.urlHash) { - // At this stage after login, Auth0 has already redirected to /signedin (Auth0-whitelisted) - // which shows the AfterLoginScreen. - // - // Here, we save the URL hash prior to login (contains a combination of itinerary search, stop/trip view, etc.), - // so that the AfterLoginScreen can redirect back there when logged-in user info is fetched. - // (For routing, it is easier to deal with the path without the hash sign.) - const hashIndex = appState.urlHash.indexOf('#') - const urlHashWithoutHash = hashIndex >= 0 - ? appState.urlHash.substr(hashIndex + 1) - : '/' - dispatch(setPathBeforeSignIn(urlHashWithoutHash)) - } else if (appState && appState.returnTo) { - // TODO: Handle other after-login situations. - // Note that when redirecting from a login-protected (e.g. account) page while logged out, - // then returnTo is set by Auth0 to this object format: - // { - // pathname: "/" - // query: { ... } - // } + if (appState && appState.returnTo) { + // Remove URL parameters that were added by auth0-react + // (see https://github.com/auth0/auth0-react/blob/adac2e810d4f6d33253cb8b2016fcedb98a3bc16/examples/cra-react-router/src/index.tsx#L7) + window.history.replaceState({}, '', window.location.pathname) + + // Here, we add to the redux state the URL route (portion after # that contains the route/page name e.g. /account, + // and includes a combination of itinerary search, stop/trip view, etc.) that was passed to appState prior to login, + // and we redirect to the "/signedin" route, where the AfterLoginScreen + // will fetch the user data and, upon fetched, redirect back to appState.returnTo. + dispatch(setPathBeforeSignIn(appState.returnTo)) + dispatch(replace('/signedin')) } } } diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index a86766ee0..c44672ce1 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -16,7 +16,7 @@ import { getCurrentPosition, receivedPositionResponse } from '../../actions/loca import { setLocationToCurrent } from '../../actions/map' import { handleBackButtonPress, matchContentToUrl } from '../../actions/ui' import { getAuth0Config } from '../../util/auth' -import { AUTH0_AUDIENCE, AUTH0_SCOPE, URL_ROOT } from '../../util/constants' +import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants' import { getActiveItinerary, getTitle } from '../../util/state' import AfterSignInScreen from '../user/after-signin-screen' import BeforeSignInScreen from '../user/before-signin-screen' @@ -291,7 +291,7 @@ class RouterWrapperWithAuth0 extends Component { onLoginError={showLoginError} onRedirectCallback={processSignIn} onRedirecting={BeforeSignInScreen} - redirectUri={URL_ROOT} + redirectUri={window.location.origin} scope={AUTH0_SCOPE} > {router} diff --git a/lib/components/user/nav-login-button-auth0.js b/lib/components/user/nav-login-button-auth0.js index 1b83387c3..2e8d7e0e6 100644 --- a/lib/components/user/nav-login-button-auth0.js +++ b/lib/components/user/nav-login-button-auth0.js @@ -1,7 +1,6 @@ import { useAuth0 } from '@auth0/auth0-react' import React from 'react' -import { URL_ROOT } from '../../util/constants' import NavLoginButton from './nav-login-button' /** @@ -16,14 +15,11 @@ const NavLoginButtonAuth0 = ({ const { isAuthenticated, loginWithRedirect, logout, user } = useAuth0() // On login, preserve the current trip query if any. - // After login, redirect to a whitelisted URL in the Auth0 dashboard. - const afterLoginPath = '/#/signedin' const handleLogin = () => loginWithRedirect({ - redirectUri: `${URL_ROOT}${afterLoginPath}`, - appState: {urlHash: window.location.hash} // The part of href from #/?, e.g. #/?ui_activeSearch=... + appState: { returnTo: window.location.hash.substr(1) } // e.g. /?ui_activeSearch=... without #. }) const handleLogout = () => logout({ - returnTo: URL_ROOT + returnTo: window.location.origin }) // On logout, it is better to "clear" the screen, so diff --git a/lib/components/user/saved-trip-list.js b/lib/components/user/saved-trip-list.js index dce789737..3676aff70 100644 --- a/lib/components/user/saved-trip-list.js +++ b/lib/components/user/saved-trip-list.js @@ -80,6 +80,12 @@ const mapDispatchToProps = { } export default withLoggedInUserSupport( - withAuthenticationRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripList)), + withAuthenticationRequired( + connect(mapStateToProps, mapDispatchToProps)(SavedTripList), + { + // Redirect back to this page if a user logs in from the "my trips" URL. + returnTo: () => window.location.hash.substr(1) + } + ), true ) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index e7650de3d..db25a02d0 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -229,6 +229,12 @@ const mapDispatchToProps = { } export default withLoggedInUserSupport( - withAuthenticationRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen)), + withAuthenticationRequired( + connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen), + { + // Redirect back to this page if a user logs in from the URL for saving a trip. + returnTo: () => window.location.hash.substr(1) + } + ), true ) diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index 9495856c3..58bb228dc 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -144,6 +144,12 @@ const mapDispatchToProps = { } export default withLoggedInUserSupport( - withAuthenticationRequired(connect(mapStateToProps, mapDispatchToProps)(UserAccountScreen)), + withAuthenticationRequired( + connect(mapStateToProps, mapDispatchToProps)(UserAccountScreen), + { + // Redirect back to this page if a user logs in from the account URL. + returnTo: () => window.location.hash.substr(1) + } + ), true ) diff --git a/lib/util/constants.js b/lib/util/constants.js index 3f16df29e..f9c51378a 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -2,6 +2,3 @@ export const AUTH0_AUDIENCE = 'https://otp-middleware' export const AUTH0_SCOPE = '' export const DEFAULT_APP_TITLE = 'OpenTripPlanner' export const PERSISTENCE_STRATEGY_OTP_MIDDLEWARE = 'otp_middleware' - -// Gets the root URL, e.g. https://otp-instance.example.com:8080, computed once for all. -export const URL_ROOT = `${window.location.protocol}//${window.location.host}` From 6fd4e62747411b61633833be60faf6727d606e9e Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 21 Sep 2020 14:52:40 -0400 Subject: [PATCH 05/20] docs(actions/auth.js): Tweak comments. --- lib/actions/auth.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/actions/auth.js b/lib/actions/auth.js index 039139469..b14e2e5a3 100644 --- a/lib/actions/auth.js +++ b/lib/actions/auth.js @@ -31,19 +31,19 @@ export function showLoginError (err) { * This function is called by the Auth0Provider component, with the described parameter(s), * after the user signs in. * @param {Object} appState The state stored when calling useAuth0().loginWithRedirect - * or a withAuhenticationRequired-enabled component. + * or when instantiating a component that uses withAuhenticationRequired. */ export function processSignIn (appState) { return function (dispatch, getState) { if (appState && appState.returnTo) { // Remove URL parameters that were added by auth0-react - // (see https://github.com/auth0/auth0-react/blob/adac2e810d4f6d33253cb8b2016fcedb98a3bc16/examples/cra-react-router/src/index.tsx#L7) + // (see https://github.com/auth0/auth0-react/blob/adac2e810d4f6d33253cb8b2016fcedb98a3bc16/examples/cra-react-router/src/index.tsx#L7). window.history.replaceState({}, '', window.location.pathname) - // Here, we add to the redux state the URL route (portion after # that contains the route/page name e.g. /account, - // and includes a combination of itinerary search, stop/trip view, etc.) that was passed to appState prior to login, - // and we redirect to the "/signedin" route, where the AfterLoginScreen - // will fetch the user data and, upon fetched, redirect back to appState.returnTo. + // Here, we add the hash to the redux state (portion of the URL after '#' that contains the route/page name e.g. /account, + // and includes a combination of itinerary search, stop/trip view, etc.) that was passed to appState.returnTo prior to login. + // Once the redux state set, we redirect to the "/signedin" route (whitelisted in Auth0 dashboard), where the AfterLoginScreen + // will in turn fetch the user data then redirect the web browser back to appState.returnTo. dispatch(setPathBeforeSignIn(appState.returnTo)) dispatch(replace('/signedin')) } From 92edadbcfdc70cdb2dc5c023cf726ceb5ca126cc Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 22 Sep 2020 17:56:10 -0400 Subject: [PATCH 06/20] refactor(util/constants): Reinstate URL_ROOT. --- lib/components/app/responsive-webapp.js | 4 ++-- lib/components/user/nav-login-button-auth0.js | 3 ++- lib/util/constants.js | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index c44672ce1..a86766ee0 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -16,7 +16,7 @@ import { getCurrentPosition, receivedPositionResponse } from '../../actions/loca import { setLocationToCurrent } from '../../actions/map' import { handleBackButtonPress, matchContentToUrl } from '../../actions/ui' import { getAuth0Config } from '../../util/auth' -import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants' +import { AUTH0_AUDIENCE, AUTH0_SCOPE, URL_ROOT } from '../../util/constants' import { getActiveItinerary, getTitle } from '../../util/state' import AfterSignInScreen from '../user/after-signin-screen' import BeforeSignInScreen from '../user/before-signin-screen' @@ -291,7 +291,7 @@ class RouterWrapperWithAuth0 extends Component { onLoginError={showLoginError} onRedirectCallback={processSignIn} onRedirecting={BeforeSignInScreen} - redirectUri={window.location.origin} + redirectUri={URL_ROOT} scope={AUTH0_SCOPE} > {router} diff --git a/lib/components/user/nav-login-button-auth0.js b/lib/components/user/nav-login-button-auth0.js index 2e8d7e0e6..32aaa9b34 100644 --- a/lib/components/user/nav-login-button-auth0.js +++ b/lib/components/user/nav-login-button-auth0.js @@ -1,6 +1,7 @@ import { useAuth0 } from '@auth0/auth0-react' import React from 'react' +import { URL_ROOT } from '../../util/constants' import NavLoginButton from './nav-login-button' /** @@ -19,7 +20,7 @@ const NavLoginButtonAuth0 = ({ appState: { returnTo: window.location.hash.substr(1) } // e.g. /?ui_activeSearch=... without #. }) const handleLogout = () => logout({ - returnTo: window.location.origin + returnTo: URL_ROOT }) // On logout, it is better to "clear" the screen, so diff --git a/lib/util/constants.js b/lib/util/constants.js index f9c51378a..9563256ae 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -2,3 +2,7 @@ export const AUTH0_AUDIENCE = 'https://otp-middleware' export const AUTH0_SCOPE = '' export const DEFAULT_APP_TITLE = 'OpenTripPlanner' export const PERSISTENCE_STRATEGY_OTP_MIDDLEWARE = 'otp_middleware' + +// Gets the root URL, e.g. https://otp-instance.example.com:8080, computed once for all. +// TODO: support root URLs that involve paths or subfolders, as in https://otp-ui.example.com/path-to-ui/ +export const URL_ROOT = `${window.location.protocol}//${window.location.host}` From 4a6c2911bce4e981f5b2162cbb4e4696cb9f4dc0 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 22 Sep 2020 21:48:28 -0400 Subject: [PATCH 07/20] refactor(util/ui): Extract methods for redirecting to current route. --- lib/components/user/nav-login-button-auth0.js | 4 +++- lib/components/user/saved-trip-list.js | 6 ++---- lib/components/user/saved-trip-screen.js | 6 ++---- lib/components/user/user-account-screen.js | 6 ++---- lib/util/ui.js | 18 ++++++++++++++++++ 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/components/user/nav-login-button-auth0.js b/lib/components/user/nav-login-button-auth0.js index 32aaa9b34..9687bd37d 100644 --- a/lib/components/user/nav-login-button-auth0.js +++ b/lib/components/user/nav-login-button-auth0.js @@ -2,6 +2,7 @@ import { useAuth0 } from '@auth0/auth0-react' import React from 'react' import { URL_ROOT } from '../../util/constants' +import { getCurrentRoute } from '../../util/ui' import NavLoginButton from './nav-login-button' /** @@ -17,9 +18,10 @@ const NavLoginButtonAuth0 = ({ // On login, preserve the current trip query if any. const handleLogin = () => loginWithRedirect({ - appState: { returnTo: window.location.hash.substr(1) } // e.g. /?ui_activeSearch=... without #. + appState: { returnTo: getCurrentRoute() } }) const handleLogout = () => logout({ + // Logout to the map with no search. returnTo: URL_ROOT }) diff --git a/lib/components/user/saved-trip-list.js b/lib/components/user/saved-trip-list.js index 3676aff70..955d371fc 100644 --- a/lib/components/user/saved-trip-list.js +++ b/lib/components/user/saved-trip-list.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux' import * as uiActions from '../../actions/ui' import DesktopNav from '../app/desktop-nav' +import { RETURN_TO_CURRENT_ROUTE } from '../../util/ui' import LinkButton from './link-button' import TripSummaryPane from './trip-summary-pane' import withLoggedInUserSupport from './with-logged-in-user-support' @@ -82,10 +83,7 @@ const mapDispatchToProps = { export default withLoggedInUserSupport( withAuthenticationRequired( connect(mapStateToProps, mapDispatchToProps)(SavedTripList), - { - // Redirect back to this page if a user logs in from the "my trips" URL. - returnTo: () => window.location.hash.substr(1) - } + RETURN_TO_CURRENT_ROUTE ), true ) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index db25a02d0..b9c7828af 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -11,6 +11,7 @@ import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' import { arrayToDayFields, WEEKDAYS } from '../../util/monitored-trip' import { getActiveItineraries, getActiveSearch } from '../../util/state' +import { RETURN_TO_CURRENT_ROUTE } from '../../util/ui' import withLoggedInUserSupport from './with-logged-in-user-support' /** @@ -231,10 +232,7 @@ const mapDispatchToProps = { export default withLoggedInUserSupport( withAuthenticationRequired( connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen), - { - // Redirect back to this page if a user logs in from the URL for saving a trip. - returnTo: () => window.location.hash.substr(1) - } + RETURN_TO_CURRENT_ROUTE ), true ) diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index 58bb228dc..440b2a5f6 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux' import { routeTo } from '../../actions/ui' import { createOrUpdateUser } from '../../actions/user' +import { RETURN_TO_CURRENT_ROUTE } from '../../util/ui' import { isNewUser } from '../../util/user' import DesktopNav from '../app/desktop-nav' import AccountSetupFinishPane from './account-setup-finish-pane' @@ -146,10 +147,7 @@ const mapDispatchToProps = { export default withLoggedInUserSupport( withAuthenticationRequired( connect(mapStateToProps, mapDispatchToProps)(UserAccountScreen), - { - // Redirect back to this page if a user logs in from the account URL. - returnTo: () => window.location.hash.substr(1) - } + RETURN_TO_CURRENT_ROUTE ), true ) diff --git a/lib/util/ui.js b/lib/util/ui.js index b9c4b3923..f48dc5a0d 100644 --- a/lib/util/ui.js +++ b/lib/util/ui.js @@ -18,3 +18,21 @@ export function renderChildrenWithProps (children, newProps) { return childrenWithProps } + +/** + * Returns the route that is in place before a user accesses the login page, + * e.g. if the URL is http://www.example.com/path/#/route?param=value, + * only /route?param=value is returned. A blank string is returned at the minimum per substr() function. + */ +export function getCurrentRoute () { + return window.location.hash.substr(1) +} + +/** + * Used in several components instantiated with auth0-react's withAuthenticationRequired(), + * so that the browser returns to the route (hash) in place before the user accessed the login page, + * e.g. /account/?ui_activeSearch=... without #. + */ +export const RETURN_TO_CURRENT_ROUTE = { + returnTo: getCurrentRoute +} From 0eedf9d0b11454855e64acc0c8c2569f5240b109 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 23 Sep 2020 10:45:33 -0400 Subject: [PATCH 08/20] refactor(WithLoggedInUserSupport): Consolidate condition for fetching. Move token to actions/user. --- lib/actions/user.js | 76 +++++++++++-------- .../user/with-logged-in-user-support.js | 41 +++++----- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 2afa386d1..121a2ee23 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -49,44 +49,56 @@ export function fetchUserMonitoredTrips (accessToken) { /** * Fetches user preferences to state.user, or set initial values under state.user if no user has been loaded. */ -export function fetchOrInitializeUser (auth0, accessToken) { +export function fetchOrInitializeUser (auth0) { return async function (dispatch, getState) { const { otp } = getState() const { otp_middleware: otpMiddleware = null } = otp.config.persistence if (otpMiddleware) { - const { user: authUser } = auth0 - const { data: user, status: fetchUserStatus } = await fetchUser(otpMiddleware, accessToken) - - // Beware! On AWS API gateway, if a user is not found in the middleware - // (e.g. they just created their Auth0 password but have not completed the account setup form yet), - // the call above will return, for example: - // { - // status: 'success', - // data: { - // "result": "ERR", - // "message": "No user with id=000000 found.", - // "code": 404, - // "detail": null - // } - // } - // - // The same call to a middleware instance that is not behind an API gateway - // will return: - // { - // status: 'error', - // message: 'Error get-ing user...' - // } - // TODO: Improve AWS response. - - const isNewAccount = fetchUserStatus === 'error' || (user && user.result === 'ERR') - if (!isNewAccount) { - // Load user's monitored trips before setting the user state. - await dispatch(fetchUserMonitoredTrips(accessToken)) + // First, get the Auth0 access token. + let accessToken + try { + accessToken = await auth0.getAccessTokenSilently() + } catch (error) { + // TODO: improve UI if there is an errror. + alert('Error obtaining an authorization token.') + } - dispatch(setCurrentUser({ accessToken, user })) - } else { - dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) })) + // Once accessToken is available, proceed to fetch or initialize loggedInUser. + if (accessToken) { + const { user: authUser } = auth0 + const { data: user, status: fetchStatus } = await fetchUser(otpMiddleware, accessToken) + + // Beware! On AWS API gateway, if a user is not found in the middleware + // (e.g. they just created their Auth0 password but have not completed the account setup form yet), + // the call above will return, for example: + // { + // status: 'success', + // data: { + // "result": "ERR", + // "message": "No user with id=000000 found.", + // "code": 404, + // "detail": null + // } + // } + // + // The same call to a middleware instance that is not behind an API gateway + // will return: + // { + // status: 'error', + // message: 'Error get-ing user...' + // } + // TODO: Improve AWS response. + + const isNewAccount = fetchStatus === 'error' || (user && user.result === 'ERR') + if (!isNewAccount) { + // Load user's monitored trips before setting the user state. + await dispatch(fetchUserMonitoredTrips(accessToken)) + + dispatch(setCurrentUser({ accessToken, user })) + } else { + dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) })) + } } } } diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index 70434dee0..60731b88e 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -37,33 +37,34 @@ export default function withLoggedInUserSupport (WrappedComponent, requireLogged * Upon completion (or if no user is logged in or if auth is disabled), it renders children. */ class UserLoaderScreen extends Component { + /** + * Determines whether user data should be fetched. + * @returns true if the logged-in user has passed Auth0 authentication and state.user.loggedInUser has not been set; false otherwise. + */ + shouldBeFetchingUser = () => { + const { auth0, loggedInUser } = this.props + return auth0 && auth0.isAuthenticated && !loggedInUser + } + async componentDidUpdate () { - const { auth0, fetchOrInitializeUser, loggedInUser } = this.props + const { auth0, fetchOrInitializeUser } = this.props - if (auth0 && auth0.isAuthenticated && !loggedInUser) { - try { - // Get the token - const token = await auth0.getAccessTokenSilently() - // Once accessToken is available, proceed to fetch or initialize loggedInUser. - fetchOrInitializeUser(auth0, token) - } catch (error) { - alert('Error obtaining an authorization token.') - } + if (this.shouldBeFetchingUser()) { + fetchOrInitializeUser(auth0) } } render () { - const { auth0, children, loggedInUser, requireLoggedInUser } = this.props + const { auth0, children, requireLoggedInUser } = this.props - if (auth0) { - if (requireLoggedInUser && auth0.isAuthenticated && !loggedInUser) { - // Display a hint while fetching user data for logged in user (from componentDidMount). - // Don't display this if loggedInUser is already available. - // TODO: Improve this screen. - return - } else { - return renderChildrenWithProps(children, { auth0 }) - } + if (requireLoggedInUser && this.shouldBeFetchingUser()) { + // If a logged-in user is required, then + // display a hint while the logged-in user data is being fetched (from componentDidMount). + // Don't display this if loggedInUser is not required or is already available. + // TODO: Improve this screen. + return + } else if (auth0) { + return renderChildrenWithProps(children, { auth0 }) } return children From 1fa97133c51a7fb62e7a7fde2bfcce3ee4f6526b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 24 Sep 2020 08:19:32 -0700 Subject: [PATCH 09/20] refactor(WithLoggedInUserSupport): Rename shouldFetchUser. --- lib/components/user/with-logged-in-user-support.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index 60731b88e..b662cf628 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -41,7 +41,7 @@ class UserLoaderScreen extends Component { * Determines whether user data should be fetched. * @returns true if the logged-in user has passed Auth0 authentication and state.user.loggedInUser has not been set; false otherwise. */ - shouldBeFetchingUser = () => { + shouldFetchUser = () => { const { auth0, loggedInUser } = this.props return auth0 && auth0.isAuthenticated && !loggedInUser } @@ -49,7 +49,7 @@ class UserLoaderScreen extends Component { async componentDidUpdate () { const { auth0, fetchOrInitializeUser } = this.props - if (this.shouldBeFetchingUser()) { + if (this.shouldFetchUser()) { fetchOrInitializeUser(auth0) } } @@ -57,7 +57,7 @@ class UserLoaderScreen extends Component { render () { const { auth0, children, requireLoggedInUser } = this.props - if (requireLoggedInUser && this.shouldBeFetchingUser()) { + if (requireLoggedInUser && this.shouldFetchUser()) { // If a logged-in user is required, then // display a hint while the logged-in user data is being fetched (from componentDidMount). // Don't display this if loggedInUser is not required or is already available. From c9bb706107fffe6b477e73123313da05a5031f74 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 9 Oct 2020 11:58:27 -0400 Subject: [PATCH 10/20] refactor(withLoggedInUserSupport): Address PR comments --- lib/actions/user.js | 4 +++- lib/components/user/with-logged-in-user-support.js | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 121a2ee23..70ee461b3 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -92,7 +92,9 @@ export function fetchOrInitializeUser (auth0) { const isNewAccount = fetchStatus === 'error' || (user && user.result === 'ERR') if (!isNewAccount) { - // Load user's monitored trips before setting the user state. + // Load user's monitored trips before setting the user state, + // so that we call setCurrentUser to set state.user.loggedInUser, + // other parts of the UI can assume that any monitored trips have already been fetched. await dispatch(fetchUserMonitoredTrips(accessToken)) dispatch(setCurrentUser({ accessToken, user })) diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index b662cf628..e128e732f 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -39,9 +39,10 @@ export default function withLoggedInUserSupport (WrappedComponent, requireLogged class UserLoaderScreen extends Component { /** * Determines whether user data should be fetched. - * @returns true if the logged-in user has passed Auth0 authentication and state.user.loggedInUser has not been set; false otherwise. + * @returns true if the logged-in user has passed Auth0 authentication + * and state.user.loggedInUser has not been set; false otherwise. */ - shouldFetchUser = () => { + loggedInUserIsUnfetched = () => { const { auth0, loggedInUser } = this.props return auth0 && auth0.isAuthenticated && !loggedInUser } @@ -49,7 +50,7 @@ class UserLoaderScreen extends Component { async componentDidUpdate () { const { auth0, fetchOrInitializeUser } = this.props - if (this.shouldFetchUser()) { + if (this.loggedInUserIsUnfetched()) { fetchOrInitializeUser(auth0) } } @@ -57,13 +58,14 @@ class UserLoaderScreen extends Component { render () { const { auth0, children, requireLoggedInUser } = this.props - if (requireLoggedInUser && this.shouldFetchUser()) { + if (requireLoggedInUser && this.loggedInUserIsUnfetched()) { // If a logged-in user is required, then // display a hint while the logged-in user data is being fetched (from componentDidMount). // Don't display this if loggedInUser is not required or is already available. // TODO: Improve this screen. return } else if (auth0) { + // When the logged-in user is fetched, forward the auth0 object to children. return renderChildrenWithProps(children, { auth0 }) } From 8fe223ef4bda834432f6ecc59d3162180af76650 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 9 Oct 2020 12:20:48 -0400 Subject: [PATCH 11/20] refactor(WithLoggedInUserSupport): Stop passing auth0 to children. --- lib/actions/user.js | 9 ++++++-- lib/components/user/user-account-screen.js | 8 ++++--- .../user/with-logged-in-user-support.js | 6 +----- lib/reducers/create-user-reducer.js | 10 +++++++-- lib/util/ui.js | 21 ------------------- 5 files changed, 21 insertions(+), 33 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 70ee461b3..bd19a978c 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -67,6 +67,7 @@ export function fetchOrInitializeUser (auth0) { // Once accessToken is available, proceed to fetch or initialize loggedInUser. if (accessToken) { const { user: authUser } = auth0 + const { email_verified: isEmailVerified } = authUser const { data: user, status: fetchStatus } = await fetchUser(otpMiddleware, accessToken) // Beware! On AWS API gateway, if a user is not found in the middleware @@ -91,15 +92,19 @@ export function fetchOrInitializeUser (auth0) { // TODO: Improve AWS response. const isNewAccount = fetchStatus === 'error' || (user && user.result === 'ERR') + + // Store the new/existing user data, + // and also the auth0 access token and email verification state + // (so they don't need to be retrieved again from the auth0 object). if (!isNewAccount) { // Load user's monitored trips before setting the user state, // so that we call setCurrentUser to set state.user.loggedInUser, // other parts of the UI can assume that any monitored trips have already been fetched. await dispatch(fetchUserMonitoredTrips(accessToken)) - dispatch(setCurrentUser({ accessToken, user })) + dispatch(setCurrentUser({ accessToken, isEmailVerified, user })) } else { - dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) })) + dispatch(setCurrentUser({ accessToken, isEmailVerified, user: createNewUser(authUser) })) } } } diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index 440b2a5f6..caacec33e 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -89,12 +89,12 @@ class UserAccountScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { auth0, loggedInUser } = this.props + const { isEmailVerified, loggedInUser } = this.props const { userData } = this.state let formContents if (isNewUser(loggedInUser)) { - if (!auth0.user.email_verified) { + if (!isEmailVerified) { // Check and prompt for email verification first to avoid extra user wait. formContents = } else { @@ -134,8 +134,10 @@ class UserAccountScreen extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { + const { isEmailVerified, loggedInUser } = state.user return { - loggedInUser: state.user.loggedInUser + isEmailVerified, + loggedInUser } } diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index e128e732f..786c4c851 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -4,7 +4,6 @@ import { connect } from 'react-redux' import * as userActions from '../../actions/user' import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants' -import { renderChildrenWithProps } from '../../util/ui' import AwaitingScreen from './awaiting-screen' /** @@ -56,7 +55,7 @@ class UserLoaderScreen extends Component { } render () { - const { auth0, children, requireLoggedInUser } = this.props + const { children, requireLoggedInUser } = this.props if (requireLoggedInUser && this.loggedInUserIsUnfetched()) { // If a logged-in user is required, then @@ -64,9 +63,6 @@ class UserLoaderScreen extends Component { // Don't display this if loggedInUser is not required or is already available. // TODO: Improve this screen. return - } else if (auth0) { - // When the logged-in user is fetched, forward the auth0 object to children. - return renderChildrenWithProps(children, { auth0 }) } return children diff --git a/lib/reducers/create-user-reducer.js b/lib/reducers/create-user-reducer.js index 2dc9afbb6..6e8c72a46 100644 --- a/lib/reducers/create-user-reducer.js +++ b/lib/reducers/create-user-reducer.js @@ -7,9 +7,15 @@ function createUserReducer () { return (state = initialState, action) => { switch (action.type) { case 'SET_CURRENT_USER': { + const { accessToken, isEmailVerified, user } = action.payload + + // Only change the email verified state if it was passed in the payload. + const emailVerifiedPart = isEmailVerified ? { isEmailVerified: { $set: isEmailVerified } } : {} + return update(state, { - accessToken: { $set: action.payload.accessToken }, - loggedInUser: { $set: action.payload.user } + ...emailVerifiedPart, + accessToken: { $set: accessToken }, + loggedInUser: { $set: user } }) } diff --git a/lib/util/ui.js b/lib/util/ui.js index f48dc5a0d..03485612d 100644 --- a/lib/util/ui.js +++ b/lib/util/ui.js @@ -1,24 +1,3 @@ -import { Children, isValidElement, cloneElement } from 'react' - -/** - * Renders children with additional props. - * Modified from - * https://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children#32371612 - * @param children the child elements to modify. - * @param newProps the props to add. - */ -export function renderChildrenWithProps (children, newProps) { - const childrenWithProps = Children.map(children, child => { - // Checking isValidElement is the safe way and avoids a TS error too. - if (isValidElement(child)) { - return cloneElement(child, { ...newProps }) - } - return child - }) - - return childrenWithProps -} - /** * Returns the route that is in place before a user accesses the login page, * e.g. if the URL is http://www.example.com/path/#/route?param=value, From e25ef5d781232cde60c0ac866b357b7610e65d47 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 21 Oct 2020 16:59:30 -0400 Subject: [PATCH 12/20] refactor(actions/user): Store auth0.user in redux state. --- lib/actions/user.js | 7 +++---- lib/components/user/user-account-screen.js | 8 ++++---- lib/reducers/create-user-reducer.js | 14 +++++++++----- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 1327549d8..b92fb291d 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -66,8 +66,7 @@ export function fetchOrInitializeUser (auth0) { // Once accessToken is available, proceed to fetch or initialize loggedInUser. if (accessToken) { - const { user: authUser } = auth0 - const { email_verified: isEmailVerified } = authUser + const { user: auth0User } = auth0 const { data: user, status: fetchStatus } = await fetchUser(otpMiddleware, accessToken) // Beware! On AWS API gateway, if a user is not found in the middleware @@ -102,9 +101,9 @@ export function fetchOrInitializeUser (auth0) { // other parts of the UI can assume that any monitored trips have already been fetched. await dispatch(fetchUserMonitoredTrips(accessToken)) - dispatch(setCurrentUser({ accessToken, isEmailVerified, user })) + dispatch(setCurrentUser({ accessToken, auth0User, user })) } else { - dispatch(setCurrentUser({ accessToken, isEmailVerified, user: createNewUser(authUser) })) + dispatch(setCurrentUser({ accessToken, auth0User, user: createNewUser(auth0User) })) } } } diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index 43c11c378..b890668eb 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -109,7 +109,7 @@ class UserAccountScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { isEmailVerified, loggedInUser } = this.props + const { auth0User, loggedInUser } = this.props // Capture the handler for use in the Formik callback (where `this` might mean something else.) const handleExit = this._handleExit @@ -135,7 +135,7 @@ class UserAccountScreen extends Component { props => { let formContents if (isNewUser(loggedInUser)) { - if (!isEmailVerified) { + if (!auth0User.email_verified) { // Check and prompt for email verification first to avoid extra user wait. formContents = } else { @@ -176,9 +176,9 @@ class UserAccountScreen extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const { isEmailVerified, loggedInUser } = state.user + const { auth0User, loggedInUser } = state.user return { - isEmailVerified, + auth0User, loggedInUser } } diff --git a/lib/reducers/create-user-reducer.js b/lib/reducers/create-user-reducer.js index 6e8c72a46..05448204f 100644 --- a/lib/reducers/create-user-reducer.js +++ b/lib/reducers/create-user-reducer.js @@ -2,18 +2,22 @@ import update from 'immutability-helper' // TODO: port user-specific code from the otp reducer. function createUserReducer () { - const initialState = {} + const initialState = { + accessToken: null, + auth0User: null, + loggedInUser: null + } return (state = initialState, action) => { switch (action.type) { case 'SET_CURRENT_USER': { - const { accessToken, isEmailVerified, user } = action.payload + const { accessToken, auth0User, user } = action.payload - // Only change the email verified state if it was passed in the payload. - const emailVerifiedPart = isEmailVerified ? { isEmailVerified: { $set: isEmailVerified } } : {} + // Only change the auth0 state if it was passed in the payload. + const auth0UserPart = auth0User ? { auth0User: { $set: auth0User } } : {} return update(state, { - ...emailVerifiedPart, + ...auth0UserPart, accessToken: { $set: accessToken }, loggedInUser: { $set: user } }) From 3eee512a4244600a76addeca0bb59a815def6f3c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 21 Oct 2020 19:42:43 -0400 Subject: [PATCH 13/20] revert: Revert commits e25ef5d, 8fe223e --- lib/actions/user.js | 10 +++------ lib/components/user/user-account-screen.js | 8 +++---- .../user/with-logged-in-user-support.js | 6 +++++- lib/reducers/create-user-reducer.js | 16 +++----------- lib/util/ui.js | 21 +++++++++++++++++++ 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index b92fb291d..9d18fe257 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -66,7 +66,7 @@ export function fetchOrInitializeUser (auth0) { // Once accessToken is available, proceed to fetch or initialize loggedInUser. if (accessToken) { - const { user: auth0User } = auth0 + const { user: authUser } = auth0 const { data: user, status: fetchStatus } = await fetchUser(otpMiddleware, accessToken) // Beware! On AWS API gateway, if a user is not found in the middleware @@ -91,19 +91,15 @@ export function fetchOrInitializeUser (auth0) { // TODO: Improve AWS response. const isNewAccount = fetchStatus === 'error' || (user && user.result === 'ERR') - - // Store the new/existing user data, - // and also the auth0 access token and email verification state - // (so they don't need to be retrieved again from the auth0 object). if (!isNewAccount) { // Load user's monitored trips before setting the user state, // so that we call setCurrentUser to set state.user.loggedInUser, // other parts of the UI can assume that any monitored trips have already been fetched. await dispatch(fetchUserMonitoredTrips(accessToken)) - dispatch(setCurrentUser({ accessToken, auth0User, user })) + dispatch(setCurrentUser({ accessToken, user })) } else { - dispatch(setCurrentUser({ accessToken, auth0User, user: createNewUser(auth0User) })) + dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) })) } } } diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index b890668eb..4c5fc812a 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -109,7 +109,7 @@ class UserAccountScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { auth0User, loggedInUser } = this.props + const { isEmailVerified, loggedInUser } = this.props // Capture the handler for use in the Formik callback (where `this` might mean something else.) const handleExit = this._handleExit @@ -135,7 +135,7 @@ class UserAccountScreen extends Component { props => { let formContents if (isNewUser(loggedInUser)) { - if (!auth0User.email_verified) { + if (!isEmailVerified) { // Check and prompt for email verification first to avoid extra user wait. formContents = } else { @@ -176,10 +176,8 @@ class UserAccountScreen extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const { auth0User, loggedInUser } = state.user return { - auth0User, - loggedInUser + loggedInUser: state.user.loggedInUser } } diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index 786c4c851..e128e732f 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux' import * as userActions from '../../actions/user' import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants' +import { renderChildrenWithProps } from '../../util/ui' import AwaitingScreen from './awaiting-screen' /** @@ -55,7 +56,7 @@ class UserLoaderScreen extends Component { } render () { - const { children, requireLoggedInUser } = this.props + const { auth0, children, requireLoggedInUser } = this.props if (requireLoggedInUser && this.loggedInUserIsUnfetched()) { // If a logged-in user is required, then @@ -63,6 +64,9 @@ class UserLoaderScreen extends Component { // Don't display this if loggedInUser is not required or is already available. // TODO: Improve this screen. return + } else if (auth0) { + // When the logged-in user is fetched, forward the auth0 object to children. + return renderChildrenWithProps(children, { auth0 }) } return children diff --git a/lib/reducers/create-user-reducer.js b/lib/reducers/create-user-reducer.js index 05448204f..2dc9afbb6 100644 --- a/lib/reducers/create-user-reducer.js +++ b/lib/reducers/create-user-reducer.js @@ -2,24 +2,14 @@ import update from 'immutability-helper' // TODO: port user-specific code from the otp reducer. function createUserReducer () { - const initialState = { - accessToken: null, - auth0User: null, - loggedInUser: null - } + const initialState = {} return (state = initialState, action) => { switch (action.type) { case 'SET_CURRENT_USER': { - const { accessToken, auth0User, user } = action.payload - - // Only change the auth0 state if it was passed in the payload. - const auth0UserPart = auth0User ? { auth0User: { $set: auth0User } } : {} - return update(state, { - ...auth0UserPart, - accessToken: { $set: accessToken }, - loggedInUser: { $set: user } + accessToken: { $set: action.payload.accessToken }, + loggedInUser: { $set: action.payload.user } }) } diff --git a/lib/util/ui.js b/lib/util/ui.js index 03485612d..f48dc5a0d 100644 --- a/lib/util/ui.js +++ b/lib/util/ui.js @@ -1,3 +1,24 @@ +import { Children, isValidElement, cloneElement } from 'react' + +/** + * Renders children with additional props. + * Modified from + * https://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children#32371612 + * @param children the child elements to modify. + * @param newProps the props to add. + */ +export function renderChildrenWithProps (children, newProps) { + const childrenWithProps = Children.map(children, child => { + // Checking isValidElement is the safe way and avoids a TS error too. + if (isValidElement(child)) { + return cloneElement(child, { ...newProps }) + } + return child + }) + + return childrenWithProps +} + /** * Returns the route that is in place before a user accesses the login page, * e.g. if the URL is http://www.example.com/path/#/route?param=value, From 67f8807fc8061de295aabe663b029e90576773e6 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 21 Oct 2020 20:07:40 -0400 Subject: [PATCH 14/20] refactor(createUserReducer): Add initial user redux state. --- lib/reducers/create-user-reducer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/reducers/create-user-reducer.js b/lib/reducers/create-user-reducer.js index 2dc9afbb6..1fa1f92c6 100644 --- a/lib/reducers/create-user-reducer.js +++ b/lib/reducers/create-user-reducer.js @@ -2,7 +2,12 @@ import update from 'immutability-helper' // TODO: port user-specific code from the otp reducer. function createUserReducer () { - const initialState = {} + const initialState = { + accessToken: null, + loggedInUser: null, + loggedInUserMonitoredTrips: null, + pathBeforeSignIn: null + } return (state = initialState, action) => { switch (action.type) { From d359a8b8930d10602543081ec3d7e16511180dae Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 21 Oct 2020 20:09:55 -0400 Subject: [PATCH 15/20] refactor(WithLoggedInUserSupport): Remove renderChidrenWithProps in better way. --- lib/components/user/user-account-screen.js | 4 ++-- .../user/with-logged-in-user-support.js | 23 ++++++++++--------- lib/util/ui.js | 21 ----------------- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index 4c5fc812a..04be67093 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -109,7 +109,7 @@ class UserAccountScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { isEmailVerified, loggedInUser } = this.props + const { auth0, loggedInUser } = this.props // Capture the handler for use in the Formik callback (where `this` might mean something else.) const handleExit = this._handleExit @@ -135,7 +135,7 @@ class UserAccountScreen extends Component { props => { let formContents if (isNewUser(loggedInUser)) { - if (!isEmailVerified) { + if (!auth0.user.email_verified) { // Check and prompt for email verification first to avoid extra user wait. formContents = } else { diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index e128e732f..f17076e06 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -4,7 +4,6 @@ import { connect } from 'react-redux' import * as userActions from '../../actions/user' import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants' -import { renderChildrenWithProps } from '../../util/ui' import AwaitingScreen from './awaiting-screen' /** @@ -25,16 +24,19 @@ import AwaitingScreen from './awaiting-screen' */ export default function withLoggedInUserSupport (WrappedComponent, requireLoggedInUser) { return props => ( - - - + ) } /** * This component ensures that values under state.user are set when a user is logged in. - * If needed by the children, this component displays a wait screen while state.user values are being fetched. - * Upon completion (or if no user is logged in or if auth is disabled), it renders children. + * If needed by the child WrappedComponent, this component displays a wait screen while state.user values are being fetched. + * Upon completion (or if no user is logged in or if auth is disabled), it renders the + * specified WrappedComponent with the passed props and the auth0 props if available. */ class UserLoaderScreen extends Component { /** @@ -56,7 +58,7 @@ class UserLoaderScreen extends Component { } render () { - const { auth0, children, requireLoggedInUser } = this.props + const { auth0, passedProps, requireLoggedInUser, WrappedComponent } = this.props if (requireLoggedInUser && this.loggedInUserIsUnfetched()) { // If a logged-in user is required, then @@ -64,12 +66,11 @@ class UserLoaderScreen extends Component { // Don't display this if loggedInUser is not required or is already available. // TODO: Improve this screen. return - } else if (auth0) { - // When the logged-in user is fetched, forward the auth0 object to children. - return renderChildrenWithProps(children, { auth0 }) } - return children + // Forward the auth0 object to the wrapped component. + // (if there is no user, auth0 will be null and not be forwarded.) + return } } diff --git a/lib/util/ui.js b/lib/util/ui.js index f48dc5a0d..03485612d 100644 --- a/lib/util/ui.js +++ b/lib/util/ui.js @@ -1,24 +1,3 @@ -import { Children, isValidElement, cloneElement } from 'react' - -/** - * Renders children with additional props. - * Modified from - * https://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children#32371612 - * @param children the child elements to modify. - * @param newProps the props to add. - */ -export function renderChildrenWithProps (children, newProps) { - const childrenWithProps = Children.map(children, child => { - // Checking isValidElement is the safe way and avoids a TS error too. - if (isValidElement(child)) { - return cloneElement(child, { ...newProps }) - } - return child - }) - - return childrenWithProps -} - /** * Returns the route that is in place before a user accesses the login page, * e.g. if the URL is http://www.example.com/path/#/route?param=value, From 99755e59acd2b46332998b7c656436acff555919 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 22 Oct 2020 11:33:01 -0400 Subject: [PATCH 16/20] refactor(actions/user): Add method to ensure user's trips are loaded. --- lib/actions/user.js | 57 ++++++++++------- lib/components/user/saved-trip-list.js | 79 ++++++++++++++---------- lib/components/user/saved-trip-screen.js | 50 ++++++++++----- 3 files changed, 115 insertions(+), 71 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 9d18fe257..76be3cb56 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -27,25 +27,6 @@ function createNewUser (auth0User) { } } -/** - * Fetches the saved/monitored trips for a user. - * We use the accessToken to fetch the data regardless of - * whether the process to populate state.user is completed or not. - */ -export function fetchUserMonitoredTrips (accessToken) { - return async function (dispatch, getState) { - const { otp } = getState() - const { otp_middleware: otpMiddleware = null } = otp.config.persistence - - if (otpMiddleware) { - const { data: trips, status: fetchStatus } = await getTrips(otpMiddleware, accessToken) - if (fetchStatus === 'success') { - dispatch(setCurrentUserMonitoredTrips(trips)) - } - } - } -} - /** * Fetches user preferences to state.user, or set initial values under state.user if no user has been loaded. */ @@ -92,11 +73,6 @@ export function fetchOrInitializeUser (auth0) { const isNewAccount = fetchStatus === 'error' || (user && user.result === 'ERR') if (!isNewAccount) { - // Load user's monitored trips before setting the user state, - // so that we call setCurrentUser to set state.user.loggedInUser, - // other parts of the UI can assume that any monitored trips have already been fetched. - await dispatch(fetchUserMonitoredTrips(accessToken)) - dispatch(setCurrentUser({ accessToken, user })) } else { dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) })) @@ -140,6 +116,39 @@ export function createOrUpdateUser (userData) { } } +/** + * Ensures user monitored trips are loaded. + * state.user.loggedInUserMonitoredTrips is at least an empty array when loaded. + */ +export function ensureUserMonitoredTripsAreLoaded () { + return async function (dispatch, getState) { + const { user } = getState() + const { accessToken, loggedInUser, loggedInUserMonitoredTrips } = user + if (accessToken && loggedInUser && !loggedInUserMonitoredTrips) { + await dispatch(fetchUserMonitoredTrips(accessToken)) + } + } +} + +/** + * Fetches the saved/monitored trips for a user. + * We use the accessToken to fetch the data regardless of + * whether the process to populate state.user is completed or not. + */ +export function fetchUserMonitoredTrips (accessToken) { + return async function (dispatch, getState) { + const { otp } = getState() + const { otp_middleware: otpMiddleware = null } = otp.config.persistence + + if (otpMiddleware) { + const { data: trips, status: fetchStatus } = await getTrips(otpMiddleware, accessToken) + if (fetchStatus === 'success') { + dispatch(setCurrentUserMonitoredTrips(trips)) + } + } + } +} + /** * Updates a logged-in user's monitored trip, * then, if that was successful, alerts (optional) diff --git a/lib/components/user/saved-trip-list.js b/lib/components/user/saved-trip-list.js index 3bac13be0..d214e687c 100644 --- a/lib/components/user/saved-trip-list.js +++ b/lib/components/user/saved-trip-list.js @@ -8,6 +8,7 @@ import * as uiActions from '../../actions/ui' import * as userActions from '../../actions/user' import DesktopNav from '../app/desktop-nav' import { RETURN_TO_CURRENT_ROUTE } from '../../util/ui' +import AwaitingScreen from './awaiting-screen' import LinkButton from './link-button' import TripSummaryPane from './trip-summary-pane' import withLoggedInUserSupport from './with-logged-in-user-support' @@ -15,39 +16,53 @@ import withLoggedInUserSupport from './with-logged-in-user-support' /** * This component displays the list of saved trips for the logged-in user. */ -const SavedTripList = ({ trips }) => { - // TODO: Improve navigation. - const accountLink =

Back to My Account

- let content - - if (!trips || trips.length === 0) { - content = ( - <> - {accountLink} -

You have no saved trips

-

Perform a trip search from the map first.

- - ) - } else { - // Stack the saved trip summaries. When the user clicks on one, they can edit that trip. - content = ( - <> - {accountLink} -

My saved trips

- {trips.map((trip, index) => )} - - ) +class SavedTripList extends Component { + componentDidMount () { + const { ensureUserMonitoredTripsAreLoaded, trips } = this.props + if (!trips) { + // If user monitored trips are not loaded, ensure they are. + ensureUserMonitoredTripsAreLoaded() + } } - return ( -
- {/* TODO: Do mobile view. */} - -
- {content} + render () { + const { trips } = this.props + // TODO: Improve navigation. + const accountLink =

Back to My Account

+ let content + + if (!trips) { + // Flash an indication while user trips are being loaded the first time. + content = + } else if (trips.length === 0) { + content = ( + <> + {accountLink} +

You have no saved trips

+

Perform a trip search from the map first.

+ + ) + } else { + // Stack the saved trip summaries and commands. + content = ( + <> + {accountLink} +

My saved trips

+ {trips.map((trip, index) => )} + + ) + } + + return ( +
+ {/* TODO: Do mobile view. */} + +
+ {content} +
-
- ) + ) + } } // connect to the redux store @@ -58,7 +73,9 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = {} +const mapDispatchToProps = { + ensureUserMonitoredTripsAreLoaded: userActions.ensureUserMonitoredTripsAreLoaded +} export default withLoggedInUserSupport( withAuthenticationRequired( diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index e919954f5..5a6d84e49 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -8,6 +8,7 @@ import * as yup from 'yup' import * as uiActions from '../../actions/ui' import * as userActions from '../../actions/user' import DesktopNav from '../app/desktop-nav' +import AwaitingScreen from './awaiting-screen' import SavedTripEditor from './saved-trip-editor' import TripBasicsPane from './trip-basics-pane' import TripNotificationsPane from './trip-notifications-pane' @@ -109,8 +110,13 @@ class SavedTripScreen extends Component { summary: TripSummaryPane } - componentDidMount () { - const { isCreating, monitoredTrips } = this.props + async componentDidMount () { + const { ensureUserMonitoredTripsAreLoaded, isCreating, monitoredTrips } = this.props + if (!monitoredTrips) { + // If user monitored trips are not loaded, ensure they are + // before checking the limit on saved trips. + await ensureUserMonitoredTripsAreLoaded() + } // There is a middleware limit of 5 saved trips, // so if that limit is already reached, alert, then show editing mode. @@ -147,21 +153,24 @@ class SavedTripScreen extends Component { render () { const { isCreating, loggedInUser, monitoredTrips } = this.props - const monitoredTrip = this._getTripToEdit(this.props) - const otherTripNames = monitoredTrips - .filter(trip => trip !== monitoredTrip) - .map(trip => trip.tripName) - - const clonedSchemaShape = clone(validationSchemaShape) - clonedSchemaShape.tripName = yup.string() - .required('Please enter a trip name') - .notOneOf(otherTripNames, 'You already using this name for another saved trip. Please enter a different name.') - const validationSchema = yup.object(clonedSchemaShape) - return ( -
- {/* TODO: Do mobile view. */} - + let screenContents + if (!monitoredTrips) { + // Flash an indication while user trips are being loaded the first time. + screenContents = + } else { + const monitoredTrip = this._getTripToEdit(this.props) + const otherTripNames = monitoredTrips + .filter(trip => trip !== monitoredTrip) + .map(trip => trip.tripName) + + const clonedSchemaShape = clone(validationSchemaShape) + clonedSchemaShape.tripName = yup.string() + .required('Please enter a trip name') + .notOneOf(otherTripNames, 'You already using this name for another saved trip. Please enter a different name.') + const validationSchema = yup.object(clonedSchemaShape) + + screenContents = ( + ) + } + + return ( +
+ {/* TODO: Do mobile view. */} + + {screenContents}
) } @@ -212,6 +229,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = { createOrUpdateUserMonitoredTrip: userActions.createOrUpdateUserMonitoredTrip, + ensureUserMonitoredTripsAreLoaded: userActions.ensureUserMonitoredTripsAreLoaded, routeTo: uiActions.routeTo } From 13d793612377e412b8d8c1d76ec5f34559ffc346 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 23 Oct 2020 14:07:14 -0400 Subject: [PATCH 17/20] fix(actions/user): Fix response with pagination. --- lib/actions/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index e35882167..231457f1e 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -144,7 +144,7 @@ export function fetchUserMonitoredTrips (accessToken) { if (otpMiddleware) { const { data: trips, status: fetchStatus } = await getTrips(otpMiddleware, accessToken) if (fetchStatus === 'success') { - dispatch(setCurrentUserMonitoredTrips(trips)) + dispatch(setCurrentUserMonitoredTrips(trips.data)) } } } From 166d00f0b4564c8b4bda20e906a5ab764bdbc635 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 27 Oct 2020 17:32:07 -0400 Subject: [PATCH 18/20] refactor(actions/user): Support async load of trips after loading user profile. --- lib/actions/user.js | 33 ++++------ lib/components/user/saved-trip-list.js | 81 ++++++++++-------------- lib/components/user/saved-trip-screen.js | 10 +-- 3 files changed, 47 insertions(+), 77 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 231457f1e..735001a05 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -73,10 +73,12 @@ export function fetchOrInitializeUser (auth0) { // TODO: Improve AWS response. const isNewAccount = fetchStatus === 'error' || (user && user.result === 'ERR') + const userData = isNewAccount ? createNewUser(authUser) : user + dispatch(setCurrentUser({ accessToken, user: userData })) + + // Also (asynchronously) load monitored trips for existing users. if (!isNewAccount) { - dispatch(setCurrentUser({ accessToken, user })) - } else { - dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) })) + dispatch(fetchUserMonitoredTrips()) } } } @@ -117,31 +119,18 @@ export function createOrUpdateUser (userData) { } } -/** - * Ensures user monitored trips are loaded. - * state.user.loggedInUserMonitoredTrips is at least an empty array when loaded. - */ -export function ensureUserMonitoredTripsAreLoaded () { - return async function (dispatch, getState) { - const { user } = getState() - const { accessToken, loggedInUser, loggedInUserMonitoredTrips } = user - if (accessToken && loggedInUser && !loggedInUserMonitoredTrips) { - await dispatch(fetchUserMonitoredTrips(accessToken)) - } - } -} - /** * Fetches the saved/monitored trips for a user. * We use the accessToken to fetch the data regardless of * whether the process to populate state.user is completed or not. */ -export function fetchUserMonitoredTrips (accessToken) { +export function fetchUserMonitoredTrips () { return async function (dispatch, getState) { - const { otp } = getState() + const { otp, user } = getState() const { otp_middleware: otpMiddleware = null } = otp.config.persistence if (otpMiddleware) { + const { accessToken } = user const { data: trips, status: fetchStatus } = await getTrips(otpMiddleware, accessToken) if (fetchStatus === 'success') { dispatch(setCurrentUserMonitoredTrips(trips.data)) @@ -177,9 +166,9 @@ export function createOrUpdateUserMonitoredTrip (tripData, isNew, silentOnSucces } // Reload user's monitored trips after add/update. - await dispatch(fetchUserMonitoredTrips(accessToken)) + await dispatch(fetchUserMonitoredTrips()) - // Finally, navigate to the saved trips page. + // Navigate to the saved trips page after the trips have been reloaded. dispatch(routeTo('/savedtrips')) } else { alert(`An error was encountered:\n${JSON.stringify(result)}`) @@ -203,7 +192,7 @@ export function deleteUserMonitoredTrip (id) { if (deleteResult.status === 'success') { // Reload user's monitored trips after deletion. - await dispatch(fetchUserMonitoredTrips(accessToken)) + dispatch(fetchUserMonitoredTrips(accessToken)) } else { alert(`An error was encountered:\n${JSON.stringify(deleteResult)}`) } diff --git a/lib/components/user/saved-trip-list.js b/lib/components/user/saved-trip-list.js index d214e687c..321e66705 100644 --- a/lib/components/user/saved-trip-list.js +++ b/lib/components/user/saved-trip-list.js @@ -16,53 +16,42 @@ import withLoggedInUserSupport from './with-logged-in-user-support' /** * This component displays the list of saved trips for the logged-in user. */ -class SavedTripList extends Component { - componentDidMount () { - const { ensureUserMonitoredTripsAreLoaded, trips } = this.props - if (!trips) { - // If user monitored trips are not loaded, ensure they are. - ensureUserMonitoredTripsAreLoaded() - } +const SavedTripList = ({ trips }) => { + // TODO: Improve navigation. + const accountLink =

Back to My Account

+ let content + + if (!trips) { + // Flash an indication while user trips are being loaded. + content = + } else if (trips.length === 0) { + content = ( + <> + {accountLink} +

You have no saved trips

+

Perform a trip search from the map first.

+ + ) + } else { + // Stack the saved trip summaries and commands. + content = ( + <> + {accountLink} +

My saved trips

+ {trips.map((trip, index) => )} + + ) } - render () { - const { trips } = this.props - // TODO: Improve navigation. - const accountLink =

Back to My Account

- let content - - if (!trips) { - // Flash an indication while user trips are being loaded the first time. - content = - } else if (trips.length === 0) { - content = ( - <> - {accountLink} -

You have no saved trips

-

Perform a trip search from the map first.

- - ) - } else { - // Stack the saved trip summaries and commands. - content = ( - <> - {accountLink} -

My saved trips

- {trips.map((trip, index) => )} - - ) - } - - return ( -
- {/* TODO: Do mobile view. */} - -
- {content} -
+ return ( +
+ {/* TODO: Do mobile view. */} + +
+ {content}
- ) - } +
+ ) } // connect to the redux store @@ -73,9 +62,7 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = { - ensureUserMonitoredTripsAreLoaded: userActions.ensureUserMonitoredTripsAreLoaded -} +const mapDispatchToProps = {} export default withLoggedInUserSupport( withAuthenticationRequired( diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index b03891233..1c8f40cc5 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -105,12 +105,7 @@ class SavedTripScreen extends Component { } async componentDidMount () { - const { ensureUserMonitoredTripsAreLoaded, isCreating, monitoredTrips } = this.props - if (!monitoredTrips) { - // If user monitored trips are not loaded, ensure they are - // before checking the limit on saved trips. - await ensureUserMonitoredTripsAreLoaded() - } + const { isCreating, monitoredTrips } = this.props // There is a middleware limit of 5 saved trips, // so if that limit is already reached, alert, then show editing mode. @@ -150,7 +145,7 @@ class SavedTripScreen extends Component { let screenContents if (!monitoredTrips) { - // Flash an indication while user trips are being loaded the first time. + // Flash an indication while user trips are being loaded. screenContents = } else { const monitoredTrip = this._getTripToEdit(this.props) @@ -224,7 +219,6 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = { createOrUpdateUserMonitoredTrip: userActions.createOrUpdateUserMonitoredTrip, - ensureUserMonitoredTripsAreLoaded: userActions.ensureUserMonitoredTripsAreLoaded, routeTo: uiActions.routeTo } From 9b74a576f40dd13476fa8c0f4a218dfe55dd6dd5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 28 Oct 2020 11:53:58 -0400 Subject: [PATCH 19/20] refactor(Remove unused arg, tweak comment.): --- lib/actions/user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 735001a05..20023e662 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -76,7 +76,7 @@ export function fetchOrInitializeUser (auth0) { const userData = isNewAccount ? createNewUser(authUser) : user dispatch(setCurrentUser({ accessToken, user: userData })) - // Also (asynchronously) load monitored trips for existing users. + // Also load monitored trips for existing users. if (!isNewAccount) { dispatch(fetchUserMonitoredTrips()) } @@ -192,7 +192,7 @@ export function deleteUserMonitoredTrip (id) { if (deleteResult.status === 'success') { // Reload user's monitored trips after deletion. - dispatch(fetchUserMonitoredTrips(accessToken)) + dispatch(fetchUserMonitoredTrips()) } else { alert(`An error was encountered:\n${JSON.stringify(deleteResult)}`) } From b27eb1886ca3ee3456a97dd89afdb79ea1a43ae3 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 28 Oct 2020 18:38:25 -0400 Subject: [PATCH 20/20] refactor: Resolve remaining merge issues from PR #224. --- lib/actions/user.js | 44 ++++++++++--------- lib/components/user/user-account-screen.js | 14 ++++-- .../user/with-logged-in-user-support.js | 2 +- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 848b28e9c..aa8e8d095 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -50,28 +50,34 @@ function getMiddlewareVariables (state) { } /** - * Fetches the saved/monitored trips for a user. - * We use the accessToken to fetch the data regardless of - * whether the process to populate state.user is completed or not. + * Attempts to fetch user preferences (or set initial values if the user is being created) + * into the redux state, under state.user. + * @param auth0 If provided, the auth0 login object used to initially obtain the auth0 access token + * for subsequent middleware fetches (and also to initialize new users from auth0 email and id). + * If absent, state.user.accessToken will be used for fetches. */ export function fetchOrInitializeUser (auth0) { return async function (dispatch, getState) { - const { apiBaseUrl, apiKey } = getMiddlewareVariables(getState()) + const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState()) const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/fromtoken` - // First, get the Auth0 access token. - let accessToken - try { - accessToken = await auth0.getAccessTokenSilently() - } catch (error) { - // TODO: improve UI if there is an errror. - alert('Error obtaining an authorization token.') + // Get the Auth0 access token. If one is in state.user, use it, + // otherwise if auth0 is provided, fetch it. + let token + if (accessToken) { + token = accessToken + } else if (auth0) { + try { + token = await auth0.getAccessTokenSilently() + } catch (error) { + // TODO: improve UI if there is an errror. + alert('Error obtaining an authorization token.') + } } // Once accessToken is available, proceed to fetch or initialize loggedInUser. - if (accessToken) { - const { user: authUser } = auth0 - const { data: user, status } = await secureFetch(requestUrl, accessToken, apiKey) + if (token) { + const { data: user, status } = await secureFetch(requestUrl, token, apiKey) // Beware! On AWS API gateway, if a user is not found in the middleware // (e.g. they just created their Auth0 password but have not completed the account setup form yet), @@ -95,8 +101,8 @@ export function fetchOrInitializeUser (auth0) { // TODO: Improve AWS response. const isNewAccount = status === 'error' || (user && user.result === 'ERR') - const userData = isNewAccount ? createNewUser(authUser) : user - dispatch(setCurrentUser({ accessToken, user: userData })) + const userData = isNewAccount ? createNewUser(auth0.user) : user + dispatch(setCurrentUser({ accessToken: token, user: userData })) // Also load monitored trips for existing users. if (!isNewAccount) { @@ -249,8 +255,7 @@ export function requestPhoneVerificationSms (newPhoneNumber) { if (status === 'success') { // Refetch user and update application state with new phone number and verification status. - // (This also refetches the user's monitored trip, and that's ok.) - dispatch(fetchOrInitializeUser({ accessToken })) + dispatch(fetchOrInitializeUser()) } else { alert(`An error was encountered:\n${JSON.stringify(message)}`) } @@ -276,8 +281,7 @@ export function verifyPhoneNumber (code) { if (status === 'success' && data) { if (data.status === 'approved') { // Refetch user and update application state with new phone number and verification status. - // (This also refetches the user's monitored trip, and that's ok.) - dispatch(fetchOrInitializeUser({ accessToken })) + dispatch(fetchOrInitializeUser()) } else { // Otherwise, the user entered a wrong/incorrect code. alert('The code you entered is invalid. Please try again.') diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js index 2421141af..1ca183e98 100644 --- a/lib/components/user/user-account-screen.js +++ b/lib/components/user/user-account-screen.js @@ -87,7 +87,9 @@ class UserAccountScreen extends Component { // In userData.savedLocations, filter out entries with blank addresses. const newUserData = clone(userData) - newUserData.savedLocations = newUserData.savedLocations.filter(({ address }) => address && address.length) + newUserData.savedLocations = newUserData.savedLocations.filter( + ({ address }) => address && address.length + ) await this.props.createOrUpdateUser(newUserData, silentOnSucceed) // TODO: Handle UI feedback (currently an alert() dialog inside createOrUpdateUser). @@ -130,7 +132,13 @@ class UserAccountScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { auth, loggedInUser, phoneFormatOptions, requestPhoneVerificationSms, verifyPhoneNumber } = this.props + const { + auth0, + loggedInUser, + phoneFormatOptions, + requestPhoneVerificationSms, + verifyPhoneNumber + } = this.props return (
@@ -156,7 +164,7 @@ class UserAccountScreen extends Component { let formContents let DisplayComponent if (this.state.isNewUser) { - if (!auth.user.email_verified) { + if (!auth0.user.email_verified) { // Check and prompt for email verification first to avoid extra user wait. formContents = } else { diff --git a/lib/components/user/with-logged-in-user-support.js b/lib/components/user/with-logged-in-user-support.js index f17076e06..951c3f743 100644 --- a/lib/components/user/with-logged-in-user-support.js +++ b/lib/components/user/with-logged-in-user-support.js @@ -49,7 +49,7 @@ class UserLoaderScreen extends Component { return auth0 && auth0.isAuthenticated && !loggedInUser } - async componentDidUpdate () { + componentDidUpdate () { const { auth0, fetchOrInitializeUser } = this.props if (this.loggedInUserIsUnfetched()) {