Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create separate transition pages to sign the user in / out #8855

Merged
merged 70 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
0275158
Once logged in, wait for betas before navigating
neil-marcellini Apr 4, 2022
ae4f499
Remove extra update to session and accountID
neil-marcellini Apr 5, 2022
e2daffb
Merge pull request #8498 from Expensify/neil-continue-setup
neil-marcellini Apr 26, 2022
ff3dbaf
Merge branch 'main' of github.com:Expensify/App into neil-transition-…
neil-marcellini Apr 26, 2022
37dfb3b
Remove beta check on reimbursement account page
neil-marcellini Apr 28, 2022
f5ac630
Don't wait for betas before navigating
neil-marcellini Apr 28, 2022
76035f9
Merge pull request #8819 from Expensify/neil-remove-beta-check
marcaaron Apr 28, 2022
1ae8690
Separate transition page to log out the old user
neil-marcellini Apr 29, 2022
732517d
Only log in the transitioning user
neil-marcellini Apr 29, 2022
c4a6a67
Separate transition pages for the transition route
neil-marcellini Apr 29, 2022
908a938
Navigate to transition exit from the auth screens
neil-marcellini Apr 30, 2022
4c65e1e
Remove extra param from signInWithShortLivedToken
neil-marcellini May 2, 2022
d1800a5
Navigate in transition page so navigation is ready
neil-marcellini May 2, 2022
9ac316a
Simplify the comment about dismissing the modal
neil-marcellini May 2, 2022
e647868
Revert "Navigate in transition page so navigation is ready"
neil-marcellini May 2, 2022
019dba9
Navigate in AuthScreens when navigation is ready
neil-marcellini May 2, 2022
56a79d2
Fix create login requests before Network is ready
neil-marcellini Mar 22, 2022
50082e7
Merge branch 'main' of github.com:Expensify/App into neil-transition-…
neil-marcellini May 6, 2022
5f621ea
Merge branch 'main' of github.com:Expensify/App into neil-separate-tr…
neil-marcellini May 6, 2022
7a243c2
Merge branch 'neil-transition-flows' into neil-separate-transition-pages
neil-marcellini May 6, 2022
328f385
Use the new createOnReadyTask for navigation
neil-marcellini May 6, 2022
f4b40d4
Revert "Fix create login requests before Network is ready"
neil-marcellini May 6, 2022
8ccf60f
Call the proper navigationReadyTask methods
neil-marcellini May 6, 2022
60635af
Fix don't show create menu on workspace/new
neil-marcellini May 9, 2022
96e7fd4
Simplify comment and remove log lines
neil-marcellini May 9, 2022
d47dce8
Rename LogOutOldUserPage to LogOutPreviousUserPage
neil-marcellini May 9, 2022
6dd4573
Only set navigation ready when we are logged in.
neil-marcellini May 9, 2022
b490551
Clean up default props and their use
neil-marcellini May 9, 2022
174e012
Fix shouldCreateFreePolicy indentation
neil-marcellini May 9, 2022
607e48d
Remove unused Log import
neil-marcellini May 9, 2022
dd54853
Clean up propTypes and props more
neil-marcellini May 9, 2022
468c5a0
Use a NavigationReadyDetector in the navigator
neil-marcellini May 17, 2022
f901904
Initial wait for onyx to clear
neil-marcellini May 18, 2022
853740e
Merge branch 'main' of github.com:Expensify/App into neil-separate-tr…
neil-marcellini May 18, 2022
d34921a
Reset required data ready task on log out
neil-marcellini May 19, 2022
f976228
Revert "Reset required data ready task on log out"
neil-marcellini May 19, 2022
56f9ba8
Merge in Network fixes from main
neil-marcellini May 20, 2022
511cc68
Don't navigate to the workspace chat on transition
neil-marcellini May 20, 2022
3a158f3
Navigation is ready once the user has transitioned
neil-marcellini May 20, 2022
35a18a0
Get the policy list even if there's no initial URL
neil-marcellini May 23, 2022
6607d59
Remove leftover fragment tags
neil-marcellini May 23, 2022
ecdbe41
Add JSDoc to waitForOnyxToClear()
neil-marcellini May 23, 2022
bb8bf06
Make transition page props required
neil-marcellini May 23, 2022
7e3030c
Get params.email directly from the required route
neil-marcellini May 23, 2022
f291838
Navigate to exit route without a separate function
neil-marcellini May 23, 2022
326b3b1
Use a promise directly to wait for Onyx to clear
neil-marcellini May 23, 2022
ed16b92
Update the navigation readiness based on its state
neil-marcellini May 23, 2022
a7cb78e
Remove unused import
neil-marcellini May 23, 2022
f0bc0f8
Track navigation readiness with a promise directly
neil-marcellini May 23, 2022
3cd291d
Fix waitForOnyxToClear promises
neil-marcellini May 23, 2022
f3b265d
Rename transition to transitionFromOldDot
neil-marcellini May 23, 2022
a34c511
Fix ROUTES.TRANSITION_FROM_OLD_DOT to match link
neil-marcellini May 23, 2022
4729aca
Create a setupPoliciesAndNavigate action
neil-marcellini May 23, 2022
35b5777
Ensure the user is logged in before policy setup
neil-marcellini May 23, 2022
320ec95
Remove unnecessary check for session in action
neil-marcellini May 24, 2022
fe274ed
Pass session to setUpPoliciesAndNavigate
neil-marcellini May 24, 2022
0921b6c
Change navigationRef import in NavigationRoot
neil-marcellini May 24, 2022
09e3bf1
Move waitForOnyxToClear into the action
neil-marcellini May 24, 2022
b5da43a
Add a detailed comment to setUpPoliciesAndNavigate
neil-marcellini May 24, 2022
1c211f1
Rename waitForOnyxToClear methods
neil-marcellini May 24, 2022
5d928e6
Fix merge conflicts in the welcome action
neil-marcellini May 24, 2022
2097b8e
Fix Onyx.clear by updating to the latest version
neil-marcellini May 31, 2022
e85a80c
Remove waitForOnyxToClear
neil-marcellini May 31, 2022
dc6fa72
Install Onyx using the npm package
neil-marcellini May 31, 2022
eee230f
Merge main to fix Onyx version conflicts
neil-marcellini May 31, 2022
dcc3e79
Wait for navigation just before navigating
neil-marcellini May 31, 2022
4baed9e
Clear the session error on a successful sign in
neil-marcellini May 31, 2022
552e13c
Again, setIsNavigationReady() in a transition page
neil-marcellini May 31, 2022
0e20ad0
Add a detailed comment about setIsNavigationReady
neil-marcellini Jun 1, 2022
0222143
Merge branch 'main' of github.com:Expensify/App into neil-separate-tr…
neil-marcellini Jun 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default {
getReportDetailsRoute: reportID => `r/${reportID}/details`,
REPORT_SETTINGS: 'r/:reportID/settings',
getReportSettingsRoute: reportID => `r/${reportID}/settings`,
LOGIN_WITH_SHORT_LIVED_TOKEN: 'transition',
TRANSITION: 'transition',
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
VALIDATE_LOGIN: 'v/:accountID/:validateCode',
GET_ASSISTANCE: 'get-assistance/:taskID',
getGetAssistanceRoute: taskID => `get-assistance/${taskID}`,
Expand Down
2 changes: 1 addition & 1 deletion src/SCREENS.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export default {
HOME: 'Home',
LOADING: 'Loading',
REPORT: 'Report',
LOG_IN_WITH_SHORT_LIVED_TOKEN: 'LogInWithShortLivedToken',
TRANSITION: 'Transition',
};
49 changes: 29 additions & 20 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import * as Policy from '../../actions/Policy';
import modalCardStyleInterpolator from './modalCardStyleInterpolator';
import createCustomModalStackNavigator from './createCustomModalStackNavigator';
import * as BankAccounts from '../../actions/BankAccounts';
import Log from '../../Log';

// Main drawer navigator
import MainDrawerNavigator from './MainDrawerNavigator';
Expand All @@ -38,11 +39,11 @@ import MainDrawerNavigator from './MainDrawerNavigator';
import * as ModalStackNavigators from './ModalStackNavigators';
import SCREENS from '../../../SCREENS';
import Timers from '../../Timers';
import LogInWithShortLivedTokenPage from '../../../pages/LogInWithShortLivedTokenPage';
import ValidateLoginPage from '../../../pages/ValidateLoginPage';
import defaultScreenOptions from './defaultScreenOptions';
import * as App from '../../actions/App';
import * as Session from '../../actions/Session';
import LogOutOldUserPage from '../../../pages/LogOutOldUserPage';

Onyx.connect({
key: ONYXKEYS.MY_PERSONAL_DETAILS,
Expand Down Expand Up @@ -131,12 +132,25 @@ class AuthScreens extends React.Component {
// Load policies, maybe creating a new policy first.
Linking.getInitialURL()
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
.then((url) => {
if (this.shouldCreateFreePolicy(url)) {
if (!url) {
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
return;
}
const path = new URL(url).pathname;
const params = new URLSearchParams(url);
const exitTo = params.get('exitTo');
const email = params.get('email');
const isLoggingInAsNewUser = this.props.session && this.props.session.email !== email;
const shouldCreateFreePolicy = !isLoggingInAsNewUser
&& Str.startsWith(path, Str.normalizeUrl(ROUTES.TRANSITION))
&& exitTo === ROUTES.WORKSPACE_NEW;
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
if (shouldCreateFreePolicy) {
Policy.createAndGetPolicyList();
return;
}

Policy.getPolicyList();
if (!isLoggingInAsNewUser && exitTo) {
this.navigateToExitRoute(exitTo);
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
}
});

// Refresh the personal details, timezone and betas every 30 minutes
Expand Down Expand Up @@ -184,22 +198,17 @@ class AuthScreens extends React.Component {
}

/**
* @param {String} [url]
* @returns {Boolean}
* Navigate to the transition exit route
*
* @param {String} exitTo
*/
shouldCreateFreePolicy(url = '') {
if (!url) {
return false;
}

const path = new URL(url).pathname;
const params = new URLSearchParams(url);
const exitTo = params.get('exitTo');
const email = params.get('email');
const isLoggingInAsNewUser = !_.isNull(this.props.session.email) && (email !== this.props.session.email);
return !isLoggingInAsNewUser
&& Str.startsWith(path, Str.normalizeUrl(ROUTES.LOGIN_WITH_SHORT_LIVED_TOKEN))
&& exitTo === ROUTES.WORKSPACE_NEW;
navigateToExitRoute(exitTo) {
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
// In order to navigate to a modal, we first have to dismiss the current modal. Without dismissing the current modal, if the user cancels out of the workspace modal,
// then they will be routed back to /transition/<accountID>/<email>/<authToken>/workspace/<policyID>/card and we don't want that. We want them to go back to `/`
// and by calling dismissModal(), the /transition/... route is removed from history so the user will get taken to `/` if they cancel out of the new workspace modal.
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
Log.info('[AuthScreens] Dismissing LogOutOldUserPage and navigating to the transition exit route');
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
Navigation.dismissModal();
Navigation.navigate(exitTo);
}

render() {
Expand Down Expand Up @@ -257,9 +266,9 @@ class AuthScreens extends React.Component {
component={ValidateLoginPage}
/>
<RootStack.Screen
name={SCREENS.LOG_IN_WITH_SHORT_LIVED_TOKEN}
name={SCREENS.TRANSITION}
options={defaultScreenOptions}
component={LogInWithShortLivedTokenPage}
component={LogOutOldUserPage}
/>

{/* These are the various modal routes */}
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/AppNavigator/PublicScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PublicScreens = () => (
component={SignInPage}
/>
<RootStack.Screen
name={SCREENS.LOG_IN_WITH_SHORT_LIVED_TOKEN}
name={SCREENS.TRANSITION}
options={defaultScreenOptions}
component={LogInWithShortLivedTokenPage}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default {
// Main Routes
SetPassword: ROUTES.SET_PASSWORD_WITH_VALIDATE_CODE,
ValidateLogin: ROUTES.VALIDATE_LOGIN,
[SCREENS.LOG_IN_WITH_SHORT_LIVED_TOKEN]: ROUTES.LOGIN_WITH_SHORT_LIVED_TOKEN,
[SCREENS.TRANSITION]: ROUTES.TRANSITION,

// Modal Screens
Settings: {
Expand Down
7 changes: 1 addition & 6 deletions src/libs/actions/Session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,19 +255,14 @@ function signIn(password, twoFactorAuthCode) {
/**
* Uses a short lived authToken to continue a user's session from OldDot
*
* @param {String} accountID
* @param {String} email
* @param {String} shortLivedToken
* @param {String} exitTo
*/
function signInWithShortLivedToken(accountID, email, shortLivedToken) {
function signInWithShortLivedToken(email, shortLivedToken) {
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});

createTemporaryLogin(shortLivedToken, email).then((response) => {
Onyx.merge(ONYXKEYS.SESSION, {
accountID,
email,
});
if (response.jsonCode === 200) {
User.getUserDetails();
Onyx.merge(ONYXKEYS.ACCOUNT, {success: true});
Expand Down
70 changes: 3 additions & 67 deletions src/pages/LogInWithShortLivedTokenPage.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import React, {Component} from 'react';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import ROUTES from '../ROUTES';
import ONYXKEYS from '../ONYXKEYS';
import * as Session from '../libs/actions/Session';
import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator';
import Navigation from '../libs/Navigation/Navigation';
import Log from '../libs/Log';

const propTypes = {
Expand All @@ -30,76 +26,20 @@ const propTypes = {
exitTo: PropTypes.string,
}),
}),

/** The data about the current session which will be set once the user is authenticated and we return to this component as an AuthScreen */
session: PropTypes.shape({
/** The authToken for the current session */
authToken: PropTypes.string,

/** The authToken for the current session */
email: PropTypes.string,
}),
};

const defaultProps = {
route: {
params: {},
},
session: {},
};

class LogInWithShortLivedTokenPage extends Component {
componentDidMount() {
const accountID = lodashGet(this.props.route.params, 'accountID', '');
const email = lodashGet(this.props.route.params, 'email', '');
const shortLivedToken = lodashGet(this.props.route.params, 'shortLivedToken', '');

const isUserSignedIn = this.props.session && this.props.session.authToken;
if (!isUserSignedIn) {
Log.info('[LoginWithShortLivedTokenPage] User not signed in - signing in with short lived token');
Session.signInWithShortLivedToken(accountID, email, shortLivedToken);
return;
}

if (this.signOutIfNeeded(email)) {
return;
}

Log.info('[LoginWithShortLivedTokenPage] User is signed in');

// exitTo is URI encoded because it could contain a variable number of slashes (i.e. "workspace/new" vs "workspace/<ID>/card")
const exitTo = decodeURIComponent(lodashGet(this.props.route.params, 'exitTo', ''));
if (exitTo === ROUTES.WORKSPACE_NEW) {
// New workspace creation is handled in AuthScreens, not in its own screen
Log.info('[LoginWithShortLivedTokenPage] exitTo is workspace/new - handling new workspace creation in AuthScreens');
return;
}

// In order to navigate to a modal, we first have to dismiss the current modal. Without dismissing the current modal, if the user cancels out of the workspace modal,
// then they will be routed back to /transition/<accountID>/<email>/<authToken>/workspace/<policyID>/card and we don't want that. We want them to go back to `/`
// and by calling dismissModal(), the /transition/... route is removed from history so the user will get taken to `/` if they cancel out of the new workspace modal.
Log.info('[LoginWithShortLivedTokenPage] Dismissing LoginWithShortLivedTokenPage and navigating to exitTo');
Navigation.dismissModal();
Navigation.navigate(exitTo);
}

/**
* If the user is trying to transition with a different account than the one
* they are currently signed in as we will sign them out, clear Onyx,
* and cancel all network requests. This component will mount again from
* PublicScreens and since they are no longer signed in, a request will be
* made to sign them in with their new account.
* @param {String} email The user's email passed as a route param.
* @returns {Boolean}
*/
signOutIfNeeded(email) {
if (this.props.session && this.props.session.email === email) {
return false;
}

Log.info('[LoginWithShortLivedTokenPage] Different user signed in - signing out');
Session.signOutAndRedirectToSignIn();
return true;
Log.info('[LoginWithShortLivedTokenPage] signing in the transitioning user');
Session.signInWithShortLivedToken(email, shortLivedToken);
}

render() {
Expand All @@ -110,8 +50,4 @@ class LogInWithShortLivedTokenPage extends Component {
LogInWithShortLivedTokenPage.propTypes = propTypes;
LogInWithShortLivedTokenPage.defaultProps = defaultProps;

export default withOnyx({
session: {
key: ONYXKEYS.SESSION,
},
})(LogInWithShortLivedTokenPage);
export default LogInWithShortLivedTokenPage;
70 changes: 70 additions & 0 deletions src/pages/LogOutOldUserPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, {Component} from 'react';
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import ONYXKEYS from '../ONYXKEYS';
import * as Session from '../libs/actions/Session';
import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator';
import Log from '../libs/Log';

const propTypes = {
/** The parameters needed to authenticate with a short lived token are in the URL */
route: PropTypes.shape({
/** The name of the route */
name: PropTypes.string,

/** Unique key associated with the route */
key: PropTypes.string,

/** Each parameter passed via the URL */
params: PropTypes.shape({
/** AccountID associated with the validation link */
accountID: PropTypes.string,

/** Short lived token */
shortLivedToken: PropTypes.string,

/** URL to exit to */
exitTo: PropTypes.string,
}),
}),

/** The data about the current session which will be set once the user is authenticated and we return to this component as an AuthScreen */
session: PropTypes.shape({
/** The authToken for the current session */
authToken: PropTypes.string,

/** The authToken for the current session */
email: PropTypes.string,
}),
};

const defaultProps = {
route: {
params: {},
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
},
session: {},
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
};

class LogOutOldUserPage extends Component {
componentDidMount() {
const email = lodashGet(this.props.route.params, 'email', '');
if (this.props.session && this.props.session.email !== email) {
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
Log.info('[LogOutOldUserPage] Different user signed in - signing out');
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
Session.signOutAndRedirectToSignIn();
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
}
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
}

render() {
return <FullScreenLoadingIndicator />;
}
}

LogOutOldUserPage.propTypes = propTypes;
LogOutOldUserPage.defaultProps = defaultProps;

export default withOnyx({
session: {
key: ONYXKEYS.SESSION,
},
})(LogOutOldUserPage);
14 changes: 0 additions & 14 deletions src/pages/ReimbursementAccount/ReimbursementAccountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import {withOnyx} from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import Log from '../../libs/Log';
import ScreenWrapper from '../../components/ScreenWrapper';
import * as BankAccounts from '../../libs/actions/BankAccounts';
import ONYXKEYS from '../../ONYXKEYS';
import ReimbursementAccountLoadingIndicator from '../../components/ReimbursementAccountLoadingIndicator';
import Permissions from '../../libs/Permissions';
import Navigation from '../../libs/Navigation/Navigation';
import CONST from '../../CONST';
import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
Expand All @@ -33,9 +31,6 @@ import reimbursementAccountPropTypes from './reimbursementAccountPropTypes';
import WorkspaceResetBankAccountModal from '../workspace/WorkspaceResetBankAccountModal';

const propTypes = {
/** List of betas */
betas: PropTypes.arrayOf(PropTypes.string).isRequired,

/** ACH data for the withdrawal account actively being set up */
reimbursementAccount: reimbursementAccountPropTypes,

Expand Down Expand Up @@ -145,12 +140,6 @@ class ReimbursementAccountPage extends React.Component {
}

render() {
if (!Permissions.canUseFreePlan(this.props.betas)) {
Log.info('Not showing new bank account page because user is not on free plan beta');
Navigation.dismissModal();
return null;
}

// The SetupWithdrawalAccount flow allows us to continue the flow from various points depending on where the
// user left off. This view will refer to the achData as the single source of truth to determine which route to
// display. We can also specify a specific route to navigate to via route params when the component first
Expand Down Expand Up @@ -250,9 +239,6 @@ export default compose(
session: {
key: ONYXKEYS.SESSION,
},
betas: {
key: ONYXKEYS.BETAS,
},
plaidLinkToken: {
key: ONYXKEYS.PLAID_LINK_TOKEN,
},
Expand Down