From 6e72ad1817987edb459ada0b5fde335c46b2f83e Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 20:19:30 -0700
Subject: [PATCH 01/81] update routes

---
 src/ROUTES.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/ROUTES.js b/src/ROUTES.js
index 0a84cb15ed98..9292a00136d3 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -47,6 +47,8 @@ export default {
     VALIDATE_LOGIN: 'v',
     VALIDATE_LOGIN_WITH_VALIDATE_CODE: 'v/:accountID/:validateCode',
     ENABLE_PAYMENTS: 'enable-payments',
+    WORKSPACE_INVITE: 'workspace/:policyID/invite',
+    getWorkspaceInviteRoute: policyID => `workspace/${policyID}/invite`,
 
     /**
      * @param {String} route

From 2a60ac7c6518cab11fa3de2b4a52d731199d82fe Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 20:22:19 -0700
Subject: [PATCH 02/81] update linkingConfig

---
 src/libs/Navigation/linkingConfig.js | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 7053c8d4f021..a1e5f2e91981 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -114,6 +114,11 @@ export default {
                     EnablePayments_Root: ROUTES.ENABLE_PAYMENTS,
                 },
             },
+            WorkspaceInvite: {
+                screens: {
+                    WorkspaceInvite_Root: ROUTES.WORKSPACE_INVITE,
+                },
+            },
         },
     },
 };

From e01059d5247b3397093d313cda601b35e602f733 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 20:55:23 -0700
Subject: [PATCH 03/81] add workspaceInvite page

---
 src/languages/en.js              |  7 +++
 src/pages/WorkspaceInvitePage.js | 85 ++++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+)
 create mode 100644 src/pages/WorkspaceInvitePage.js

diff --git a/src/languages/en.js b/src/languages/en.js
index cfe1a9790ade..b8ca2654ee5b 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -32,6 +32,7 @@ export default {
         recents: 'Recents',
         close: 'Close',
         settings: 'Settings',
+        invite: 'Invite',
     },
     attachmentPicker: {
         cameraPermissionRequired: 'Camera Permission Required',
@@ -318,4 +319,10 @@ export default {
     session: {
         offlineMessage: 'Looks like you\'re not connected to internet. Can you check your connection and try again?',
     },
+    workspaceInvitePage: {
+        invitePeople: 'Invite People',
+        invitePeoplePrompt: 'Invite a colleague to your workspace.',
+        enterEmailOrPhone: 'Email or Phone',
+        welcomeMessage: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
+    },
 };
diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
new file mode 100644
index 000000000000..4ee7b42a0a8f
--- /dev/null
+++ b/src/pages/WorkspaceInvitePage.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {TextInput, View} from 'react-native';
+import {withLocalizePropTypes} from '../components/withLocalize';
+import ScreenWrapper from '../components/ScreenWrapper';
+import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
+import Navigation from '../libs/Navigation/Navigation';
+import styles from '../styles/styles';
+import Text from '../components/Text';
+import Button from '../components/Button';
+
+const propTypes = {
+    ...withLocalizePropTypes,
+
+    /** PolicyID of the workspace */
+    policyID: PropTypes.string.isRequired,
+};
+
+class WorkspaceInvitePage extends React.Component {
+    constructor(props) {
+        super(props);
+
+        this.state = {
+            emailOrPhone: '',
+            welcomeMessage: '',
+        };
+    }
+
+    inviteUser() {
+        console.debug('user invited');
+    }
+
+    render() {
+        return (
+            <ScreenWrapper>
+                <HeaderWithCloseButton
+                    title={this.props.translate('workspaceInvitePage.invitePeople')}
+                    shouldShowBackButton
+                    onCloseButtonPress={() => Navigation.dismissModal()}
+                />
+                <View style={[styles.p5, styles.flex1, styles.overflowAuto]}>
+                    <View style={styles.flexGrow1}>
+                        <Text style={[styles.mb6, styles.textP]}>
+                            {this.props.translate('workspaceInvitePage.invitePeople')}
+                        </Text>
+                        <TextInput
+                            autoCompleteType="off"
+                            autoCorrect={false}
+                            style={[styles.textInput]}
+                            value={this.state.phoneOrEmail}
+                            placeholder={this.props.translate('workspaceInvitePage.enterEmailOrPhone')}
+                            onChangeText={text => this.setState({phoneOrEmail: text})}
+                        />
+                        <View style={styles.mb6}>
+                            <TextInput
+                                autoCompleteType="off"
+                                autoCorrect={false}
+                                style={[styles.textInput]}
+                                numberOfLines={10}
+                                value={this.state.welcomeMessage}
+                                placeholder={this.props.translate('workspaceInvitePage.welcomeMessage', {
+                                    workspaceName: 'TODO testing',
+                                })}
+                                onChangeText={text => this.setState({welcomeMessage: text})}
+                            />
+                        </View>
+                    </View>
+                    <View style={styles.flexGrow0}>
+                        <Button
+                            success
+                            style={[styles.mb2]}
+                            isDisabled={!this.state.emailOrPhone}
+                            text={this.props.translate('common.invite')}
+                            onPress={this.inviteUser}
+                        />
+                    </View>
+                </View>
+            </ScreenWrapper>
+        );
+    }
+}
+
+WorkspaceInvitePage.propTypes = propTypes;
+
+export default WorkspaceInvitePage;

From c255c9ee939959ed19935dbe69257b0a3792e58d Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 20:56:41 -0700
Subject: [PATCH 04/81] add placeholder values for spanish translations

---
 src/languages/es.js | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/languages/es.js b/src/languages/es.js
index a199c313d8da..02797676fec3 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -30,6 +30,7 @@ export default {
         contacts: 'Contactos',
         recents: 'Recientes',
         settings: 'Configuración',
+        invite: 'Invitar',
     },
     attachmentPicker: {
         cameraPermissionRequired: 'Se necesita permiso para usar la cámara',
@@ -275,4 +276,10 @@ export default {
     session: {
         offlineMessage: 'Parece que no estás conectado a internet. Comprueba tu conexión e inténtalo de nuevo.',
     },
+    workspaceInvitePage: {
+        invitePeople: 'Invite People',
+        invitePeoplePrompt: 'Invite a colleague to your workspace.',
+        enterEmailOrPhone: 'Email or Phone',
+        welcomeMessage: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
+    },
 };

From ba6e9ce913c00cc58909e465c8ab7c6c96300ecb Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 20:57:52 -0700
Subject: [PATCH 05/81] import withLocalize

---
 src/pages/WorkspaceInvitePage.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 4ee7b42a0a8f..56eb90213200 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import {TextInput, View} from 'react-native';
-import {withLocalizePropTypes} from '../components/withLocalize';
+import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
 import ScreenWrapper from '../components/ScreenWrapper';
 import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
 import Navigation from '../libs/Navigation/Navigation';
@@ -82,4 +82,4 @@ class WorkspaceInvitePage extends React.Component {
 
 WorkspaceInvitePage.propTypes = propTypes;
 
-export default WorkspaceInvitePage;
+export default withLocalize(WorkspaceInvitePage);

From b87c128c8a70c12500505ed6e452af9b38649741 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 21:01:32 -0700
Subject: [PATCH 06/81] add new screen and navigator

---
 src/libs/Navigation/AppNavigator/AuthScreens.js          | 8 +++++++-
 src/libs/Navigation/AppNavigator/ModalStackNavigators.js | 7 +++++++
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index a032193f97f8..215506c16e64 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -49,7 +49,7 @@ import {
     NewChatModalStackNavigator,
     SettingsModalStackNavigator,
     EnablePaymentsStackNavigator,
-    AddPersonalBankAccountModalStackNavigator,
+    AddPersonalBankAccountModalStackNavigator, WorkspaceInviteModalStackNavigator,
 } from './ModalStackNavigators';
 import SCREENS from '../../../SCREENS';
 import Timers from '../../Timers';
@@ -275,6 +275,12 @@ class AuthScreens extends React.Component {
                     component={AddPersonalBankAccountModalStackNavigator}
                     listeners={modalScreenListeners}
                 />
+                <RootStack.Screen
+                    name="WorkspaceInvite"
+                    options={modalScreenOptions}
+                    component={WorkspaceInviteModalStackNavigator}
+                    listeners={modalScreenListeners}
+                />
             </RootStack.Navigator>
         );
     }
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 7a3685f25067..8ea0d85f32fc 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -21,6 +21,7 @@ import IOUCurrencySelection from '../../../pages/iou/IOUCurrencySelection';
 import ReportParticipantsPage from '../../../pages/ReportParticipantsPage';
 import EnablePaymentsPage from '../../../pages/EnablePayments';
 import AddPersonalBankAccountPage from '../../../pages/AddPersonalBankAccountPage';
+import WorkspaceInvitePage from '../../../pages/WorkspaceInvitePage';
 
 const defaultSubRouteOptions = {
     cardStyle: styles.navigationScreenCardStyle,
@@ -152,6 +153,11 @@ const AddPersonalBankAccountModalStackNavigator = createModalStackNavigator([{
     name: 'AddPersonalBankAccount_Root',
 }]);
 
+const WorkspaceInviteModalStackNavigator = createModalStackNavigator([{
+    Component: WorkspaceInvitePage,
+    name: 'WorkspaceInvite_Root',
+}]);
+
 export {
     IOUBillStackNavigator,
     IOURequestModalStackNavigator,
@@ -164,4 +170,5 @@ export {
     SettingsModalStackNavigator,
     EnablePaymentsStackNavigator,
     AddPersonalBankAccountModalStackNavigator,
+    WorkspaceInviteModalStackNavigator,
 };

From 89dd12f0ca04f86b9560440c8c425f8f0dd89036 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 21:16:06 -0700
Subject: [PATCH 07/81] update text, fix broken state

---
 src/pages/WorkspaceInvitePage.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 56eb90213200..fbbbb1c55111 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -35,21 +35,20 @@ class WorkspaceInvitePage extends React.Component {
             <ScreenWrapper>
                 <HeaderWithCloseButton
                     title={this.props.translate('workspaceInvitePage.invitePeople')}
-                    shouldShowBackButton
                     onCloseButtonPress={() => Navigation.dismissModal()}
                 />
                 <View style={[styles.p5, styles.flex1, styles.overflowAuto]}>
                     <View style={styles.flexGrow1}>
                         <Text style={[styles.mb6, styles.textP]}>
-                            {this.props.translate('workspaceInvitePage.invitePeople')}
+                            {this.props.translate('workspaceInvitePage.invitePeoplePrompt')}
                         </Text>
                         <TextInput
                             autoCompleteType="off"
                             autoCorrect={false}
                             style={[styles.textInput]}
-                            value={this.state.phoneOrEmail}
+                            value={this.state.emailOrPhone}
                             placeholder={this.props.translate('workspaceInvitePage.enterEmailOrPhone')}
-                            onChangeText={text => this.setState({phoneOrEmail: text})}
+                            onChangeText={text => this.setState({emailOrPhone: text})}
                         />
                         <View style={styles.mb6}>
                             <TextInput

From 41d45d3f72a7d413e77bc3dc5b6c8102fdf6fc92 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 21:38:20 -0700
Subject: [PATCH 08/81] style welcome message box

---
 src/pages/WorkspaceInvitePage.js | 19 +++++++++++--------
 src/styles/styles.js             |  4 ++++
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index fbbbb1c55111..e3da36e2e6b1 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -42,20 +42,23 @@ class WorkspaceInvitePage extends React.Component {
                         <Text style={[styles.mb6, styles.textP]}>
                             {this.props.translate('workspaceInvitePage.invitePeoplePrompt')}
                         </Text>
-                        <TextInput
-                            autoCompleteType="off"
-                            autoCorrect={false}
-                            style={[styles.textInput]}
-                            value={this.state.emailOrPhone}
-                            placeholder={this.props.translate('workspaceInvitePage.enterEmailOrPhone')}
-                            onChangeText={text => this.setState({emailOrPhone: text})}
-                        />
                         <View style={styles.mb6}>
                             <TextInput
                                 autoCompleteType="off"
                                 autoCorrect={false}
                                 style={[styles.textInput]}
+                                value={this.state.emailOrPhone}
+                                placeholder={this.props.translate('workspaceInvitePage.enterEmailOrPhone')}
+                                onChangeText={text => this.setState({emailOrPhone: text})}
+                            />
+                        </View>
+                        <View style={styles.mb6}>
+                            <TextInput
+                                autoCompleteType="off"
+                                autoCorrect={false}
+                                style={[styles.textInput, styles.workspaceInviteWelcome]}
                                 numberOfLines={10}
+                                multiline
                                 value={this.state.welcomeMessage}
                                 placeholder={this.props.translate('workspaceInvitePage.welcomeMessage', {
                                     workspaceName: 'TODO testing',
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 170b7fea55c0..08ccf6853286 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1521,6 +1521,10 @@ const styles = {
     cursorDisabled: {
         cursor: 'not-allowed',
     },
+
+    workspaceInviteWelcome: {
+        minHeight: 200,
+    },
 };
 
 const baseCodeTagStyles = {

From 39d1b51cfc70b263f5a3d2b5fab7a8b5a995ab83 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 22:01:46 -0700
Subject: [PATCH 09/81] get policy name showing up in welcome text

---
 src/pages/WorkspaceInvitePage.js | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index e3da36e2e6b1..462fb41ad079 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import {TextInput, View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
 import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
 import ScreenWrapper from '../components/ScreenWrapper';
 import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
@@ -8,12 +9,23 @@ import Navigation from '../libs/Navigation/Navigation';
 import styles from '../styles/styles';
 import Text from '../components/Text';
 import Button from '../components/Button';
+import compose from '../libs/compose';
+import ONYXKEYS from '../ONYXKEYS';
 
 const propTypes = {
     ...withLocalizePropTypes,
 
-    /** PolicyID of the workspace */
-    policyID: PropTypes.string.isRequired,
+    /** The policy passed via the route */
+    policy: PropTypes.shape({
+        /** The policy name */
+        name: PropTypes.string,
+    }),
+};
+
+const defaultProps = {
+    policy: {
+        name: '',
+    },
 };
 
 class WorkspaceInvitePage extends React.Component {
@@ -61,7 +73,7 @@ class WorkspaceInvitePage extends React.Component {
                                 multiline
                                 value={this.state.welcomeMessage}
                                 placeholder={this.props.translate('workspaceInvitePage.welcomeMessage', {
-                                    workspaceName: 'TODO testing',
+                                    workspaceName: this.props.policy.name,
                                 })}
                                 onChangeText={text => this.setState({welcomeMessage: text})}
                             />
@@ -83,5 +95,13 @@ class WorkspaceInvitePage extends React.Component {
 }
 
 WorkspaceInvitePage.propTypes = propTypes;
+WorkspaceInvitePage.defaultProps = defaultProps;
 
-export default withLocalize(WorkspaceInvitePage);
+export default compose(
+    withLocalize,
+    withOnyx({
+        policy: {
+            key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`,
+        },
+    }),
+)(WorkspaceInvitePage);

From 0bde5fc227e21b4cfe1f2140fcb378b251bfa6cf Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 22:10:21 -0700
Subject: [PATCH 10/81] add Policy_Employees_Merge api command

---
 src/libs/API.js | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/libs/API.js b/src/libs/API.js
index 5a3918173bbf..ac276c60ce21 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -783,6 +783,20 @@ function BankAccount_Get(parameters) {
     return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
 }
 
+/**
+ * @param {Object} parameters
+ * @param {String[]} parameters.employees
+ * @param {String} parameters.welcomeNote
+ * @returns {Promise}
+ */
+function Policy_Employees_Merge(parameters) {
+    const commandName = 'Policy_Employees_Merge';
+    requireParameters(['employees', 'welcomeNote'], parameters, commandName);
+
+    // Always include returnPersonalDetails to ensure we get the employee's personal details in the response
+    return Network.post(commandName, {...parameters, returnPersonalDetails: true});
+}
+
 /**
  * @param {Object} parameters
  * @param {String} parameters.accountNumber

From 8be51d88609a6f78edc190d9c8d295a1c431fbea Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 8 Jun 2021 22:12:57 -0700
Subject: [PATCH 11/81] style

---
 src/libs/Navigation/AppNavigator/AuthScreens.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 215506c16e64..7c55b7b91ba8 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -49,7 +49,8 @@ import {
     NewChatModalStackNavigator,
     SettingsModalStackNavigator,
     EnablePaymentsStackNavigator,
-    AddPersonalBankAccountModalStackNavigator, WorkspaceInviteModalStackNavigator,
+    AddPersonalBankAccountModalStackNavigator,
+    WorkspaceInviteModalStackNavigator,
 } from './ModalStackNavigators';
 import SCREENS from '../../../SCREENS';
 import Timers from '../../Timers';

From c219c9e2abe96fe210e9800de361c6777a8eea24 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Wed, 9 Jun 2021 16:28:15 -1000
Subject: [PATCH 12/81] add models

---
 src/libs/API.js                         |  43 +++++
 src/libs/actions/BankAccounts.js        | 217 +++++++++++++++++++++++-
 src/libs/models/BankAccount.js          |   5 +
 src/pages/AddPersonalBankAccountPage.js |   4 +-
 4 files changed, 265 insertions(+), 4 deletions(-)
 create mode 100644 src/libs/models/BankAccount.js

diff --git a/src/libs/API.js b/src/libs/API.js
index 5a3918173bbf..e3f73804cb2e 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -812,6 +812,48 @@ function BankAccount_Create(parameters) {
     return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
 }
 
+/**
+ * @param {*} parameters
+ * @returns {Promise}
+ */
+function BankAccount_SetupWithdrawal(parameters) {
+    const commandName = 'BankAccount_SetupWithdrawal';
+    let allowedParameters = [
+        'currentStep', 'policyID', 'bankAccountID', 'useOnfido', 'errorAttemptsCount',
+
+        // data from bankAccount step:
+        'setupType', 'routingNumber', 'accountNumber', 'addressName', 'plaidAccountID', 'ownershipType', 'isSavings',
+        'acceptTerms', 'bankName', 'plaidAccessToken', 'alternateRoutingNumber',
+
+        // data from company step:
+        'companyName', 'companyTaxID', 'addressStreet', 'addressCity', 'addressState', 'addressZipCode',
+        'hasNoConnectionToCannabis', 'incorporationType', 'incorporationState', 'incorporationDate', 'industryCode',
+        'website', 'companyPhone', 'ficticiousBusinessName',
+
+        // data from requestor step:
+        'firstName', 'lastName', 'dob', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState',
+        'requestorAddressZipCode', 'isOnfidoSetupComplete', 'onfidoData', 'isControllingOfficer', 'ssnLast4',
+
+        // data from ACHContract step (which became the "Beneficial Owners" step, but the key is still ACHContract as
+        // it's used in several logic:
+        'ownsMoreThan25Percent', 'beneficialOwners', 'acceptTermsAndConditions', 'certifyTrueInformation',
+    ];
+
+    if (!parameters.useOnfido) {
+        allowedParameters = allowedParameters.concat(['passport', 'answers']);
+    }
+
+    // Only keep allowed parameters in the additionalData object
+    const additionalData = _.pick(parameters, allowedParameters);
+
+    requireParameters(['currentStep'], parameters, commandName);
+    return Network.post(
+        commandName, {additionalData: JSON.stringify(additionalData), password: parameters.password},
+        CONST.NETWORK.METHOD.POST,
+        true,
+    );
+}
+
 /**
  * @param {Object} parameters
  * @param {String[]} data
@@ -850,6 +892,7 @@ export {
     Authenticate,
     BankAccount_Create,
     BankAccount_Get,
+    BankAccount_SetupWithdrawal,
     ChangePassword,
     CreateChatReport,
     CreateLogin,
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 0b9e2f9e6ee9..e3d3e2851d55 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -5,6 +5,8 @@ import _ from 'underscore';
 import CONST from '../../CONST';
 import ONYXKEYS from '../../ONYXKEYS';
 import * as API from '../API';
+import Navigation from '../Navigation/Navigation';
+import BankAccount from '../models/BankAccount';
 
 /**
  * Gets the Plaid Link token used to initialize the Plaid SDK
@@ -75,7 +77,7 @@ function clearPlaidBankAccounts() {
  * @param {String} password
  * @param {String} plaidLinkToken
  */
-function addPlaidBankAccount(account, password, plaidLinkToken) {
+function addPersonalBankAccount(account, password, plaidLinkToken) {
     const unmaskedAccount = _.find(plaidBankAccounts, bankAccount => (
         bankAccount.plaidAccountID === account.plaidAccountID
     ));
@@ -269,12 +271,223 @@ function fetchUserWallet() {
         });
 }
 
+/**
+ * @private
+ * @param {String} stepID
+ * @returns {String}
+ */
+function getNextWithdrawalAccountSetupStep(stepID) {
+    const withdrawalAccountSteps = [
+        {
+            id: 'BankAccountStep',
+            title: 'Bank Account',
+        },
+        {
+            id: 'CompanyStep',
+            title: 'Company Information',
+        },
+        {
+            id: 'RequestorStep',
+            title: 'Requestor Information',
+        },
+        {
+            id: 'ACHContractStep',
+            title: 'Beneficial Owners',
+        },
+        {
+            id: 'ValidationStep',
+            title: 'Validate',
+        },
+        {
+            id: 'EnableStep',
+            title: 'Enable',
+        },
+    ];
+
+    const index = _.findIndex(withdrawalAccountSteps, step => step.id === stepID);
+    const nextStepIndex = Math.min(index + 1, withdrawalAccountSteps.length - 1);
+    return lodashGet(withdrawalAccountSteps, [nextStepIndex, 'id']);
+}
+
+/**
+ * @param {String} stepID
+ * @param {Object} previousACHData
+ * @param {Object} newACHData
+ * @returns {Object}
+ */
+function goToWithdrawlAccountStep(stepID, previousACHData, newACHData = {}) {
+    // @TODO I'm lost on how to migrate this one... seems like kind of tacked on mutations of the this.achData...
+    // I would like to maybe get rid of this.achData as a concept here... maybe the achData belongs in Onyx instead
+    // there's some weird stuff happening like setting the domain limit locally and the logic is not easy to follow yet.
+
+
+    const modifiedPreviousACHData = {...previousACHData};
+
+    // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
+    if (!modifiedPreviousACHData.useOnfido && stepID === 'RequestorStep') {
+        delete modifiedPreviousACHData.questions;
+        delete modifiedPreviousACHData.answers;
+        if (_.has(modifiedPreviousACHData, 'verifications.externalApiResponses')) {
+            delete modifiedPreviousACHData.verifications.externalApiResponses.requestorIdentityID;
+            delete modifiedPreviousACHData.verifications.externalApiResponses.requestorIdentityKBA;
+        }
+    }
+
+    // When going from companyStep to bankAccountStep, show the manual form instead of Plaid
+    if (modifiedPreviousACHData.currentStep === 'CompanyStep' && stepID === 'BankAccountStep') {
+        modifiedPreviousACHData.subStep = 'manual';
+    }
+
+    _.extend(modifiedPreviousACHData, newACHData, {currentStep: stepID});
+
+    if (stepID === 'EnableStep') {
+        // Calculate the Expensify Card limit associated with the bankAccountID
+        API.BankAccount_CalculateDomainLimit({
+            bankAccountID: modifiedPreviousACHData.bankAccountID,
+            useExistingDomainLimitIfAvailable: true,
+        })
+            .done((response) => {
+                // @TODO this doesn't really work because we'd have already returned this object - not entirely sure if
+                // we need to set a domain limit here anyway as the user can't modify it in E.cash
+                modifiedPreviousACHData.domainLimit = response.domainLimit || 3000000;
+            });
+    } else {
+        // previously we called refreshView() - but we probably will not do that in the E.cash version and will instead
+        // call Navigation.navigate() and just go to the view... I think probably we can kill this method also and just
+        // do this in setupWithdrawalAccount
+        Navigation.navigate(stepID, modifiedPreviousACHData);
+    }
+
+    return modifiedPreviousACHData;
+}
+
+/**
+ * @param {Object} previousACHData
+ * @param {Object} data
+ * @returns {Object}
+ */
+function setupWithdrawalAccount(previousACHData, data) {
+    // In Web-Secure this is referring to this.achData - still need to look at how that works...
+    const achData = _.extend(previousACHData, {
+        ...data,
+        isSavings: data
+            && !_.isUndefined(data.isSavings)
+            && Boolean(data.isSavings),
+    });
+
+    if (!achData.setupType) {
+        // @TODO use CONST
+        achData.setupType = achData.plaidAccountID ? 'plaid' : 'manual';
+    }
+
+    let nextStep = achData.currentStep;
+
+    API.BankAccount_SetupWithdrawal(achData)
+        .finally((response) => {
+            const currentStep = achData.currentStep;
+            let responseACHData = response.achData;
+            let error = lodashGet(responseACHData, 'verifications.errorMessage');
+
+            if (response.jsonCode === 200 && !error) {
+                // Show warning if another account already set up this bank account and promote share
+                if (response.existingOwners) {
+                    // @TODO show this error
+                    return;
+                }
+
+                // @TODO use CONST for step name
+                if (currentStep === 'RequestorStep') {
+                    // @TODO use CONST
+                    const requestorResponse = lodashGet(
+                        responseACHData,
+                        'verifications.externalApiResponses.requestorIdentityID',
+                    );
+
+                    if (responseACHData.useOnFido) {
+                        const onfidoResponse = lodashGet(
+                            responseACHData, 'verifications.externalApiResponses.requstorIdentityOnfido',
+                        );
+                        const sdkToken = lodashGet(onfidoResponse, 'apiResult.sdkToken');
+                        if (sdkToken && !achData.isOnfidoSetupComplete && onfidoResponse.status !== 'pass') {
+                            // Requestor Step still needs to run Onfido
+                            responseACHData.sdkToken = sdkToken;
+
+                            // Navigate to "RequestorStep" with responseACHData
+                            Navigation.navigate('/bank-account/requestor', responseACHData);
+                            return;
+                        }
+                    } else if (requestorResponse) {
+                        // Don't go to next step if Requestor Step needs to ask some questions
+                        let questions = _.get(requestorResponse, 'apiResult.questions.question') || [];
+                        if (_.isEmpty(questions)) {
+                            const differentiatorQuestion = lodashGet(
+                                requestorResponse,
+                                'apiResult.differentiator-question',
+                            );
+                            if (differentiatorQuestion) {
+                                questions = [differentiatorQuestion];
+                            }
+                        }
+                        if (!_.isEmpty(questions)) {
+                            responseACHData.questions = questions;
+                            Navigation.navigate('/bank-account/requestor', responseACHData);
+                            return;
+                        }
+                    }
+                }
+
+                // @TODO figure out why this is returning a promise...
+                if (currentStep === 'ACHContractStep') {
+                    // Get an up-to-date bank account list so that we can allow the user to validate their newly
+                    // generated bank account
+                    return API.Get({returnValueList: 'bankAccountList'}, true)
+                        .then((json) => {
+                            const bankAccount = new BankAccount(
+                                _.findWhere(json.bankAccountList, {bankAccountID: achData.bankAccountID}),
+                            );
+                            responseACHData = bankAccount.toACHData();
+                            const needsToPassLatestChecks = responseACHData.state === BankAccount.STATE.OPEN
+                                && responseACHData.needsToPassLatestChecks;
+                            responseACHData.bankAccountInReview = needsToPassLatestChecks
+                                || responseACHData.state === BankAccount.STATE.VERIFYING;
+                            Navigation.navigate('/bank-account/validation', responseACHData);
+                        });
+                }
+
+                if ((currentStep === 'ValidationStep' && achData.bankAccountInReview)
+                    || currentStep === 'EnableStep'
+                ) {
+                    // We're done. Close the view.
+                    // @TODO actually close it
+                } else {
+                    nextStep = getNextWithdrawalAccountSetupStep(achData);
+                }
+            } else {
+                if (response.jsonCode === 666) {
+                    error = response.message;
+                }
+                if (lodashGet(responseACHData, 'verifications.throttled')) {
+                    responseACHData.disableFields = true;
+                }
+            }
+
+            goToWithdrawlAccountStep(nextStep, achData, responseACHData);
+
+            if (error) {
+                // @TODO Show the error somehow
+            }
+        });
+
+    return achData;
+}
+
 export {
     fetchPlaidLinkToken,
-    addPlaidBankAccount,
+    addPersonalBankAccount,
     getPlaidBankAccounts,
     clearPlaidBankAccounts,
     fetchOnfidoToken,
     activateWallet,
     fetchUserWallet,
+    setupWithdrawalAccount,
 };
diff --git a/src/libs/models/BankAccount.js b/src/libs/models/BankAccount.js
new file mode 100644
index 000000000000..02566acabbbd
--- /dev/null
+++ b/src/libs/models/BankAccount.js
@@ -0,0 +1,5 @@
+class BankAccount {
+
+}
+
+export default BankAccount;
diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js
index 9fb582421717..787773244b47 100644
--- a/src/pages/AddPersonalBankAccountPage.js
+++ b/src/pages/AddPersonalBankAccountPage.js
@@ -14,7 +14,7 @@ import ScreenWrapper from '../components/ScreenWrapper';
 import Navigation from '../libs/Navigation/Navigation';
 import PlaidLink from '../components/PlaidLink';
 import {
-    addPlaidBankAccount,
+    addPersonalBankAccount,
     clearPlaidBankAccounts,
     fetchPlaidLinkToken,
     getPlaidBankAccounts,
@@ -79,7 +79,7 @@ class AddPersonalBankAccountPage extends React.Component {
 
     addSelectedAccount() {
         const account = this.getAccounts()[this.state.selectedIndex];
-        addPlaidBankAccount(account, this.state.password, this.props.plaidLinkToken);
+        addPersonalBankAccount(account, this.state.password, this.props.plaidLinkToken);
         this.setState({isCreatingAccount: true});
     }
 

From 5dfd8b329474a2976a52033a90cc250dea9280c5 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Thu, 10 Jun 2021 20:31:36 +0530
Subject: [PATCH 13/81] new: modal types added

---
 src/CONST.js                 |  1 +
 src/styles/getModalStyles.js | 30 ++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/src/CONST.js b/src/CONST.js
index d268f302177f..7b5fd899d31b 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -73,6 +73,7 @@ const CONST = {
             BOTTOM_DOCKED: 'bottom_docked',
             POPOVER: 'popover',
             RIGHT_DOCKED: 'right_docked',
+            CONTEXT_MENU: 'context_menu',
         },
         ANCHOR_ORIGIN_VERTICAL: {
             TOP: 'top',
diff --git a/src/styles/getModalStyles.js b/src/styles/getModalStyles.js
index 8921167c1cb1..69e126e3ee33 100644
--- a/src/styles/getModalStyles.js
+++ b/src/styles/getModalStyles.js
@@ -17,6 +17,8 @@ export default (type, windowDimensions, popoverAnchorPosition = {}) => {
     let hideBackdrop = false;
     let shouldAddBottomSafeAreaPadding = false;
     let shouldAddTopSafeAreaPadding = false;
+    let coverScreen = true;
+    let hasBackdrop = true;
 
     switch (type) {
         case CONST.MODAL.MODAL_TYPE.CONFIRM:
@@ -131,6 +133,32 @@ export default (type, windowDimensions, popoverAnchorPosition = {}) => {
             animationIn = 'fadeIn';
             animationOut = 'fadeOut';
             break;
+        case CONST.MODAL.MODAL_TYPE.CONTEXT_MENU:
+            modalStyle = {
+                ...modalStyle,
+                ...popoverAnchorPosition,
+                ...{
+                    position: 'absolute',
+                    alignItems: 'center',
+                    justifyContent: 'flex-end',
+                },
+            };
+            modalContainerStyle = {
+                borderRadius: 12,
+                borderWidth: 1,
+                borderColor: themeColors.border,
+                justifyContent: 'center',
+                overflow: 'hidden',
+                boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.025)',
+            };
+
+            hideBackdrop = true;
+            swipeDirection = undefined;
+            animationIn = 'fadeIn';
+            animationOut = 'fadeOut';
+            coverScreen = false;
+            hasBackdrop = false;
+            break;
         case CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED:
             modalStyle = {
                 ...modalStyle,
@@ -184,5 +212,7 @@ export default (type, windowDimensions, popoverAnchorPosition = {}) => {
         hideBackdrop,
         shouldAddBottomSafeAreaPadding,
         shouldAddTopSafeAreaPadding,
+        coverScreen,
+        hasBackdrop,
     };
 };

From a530555d702d6ecd77718006db2801949a687973 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Thu, 10 Jun 2021 20:32:57 +0530
Subject: [PATCH 14/81] new : context menu modal added

---
 src/components/ContextMenuPopover/index.js    | 33 +++++++++++++++++++
 .../ContextMenuPopover/index.native.js        | 21 ++++++++++++
 src/components/Popover/PopoverPropTypes.js    |  5 +++
 src/components/Popover/index.js               |  2 +-
 4 files changed, 60 insertions(+), 1 deletion(-)
 create mode 100644 src/components/ContextMenuPopover/index.js
 create mode 100644 src/components/ContextMenuPopover/index.native.js

diff --git a/src/components/ContextMenuPopover/index.js b/src/components/ContextMenuPopover/index.js
new file mode 100644
index 000000000000..aba3c883e9d8
--- /dev/null
+++ b/src/components/ContextMenuPopover/index.js
@@ -0,0 +1,33 @@
+import _ from 'underscore';
+import React from 'react';
+import {createPortal} from 'react-dom';
+import {propTypes, defaultProps as defaultPopoverProps} from '../Popover/PopoverPropTypes';
+import withWindowDimensions from '../withWindowDimensions';
+import Popover from '../Popover';
+import CONST from '../../CONST';
+
+const defaultProps = {
+    ...(_.omit(defaultPopoverProps, 'type')),
+};
+
+const ContextMenuPopover = props => (!props.isSmallScreenWidth ? createPortal(
+    <Popover
+        type={CONST.MODAL.MODAL_TYPE.CONTEXT_MENU}
+        shouldCloseOnOutsideClick
+        // eslint-disable-next-line react/jsx-props-no-spreading
+        {...props}
+    />,
+    document.body,
+) : (
+    <Popover
+        type={CONST.MODAL.MODAL_TYPE.CONTEXT_MENU}
+        // eslint-disable-next-line react/jsx-props-no-spreading
+        {...props}
+    />
+));
+
+ContextMenuPopover.propTypes = propTypes;
+ContextMenuPopover.defaultProps = defaultProps;
+ContextMenuPopover.displayName = 'ContextMenuPopover';
+
+export default withWindowDimensions(ContextMenuPopover);
diff --git a/src/components/ContextMenuPopover/index.native.js b/src/components/ContextMenuPopover/index.native.js
new file mode 100644
index 000000000000..9e9a9e3fa537
--- /dev/null
+++ b/src/components/ContextMenuPopover/index.native.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import {propTypes, defaultProps} from '../Popover/PopoverPropTypes';
+import withWindowDimensions from '../withWindowDimensions';
+import Popover from '../Popover';
+
+/*
+ * This is a convenience wrapper around the Modal component for a responsive Popover.
+ * On small screen widths, it uses BottomDocked modal type, and a Popover type on wide screen widths.
+ */
+const ContextMenuPopover = props => (
+    <Popover
+        // eslint-disable-next-line react/jsx-props-no-spreading
+        {...props}
+    />
+);
+
+ContextMenuPopover.propTypes = propTypes;
+ContextMenuPopover.defaultProps = defaultProps;
+ContextMenuPopover.displayName = 'ContextMenuPopover';
+
+export default withWindowDimensions(ContextMenuPopover);
diff --git a/src/components/Popover/PopoverPropTypes.js b/src/components/Popover/PopoverPropTypes.js
index ae11632acc0e..82dbfe9cc910 100644
--- a/src/components/Popover/PopoverPropTypes.js
+++ b/src/components/Popover/PopoverPropTypes.js
@@ -1,6 +1,7 @@
 import _ from 'underscore';
 import PropTypes from 'prop-types';
 import {propTypes as modalPropTypes, defaultProps as defaultModalProps} from '../Modal/ModalPropTypes';
+import CONST from '../../CONST';
 
 const propTypes = {
     ...(_.omit(modalPropTypes, 'type', 'popoverAnchorPosition')),
@@ -12,6 +13,9 @@ const propTypes = {
         bottom: PropTypes.number,
         left: PropTypes.number,
     }),
+
+    /** Modal type to override the default Popover type */
+    type: PropTypes.oneOf(_.values(CONST.MODAL.MODAL_TYPE)),
 };
 
 const defaultProps = {
@@ -19,6 +23,7 @@ const defaultProps = {
 
     // Anchor position is optional only because it is not relevant on mobile
     anchorPosition: {},
+    type: CONST.MODAL.MODAL_TYPE.POPOVER,
 };
 
 export {propTypes, defaultProps};
diff --git a/src/components/Popover/index.js b/src/components/Popover/index.js
index 9879d1d79a5f..c558823a9b92 100644
--- a/src/components/Popover/index.js
+++ b/src/components/Popover/index.js
@@ -10,7 +10,7 @@ import withWindowDimensions from '../withWindowDimensions';
  */
 const Popover = props => (
     <Modal
-        type={props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.POPOVER}
+        type={props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : props.type}
         popoverAnchorPosition={props.anchorPosition}
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...props}

From c444633623b4e4397750744c9c76a82bf4001c74 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Thu, 10 Jun 2021 20:33:39 +0530
Subject: [PATCH 15/81] replace popover with context menu popover in report
 actionitem

---
 src/components/PopoverWithMeasuredContent.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js
index bf67ba3a3327..bc3830944900 100644
--- a/src/components/PopoverWithMeasuredContent.js
+++ b/src/components/PopoverWithMeasuredContent.js
@@ -2,7 +2,7 @@ import _ from 'underscore';
 import React, {Component} from 'react';
 import PropTypes from 'prop-types';
 import {View} from 'react-native';
-import Popover from './Popover';
+import ContextMenuPopover from './ContextMenuPopover';
 import {propTypes as popoverPropTypes, defaultProps as defaultPopoverProps} from './Popover/PopoverPropTypes';
 import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
 import CONST from '../CONST';
@@ -34,7 +34,7 @@ const propTypes = {
 };
 
 const defaultProps = {
-    ...defaultPopoverProps,
+    ...(_.omit(defaultPopoverProps, 'type')),
 
     // Default positioning of the popover
     anchorOrigin: {
@@ -149,13 +149,13 @@ class PopoverWithMeasuredContent extends Component {
         };
         return this.state.isContentMeasured
             ? (
-                <Popover
+                <ContextMenuPopover
                     // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this.props}
                     anchorPosition={shifedAnchorPosition}
                 >
                     {this.props.measureContent()}
-                </Popover>
+                </ContextMenuPopover>
             ) : (
 
                 /*

From e2c64b9dc4146a1061cbc04e959f271ec17358c4 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Thu, 10 Jun 2021 20:34:48 +0530
Subject: [PATCH 16/81] added props to base modal to remove the backdrop

---
 src/components/Modal/BaseModal.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index f0b5fb6a1a7a..98584e7206dc 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -56,6 +56,8 @@ class BaseModal extends PureComponent {
             shouldAddTopSafeAreaPadding,
             shouldAddBottomSafeAreaPadding,
             hideBackdrop,
+            hasBackdrop,
+            coverScreen,
         } = getModalStyles(
             this.props.type,
             {
@@ -89,6 +91,8 @@ class BaseModal extends PureComponent {
                 backdropColor={themeColors.modalBackdrop}
                 backdropOpacity={hideBackdrop ? 0 : 0.5}
                 backdropTransitionOutTiming={0}
+                hasBackdrop={hasBackdrop}
+                coverScreen={coverScreen}
                 style={modalStyle}
                 deviceHeight={this.props.windowHeight}
                 deviceWidth={this.props.windowWidth}
@@ -123,6 +127,7 @@ class BaseModal extends PureComponent {
                                     ...modalContainerStyle,
                                     ...modalPaddingStyles,
                                 }}
+                                ref={this.props.forwardRef}
                             >
                                 {this.props.children}
                             </View>
@@ -137,4 +142,7 @@ class BaseModal extends PureComponent {
 BaseModal.propTypes = propTypes;
 BaseModal.defaultProps = defaultProps;
 BaseModal.displayName = 'BaseModal';
-export default BaseModal;
+export default React.forwardRef((props, ref) => (
+    // eslint-disable-next-line react/jsx-props-no-spreading
+    <BaseModal {...props} forwardRef={ref} />
+));

From e842554186a7185704243fbf1e087d1119411bf8 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Thu, 10 Jun 2021 20:35:36 +0530
Subject: [PATCH 17/81] outside click close to Modal

---
 src/components/Modal/index.js | 47 +++++++++++++++++++++++++++++------
 1 file changed, 40 insertions(+), 7 deletions(-)

diff --git a/src/components/Modal/index.js b/src/components/Modal/index.js
index 169b63d794e6..c3faef65332d 100644
--- a/src/components/Modal/index.js
+++ b/src/components/Modal/index.js
@@ -1,14 +1,47 @@
-import React from 'react';
+import React, {PureComponent} from 'react';
 import withWindowDimensions from '../withWindowDimensions';
 import BaseModal from './BaseModal';
 import {propTypes, defaultProps} from './ModalPropTypes';
 
-const Modal = props => (
-    // eslint-disable-next-line react/jsx-props-no-spreading
-    <BaseModal {...props}>
-        {props.children}
-    </BaseModal>
-);
+
+class Modal extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.closeOnOutsideClick = this.closeOnOutsideClick.bind(this);
+    }
+
+    componentDidMount() {
+        if (this.props.shouldCloseOnOutsideClick) {
+            document.addEventListener('mousedown', this.closeOnOutsideClick);
+        }
+    }
+
+    componentWillUnmount() {
+        if (this.props.shouldCloseOnOutsideClick) {
+            document.removeEventListener('mousedown', this.closeOnOutsideClick);
+        }
+    }
+
+    closeOnOutsideClick(event) {
+        if (this.props.isVisible
+            && this.baseModalRef && !this.baseModalRef.contains(event.target)
+            && this.props.shouldCloseOnOutsideClick) {
+            this.props.onClose();
+        }
+    }
+
+    render() {
+        return (
+            <BaseModal
+                ref={el => this.baseModalRef = el}
+                // eslint-disable-next-line react/jsx-props-no-spreading
+                {...this.props}
+            >
+                {this.props.children}
+            </BaseModal>
+        );
+    }
+}
 
 Modal.propTypes = propTypes;
 Modal.defaultProps = defaultProps;

From 18b9be052aa7bc0c3bb02fa347df25b906d21001 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Thu, 10 Jun 2021 21:15:05 +0530
Subject: [PATCH 18/81] fix: Prop types

---
 src/components/ContextMenuPopover/index.js   | 12 +++---------
 src/components/Modal/ModalPropTypes.js       |  4 ++++
 src/components/Popover/PopoverPropTypes.js   |  7 ++-----
 src/components/PopoverWithMeasuredContent.js |  4 ++--
 4 files changed, 11 insertions(+), 16 deletions(-)

diff --git a/src/components/ContextMenuPopover/index.js b/src/components/ContextMenuPopover/index.js
index aba3c883e9d8..56bfefc147ac 100644
--- a/src/components/ContextMenuPopover/index.js
+++ b/src/components/ContextMenuPopover/index.js
@@ -1,26 +1,20 @@
-import _ from 'underscore';
 import React from 'react';
 import {createPortal} from 'react-dom';
-import {propTypes, defaultProps as defaultPopoverProps} from '../Popover/PopoverPropTypes';
+import {propTypes, defaultProps} from '../Popover/PopoverPropTypes';
 import withWindowDimensions from '../withWindowDimensions';
 import Popover from '../Popover';
 import CONST from '../../CONST';
 
-const defaultProps = {
-    ...(_.omit(defaultPopoverProps, 'type')),
-};
-
 const ContextMenuPopover = props => (!props.isSmallScreenWidth ? createPortal(
     <Popover
-        type={CONST.MODAL.MODAL_TYPE.CONTEXT_MENU}
-        shouldCloseOnOutsideClick
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...props}
+        type={CONST.MODAL.MODAL_TYPE.CONTEXT_MENU}
+        shouldCloseOnOutsideClick
     />,
     document.body,
 ) : (
     <Popover
-        type={CONST.MODAL.MODAL_TYPE.CONTEXT_MENU}
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...props}
     />
diff --git a/src/components/Modal/ModalPropTypes.js b/src/components/Modal/ModalPropTypes.js
index 6e6f7dd850d7..f375f0f67841 100644
--- a/src/components/Modal/ModalPropTypes.js
+++ b/src/components/Modal/ModalPropTypes.js
@@ -4,6 +4,9 @@ import CONST from '../../CONST';
 import {windowDimensionsPropTypes} from '../withWindowDimensions';
 
 const propTypes = {
+    /** Should we close modal on outside click */
+    shouldCloseOnOutsideClick: PropTypes.bool,
+
     /** Callback method fired when the user requests to close the modal */
     onClose: PropTypes.func.isRequired,
 
@@ -49,6 +52,7 @@ const propTypes = {
 };
 
 const defaultProps = {
+    shouldCloseOnOutsideClick: false,
     onSubmit: null,
     type: '',
     onModalHide: () => {},
diff --git a/src/components/Popover/PopoverPropTypes.js b/src/components/Popover/PopoverPropTypes.js
index 82dbfe9cc910..7d87de319da9 100644
--- a/src/components/Popover/PopoverPropTypes.js
+++ b/src/components/Popover/PopoverPropTypes.js
@@ -4,7 +4,7 @@ import {propTypes as modalPropTypes, defaultProps as defaultModalProps} from '..
 import CONST from '../../CONST';
 
 const propTypes = {
-    ...(_.omit(modalPropTypes, 'type', 'popoverAnchorPosition')),
+    ...(_.omit(modalPropTypes, 'popoverAnchorPosition')),
 
     /** The anchor position of the popover */
     anchorPosition: PropTypes.shape({
@@ -13,13 +13,10 @@ const propTypes = {
         bottom: PropTypes.number,
         left: PropTypes.number,
     }),
-
-    /** Modal type to override the default Popover type */
-    type: PropTypes.oneOf(_.values(CONST.MODAL.MODAL_TYPE)),
 };
 
 const defaultProps = {
-    ...(_.omit(defaultModalProps, 'type', 'popoverAnchorPosition')),
+    ...(_.omit(defaultModalProps, 'popoverAnchorPosition')),
 
     // Anchor position is optional only because it is not relevant on mobile
     anchorPosition: {},
diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js
index bc3830944900..e68bc1a6f649 100644
--- a/src/components/PopoverWithMeasuredContent.js
+++ b/src/components/PopoverWithMeasuredContent.js
@@ -12,7 +12,7 @@ import {computeHorizontalShift, computeVerticalShift} from '../styles/getPopover
 const propTypes = {
     // All popover props except:
     // 1) anchorPosition (which is overridden for this component)
-    ...(_.omit(popoverPropTypes, ['anchorPosition'])),
+    ...(_.omit(popoverPropTypes, ['type', 'anchorPosition'])),
 
     /** The horizontal and vertical anchors points for the popover */
     anchorPosition: PropTypes.shape({
@@ -34,7 +34,7 @@ const propTypes = {
 };
 
 const defaultProps = {
-    ...(_.omit(defaultPopoverProps, 'type')),
+    ...(_.omit(defaultPopoverProps, ['type'])),
 
     // Default positioning of the popover
     anchorOrigin: {

From 80869c948855583643b0986780db81bcccc55832 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Thu, 10 Jun 2021 21:31:46 +0530
Subject: [PATCH 19/81] fix: props for contextmenu

---
 src/components/ContextMenuPopover/index.js        | 1 +
 src/components/ContextMenuPopover/index.native.js | 2 ++
 src/components/Popover/PopoverPropTypes.js        | 2 --
 src/components/Popover/index.js                   | 2 +-
 4 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/components/ContextMenuPopover/index.js b/src/components/ContextMenuPopover/index.js
index 56bfefc147ac..03d325b0e67e 100644
--- a/src/components/ContextMenuPopover/index.js
+++ b/src/components/ContextMenuPopover/index.js
@@ -17,6 +17,7 @@ const ContextMenuPopover = props => (!props.isSmallScreenWidth ? createPortal(
     <Popover
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...props}
+        type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
     />
 ));
 
diff --git a/src/components/ContextMenuPopover/index.native.js b/src/components/ContextMenuPopover/index.native.js
index 9e9a9e3fa537..6d278e9e321a 100644
--- a/src/components/ContextMenuPopover/index.native.js
+++ b/src/components/ContextMenuPopover/index.native.js
@@ -2,6 +2,7 @@ import React from 'react';
 import {propTypes, defaultProps} from '../Popover/PopoverPropTypes';
 import withWindowDimensions from '../withWindowDimensions';
 import Popover from '../Popover';
+import CONST from '../../CONST';
 
 /*
  * This is a convenience wrapper around the Modal component for a responsive Popover.
@@ -11,6 +12,7 @@ const ContextMenuPopover = props => (
     <Popover
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...props}
+        type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
     />
 );
 
diff --git a/src/components/Popover/PopoverPropTypes.js b/src/components/Popover/PopoverPropTypes.js
index 7d87de319da9..e6f9c6856cfe 100644
--- a/src/components/Popover/PopoverPropTypes.js
+++ b/src/components/Popover/PopoverPropTypes.js
@@ -1,7 +1,6 @@
 import _ from 'underscore';
 import PropTypes from 'prop-types';
 import {propTypes as modalPropTypes, defaultProps as defaultModalProps} from '../Modal/ModalPropTypes';
-import CONST from '../../CONST';
 
 const propTypes = {
     ...(_.omit(modalPropTypes, 'popoverAnchorPosition')),
@@ -20,7 +19,6 @@ const defaultProps = {
 
     // Anchor position is optional only because it is not relevant on mobile
     anchorPosition: {},
-    type: CONST.MODAL.MODAL_TYPE.POPOVER,
 };
 
 export {propTypes, defaultProps};
diff --git a/src/components/Popover/index.js b/src/components/Popover/index.js
index c558823a9b92..9879d1d79a5f 100644
--- a/src/components/Popover/index.js
+++ b/src/components/Popover/index.js
@@ -10,7 +10,7 @@ import withWindowDimensions from '../withWindowDimensions';
  */
 const Popover = props => (
     <Modal
-        type={props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : props.type}
+        type={props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.POPOVER}
         popoverAnchorPosition={props.anchorPosition}
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...props}

From 6eac7d48d5c8e11ea73a95dfdc0605c0c2677d19 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Thu, 10 Jun 2021 09:47:30 -1000
Subject: [PATCH 20/81] add BankAccount model

---
 src/CONST.js                   |   6 +
 src/libs/models/BankAccount.js | 322 +++++++++++++++++++++++++++++++++
 2 files changed, 328 insertions(+)

diff --git a/src/CONST.js b/src/CONST.js
index 71a80cf4f0b7..09cb88d02b9f 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -19,6 +19,12 @@ const CONST = {
         PRESSED: 'pressed',
         COMPLETE: 'complete',
     },
+    COUNTRY: {
+        US: 'US',
+        MX: 'MX',
+        AU: 'AU',
+        CA: 'CA',
+    },
     PLATFORM: {
         IOS: 'ios',
         ANDROID: 'android',
diff --git a/src/libs/models/BankAccount.js b/src/libs/models/BankAccount.js
index 02566acabbbd..cfecacb67af6 100644
--- a/src/libs/models/BankAccount.js
+++ b/src/libs/models/BankAccount.js
@@ -1,5 +1,327 @@
+import _ from 'underscore';
+import Onyx from 'react-native-onyx';
+import Str from 'expensify-common/lib/str';
+import lodashGet from 'lodash/get';
+import lodashHas from 'lodash/has';
+import CONST from '../../CONST';
+import ONYXKEYS from '../../ONYXKEYS';
+
+let currentUserLogin;
+
+Onyx.connect({
+    key: ONYXKEYS.SESSION,
+    callback: val => currentUserLogin = val && val.email,
+});
+
 class BankAccount {
+    static STATE = {
+        PENDING: 'PENDING',
+        OPEN: 'OPEN',
+        DELETED: 'DELETED',
+        LOCKED: 'LOCKED',
+        SETUP: 'SETUP',
+        VERIFYING: 'VERIFYING',
+    };
+
+    constructor(accountJSON) {
+        this.json = accountJSON;
+    }
+
+    /**
+     * Return the ID of the reimbursement account
+     *
+     * @returns {Number}
+     */
+    getID() {
+        return this.json.bankAccountID;
+    }
+
+    /**
+     * Return the account number, which has been obfuscate by the back end
+     * example "XXXXXX3956"
+     *
+     * @returns {String}
+     */
+    getMaskedAccountNumber() {
+        return this.json.accountNumber;
+    }
+
+    /**
+     * Used as the display name for the account...
+     * @returns {String}
+     */
+    getAddressName() {
+        return this.json.addressName;
+    }
+
+    /**
+     * @returns {String}
+     */
+    getProcessor() {
+        return this.json.processor;
+    }
+
+    /**
+     * @returns {String}
+     */
+    getRoutingNumber() {
+        return this.json.routingNumber;
+    }
+
+    /**
+     * Get all user emails having access to this bank account
+     * @return {String[]}
+     */
+    getSharees() {
+        return this.json.sharees;
+    }
+
+    /**
+     * @returns {String}
+     * @private
+     */
+    getState() {
+        return this.json.state;
+    }
+
+    /**
+     * @returns {Boolean}
+     */
+    isOpen() {
+        return this.getState() === BankAccount.STATE.OPEN;
+    }
+
+    /**
+     * @deprecated Use !isPending instead.
+     * @returns {Boolean}
+     */
+    isVerified() {
+        return !this.isPending();
+    }
+
+    /**
+     * If the user still needs to enter the 3 micro deposit amounts.
+     * @returns {Boolean}
+     */
+    isPending() {
+        return this.getState() === BankAccount.STATE.PENDING;
+    }
+
+    /**
+     * If success team is currently verifying the bank account data provided by the user.
+     * @returns {Boolean}
+     */
+    isVerifying() {
+        return Boolean(this.json.validating) || this.getState() === BankAccount.STATE.VERIFYING;
+    }
+
+    /**
+     * If the user didn't finish entering all his info.
+     * @returns {Boolean}
+     */
+    isInSetup() {
+        return this.getState() === BankAccount.STATE.SETUP;
+    }
+
+    /**
+     * @returns {Boolean}
+     */
+    isLocked() {
+        return this.getState() === BankAccount.STATE.LOCKED;
+    }
+
+    /**
+     * If someone asked to share the bank account with me, and the request is still pending
+     * @returns {Boolean}
+     */
+    isSharePending() {
+        return Boolean(this.json.shareComplete === false && this.getOwner() !== currentUserLogin);
+    }
+
+    /**
+     * Who shared this account with me?
+     * @returns {String}
+     */
+    getOwner() {
+        return this.json.ownedBy;
+    }
+
+    /**
+     * Is it the account to use by default to receive money?
+     *
+     * @returns {Boolean}
+     */
+    isDefaultCredit() {
+        return this.json.defaultCredit === true;
+    }
+
+    /**
+     * Can we use this account to pay other people?
+     *
+     * @returns {Boolean}
+     */
+    isWithdrawal() {
+        return this.json.allowDebit === true;
+    }
+
+    /**
+     * Get when the user last updated their bank account.
+     * @return {*|String}
+     */
+    getDateSigned() {
+        return lodashGet(this.json, ['additionalData', 'dateSigned']) || '';
+    }
+
+    /**
+     * Return the client ID of this bank account
+     *
+     * @NOTE WARNING KEEP IN SYNC WITH THE PHP
+     * @returns {String}
+     */
+    getClientID() {
+        // eslint-disable-next-line max-len
+        return `${Str.makeID(this.getMaskedAccountNumber())}${Str.makeID(this.getAddressName())}${Str.makeID(this.getRoutingNumber())}${this.getType()}`;
+    }
+
+    /**
+     * @returns {String}
+     * @private
+     */
+    getType() {
+        return this.isWithdrawal() ? 'withdrawal' : 'direct-deposit';
+    }
+
+    /**
+     * Return the internal json data structure used by auth
+     * @returns {Object}
+     */
+    getJSON() {
+        return this.json;
+    }
+
+    /**
+     * Return whether or not this bank account has been risk checked
+     * @returns {Boolean}
+     */
+    isRiskChecked() {
+        return Boolean(this.json.riskChecked);
+    }
+
+    /**
+     * Return when the 3 micro amounts for validation were supposed to reach the bank account.
+     * @returns {String}
+     */
+    getValidateCodeExpectedDate() {
+        return this.json.validateCodeExpectedDate || '';
+    }
+
+    /**
+     * In which country is the bank account?
+     * @returns {string}
+     */
+    getCountry() {
+        return lodashGet(this.json, ['additionalData', 'country'], CONST.COUNTRY.US);
+    }
+
+    /**
+     * In which currency is the bank account?
+     * @returns {String}
+     */
+    getCurrency() {
+        return lodashGet(this.json, ['additionalData', 'currency'], 'USD');
+    }
+
+    /**
+     * In which bank is the bank account?
+     * @returns {String}
+     */
+    getBankName() {
+        return lodashGet(this.json, ['additionalData', 'bankName'], lodashGet(this.json, 'bankName'));
+    }
+
+    /**
+     * Did we get bank account details for local transfer or international wire?
+     * @returns {Boolean}
+     */
+    hasInternationalWireDetails() {
+        return lodashGet(this.json, ['additionalData', 'fieldsType'], 'local') === 'international';
+    }
+
+    /**
+     * Get the additional data of a bankAccount
+     * @returns {Object}
+     */
+    getAdditionalData() {
+        return this.json.additionalData || {};
+    }
+
+    /**
+     * Return a map needed to setup a withdrawal account
+     * @returns {Object}
+     */
+    toACHData() {
+        return _.extend({
+            routingNumber: this.getRoutingNumber(),
+            accountNumber: this.getMaskedAccountNumber(),
+            addressName: this.getAddressName(),
+            isSavings: this.json.isSavings,
+            bankAccountID: this.getID(),
+            state: this.getState(),
+            validateCodeExpectedDate: this.getValidateCodeExpectedDate(),
+            needsToPassLatestChecks: this.needsToPassLatestChecks(),
+            needsToUpgrade: this.needsToUpgrade(),
+        }, this.getAdditionalData());
+    }
+
+
+    /**
+     * Check if user hasn't upgraded their bank account yet.
+     * @return {Boolean}
+     */
+    needsToUpgrade() {
+        return !this.isInSetup() && !lodashHas(this.json, ['additionalData', 'beneficialOwners']);
+    }
+
+    /**
+     * Check if we've performed the most recently implemented checks on the bank account, and they all passed.
+     * Same logic as in BankAccount.php needsToPassLatestChecks
+     * @return {Boolean}
+     */
+    needsToPassLatestChecks() {
+        if (!lodashGet(this.json, ['additionalData', 'hasFullSSN'])) {
+            return true;
+        }
+
+        const beneficialOwners = lodashGet(this.json, ['additionalData', 'beneficialOwners']);
+        if (!beneficialOwners) {
+            return true;
+        }
+
+        const city = lodashGet(this.json, ['additionalData', 'requestorAddressCity']);
+        if (!city) {
+            return true;
+        }
+
+        if (_.isArray(beneficialOwners)) {
+            const hasBeneficialOwnerError = _.any(beneficialOwners, (beneficialOwner) => {
+                const hasFullSSN = lodashGet(beneficialOwner, 'hasFullSSN')
+                    || !_.isEmpty(lodashGet(beneficialOwner, 'ssn'));
+                return !lodashGet(beneficialOwner, 'isRequestor')
+                    && (lodashGet(beneficialOwner, ['expectIDPA', 'status']) !== 'pass'
+                        || !lodashGet(beneficialOwner, 'city') || !hasFullSSN
+                    );
+            });
+            if (hasBeneficialOwnerError) {
+                return true;
+            }
+        }
 
+        return _.any(['realSearchResult', 'lexisNexisInstantIDResult', 'requestorIdentityID'], field => (
+            lodashGet(this.json, [
+                'additionalData', 'verifications', 'externalApiResponses', field, 'status',
+            ]) !== 'pass'
+        ));
+    }
 }
 
 export default BankAccount;

From cc78e0169af567d8e6bf9ea10671649c995403d9 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Thu, 10 Jun 2021 10:15:45 -1000
Subject: [PATCH 21/81] update some consts

---
 src/CONST.js                     | 32 +++++++++++++
 src/libs/actions/BankAccounts.js | 78 ++++++++++++++++++--------------
 2 files changed, 75 insertions(+), 35 deletions(-)

diff --git a/src/CONST.js b/src/CONST.js
index 09cb88d02b9f..edaee01ab906 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -6,6 +6,38 @@ const CONST = {
         IOS: 'https://apps.apple.com/us/app/expensify-cash/id1530278510',
         DESKTOP: 'https://expensify.cash/Expensify.cash.dmg',
     },
+    BANK_ACCOUNT: {
+        STEP: {
+            BANK_ACCOUNT: 'BankAccountStep',
+            COMPANY: 'CompanyStep',
+            REQUESTOR: 'RequestorStep',
+            ACH_CONTRACT: 'ACHContractStep',
+            VALIDATION: 'ValidationStep',
+            ENABLE: 'EnableStep',
+        },
+        SUBSTEP: {
+            MANUAL: 'manual',
+        },
+        VERIFICATIONS: {
+            ERROR_MESSAGE: 'verifications.errorMessage',
+            EXTERNAL_API_RESPONSES: 'verifications.externalApiResponses',
+            REQUESTOR_IDENTITY_ID: 'verifications.externalApiResponses.requestorIdentityID',
+            REQUESTOR_IDENTITY_ONFIDO: 'verifications.externalApiResponses.requestorIdentityOnfido',
+            THROTTLED: 'verifications.throttled',
+        },
+        ONFIDO_RESPONSE: {
+            SDK_TOKEN: 'apiResult.sdkToken',
+            PASS: 'pass',
+        },
+        QUESTIONS: {
+            QUESTION: 'apiResult.questions.question',
+            DIFFERENTIATOR_QUESTION: 'apiResult.differentiator-question',
+        },
+        SETUP_TYPE: {
+            MANUAL: 'manual',
+            PLAID: 'plaid',
+        },
+    },
     BETAS: {
         ALL: 'all',
         CHRONOS_IN_CASH: 'chronosInCash',
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index e3d3e2851d55..3217bdc1375f 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -1,4 +1,5 @@
 import lodashGet from 'lodash/get';
+import lodashHas from 'lodash/has';
 import Str from 'expensify-common/lib/str';
 import Onyx from 'react-native-onyx';
 import _ from 'underscore';
@@ -279,27 +280,27 @@ function fetchUserWallet() {
 function getNextWithdrawalAccountSetupStep(stepID) {
     const withdrawalAccountSteps = [
         {
-            id: 'BankAccountStep',
+            id: CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT,
             title: 'Bank Account',
         },
         {
-            id: 'CompanyStep',
+            id: CONST.BANK_ACCOUNT.STEP.COMPANY,
             title: 'Company Information',
         },
         {
-            id: 'RequestorStep',
+            id: CONST.BANK_ACCOUNT.STEP.REQUESTOR,
             title: 'Requestor Information',
         },
         {
-            id: 'ACHContractStep',
+            id: CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT,
             title: 'Beneficial Owners',
         },
         {
-            id: 'ValidationStep',
+            id: CONST.BANK_ACCOUNT.STEP.VALIDATION,
             title: 'Validate',
         },
         {
-            id: 'EnableStep',
+            id: CONST.BANK_ACCOUNT.STEP.ENABLE,
             title: 'Enable',
         },
     ];
@@ -320,37 +321,40 @@ function goToWithdrawlAccountStep(stepID, previousACHData, newACHData = {}) {
     // I would like to maybe get rid of this.achData as a concept here... maybe the achData belongs in Onyx instead
     // there's some weird stuff happening like setting the domain limit locally and the logic is not easy to follow yet.
 
-
     const modifiedPreviousACHData = {...previousACHData};
 
     // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
-    if (!modifiedPreviousACHData.useOnfido && stepID === 'RequestorStep') {
+    if (!modifiedPreviousACHData.useOnfido && stepID === CONST.BANK_ACCOUNT.STEP.REQUESTOR) {
         delete modifiedPreviousACHData.questions;
         delete modifiedPreviousACHData.answers;
-        if (_.has(modifiedPreviousACHData, 'verifications.externalApiResponses')) {
+        if (lodashHas(modifiedPreviousACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.EXTERNAL_API_RESPONSES)) {
             delete modifiedPreviousACHData.verifications.externalApiResponses.requestorIdentityID;
             delete modifiedPreviousACHData.verifications.externalApiResponses.requestorIdentityKBA;
         }
     }
 
     // When going from companyStep to bankAccountStep, show the manual form instead of Plaid
-    if (modifiedPreviousACHData.currentStep === 'CompanyStep' && stepID === 'BankAccountStep') {
-        modifiedPreviousACHData.subStep = 'manual';
+    if (modifiedPreviousACHData.currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY
+        && stepID === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT
+    ) {
+        modifiedPreviousACHData.subStep = CONST.BANK_ACCOUNT.SUBSTEP.MANUAL;
     }
 
     _.extend(modifiedPreviousACHData, newACHData, {currentStep: stepID});
 
-    if (stepID === 'EnableStep') {
+    if (stepID === CONST.BANK_ACCOUNT.STEP.ENABLE) {
+        console.debug('EnableStep...');
+
+        // @TODO - Pretty sure we do not need to do this step here...
+        // as 1. is not a write command 2. we are getting rid of the EnableStep entirely as per the doc...
         // Calculate the Expensify Card limit associated with the bankAccountID
-        API.BankAccount_CalculateDomainLimit({
-            bankAccountID: modifiedPreviousACHData.bankAccountID,
-            useExistingDomainLimitIfAvailable: true,
-        })
-            .done((response) => {
-                // @TODO this doesn't really work because we'd have already returned this object - not entirely sure if
-                // we need to set a domain limit here anyway as the user can't modify it in E.cash
-                modifiedPreviousACHData.domainLimit = response.domainLimit || 3000000;
-            });
+        // API.BankAccount_CalculateDomainLimit({
+        //     bankAccountID: modifiedPreviousACHData.bankAccountID,
+        //     useExistingDomainLimitIfAvailable: true,
+        // })
+        //     .done((response) => {
+        //         modifiedPreviousACHData.domainLimit = response.domainLimit || 3000000;
+        //     });
     } else {
         // previously we called refreshView() - but we probably will not do that in the E.cash version and will instead
         // call Navigation.navigate() and just go to the view... I think probably we can kill this method also and just
@@ -376,8 +380,9 @@ function setupWithdrawalAccount(previousACHData, data) {
     });
 
     if (!achData.setupType) {
-        // @TODO use CONST
-        achData.setupType = achData.plaidAccountID ? 'plaid' : 'manual';
+        achData.setupType = achData.plaidAccountID
+            ? CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID
+            : CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL;
     }
 
     let nextStep = achData.currentStep;
@@ -386,7 +391,7 @@ function setupWithdrawalAccount(previousACHData, data) {
         .finally((response) => {
             const currentStep = achData.currentStep;
             let responseACHData = response.achData;
-            let error = lodashGet(responseACHData, 'verifications.errorMessage');
+            let error = lodashGet(responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.ERROR_MESSAGE);
 
             if (response.jsonCode === 200 && !error) {
                 // Show warning if another account already set up this bank account and promote share
@@ -396,19 +401,22 @@ function setupWithdrawalAccount(previousACHData, data) {
                 }
 
                 // @TODO use CONST for step name
-                if (currentStep === 'RequestorStep') {
+                if (currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR) {
                     // @TODO use CONST
                     const requestorResponse = lodashGet(
                         responseACHData,
-                        'verifications.externalApiResponses.requestorIdentityID',
+                        CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ID,
                     );
 
                     if (responseACHData.useOnFido) {
                         const onfidoResponse = lodashGet(
-                            responseACHData, 'verifications.externalApiResponses.requstorIdentityOnfido',
+                            responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO,
                         );
-                        const sdkToken = lodashGet(onfidoResponse, 'apiResult.sdkToken');
-                        if (sdkToken && !achData.isOnfidoSetupComplete && onfidoResponse.status !== 'pass') {
+                        const sdkToken = lodashGet(onfidoResponse, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
+                        if (sdkToken
+                            && !achData.isOnfidoSetupComplete
+                            && onfidoResponse.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
+                        ) {
                             // Requestor Step still needs to run Onfido
                             responseACHData.sdkToken = sdkToken;
 
@@ -418,11 +426,11 @@ function setupWithdrawalAccount(previousACHData, data) {
                         }
                     } else if (requestorResponse) {
                         // Don't go to next step if Requestor Step needs to ask some questions
-                        let questions = _.get(requestorResponse, 'apiResult.questions.question') || [];
+                        let questions = _.get(requestorResponse, CONST.BANK_ACCOUNT.QUESTIONS.QUESTION) || [];
                         if (_.isEmpty(questions)) {
                             const differentiatorQuestion = lodashGet(
                                 requestorResponse,
-                                'apiResult.differentiator-question',
+                                CONST.BANK_ACCOUNT.QUESTIONS.DIFFERENTIATOR_QUESTION,
                             );
                             if (differentiatorQuestion) {
                                 questions = [differentiatorQuestion];
@@ -437,7 +445,7 @@ function setupWithdrawalAccount(previousACHData, data) {
                 }
 
                 // @TODO figure out why this is returning a promise...
-                if (currentStep === 'ACHContractStep') {
+                if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) {
                     // Get an up-to-date bank account list so that we can allow the user to validate their newly
                     // generated bank account
                     return API.Get({returnValueList: 'bankAccountList'}, true)
@@ -454,8 +462,8 @@ function setupWithdrawalAccount(previousACHData, data) {
                         });
                 }
 
-                if ((currentStep === 'ValidationStep' && achData.bankAccountInReview)
-                    || currentStep === 'EnableStep'
+                if ((currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && achData.bankAccountInReview)
+                    || currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE
                 ) {
                     // We're done. Close the view.
                     // @TODO actually close it
@@ -466,7 +474,7 @@ function setupWithdrawalAccount(previousACHData, data) {
                 if (response.jsonCode === 666) {
                     error = response.message;
                 }
-                if (lodashGet(responseACHData, 'verifications.throttled')) {
+                if (lodashGet(responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.THROTTLED)) {
                     responseACHData.disableFields = true;
                 }
             }

From c810c56a477b84d38ecaa3e7cb992904f79bc187 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Thu, 10 Jun 2021 12:41:46 -1000
Subject: [PATCH 22/81] attempt to migrate stuff to a controller page for now
 while make sense of it all

---
 .../Navigation/AppNavigator/AuthScreens.js    |   7 +
 .../AppNavigator/ModalStackNavigators.js      |   7 +
 src/libs/Navigation/linkingConfig.js          |   5 +
 src/libs/actions/BankAccounts.js              |   2 +
 src/pages/ReimbursementAccountPage.js         | 130 ++++++++++++++++++
 5 files changed, 151 insertions(+)
 create mode 100644 src/pages/ReimbursementAccountPage.js

diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index a032193f97f8..f46b73793b3d 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -50,6 +50,7 @@ import {
     SettingsModalStackNavigator,
     EnablePaymentsStackNavigator,
     AddPersonalBankAccountModalStackNavigator,
+    ReimbursementAccountModalStackNavigator,
 } from './ModalStackNavigators';
 import SCREENS from '../../../SCREENS';
 import Timers from '../../Timers';
@@ -275,6 +276,12 @@ class AuthScreens extends React.Component {
                     component={AddPersonalBankAccountModalStackNavigator}
                     listeners={modalScreenListeners}
                 />
+                <RootStack.Screen
+                    name="ReimbursementAccount"
+                    options={modalScreenOptions}
+                    component={ReimbursementAccountModalStackNavigator}
+                    listeners={modalScreenListeners}
+                />
             </RootStack.Navigator>
         );
     }
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 7a3685f25067..91c7fd9a5aae 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -21,6 +21,7 @@ import IOUCurrencySelection from '../../../pages/iou/IOUCurrencySelection';
 import ReportParticipantsPage from '../../../pages/ReportParticipantsPage';
 import EnablePaymentsPage from '../../../pages/EnablePayments';
 import AddPersonalBankAccountPage from '../../../pages/AddPersonalBankAccountPage';
+import ReimbursementAccountPage from '../../../pages/ReimbursementAccountPage';
 
 const defaultSubRouteOptions = {
     cardStyle: styles.navigationScreenCardStyle,
@@ -152,6 +153,11 @@ const AddPersonalBankAccountModalStackNavigator = createModalStackNavigator([{
     name: 'AddPersonalBankAccount_Root',
 }]);
 
+const ReimbursementAccountModalStackNavigator = createModalStackNavigator([{
+    Component: ReimbursementAccountPage,
+    name: 'ReimbursementAccount_Root',
+}]);
+
 export {
     IOUBillStackNavigator,
     IOURequestModalStackNavigator,
@@ -164,4 +170,5 @@ export {
     SettingsModalStackNavigator,
     EnablePaymentsStackNavigator,
     AddPersonalBankAccountModalStackNavigator,
+    ReimbursementAccountModalStackNavigator,
 };
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 7053c8d4f021..f3652f951d34 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -114,6 +114,11 @@ export default {
                     EnablePayments_Root: ROUTES.ENABLE_PAYMENTS,
                 },
             },
+            ReimbursementAccount: {
+                screens: {
+                    ReimbursementAccount_Root: 'reimbursement-account',
+                },
+            },
         },
     },
 };
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 3217bdc1375f..3a8a4eea2f6c 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -299,6 +299,8 @@ function getNextWithdrawalAccountSetupStep(stepID) {
             id: CONST.BANK_ACCOUNT.STEP.VALIDATION,
             title: 'Validate',
         },
+
+        // @TODO maybe can be removed depending on how we handle the enable step moving forward
         {
             id: CONST.BANK_ACCOUNT.STEP.ENABLE,
             title: 'Enable',
diff --git a/src/pages/ReimbursementAccountPage.js b/src/pages/ReimbursementAccountPage.js
new file mode 100644
index 000000000000..d09a091d1332
--- /dev/null
+++ b/src/pages/ReimbursementAccountPage.js
@@ -0,0 +1,130 @@
+/* eslint-disable max-len */
+/* eslint-disable react/no-unused-state */
+import lodashGet from 'lodash/get';
+import React from 'react';
+import {View} from 'react-native';
+import PropTypes from 'prop-types';
+import ScreenWrapper from '../components/ScreenWrapper';
+
+const WITHDRAWAL_ACCOUNT_STEPS = [
+    {
+        id: 'BankAccountStep',
+        title: 'Bank Account',
+    },
+    {
+        id: 'CompanyStep',
+        title: 'Company Information',
+    },
+    {
+        id: 'RequestorStep',
+        title: 'Requestor Information',
+    },
+    {
+        id: 'ACHContractStep',
+        title: 'Beneficial Owners',
+    },
+    {
+        id: 'ValidationStep',
+        title: 'Validate',
+    },
+    {
+        id: 'EnableStep',
+        title: 'Enable',
+    },
+];
+
+const propTypes = {
+    skipOnfido: PropTypes.bool,
+};
+
+const defaultProps = {
+    skipOnfido: false,
+};
+
+class ReimbursementAccountPage extends React.Component {
+    constructor(props) {
+        super(props);
+
+        this.state = {
+            bankAccountID: undefined,
+            isLoading: true,
+            isPlaidDisabled: false,
+            achData: {},
+            isWithdrawal: true,
+            lastDataWithError: {},
+            currentStep: 'BankAccountStep',
+        };
+    }
+
+    componentDidMount() {
+        // If the user already is already setting up a bank account we will want to continue the flow for them
+        let currentStep;
+        const bankAccountID = parseInt(this.props.bankAccountID, 10) || 0; // This will need to come from either NVP or route param
+        const bankAccount = bankAccountID ? this.props.bankAccounts[bankAccountID] : null; // The list of bankAccounts will need to come from Onyx
+        const achData = bankAccount ? bankAccount.toACHData() : {};
+        achData.useOnfido = !this.props.skipOnfido;
+        achData.policyID = this.props.policyId || '';
+        achData.plaidLinkToken = this.props.plaidLinkToken; // I think probably we won't have this until we start the Plaid flow but maybe...?
+        achData.isInSetup = !bankAccount || bankAccount.isInSetup();
+        achData.bankAccountInReview = bankAccount && bankAccount.isVerifying();
+        achData.domainLimi = 0;
+        achData.isDomainUsingExpensifyCard = false; // Maybe also needs to be a prop... not too sure.
+        achData.subStep = this.props.subStep; // Unsure what the substeps are used for so far...
+
+        // If we're not in setup, it means we already have a withdrawal account and we're upgrading it to a business bank account.
+        // So let the user review all steps with all info prefilled and editable, unless a specific step was passed.
+        if (!achData.isInSetup) {
+            currentStep = this.props.currentStep; // Not sure if we need this as user's won't be upgrading these accounts...
+        }
+
+        // Temporary fix for Onfido flow. Can be removed by nkuoch after Sept 1 2020. - not sure if we need this or what this is about...
+        if (currentStep === 'ACHContractStep' && achData.useOnfido) {
+            const onfidoRes = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityOnfido');
+            const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
+            if (sdkToken && !achData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
+                currentStep = 'RequestorStep';
+            }
+        }
+
+        // Ensure we route the user to the correct step based on the status of their bank account
+        if (bankAccount && !currentStep) {
+            currentStep = bankAccount.isPending() || bankAccount.isVerifying() ? 'ValidationStep' : 'BankAccountStep';
+
+            // Again, not sure how much of this logic is needed right now as we shouldn't be handling any open accounts in E.cash yet...
+            if (bankAccount.isOpen()) {
+                if (bankAccount.needsToPassLatestChecks()) {
+                    // const hasTriedToUpgrade = bankAccount.getDateSigned() > (NVP.get('expensify_migration_2020_04_28_RunKycVerifications') || '2020-01-13');
+                    // currentStep = hasTriedToUpgrade ? 'ValidationStep' : 'CompanyStep';
+                    // achData.bankAccountInReview = hasTriedToUpgrade;
+                } else {
+                    // Not handling the EnableStep...
+                    // currentStep = 'EnableStep';
+                }
+            }
+        }
+
+        // If at this point we still don't have a current step, default to the BankAccountStep
+        if (!currentStep) {
+            currentStep = 'BankAccountStep';
+        }
+
+        console.log({currentStep, achData});
+        this.setState({isLoading: false, currentStep, achData});
+    }
+
+    render() {
+        if (this.state.isLoading) {
+            return null;
+        }
+
+        return (
+            <ScreenWrapper>
+                <View />
+            </ScreenWrapper>
+        );
+    }
+}
+
+ReimbursementAccountPage.propTypes = propTypes;
+ReimbursementAccountPage.defaultProps = defaultProps;
+export default ReimbursementAccountPage;

From 44f095ab6f3eb7681687e34a99fb037c082658ea Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Thu, 10 Jun 2021 17:21:59 -0700
Subject: [PATCH 23/81] add policyID to params

---
 src/libs/API.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/libs/API.js b/src/libs/API.js
index ac276c60ce21..c80a3e4cac44 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -785,13 +785,14 @@ function BankAccount_Get(parameters) {
 
 /**
  * @param {Object} parameters
- * @param {String[]} parameters.employees
+ * @param {Object[]} parameters.employees
  * @param {String} parameters.welcomeNote
+ * @param {String} parameters.policyID
  * @returns {Promise}
  */
 function Policy_Employees_Merge(parameters) {
     const commandName = 'Policy_Employees_Merge';
-    requireParameters(['employees', 'welcomeNote'], parameters, commandName);
+    requireParameters(['employees', 'welcomeNote', 'policyID'], parameters, commandName);
 
     // Always include returnPersonalDetails to ensure we get the employee's personal details in the response
     return Network.post(commandName, {...parameters, returnPersonalDetails: true});
@@ -880,6 +881,7 @@ export {
     PersonalDetails_GetForEmails,
     PersonalDetails_Update,
     Plaid_GetLinkToken,
+    Policy_Employees_Merge,
     Push_Authenticate,
     RejectTransaction,
     Report_AddComment,

From 04382bbc31a7d98643a847dba388276319f54ab5 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Thu, 10 Jun 2021 19:05:16 -0700
Subject: [PATCH 24/81] add API call for policyList

---
 src/libs/API.js                               | 12 +++++++
 .../Navigation/AppNavigator/AuthScreens.js    |  3 +-
 src/libs/actions/Policy.js                    | 34 ++++++++++++++++++-
 3 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/src/libs/API.js b/src/libs/API.js
index c80a3e4cac44..d64b79a931ac 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -406,6 +406,17 @@ function GetIOUReport(parameters) {
     return Network.post(commandName, parameters);
 }
 
+/**
+ * @returns {Promise}
+ */
+function GetPolicyList() {
+    const commandName = 'Get';
+    const parameters = {
+        returnValueList: 'policyList',
+    };
+    return Network.post(commandName, parameters);
+}
+
 /**
  * @returns {Promise}
  */
@@ -872,6 +883,7 @@ export {
     Get,
     GetAccountStatus,
     GetIOUReport,
+    GetPolicyList,
     GetPolicySummaryList,
     GetRequestCountryCode,
     Graphite_Timer,
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 7c55b7b91ba8..3b6631ad1be9 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -27,7 +27,7 @@ import Navigation from '../Navigation';
 import * as User from '../../actions/User';
 import {setModalVisibility} from '../../actions/Modal';
 import NameValuePair from '../../actions/NameValuePair';
-import {getPolicySummaries} from '../../actions/Policy';
+import {getPolicySummaries, getPolicyList} from '../../actions/Policy';
 import modalCardStyleInterpolator from './modalCardStyleInterpolator';
 import createCustomModalStackNavigator from './createCustomModalStackNavigator';
 
@@ -126,6 +126,7 @@ class AuthScreens extends React.Component {
         fetchCountryCodeByRequestIP();
         UnreadIndicatorUpdater.listenForReportChanges();
         getPolicySummaries();
+        getPolicyList();
 
         // Refresh the personal details, timezone and betas every 30 minutes
         // There is no pusher event that sends updated personal details data yet
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index af04011c3c94..2759752d883d 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -1,6 +1,6 @@
 import _ from 'underscore';
 import Onyx from 'react-native-onyx';
-import {GetPolicySummaryList} from '../API';
+import {GetPolicySummaryList, GetPolicyList} from '../API';
 import ONYXKEYS from '../../ONYXKEYS';
 
 /**
@@ -17,6 +17,24 @@ function getSimplifiedPolicyObject(fullPolicy) {
     };
 }
 
+/**
+ * Simplifies the policyList response into an object containing an array of emails
+ *
+ * @param {Object} fullPolicy
+ * @returns {Object}
+ */
+function getSimplifiedMemberList(fullPolicy) {
+    const employeeListEmails = _.chain(fullPolicy.employeeList)
+        .pluck('email')
+        .flatten()
+        .unique()
+        .value();
+
+    return {
+        employeeList: employeeListEmails,
+    };
+}
+
 function getPolicySummaries() {
     GetPolicySummaryList()
         .then((data) => {
@@ -30,7 +48,21 @@ function getPolicySummaries() {
         });
 }
 
+function getPolicyList() {
+    GetPolicyList()
+        .then((data) => {
+            if (data.jsonCode === 200) {
+                const policyDataToStore = _.reduce(data.policyList, (memo, policy) => ({
+                    ...memo,
+                    [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: getSimplifiedMemberList(policy),
+                }), {});
+                Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataToStore);
+            }
+        });
+}
+
 export {
     // eslint-disable-next-line import/prefer-default-export
     getPolicySummaries,
+    getPolicyList,
 };

From b92ebaf939fd6addd0bd9cd9d5c1f17ec294cb7e Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Thu, 10 Jun 2021 19:17:23 -0700
Subject: [PATCH 25/81] fix empty employeeList

---
 src/libs/actions/Policy.js | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 2759752d883d..10892481a4b8 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -24,7 +24,7 @@ function getSimplifiedPolicyObject(fullPolicy) {
  * @returns {Object}
  */
 function getSimplifiedMemberList(fullPolicy) {
-    const employeeListEmails = _.chain(fullPolicy.employeeList)
+    const employeeListEmails = _.chain(fullPolicy.value.employeeList)
         .pluck('email')
         .flatten()
         .unique()
@@ -35,6 +35,9 @@ function getSimplifiedMemberList(fullPolicy) {
     };
 }
 
+/**
+ * Fetches the policySummaryList from the API and saves a simplified version in Onyx
+ */
 function getPolicySummaries() {
     GetPolicySummaryList()
         .then((data) => {
@@ -48,6 +51,9 @@ function getPolicySummaries() {
         });
 }
 
+/**
+ * Fetches the policyList from the API and saves a simplified version in Onyx
+ */
 function getPolicyList() {
     GetPolicyList()
         .then((data) => {

From 5604e300033e65fe31a42387c7445ca069308c22 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Thu, 10 Jun 2021 19:41:44 -0700
Subject: [PATCH 26/81] pass data to invite, optimisitcally add user

---
 src/languages/en.js              |  2 +-
 src/languages/es.js              |  2 +-
 src/libs/actions/Policy.js       | 28 +++++++++++++++++++++++++++-
 src/pages/WorkspaceInvitePage.js | 22 +++++++++++++++++-----
 4 files changed, 46 insertions(+), 8 deletions(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index b8ca2654ee5b..9a0ee522ed0a 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -323,6 +323,6 @@ export default {
         invitePeople: 'Invite People',
         invitePeoplePrompt: 'Invite a colleague to your workspace.',
         enterEmailOrPhone: 'Email or Phone',
-        welcomeMessage: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
+        welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
     },
 };
diff --git a/src/languages/es.js b/src/languages/es.js
index 02797676fec3..af17837f1a6e 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -280,6 +280,6 @@ export default {
         invitePeople: 'Invite People',
         invitePeoplePrompt: 'Invite a colleague to your workspace.',
         enterEmailOrPhone: 'Email or Phone',
-        welcomeMessage: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
+        welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
     },
 };
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 10892481a4b8..18efd1471bca 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -1,6 +1,6 @@
 import _ from 'underscore';
 import Onyx from 'react-native-onyx';
-import {GetPolicySummaryList, GetPolicyList} from '../API';
+import {GetPolicySummaryList, GetPolicyList, Policy_Employees_Merge} from '../API';
 import ONYXKEYS from '../../ONYXKEYS';
 
 /**
@@ -67,8 +67,34 @@ function getPolicyList() {
         });
 }
 
+/**
+ * Merges the passed in login into the specified policy
+ *
+ * @param {String} login
+ * @param {String} welcomeNote
+ * @param {String} policyID
+ */
+function invite(login, welcomeNote, policyID) {
+    // Optimistically add the user to the policy
+    const key = `${ONYXKEYS.COLLECTION.POLICY}${policyID}`;
+    const dataToStore = {};
+    dataToStore[key] = {
+        employeeList: [login]
+    };
+    Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, dataToStore);
+
+    Policy_Employees_Merge([{email: login}], welcomeNote, policyID)
+        .then((data) => {
+            // Save data.personalDetails
+        })
+        .fail(() => {
+            // Remove data from Onyx
+        });
+}
+
 export {
     // eslint-disable-next-line import/prefer-default-export
     getPolicySummaries,
     getPolicyList,
+    invite,
 };
diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 462fb41ad079..40702573c4fc 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -11,6 +11,7 @@ import Text from '../components/Text';
 import Button from '../components/Button';
 import compose from '../libs/compose';
 import ONYXKEYS from '../ONYXKEYS';
+import {invite} from '../libs/actions/Policy';
 
 const propTypes = {
     ...withLocalizePropTypes,
@@ -20,6 +21,15 @@ const propTypes = {
         /** The policy name */
         name: PropTypes.string,
     }),
+
+    /** URL Route params */
+    route: PropTypes.shape({
+        /** Params from the URL path */
+        params: PropTypes.shape({
+            /** policyID passed via route: /workspace/:policyID/invite */
+            policyID: PropTypes.string,
+        }),
+    }).isRequired,
 };
 
 const defaultProps = {
@@ -34,12 +44,14 @@ class WorkspaceInvitePage extends React.Component {
 
         this.state = {
             emailOrPhone: '',
-            welcomeMessage: '',
+            welcomeNote: '',
         };
+
+        this.inviteUser = this.inviteUser.bind(this);
     }
 
     inviteUser() {
-        console.debug('user invited');
+        invite(this.state.emailOrPhone, this.state.welcomeNote, this.props.route.params.policyID);
     }
 
     render() {
@@ -71,11 +83,11 @@ class WorkspaceInvitePage extends React.Component {
                                 style={[styles.textInput, styles.workspaceInviteWelcome]}
                                 numberOfLines={10}
                                 multiline
-                                value={this.state.welcomeMessage}
-                                placeholder={this.props.translate('workspaceInvitePage.welcomeMessage', {
+                                value={this.state.welcomeNote}
+                                placeholder={this.props.translate('workspaceInvitePage.welcomeNote', {
                                     workspaceName: this.props.policy.name,
                                 })}
-                                onChangeText={text => this.setState({welcomeMessage: text})}
+                                onChangeText={text => this.setState({welcomeNote: text})}
                             />
                         </View>
                     </View>

From 4f96379f03ec190148f3ba71cb8215f007f2722f Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Thu, 10 Jun 2021 18:27:31 -1000
Subject: [PATCH 27/81] sketch out plan

---
 src/CONST.js                                  |   2 +
 src/ONYXKEYS.js                               |   4 +
 .../Navigation/AppNavigator/AuthScreens.js    |   1 +
 src/libs/actions/BankAccounts.js              | 246 ++----------
 src/pages/ReimbursementAccountPage.js         | 356 +++++++++++++++++-
 5 files changed, 389 insertions(+), 220 deletions(-)

diff --git a/src/CONST.js b/src/CONST.js
index edaee01ab906..5cc8bd26354b 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -147,6 +147,8 @@ const CONST = {
         PAYPAL_ME_ADDRESS: 'expensify_payPalMeAddress',
         PRIORITY_MODE: 'priorityMode',
         TIMEZONE: 'timeZone',
+        FREE_PLAN_BANK_ACCOUNT_ID: 'expensify_freePlanBankAccountID',
+        ACH_DATA_THROTTLED: 'expensify_ACHData_throttled',
     },
     DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'},
     DEFAULT_ACCOUNT_DATA: {error: '', success: '', loading: false},
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index d7fcd65074f3..b3271f8b3911 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -75,6 +75,7 @@ export default {
 
     // Collection Keys
     COLLECTION: {
+        BANK_ACCOUNT: 'bankAccount_',
         REPORT: 'report_',
         REPORT_ACTIONS: 'reportActions_',
         REPORT_DRAFT_COMMENT: 'reportDraftComment_',
@@ -98,4 +99,7 @@ export default {
 
     // Object containing Wallet terms step state
     WALLET_TERMS: 'walletTerms',
+
+    // Stores information about the free plan bank account being set up
+    FREE_PLAN_BANK_ACCOUNT: 'freePlanBankAccount',
 };
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index f46b73793b3d..7aaf98cc72cd 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -13,6 +13,7 @@ import {
     fetchAllReports,
 } from '../../actions/Report';
 import * as PersonalDetails from '../../actions/PersonalDetails';
+import * as BankAccounts from '../../actions/BankAccounts';
 import * as Pusher from '../../Pusher/pusher';
 import PusherConnectionManager from '../../PusherConnectionManager';
 import UnreadIndicatorUpdater from '../../UnreadIndicatorUpdater';
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 3a8a4eea2f6c..33c3f72f7c86 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -1,13 +1,10 @@
 import lodashGet from 'lodash/get';
-import lodashHas from 'lodash/has';
 import Str from 'expensify-common/lib/str';
 import Onyx from 'react-native-onyx';
 import _ from 'underscore';
 import CONST from '../../CONST';
 import ONYXKEYS from '../../ONYXKEYS';
 import * as API from '../API';
-import Navigation from '../Navigation/Navigation';
-import BankAccount from '../models/BankAccount';
 
 /**
  * Gets the Plaid Link token used to initialize the Plaid SDK
@@ -273,222 +270,45 @@ function fetchUserWallet() {
 }
 
 /**
- * @private
- * @param {String} stepID
- * @returns {String}
- */
-function getNextWithdrawalAccountSetupStep(stepID) {
-    const withdrawalAccountSteps = [
-        {
-            id: CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT,
-            title: 'Bank Account',
-        },
-        {
-            id: CONST.BANK_ACCOUNT.STEP.COMPANY,
-            title: 'Company Information',
-        },
-        {
-            id: CONST.BANK_ACCOUNT.STEP.REQUESTOR,
-            title: 'Requestor Information',
-        },
-        {
-            id: CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT,
-            title: 'Beneficial Owners',
-        },
-        {
-            id: CONST.BANK_ACCOUNT.STEP.VALIDATION,
-            title: 'Validate',
-        },
-
-        // @TODO maybe can be removed depending on how we handle the enable step moving forward
-        {
-            id: CONST.BANK_ACCOUNT.STEP.ENABLE,
-            title: 'Enable',
-        },
-    ];
-
-    const index = _.findIndex(withdrawalAccountSteps, step => step.id === stepID);
-    const nextStepIndex = Math.min(index + 1, withdrawalAccountSteps.length - 1);
-    return lodashGet(withdrawalAccountSteps, [nextStepIndex, 'id']);
-}
-
-/**
- * @param {String} stepID
- * @param {Object} previousACHData
- * @param {Object} newACHData
- * @returns {Object}
+ * Fetch the bank account currently being set up by the user for the free plan if it exists.
  */
-function goToWithdrawlAccountStep(stepID, previousACHData, newACHData = {}) {
-    // @TODO I'm lost on how to migrate this one... seems like kind of tacked on mutations of the this.achData...
-    // I would like to maybe get rid of this.achData as a concept here... maybe the achData belongs in Onyx instead
-    // there's some weird stuff happening like setting the domain limit locally and the logic is not easy to follow yet.
-
-    const modifiedPreviousACHData = {...previousACHData};
-
-    // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
-    if (!modifiedPreviousACHData.useOnfido && stepID === CONST.BANK_ACCOUNT.STEP.REQUESTOR) {
-        delete modifiedPreviousACHData.questions;
-        delete modifiedPreviousACHData.answers;
-        if (lodashHas(modifiedPreviousACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.EXTERNAL_API_RESPONSES)) {
-            delete modifiedPreviousACHData.verifications.externalApiResponses.requestorIdentityID;
-            delete modifiedPreviousACHData.verifications.externalApiResponses.requestorIdentityKBA;
-        }
-    }
-
-    // When going from companyStep to bankAccountStep, show the manual form instead of Plaid
-    if (modifiedPreviousACHData.currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY
-        && stepID === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT
-    ) {
-        modifiedPreviousACHData.subStep = CONST.BANK_ACCOUNT.SUBSTEP.MANUAL;
-    }
-
-    _.extend(modifiedPreviousACHData, newACHData, {currentStep: stepID});
-
-    if (stepID === CONST.BANK_ACCOUNT.STEP.ENABLE) {
-        console.debug('EnableStep...');
+function fetchFreePlanVerifiedBankAccount() {
+    Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: true});
+    let bankAccountID;
+    let bankAccount;
+    API.Get({
+        returnValueList: 'nameValuePairs',
+        name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
+    })
+        .then((response) => {
+            bankAccountID = lodashGet(response.nameValuePairs, CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, '');
 
-        // @TODO - Pretty sure we do not need to do this step here...
-        // as 1. is not a write command 2. we are getting rid of the EnableStep entirely as per the doc...
-        // Calculate the Expensify Card limit associated with the bankAccountID
-        // API.BankAccount_CalculateDomainLimit({
-        //     bankAccountID: modifiedPreviousACHData.bankAccountID,
-        //     useExistingDomainLimitIfAvailable: true,
-        // })
-        //     .done((response) => {
-        //         modifiedPreviousACHData.domainLimit = response.domainLimit || 3000000;
-        //     });
-    } else {
-        // previously we called refreshView() - but we probably will not do that in the E.cash version and will instead
-        // call Navigation.navigate() and just go to the view... I think probably we can kill this method also and just
-        // do this in setupWithdrawalAccount
-        Navigation.navigate(stepID, modifiedPreviousACHData);
-    }
+            if (!bankAccountID) {
+                return Promise.resolve({});
+            }
 
-    return modifiedPreviousACHData;
+            return API.Get({returnValueList: 'bankAccountList'});
+        })
+        .then((response) => {
+            bankAccount = _.find(response.bankAccountList, account => account.bankAccountID === bankAccountID);
+            return API.Get({
+                returnValueList: 'nameValuePairs',
+                name: CONST.NVP.ACH_DATA_THROTTLED,
+            });
+        })
+        .then((response) => {
+            const throttledDate = lodashGet(response.nameValuePairs, CONST.NVP.ACH_DATA_THROTTLED, '');
+            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {bankAccount, loading: false, throttledDate});
+        });
 }
 
 /**
- * @param {Object} previousACHData
- * @param {Object} data
- * @returns {Object}
+ * @private
+ * @param {Number} bankAccountID
  */
-function setupWithdrawalAccount(previousACHData, data) {
-    // In Web-Secure this is referring to this.achData - still need to look at how that works...
-    const achData = _.extend(previousACHData, {
-        ...data,
-        isSavings: data
-            && !_.isUndefined(data.isSavings)
-            && Boolean(data.isSavings),
-    });
-
-    if (!achData.setupType) {
-        achData.setupType = achData.plaidAccountID
-            ? CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID
-            : CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL;
-    }
-
-    let nextStep = achData.currentStep;
-
-    API.BankAccount_SetupWithdrawal(achData)
-        .finally((response) => {
-            const currentStep = achData.currentStep;
-            let responseACHData = response.achData;
-            let error = lodashGet(responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.ERROR_MESSAGE);
-
-            if (response.jsonCode === 200 && !error) {
-                // Show warning if another account already set up this bank account and promote share
-                if (response.existingOwners) {
-                    // @TODO show this error
-                    return;
-                }
-
-                // @TODO use CONST for step name
-                if (currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR) {
-                    // @TODO use CONST
-                    const requestorResponse = lodashGet(
-                        responseACHData,
-                        CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ID,
-                    );
-
-                    if (responseACHData.useOnFido) {
-                        const onfidoResponse = lodashGet(
-                            responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO,
-                        );
-                        const sdkToken = lodashGet(onfidoResponse, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
-                        if (sdkToken
-                            && !achData.isOnfidoSetupComplete
-                            && onfidoResponse.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
-                        ) {
-                            // Requestor Step still needs to run Onfido
-                            responseACHData.sdkToken = sdkToken;
-
-                            // Navigate to "RequestorStep" with responseACHData
-                            Navigation.navigate('/bank-account/requestor', responseACHData);
-                            return;
-                        }
-                    } else if (requestorResponse) {
-                        // Don't go to next step if Requestor Step needs to ask some questions
-                        let questions = _.get(requestorResponse, CONST.BANK_ACCOUNT.QUESTIONS.QUESTION) || [];
-                        if (_.isEmpty(questions)) {
-                            const differentiatorQuestion = lodashGet(
-                                requestorResponse,
-                                CONST.BANK_ACCOUNT.QUESTIONS.DIFFERENTIATOR_QUESTION,
-                            );
-                            if (differentiatorQuestion) {
-                                questions = [differentiatorQuestion];
-                            }
-                        }
-                        if (!_.isEmpty(questions)) {
-                            responseACHData.questions = questions;
-                            Navigation.navigate('/bank-account/requestor', responseACHData);
-                            return;
-                        }
-                    }
-                }
-
-                // @TODO figure out why this is returning a promise...
-                if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) {
-                    // Get an up-to-date bank account list so that we can allow the user to validate their newly
-                    // generated bank account
-                    return API.Get({returnValueList: 'bankAccountList'}, true)
-                        .then((json) => {
-                            const bankAccount = new BankAccount(
-                                _.findWhere(json.bankAccountList, {bankAccountID: achData.bankAccountID}),
-                            );
-                            responseACHData = bankAccount.toACHData();
-                            const needsToPassLatestChecks = responseACHData.state === BankAccount.STATE.OPEN
-                                && responseACHData.needsToPassLatestChecks;
-                            responseACHData.bankAccountInReview = needsToPassLatestChecks
-                                || responseACHData.state === BankAccount.STATE.VERIFYING;
-                            Navigation.navigate('/bank-account/validation', responseACHData);
-                        });
-                }
-
-                if ((currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && achData.bankAccountInReview)
-                    || currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE
-                ) {
-                    // We're done. Close the view.
-                    // @TODO actually close it
-                } else {
-                    nextStep = getNextWithdrawalAccountSetupStep(achData);
-                }
-            } else {
-                if (response.jsonCode === 666) {
-                    error = response.message;
-                }
-                if (lodashGet(responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.THROTTLED)) {
-                    responseACHData.disableFields = true;
-                }
-            }
-
-            goToWithdrawlAccountStep(nextStep, achData, responseACHData);
-
-            if (error) {
-                // @TODO Show the error somehow
-            }
-        });
-
-    return achData;
+function setFreePlanVerifiedBankAccountID(bankAccountID) {
+    Onyx.merge('freePlanBankAccount', {bankAccountID, loading: false});
+    API.SetNameValuePair({name: 'expensify_freePlanBankAccountID', value: bankAccountID});
 }
 
 export {
@@ -499,5 +319,5 @@ export {
     fetchOnfidoToken,
     activateWallet,
     fetchUserWallet,
-    setupWithdrawalAccount,
+    fetchFreePlanVerifiedBankAccount,
 };
diff --git a/src/pages/ReimbursementAccountPage.js b/src/pages/ReimbursementAccountPage.js
index d09a091d1332..d6c9a05fa4ef 100644
--- a/src/pages/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccountPage.js
@@ -1,10 +1,19 @@
 /* eslint-disable max-len */
 /* eslint-disable react/no-unused-state */
+import _ from 'underscore';
+import moment from 'moment';
 import lodashGet from 'lodash/get';
+import lodashHas from 'lodash/has';
 import React from 'react';
+import {withOnyx} from 'react-native-onyx';
+import Str from 'expensify-common/lib/str';
 import {View} from 'react-native';
 import PropTypes from 'prop-types';
 import ScreenWrapper from '../components/ScreenWrapper';
+import {fetchFreePlanVerifiedBankAccount} from '../libs/actions/BankAccounts';
+import ONYXKEYS from '../ONYXKEYS';
+import BankAccount from '../libs/models/BankAccount';
+import * as API from '../libs/API';
 
 const WITHDRAWAL_ACCOUNT_STEPS = [
     {
@@ -35,10 +44,18 @@ const WITHDRAWAL_ACCOUNT_STEPS = [
 
 const propTypes = {
     skipOnfido: PropTypes.bool,
+    freePlanBankAccount: PropTypes.shape({
+        bankAccount: PropTypes.shape({}),
+        loading: PropTypes.bool,
+        throttledDate: PropTypes.string,
+    }),
 };
 
 const defaultProps = {
     skipOnfido: false,
+    freePlanBankAccount: {
+        loading: true,
+    },
 };
 
 class ReimbursementAccountPage extends React.Component {
@@ -48,6 +65,10 @@ class ReimbursementAccountPage extends React.Component {
         this.state = {
             bankAccountID: undefined,
             isLoading: true,
+
+            // In Web-Secure Plaid is disabled if there is no window.Plaid global (SDK script didn't load) or if we explicitly disable it because the user made too many calls to BankAccount_Get
+            // See: https://github.com/Expensify/Web-Secure/blob/044c82affb78812a58b881a6d5ba026d91dace3b/site/app/settings/reimbursement/PlaidBankForm.jsx#L171-L176
+            // @TODO we should handle the too many calls to Plaid situation, but for now it will remain enabled
             isPlaidDisabled: false,
             achData: {},
             isWithdrawal: true,
@@ -57,10 +78,188 @@ class ReimbursementAccountPage extends React.Component {
     }
 
     componentDidMount() {
+        fetchFreePlanVerifiedBankAccount();
+    }
+
+    componentDidUpdate(prevProps) {
+        if (prevProps.freePlanBankAccount.loading && !this.props.freePlanBankAccount.loading) {
+            this.init();
+        }
+    }
+
+    /**
+     * Get step position in the array
+     * @param {String} stepID
+     * @return {Number}
+     */
+    getIndexByStepID(stepID) {
+        return _.findIndex(WITHDRAWAL_ACCOUNT_STEPS, step => step.id === stepID);
+    }
+
+    /**
+     * Get next step ID
+     * @return {String}
+     */
+    getNextStepID() {
+        const nextStepIndex = Math.min(this.getIndexByStepID(this.state.achData.currentStep) + 1, WITHDRAWAL_ACCOUNT_STEPS.length - 1);
+        return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], 'BankAccountStep');
+    }
+
+    /**
+     * Create or update the bank account in db with the updated data.
+     *
+     * @param {Object} [data]
+     */
+    setupWithdrawalAccount(data) {
+        // @TODO trigger loading
+
+        // Create a shallow copy of the data and overwrite new values
+        const newAchData = {...this.state.achData};
+        _.extend(newAchData, data);
+        if (data && data.isSavings !== undefined) {
+            newAchData.isSavings = Boolean(data.isSavings);
+        }
+        if (!newAchData.setupType) {
+            newAchData.setupType = newAchData.plaidAccountID ? 'plaid' : 'manual';
+        }
+
+        let nextStep = newAchData.currentStep;
+
+        // @TODO move this to an action instead
+        API.BankAccount_SetupWithdrawal(newAchData)
+            .finally((response) => {
+                // @TODO hide the loader
+
+                const currentStep = newAchData.currentStep;
+                let achData = response.achData;
+                let error = lodashGet(achData, 'verifications.errorMessage');
+
+                if (response.jsonCode === 200 && !error) {
+                    // Show warning if another account already set up this bank account and promote share
+                    if (response.existingOwners) {
+                        // @TODO Show error about existing owners
+                        return;
+                    }
+
+                    if (currentStep === 'RequestorStep') {
+                        const requestorResponse = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityID');
+                        if (newAchData.useOnfido) {
+                            const onfidoRes = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityOnfido');
+                            const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
+                            if (sdkToken && !newAchData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
+                                // Requestor Step still needs to run Onfido
+                                achData.sdkToken = sdkToken;
+                                this.setState({achData: newAchData}, () => {
+                                    this.goToStepID('RequestorStep', achData);
+                                });
+                                return;
+                            }
+                        } else if (requestorResponse) {
+                            // Don't go to next step if Requestor Step needs to ask some questions
+                            let questions = lodashGet(requestorResponse, 'apiResult.questions.question') || [];
+                            if (_.isEmpty(questions)) {
+                                const differentiatorQuestion = lodashGet(requestorResponse, 'apiResult.differentiator-question');
+                                if (differentiatorQuestion) {
+                                    questions = [differentiatorQuestion];
+                                }
+                            }
+                            if (!_.isEmpty(questions)) {
+                                achData.questions = questions;
+                                this.setState({achData: newAchData}, () => {
+                                    this.goToStepID('RequestorStep', achData);
+                                });
+                                return;
+                            }
+                        }
+                    }
+                    if (currentStep === 'ACHContractStep') {
+                        // const promise = $.Deferred(); // Not sure what is awaiting this promise yet but doesn't seem like something we will want to do...
+
+                        // We want to make a task completion so we can pay guides, but we don't want to close the iframe so mark completeSetup as false
+                        // this.triggerParentSuccessMessage(response, false); - Not really sure if this applies in E.cash
+
+                        // Get an up-to-date bank account list so that we can allow the user to validate their newly generated bank account
+                        return API.get({returnValueList: 'bankAccountList'})
+                            .done((json) => {
+                                const bankAccount = new BankAccount(_.findWhere(json.bankAccountList, {bankAccountID: newAchData.bankAccountID}));
+                                achData = bankAccount.toACHData();
+                                const needsToPassLatestChecks = achData.state === BankAccount.STATE.OPEN && achData.needsToPassLatestChecks;
+                                achData.bankAccountInReview = needsToPassLatestChecks || achData.state === BankAccount.STATE.VERIFYING;
+                                this.setState({achData: newAchData}, () => {
+                                    this.goToStepID('ValidationStep', achData);
+                                });
+                            });
+                    }
+                    if ((currentStep === 'ValidationStep' && newAchData.bankAccountInReview) || currentStep === 'EnableStep') {
+                        // Setup done! We can close the modal - @TODO - not sure what this is doing or if we need it
+                        // this.triggerParentSuccessMessage(response);
+                    } else {
+                        nextStep = this.getNextStepID();
+                    }
+                } else {
+                    if (response.jsonCode === 666) {
+                        error = response.message;
+                    }
+                    if (lodashGet(achData, 'verifications.throttled')) {
+                        achData.disableFields = true;
+                    }
+                }
+
+                // Go to next step
+                this.setState({achData: newAchData}, () => {
+                    this.goToStepID(nextStep, achData);
+                });
+
+                if (error) {
+                    // @TODO - Show the error
+                }
+            });
+    }
+
+    getDefaultCountry() {
+        const defaultCountry = this.state.achData.country || 'US'; // @TODO - In Web-Expensify this fallback refers to User.getIpCountry()
+
+        // In Web-Secure we check the policy to find out the defaultCountry. The policy is passed in here:
+        // https://github.com/Expensify/Web-Expensify/blob/896941794f68d7dce64466d83a3e86a5f8122e45/site/app/policyEditor/policyEditorPage.jsx#L2169-L2171
+        // @TODO figure out whether we need to check the policy or not - not sure if it's necessary for V1 of Free Plan
+        return defaultCountry;
+    }
+
+    /**
+     * Go to a specific step id.
+     * @param {String} stepID
+     * @param {Object} [achData]
+     */
+    goToStepID(stepID, achData) {
+        // Setting state again will refresh the view and progress us to the next step
+        this.setState((prevState) => {
+            const newAchData = {...prevState.achData};
+
+            // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
+            if (!newAchData.useOnfido && stepID === 'RequestorStep') {
+                delete newAchData.questions;
+                delete newAchData.answers;
+                if (lodashHas(newAchData, 'verifications.externalApiResponses')) {
+                    delete newAchData.verifications.externalApiResponses.requestorIdentityID;
+                    delete newAchData.verifications.externalApiResponses.requestorIdentityKBA;
+                }
+            }
+
+            // When going from companyStep to bankAccountStep, show the manual form instead of Plaid
+            if (newAchData.currentStep === 'CompanyStep' && stepID === 'BankAccountStep') {
+                newAchData.subStep = 'manual';
+            }
+
+            _.extend(newAchData, achData, {currentStep: stepID});
+            return ({achData: newAchData});
+        });
+    }
+
+    init() {
         // If the user already is already setting up a bank account we will want to continue the flow for them
         let currentStep;
-        const bankAccountID = parseInt(this.props.bankAccountID, 10) || 0; // This will need to come from either NVP or route param
-        const bankAccount = bankAccountID ? this.props.bankAccounts[bankAccountID] : null; // The list of bankAccounts will need to come from Onyx
+        const bankAccountJSON = parseInt(lodashGet(this.props, 'freePlanBankAccount.bankAccount.bankAccountID', '0'), 10) || 0;
+        const bankAccount = bankAccountJSON ? new BankAccount(bankAccountJSON) : null;
         const achData = bankAccount ? bankAccount.toACHData() : {};
         achData.useOnfido = !this.props.skipOnfido;
         achData.policyID = this.props.policyId || '';
@@ -69,7 +268,10 @@ class ReimbursementAccountPage extends React.Component {
         achData.bankAccountInReview = bankAccount && bankAccount.isVerifying();
         achData.domainLimi = 0;
         achData.isDomainUsingExpensifyCard = false; // Maybe also needs to be a prop... not too sure.
-        achData.subStep = this.props.subStep; // Unsure what the substeps are used for so far...
+
+        // Unsure what the substeps are used for so far... pretty sure it's just to advance to the "login" step here and not used anywhere else
+        // https://github.com/Expensify/Web-Expensify/blob/896941794f68d7dce64466d83a3e86a5f8122e45/site/app/settings/reimbursement/bankAccountView.jsx#L356-L357
+        achData.subStep = this.props.subStep;
 
         // If we're not in setup, it means we already have a withdrawal account and we're upgrading it to a business bank account.
         // So let the user review all steps with all info prefilled and editable, unless a specific step was passed.
@@ -108,15 +310,143 @@ class ReimbursementAccountPage extends React.Component {
             currentStep = 'BankAccountStep';
         }
 
-        console.log({currentStep, achData});
-        this.setState({isLoading: false, currentStep, achData});
+        this.setState({isLoading: false, currentStep, achData}, () => {
+            // @TODO this isn't really ideal - mostly doing this so we don't have to deviate too far from what is being migrated from Web-Secure for now.
+            this.goToStepID(currentStep, achData);
+        });
     }
 
     render() {
-        if (this.state.isLoading) {
+        if (this.props.freePlanBankAccount.loading) {
+            return null;
+        }
+
+        const defaultCountry = this.getDefaultCountry();
+        const personalDetails = {firstName: this.props.personalDetails.firstName, lastName: this.props.personalDetails.lastName};
+        const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, '@expensify.sms');
+
+        // These are all the parameters passed to React.v.AddWithdrawalAccountForm
+        console.debug({
+            defaultCountry,
+            personalDetails,
+            userHasPhonePrimaryEmail,
+            achData: this.state.achData,
+            steps: WITHDRAWAL_ACCOUNT_STEPS,
+            isPlaidDisabled: this.state.isPlaidDisabled,
+        });
+
+        // React.v.AddWithdrawalAccountForm mostly exists to
+        // 1. show a message about being throttled
+        // 2. show the step progress bar (that we're not using here)
+        // 3. block people who have primary phone logins from adding VBA
+        // https://github.com/Expensify/Web-Secure/blob/044c82affb78812a58b881a6d5ba026d91dace3b/site/app/dialogs/reimbursementAccount/addWithdrawalAccountForm.jsx#L56-L73
+
+        if (userHasPhonePrimaryEmail) {
+            // @TODO message explaining that they need to make their primary login an email
             return null;
         }
 
+        // See if they is throttled
+        const throttledDate = lodashGet(this.props, 'freePlanBankAccount.throttledDate');
+        if (throttledDate) {
+            const throttledEnd = moment().add(24, 'hours');
+            if (moment() < throttledEnd) {
+                // @TODO message explaining that the user has been throttled
+                return null;
+            }
+        }
+
+        // If we made it this far then we will render React.v.AddBankAccountForm with the isWithdrawal passed and the following params
+        console.debug({
+            achData: this.state.achData,
+            defaultCountry,
+            preventCountryEdit: false, // Maybe we don't need to worry about this yet since there is no country selection?
+            personalDetails,
+            userHasPhonePrimaryEmail,
+            steps: WITHDRAWAL_ACCOUNT_STEPS,
+            isPlaidDisabled: this.state.isPlaidDisabled,
+        });
+
+        // This is where stuff gets fun... this form is used for both withdrawal and deposit accounts and displays the "step views" there are also some controls for
+        // navigating to next and previous steps, error, and loading states etc. Errors + loaders are set with PubSub events. We will likely want to use Onyx for that
+        // instead. One thing we are going to run into though is that the "stepped" view doesn't really work so great with react-navigation. But we can refactor it later
+        // to improve the UX and naively just swap the views for now with no transitions just to get something cooking in this big monolithic view.
+
+        // Submitting a form - there is a global submit method that will capture the input of whatever child view is rendered... we're not gonna do that because it's
+        // sort of a tough pattern to understand IMO.
+
+        // We also let a child view tell us whether it has a "nextStep" or not based on whether it has implemented a nextStep() method. This practice is also really
+        // strange IMO and hard to wrap one's head around. As an alternative I'd suggest that each view just fire off an action on submit. There is also something like a
+        // validate() class method interface that gets called on submit. We should just let each view handle it's own validation instead IMO to keep the logic in one spot.
+        // If something hasn't implemented the nextStep() method then we tell the controller (this component currently) to figure out what to do next + pass it the form
+        // values (which are also grabbed from a weird interface method). All of this stuff makes understanding the code extremely difficult so I want to basically follow
+        // this pattern instead of the interface/getFunctionFromDeepestView() style which is damn near impossible to reason about...
+
+        // Each view will:
+        // 1. Implement it's own validation
+        // 2. Implement it's own submit method
+        // 3. Store it's form values on state
+
+        // There are also "previousStep" methods that should now be implemented by each view and not called from a parent function but rather the view itself
+
+        // With all of that out of the way, here's a break down of each step in the Withdrawal account flow and which methods it is implementing...
+
+        /**
+         * BankAccountStep (React.v.BankAccountStep)
+         *    - This one is tricky because it also has it's own "sub steps" and even implemented a history stack - so our "magic" methods might be found on the sub steps
+         *    - AddBankCountry - this is a country selector that has implemented a nextStep() method so when the form is submitted it will call onCountryCurrencySubmit()
+         *    - AddAccountNumbersManually - This is basically the manual account adding flow and it has no magic methods - but it does have form values so on submit it will
+         *        call ReimbursementAccountPage.nextStep() which will call ReimbursementAccountPage.setupWithdrawalAccount() with those values
+         *    - PlaidBankForm -
+         *        - has a previousStep() implemented to either take us back to the `login` step if we are looking at the list of accounts or to call
+         *            ReimbursementAccountPage.previousStep() - I think one way we can improve this is to just get rid of the whole next/previous step concept as controlled by
+         *            ReimbursementAccountPage and instead just explicitly navigate to the next view from inside another one... it's all too magical!
+         *        - Similar to the BankAccountStep itself the PlaidBankForm also has it's own "steps" which are 'accounts' and 'login'
+         *            - PlaidLogin - offers the add method selection either plaid or manual
+         *              - plaid
+         *                  - Login with Plaid (or switch to manual mode under certain conditions)
+         *                  - Call getPlaidBankAccounts() which calls BankAccount_Get and sets the accounts then displays the list (or handle various errors and redirects)
+         *              - manual
+         *                  - this will send us to the manual step via "showCountryFields()" a method that literally will show the required information for a given country
+         *                    and not a selector for a country - but stuff like accountNumber, routingNumber, etc with the proper validation. If we call that function then we
+         *                    will see the AddAccountNumbersManually view.
+         *            - PlaidAccountList - let's us choose an account from the list if we went down the plaid road. We won't find any submit buttons in here since we are
+         *                still working with our magical functions yay! There is a nextStep() function in here that will be called on submit. It does some stuff and then we
+         *                call ReimbursementAccountPage.setupWithdrawlAccount().
+         *
+         * CompanyStep (React.v.CompanyStep)
+         *     - Infinitely easier to understand here. There's just a form and no magic methods at all so you know that all we will do is call getFormValues() via FormInState
+         *       mixin and then ReimbursementAccountPage.nextStep(). The only catch here is that there could be some validate() methods in FormInState which might not be obvious.
+         *
+         * RequestorStep (React.v.RequestorStep)
+         *     - This one is slightly tricky as well since we will only show the Onfido SDK junk if we have a token and we only get a token when we call VerificationAPI::verify()
+         *       with the RequestorStep and the first time we do that is when the user goes through React.v.GetRequestorIdentity
+         *     - So, here's the whole flow broken down:
+         *         - GetRequestorIdentity - User fills out the React.v.IdentityForm and GetRequestorIdentity.nextStep() is called on submit which does some validation stuff
+         *               before calling ReimbursementAccountPage.nextStep() - this will ultimately hit ExpectID::verifyIDExists() and then return a bunch of questions (maybe)
+         *         - AskRequestorIdentityQuestions - If we have questions in the response then we will have to answer them in this flow or we will use Onfido... I think...?
+         *             This view cycles through questions/answers and then calls ReimbursementAccountPage.nextStep() again at the end.
+         *         - Onfido SDK - this actually has no View in Web-Secure we just handle the callbacks from the SDK and once it's complete we
+         *             call ReimbursementAccountPage.completeOnfido() - which basically just calls ReimbursementAccountPage.setupWithdrawalAccount() again with the onfidoData.
+         *
+         * ACHContractStep (React.v.BeneficialOwnersStep)
+         *     - This is another easy one. Form values -> call nextStep() / validate -> ReimbursementAccountPage.nextStep()
+         *
+         * ValidationStep (React.v.ValidationStep)
+         *     - This view is easy to understand as it basically looks at different things like achData.bankAccountInReview, achData.state === PENDING or state === OPEN.
+         *     - Only if the account is PENDING do we asked the user to enter the 3 values then we call ReimbursementAccountPage.validateBankAccount() which is a separate
+         *         API from setupWithdrawalAccount() but once we successfully validate we will call setupWithdrawalAccount() again viw nextStep()
+         *     - The other two views will show messages and can't really be actioned on.
+         *
+         * EnableStep - We're killing this step so we don't need to worry about it. However, I think we might still need to handle it somehow in E.cash - but not entirely sure
+         *     we'll find out more when testing.
+         *
+         * As for the whole this.achData thing if feels like the pattern of:
+         *  - modififying the achData locally in the .then() of setupWithdrawalAccount() then "refreshing" the view
+         *
+         * Should be replaced with:
+         *  - action called setupWithdrawalAccount() that modifies an achData key in Onyx and a larger view to render the correct steps and subscribe to this key in a dumb way
+         */
         return (
             <ScreenWrapper>
                 <View />
@@ -127,4 +457,16 @@ class ReimbursementAccountPage extends React.Component {
 
 ReimbursementAccountPage.propTypes = propTypes;
 ReimbursementAccountPage.defaultProps = defaultProps;
-export default ReimbursementAccountPage;
+export default withOnyx({
+    freePlanBankAccount: {
+        key: ONYXKEYS.FREE_PLAN_BANK_ACCOUNT,
+    },
+    personalDetails: {
+        key: ONYXKEYS.MY_PERSONAL_DETAILS,
+    },
+    session: {
+        key: ONYXKEYS.SESSION,
+    },
+
+    // @TODO we maybe need the user policyID + currency + default country + whether plaid is disabled ??
+})(ReimbursementAccountPage);

From 4023ff83e45279efcdaae73c5e48522a2c43e42d Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Thu, 10 Jun 2021 21:44:30 -0700
Subject: [PATCH 28/81] insert personal details into onyx

---
 src/libs/actions/Policy.js | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 18efd1471bca..89d9f44754ff 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -79,16 +79,25 @@ function invite(login, welcomeNote, policyID) {
     const key = `${ONYXKEYS.COLLECTION.POLICY}${policyID}`;
     const dataToStore = {};
     dataToStore[key] = {
-        employeeList: [login]
+        employeeList: [login],
     };
     Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, dataToStore);
 
-    Policy_Employees_Merge([{email: login}], welcomeNote, policyID)
+    Policy_Employees_Merge({
+        employees: JSON.stringify([{email: login}]),
+        welcomeNote,
+        policyID
+    })
         .then((data) => {
             // Save data.personalDetails
-        })
-        .fail(() => {
-            // Remove data from Onyx
+            if (data.jsonCode === 200) {
+                // TODO: need to match personalDetails to data in PersonalDetails.formatPersonalDetails
+                Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[login]: data.personalDetails[login]});
+                return;
+            }
+
+            // If the operation failed, undo the optimistic addition
+            
         });
 }
 

From 686db89d2f041f9fb6cd8c42a328b5ae5c2ac506 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Thu, 10 Jun 2021 22:51:06 -0700
Subject: [PATCH 29/81] test mergeCollection bug

---
 src/libs/actions/Policy.js | 59 +++++++++++++++++++++++++++-----------
 1 file changed, 42 insertions(+), 17 deletions(-)

diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 89d9f44754ff..51fc68ffcbe9 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -3,6 +3,16 @@ import Onyx from 'react-native-onyx';
 import {GetPolicySummaryList, GetPolicyList, Policy_Employees_Merge} from '../API';
 import ONYXKEYS from '../../ONYXKEYS';
 
+const allPolicies = {};
+Onyx.connect({
+    key: ONYXKEYS.COLLECTION.POLICY,
+    callback: (val, key) => {
+        if (val && key) {
+            allPolicies[key] = val;
+        }
+    },
+});
+
 /**
  * Takes a full policy summary that is returned from the policySummaryList and simplifies it so we are only storing
  * the pieces of data that we need to in Onyx
@@ -79,26 +89,41 @@ function invite(login, welcomeNote, policyID) {
     const key = `${ONYXKEYS.COLLECTION.POLICY}${policyID}`;
     const dataToStore = {};
     dataToStore[key] = {
-        employeeList: [login],
+        employeeList: allPolicies[key].employeeList.concat([login]),
     };
-    Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, dataToStore);
-
-    Policy_Employees_Merge({
-        employees: JSON.stringify([{email: login}]),
-        welcomeNote,
-        policyID
-    })
-        .then((data) => {
-            // Save data.personalDetails
-            if (data.jsonCode === 200) {
-                // TODO: need to match personalDetails to data in PersonalDetails.formatPersonalDetails
-                Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[login]: data.personalDetails[login]});
-                return;
-            }
 
-            // If the operation failed, undo the optimistic addition
-            
+    Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, dataToStore)
+        .then(() => {
+            const policyDataWithoutLogin = {};
+            policyDataWithoutLogin[key] = {
+                employeeList: _.without(allPolicies[key].employeeList, login),
+            };
+            Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataWithoutLogin)
+                .then(() => {
+                   console.debug('done');
+                });
         });
+
+    // Policy_Employees_Merge({
+    //     employees: JSON.stringify([{email: login}]),
+    //     welcomeNote,
+    //     policyID,
+    // })
+    //     .then((data) => {
+    //         // Save data.personalDetails
+    //         if (data.jsonCode !== 200) {
+    //             // TODO: need to match personalDetails to data in PersonalDetails.formatPersonalDetails
+    //             Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[login]: data.personalDetails[login]});
+    //             return;
+    //         }
+    //
+    //         // If the operation failed, undo the optimistic addition
+    //         const policyDataWithoutLogin = {};
+    //         policyDataWithoutLogin[key] = {
+    //             employeeList: _.without(allPolicies[key].employeeList, login),
+    //         };
+    //         Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataWithoutLogin);
+    //     });
 }
 
 export {

From 99e0f81a62513670049db51570cc241c06f5c53e Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Thu, 10 Jun 2021 20:04:00 -1000
Subject: [PATCH 30/81] move setupWithdrawalAccount into an action so that our
 views can purely respond to Onyx setting the achData

---
 src/libs/actions/BankAccounts.js      | 292 +++++++++++++++++++++++-
 src/pages/ReimbursementAccountPage.js | 309 +++-----------------------
 2 files changed, 315 insertions(+), 286 deletions(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 33c3f72f7c86..7cb8ad01f3ea 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -1,10 +1,12 @@
 import lodashGet from 'lodash/get';
+import lodashHas from 'lodash/has';
 import Str from 'expensify-common/lib/str';
 import Onyx from 'react-native-onyx';
 import _ from 'underscore';
 import CONST from '../../CONST';
 import ONYXKEYS from '../../ONYXKEYS';
 import * as API from '../API';
+import BankAccount from '../models/BankAccount';
 
 /**
  * Gets the Plaid Link token used to initialize the Plaid SDK
@@ -269,6 +271,35 @@ function fetchUserWallet() {
         });
 }
 
+let previousACHData = {};
+Onyx.connect({
+    key: ONYXKEYS.FREE_PLAN_BANK_ACCOUNT,
+    callback: (val = {}) => {
+        previousACHData = val.achData || {};
+    },
+});
+
+function goToStepID(stepID, achData) {
+    const newACHData = {...previousACHData};
+
+    // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
+    if (!newACHData.useOnfido && stepID === 'RequestorStep') {
+        delete newACHData.questions;
+        delete newACHData.answers;
+        if (lodashHas(achData, 'verifications.externalApiResponses')) {
+            delete newACHData.verifications.externalApiResponses.requestorIdentityID;
+            delete newACHData.verifications.externalApiResponses.requestorIdentityKBA;
+        }
+    }
+
+    // When going from CompanyStep to BankAccountStep, show the manual form instead of Plaid
+    if (newACHData.currentStep === 'CompanyStep' && stepID === 'BankAccountStep') {
+        newACHData.subStep = 'manual';
+    }
+
+    Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {achData: {...newACHData, ...achData, currentStep: stepID}});
+}
+
 /**
  * Fetch the bank account currently being set up by the user for the free plan if it exists.
  */
@@ -276,6 +307,7 @@ function fetchFreePlanVerifiedBankAccount() {
     Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: true});
     let bankAccountID;
     let bankAccount;
+    let kycVerificationsMigration;
     API.Get({
         returnValueList: 'nameValuePairs',
         name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
@@ -287,10 +319,24 @@ function fetchFreePlanVerifiedBankAccount() {
                 return Promise.resolve({});
             }
 
+            return API.Get({
+                returnValueList: 'nameValuePairs',
+                name: 'expensify_migration_2020_04_28_RunKycVerifications',
+            });
+        })
+        .then((response) => {
+            kycVerificationsMigration = lodashGet(
+                response.nameValuePairs,
+                'expensify_migration_2020_04_28_RunKycVerifications',
+                '',
+            );
             return API.Get({returnValueList: 'bankAccountList'});
         })
         .then((response) => {
-            bankAccount = _.find(response.bankAccountList, account => account.bankAccountID === bankAccountID);
+            const bankAccountJSON = _.find(response.bankAccountList, account => (
+                account.bankAccountID === bankAccountID
+            ));
+            bankAccount = bankAccountJSON ? new BankAccount(bankAccountJSON) : null;
             return API.Get({
                 returnValueList: 'nameValuePairs',
                 name: CONST.NVP.ACH_DATA_THROTTLED,
@@ -298,19 +344,258 @@ function fetchFreePlanVerifiedBankAccount() {
         })
         .then((response) => {
             const throttledDate = lodashGet(response.nameValuePairs, CONST.NVP.ACH_DATA_THROTTLED, '');
-            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {bankAccount, loading: false, throttledDate});
+
+            // Next we'll build the achData and save it to Onyx
+            // If the user already is already setting up a bank account we will want to continue the flow for them
+            let currentStep;
+            const achData = bankAccount ? bankAccount.toACHData() : {};
+            achData.useOnfido = true;
+            achData.policyID = '';
+
+            // @TODO I think probably we won't set the token until we actually ask to start the the Plaid flow
+            // but we could also get it here with another API call...
+            achData.plaidLinkToken = '';
+            achData.isInSetup = !bankAccount || bankAccount.isInSetup();
+            achData.bankAccountInReview = bankAccount && bankAccount.isVerifying();
+            achData.domainLimit = 0;
+            achData.isDomainUsingExpensifyCard = false; // @TODO - Not actually sure if we need this
+
+            // @TODO This subStep is used to either show the Plaid "login" view or the "manual" view - but not sure if
+            // we need to implement it yet...
+            // eslint-disable-next-line max-len
+            // See Web-Secure: https://github.com/Expensify/Web-Expensify/blob/896941794f68d7dce64466d83a3e86a5f8122e45/site/app/settings/reimbursement/bankAccountView.jsx#L356-L357
+            achData.subStep = '';
+
+            // If we're not in setup, it means we already have a withdrawal account and we're upgrading it to a business
+            // bank account. So let the user review all steps with all info prefilled and editable, unless a specific
+            // step was passed.
+            if (!achData.isInSetup) {
+                // @TODO Not sure if we need to do this since for NewDot none of the accounts are pre-existing ones
+                currentStep = '';
+            }
+
+            // Temporary fix for Onfido flow. Can be removed by nkuoch after Sept 1 2020.
+            // @TODO not sure if we still need this or what this is about, but seems like maybe yes...
+            if (currentStep === 'ACHContractStep' && achData.useOnfido) {
+                const onfidoRes = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityOnfido');
+                const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
+                if (sdkToken && !achData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
+                    currentStep = 'RequestorStep';
+                }
+            }
+
+            // Ensure we route the user to the correct step based on the status of their bank account
+            if (bankAccount && !currentStep) {
+                currentStep = bankAccount.isPending()
+                    || bankAccount.isVerifying() ? 'ValidationStep' : 'BankAccountStep';
+
+                // @TODO Again, not sure how much of this logic is needed right now as we shouldn't be handling any
+                // open accounts in E.cash yet that need to pass any more checks or can be upgraded, but leaving in for
+                // possible future compatibility.
+                if (bankAccount.isOpen()) {
+                    if (bankAccount.needsToPassLatestChecks()) {
+                        const hasTriedToUpgrade = bankAccount.getDateSigned()
+                            > (kycVerificationsMigration || '2020-01-13');
+                        currentStep = hasTriedToUpgrade ? 'ValidationStep' : 'CompanyStep';
+                        achData.bankAccountInReview = hasTriedToUpgrade;
+                    } else {
+                        // We're not going to have a EnableStep, but we can show this account as open if accessed
+                        currentStep = 'EnableStep';
+                    }
+                }
+            }
+
+            // If at this point we still don't have a current step, default to the BankAccountStep
+            if (!currentStep) {
+                currentStep = 'BankAccountStep';
+            }
+
+            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {throttledDate});
+            goToStepID(currentStep, achData);
+        })
+        .finally(() => {
+            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: false});
         });
 }
 
+const WITHDRAWAL_ACCOUNT_STEPS = [
+    {
+        id: 'BankAccountStep',
+        title: 'Bank Account',
+    },
+    {
+        id: 'CompanyStep',
+        title: 'Company Information',
+    },
+    {
+        id: 'RequestorStep',
+        title: 'Requestor Information',
+    },
+    {
+        id: 'ACHContractStep',
+        title: 'Beneficial Owners',
+    },
+    {
+        id: 'ValidationStep',
+        title: 'Validate',
+    },
+    {
+        id: 'EnableStep',
+        title: 'Enable',
+    },
+];
+
+/**
+ * Get step position in the array
+ * @param {String} stepID
+ * @return {Number}
+ */
+function getIndexByStepID(stepID) {
+    return _.findIndex(WITHDRAWAL_ACCOUNT_STEPS, step => step.id === stepID);
+}
+
+/**
+ * Get next step ID
+ * @return {String}
+ */
+function getNextStepID() {
+    const nextStepIndex = Math.min(
+        getIndexByStepID(this.state.achData.currentStep) + 1,
+        WITHDRAWAL_ACCOUNT_STEPS.length - 1,
+    );
+    return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], 'BankAccountStep');
+}
+
+
 /**
  * @private
  * @param {Number} bankAccountID
  */
 function setFreePlanVerifiedBankAccountID(bankAccountID) {
-    Onyx.merge('freePlanBankAccount', {bankAccountID, loading: false});
     API.SetNameValuePair({name: 'expensify_freePlanBankAccountID', value: bankAccountID});
 }
 
+/**
+ * Create or update the bank account in db with the updated data.
+ *
+ * @param {Object} [data]
+ */
+function setupWithdrawalAccount(data) {
+    Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: true});
+
+    previousACHData = {...previousACHData, ...data};
+    if (data && data.isSavings !== undefined) {
+        previousACHData.isSavings = Boolean(data.isSavings);
+    }
+    if (!previousACHData.setupType) {
+        previousACHData.setupType = previousACHData.plaidAccountID ? 'plaid' : 'manual';
+    }
+
+    let nextStep = previousACHData.currentStep;
+
+    API.BankAccount_SetupWithdrawal(previousACHData)
+        .finally((response) => {
+            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: false});
+
+            const currentStep = previousACHData.currentStep;
+            let achData = response.achData;
+            let error = lodashGet(achData, 'verifications.errorMessage');
+
+            if (response.jsonCode === 200 && !error) {
+                // Save an NVP with the bankAccountID for this account. This is temporary since we are not showing lists
+                // of accounts yet and must have some kind of record of which account is the one the user is trying to
+                // set up for the free plan.
+                setFreePlanVerifiedBankAccountID(achData.bankAccountID);
+
+                // Show warning if another account already set up this bank account and promote share
+                if (response.existingOwners) {
+                    // @TODO Show better error in UI about existing owners
+                    console.error('Cannot set up withdrawal account due to existing owners');
+                    return;
+                }
+
+                if (currentStep === 'RequestorStep') {
+                    const requestorResponse = lodashGet(
+                        achData,
+                        'verifications.externalApiResponses.requestorIdentityID',
+                    );
+                    if (previousACHData.useOnfido) {
+                        const onfidoRes = lodashGet(
+                            achData,
+                            'verifications.externalApiResponses.requestorIdentityOnfido',
+                        );
+                        const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
+                        if (sdkToken && !previousACHData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
+                            // Requestor Step still needs to run Onfido
+                            achData.sdkToken = sdkToken;
+                            goToStepID('RequestorStep', achData);
+                            return;
+                        }
+                    } else if (requestorResponse) {
+                        // Don't go to next step if Requestor Step needs to ask some questions
+                        let questions = lodashGet(requestorResponse, 'apiResult.questions.question') || [];
+                        if (_.isEmpty(questions)) {
+                            const differentiatorQuestion = lodashGet(
+                                requestorResponse,
+                                'apiResult.differentiator-question',
+                            );
+                            if (differentiatorQuestion) {
+                                questions = [differentiatorQuestion];
+                            }
+                        }
+                        if (!_.isEmpty(questions)) {
+                            achData.questions = questions;
+                            goToStepID('RequestorStep', achData);
+                            return;
+                        }
+                    }
+                }
+
+                if (currentStep === 'ACHContractStep') {
+                    // Get an up-to-date bank account list so that we can allow the user to validate their newly
+                    // generated bank account
+                    return API.get({returnValueList: 'bankAccountList'})
+                        .done((json) => {
+                            const bankAccountJSON = _.findWhere(json.bankAccountList, {
+                                bankAccountID: previousACHData.bankAccountID,
+                            });
+                            const bankAccount = new BankAccount(bankAccountJSON);
+                            achData = bankAccount.toACHData();
+                            const needsToPassLatestChecks = achData.state === BankAccount.STATE.OPEN
+                                && achData.needsToPassLatestChecks;
+                            achData.bankAccountInReview = needsToPassLatestChecks
+                                || achData.state === BankAccount.STATE.VERIFYING;
+
+                            goToStepID('ValidationStep', achData);
+                        });
+                }
+
+                if ((currentStep === 'ValidationStep' && previousACHData.bankAccountInReview)
+                    || currentStep === 'EnableStep'
+                ) {
+                    // Setup done!
+                } else {
+                    nextStep = getNextStepID();
+                }
+            } else {
+                if (response.jsonCode === 666) {
+                    error = response.message;
+                }
+                if (lodashGet(achData, 'verifications.throttled')) {
+                    achData.disableFields = true;
+                }
+            }
+
+            // Go to next step
+            goToStepID(nextStep, achData);
+
+            if (error) {
+                // @TODO - Show the error for real
+                console.error(`Error setting up withdrawal account: ${error}`);
+            }
+        });
+}
+
 export {
     fetchPlaidLinkToken,
     addPersonalBankAccount,
@@ -320,4 +605,5 @@ export {
     activateWallet,
     fetchUserWallet,
     fetchFreePlanVerifiedBankAccount,
+    setupWithdrawalAccount,
 };
diff --git a/src/pages/ReimbursementAccountPage.js b/src/pages/ReimbursementAccountPage.js
index d6c9a05fa4ef..da990bdf32c8 100644
--- a/src/pages/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccountPage.js
@@ -1,9 +1,7 @@
 /* eslint-disable max-len */
 /* eslint-disable react/no-unused-state */
-import _ from 'underscore';
 import moment from 'moment';
 import lodashGet from 'lodash/get';
-import lodashHas from 'lodash/has';
 import React from 'react';
 import {withOnyx} from 'react-native-onyx';
 import Str from 'expensify-common/lib/str';
@@ -12,50 +10,31 @@ import PropTypes from 'prop-types';
 import ScreenWrapper from '../components/ScreenWrapper';
 import {fetchFreePlanVerifiedBankAccount} from '../libs/actions/BankAccounts';
 import ONYXKEYS from '../ONYXKEYS';
-import BankAccount from '../libs/models/BankAccount';
-import * as API from '../libs/API';
-
-const WITHDRAWAL_ACCOUNT_STEPS = [
-    {
-        id: 'BankAccountStep',
-        title: 'Bank Account',
-    },
-    {
-        id: 'CompanyStep',
-        title: 'Company Information',
-    },
-    {
-        id: 'RequestorStep',
-        title: 'Requestor Information',
-    },
-    {
-        id: 'ACHContractStep',
-        title: 'Beneficial Owners',
-    },
-    {
-        id: 'ValidationStep',
-        title: 'Validate',
-    },
-    {
-        id: 'EnableStep',
-        title: 'Enable',
-    },
-];
 
 const propTypes = {
-    skipOnfido: PropTypes.bool,
     freePlanBankAccount: PropTypes.shape({
-        bankAccount: PropTypes.shape({}),
         loading: PropTypes.bool,
         throttledDate: PropTypes.string,
+        achData: PropTypes.shape({
+            country: PropTypes.string,
+        }),
+    }),
+
+    personalDetails: PropTypes.shape({
+        firstName: PropTypes.string,
+        lastName: PropTypes.string,
     }),
+
+    session: PropTypes.shape({
+        email: PropTypes.string,
+    }).isRequired,
 };
 
 const defaultProps = {
-    skipOnfido: false,
     freePlanBankAccount: {
         loading: true,
     },
+    personalDetails: {},
 };
 
 class ReimbursementAccountPage extends React.Component {
@@ -65,11 +44,6 @@ class ReimbursementAccountPage extends React.Component {
         this.state = {
             bankAccountID: undefined,
             isLoading: true,
-
-            // In Web-Secure Plaid is disabled if there is no window.Plaid global (SDK script didn't load) or if we explicitly disable it because the user made too many calls to BankAccount_Get
-            // See: https://github.com/Expensify/Web-Secure/blob/044c82affb78812a58b881a6d5ba026d91dace3b/site/app/settings/reimbursement/PlaidBankForm.jsx#L171-L176
-            // @TODO we should handle the too many calls to Plaid situation, but for now it will remain enabled
-            isPlaidDisabled: false,
             achData: {},
             isWithdrawal: true,
             lastDataWithError: {},
@@ -81,247 +55,17 @@ class ReimbursementAccountPage extends React.Component {
         fetchFreePlanVerifiedBankAccount();
     }
 
-    componentDidUpdate(prevProps) {
-        if (prevProps.freePlanBankAccount.loading && !this.props.freePlanBankAccount.loading) {
-            this.init();
-        }
-    }
-
-    /**
-     * Get step position in the array
-     * @param {String} stepID
-     * @return {Number}
-     */
-    getIndexByStepID(stepID) {
-        return _.findIndex(WITHDRAWAL_ACCOUNT_STEPS, step => step.id === stepID);
-    }
-
-    /**
-     * Get next step ID
-     * @return {String}
-     */
-    getNextStepID() {
-        const nextStepIndex = Math.min(this.getIndexByStepID(this.state.achData.currentStep) + 1, WITHDRAWAL_ACCOUNT_STEPS.length - 1);
-        return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], 'BankAccountStep');
-    }
-
-    /**
-     * Create or update the bank account in db with the updated data.
-     *
-     * @param {Object} [data]
-     */
-    setupWithdrawalAccount(data) {
-        // @TODO trigger loading
-
-        // Create a shallow copy of the data and overwrite new values
-        const newAchData = {...this.state.achData};
-        _.extend(newAchData, data);
-        if (data && data.isSavings !== undefined) {
-            newAchData.isSavings = Boolean(data.isSavings);
-        }
-        if (!newAchData.setupType) {
-            newAchData.setupType = newAchData.plaidAccountID ? 'plaid' : 'manual';
+    render() {
+        if (this.props.freePlanBankAccount.loading) {
+            return null;
         }
 
-        let nextStep = newAchData.currentStep;
-
-        // @TODO move this to an action instead
-        API.BankAccount_SetupWithdrawal(newAchData)
-            .finally((response) => {
-                // @TODO hide the loader
-
-                const currentStep = newAchData.currentStep;
-                let achData = response.achData;
-                let error = lodashGet(achData, 'verifications.errorMessage');
-
-                if (response.jsonCode === 200 && !error) {
-                    // Show warning if another account already set up this bank account and promote share
-                    if (response.existingOwners) {
-                        // @TODO Show error about existing owners
-                        return;
-                    }
-
-                    if (currentStep === 'RequestorStep') {
-                        const requestorResponse = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityID');
-                        if (newAchData.useOnfido) {
-                            const onfidoRes = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityOnfido');
-                            const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
-                            if (sdkToken && !newAchData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
-                                // Requestor Step still needs to run Onfido
-                                achData.sdkToken = sdkToken;
-                                this.setState({achData: newAchData}, () => {
-                                    this.goToStepID('RequestorStep', achData);
-                                });
-                                return;
-                            }
-                        } else if (requestorResponse) {
-                            // Don't go to next step if Requestor Step needs to ask some questions
-                            let questions = lodashGet(requestorResponse, 'apiResult.questions.question') || [];
-                            if (_.isEmpty(questions)) {
-                                const differentiatorQuestion = lodashGet(requestorResponse, 'apiResult.differentiator-question');
-                                if (differentiatorQuestion) {
-                                    questions = [differentiatorQuestion];
-                                }
-                            }
-                            if (!_.isEmpty(questions)) {
-                                achData.questions = questions;
-                                this.setState({achData: newAchData}, () => {
-                                    this.goToStepID('RequestorStep', achData);
-                                });
-                                return;
-                            }
-                        }
-                    }
-                    if (currentStep === 'ACHContractStep') {
-                        // const promise = $.Deferred(); // Not sure what is awaiting this promise yet but doesn't seem like something we will want to do...
-
-                        // We want to make a task completion so we can pay guides, but we don't want to close the iframe so mark completeSetup as false
-                        // this.triggerParentSuccessMessage(response, false); - Not really sure if this applies in E.cash
-
-                        // Get an up-to-date bank account list so that we can allow the user to validate their newly generated bank account
-                        return API.get({returnValueList: 'bankAccountList'})
-                            .done((json) => {
-                                const bankAccount = new BankAccount(_.findWhere(json.bankAccountList, {bankAccountID: newAchData.bankAccountID}));
-                                achData = bankAccount.toACHData();
-                                const needsToPassLatestChecks = achData.state === BankAccount.STATE.OPEN && achData.needsToPassLatestChecks;
-                                achData.bankAccountInReview = needsToPassLatestChecks || achData.state === BankAccount.STATE.VERIFYING;
-                                this.setState({achData: newAchData}, () => {
-                                    this.goToStepID('ValidationStep', achData);
-                                });
-                            });
-                    }
-                    if ((currentStep === 'ValidationStep' && newAchData.bankAccountInReview) || currentStep === 'EnableStep') {
-                        // Setup done! We can close the modal - @TODO - not sure what this is doing or if we need it
-                        // this.triggerParentSuccessMessage(response);
-                    } else {
-                        nextStep = this.getNextStepID();
-                    }
-                } else {
-                    if (response.jsonCode === 666) {
-                        error = response.message;
-                    }
-                    if (lodashGet(achData, 'verifications.throttled')) {
-                        achData.disableFields = true;
-                    }
-                }
-
-                // Go to next step
-                this.setState({achData: newAchData}, () => {
-                    this.goToStepID(nextStep, achData);
-                });
-
-                if (error) {
-                    // @TODO - Show the error
-                }
-            });
-    }
-
-    getDefaultCountry() {
-        const defaultCountry = this.state.achData.country || 'US'; // @TODO - In Web-Expensify this fallback refers to User.getIpCountry()
+        const {achData} = this.props.freePlanBankAccount;
 
         // In Web-Secure we check the policy to find out the defaultCountry. The policy is passed in here:
         // https://github.com/Expensify/Web-Expensify/blob/896941794f68d7dce64466d83a3e86a5f8122e45/site/app/policyEditor/policyEditorPage.jsx#L2169-L2171
         // @TODO figure out whether we need to check the policy or not - not sure if it's necessary for V1 of Free Plan
-        return defaultCountry;
-    }
-
-    /**
-     * Go to a specific step id.
-     * @param {String} stepID
-     * @param {Object} [achData]
-     */
-    goToStepID(stepID, achData) {
-        // Setting state again will refresh the view and progress us to the next step
-        this.setState((prevState) => {
-            const newAchData = {...prevState.achData};
-
-            // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
-            if (!newAchData.useOnfido && stepID === 'RequestorStep') {
-                delete newAchData.questions;
-                delete newAchData.answers;
-                if (lodashHas(newAchData, 'verifications.externalApiResponses')) {
-                    delete newAchData.verifications.externalApiResponses.requestorIdentityID;
-                    delete newAchData.verifications.externalApiResponses.requestorIdentityKBA;
-                }
-            }
-
-            // When going from companyStep to bankAccountStep, show the manual form instead of Plaid
-            if (newAchData.currentStep === 'CompanyStep' && stepID === 'BankAccountStep') {
-                newAchData.subStep = 'manual';
-            }
-
-            _.extend(newAchData, achData, {currentStep: stepID});
-            return ({achData: newAchData});
-        });
-    }
-
-    init() {
-        // If the user already is already setting up a bank account we will want to continue the flow for them
-        let currentStep;
-        const bankAccountJSON = parseInt(lodashGet(this.props, 'freePlanBankAccount.bankAccount.bankAccountID', '0'), 10) || 0;
-        const bankAccount = bankAccountJSON ? new BankAccount(bankAccountJSON) : null;
-        const achData = bankAccount ? bankAccount.toACHData() : {};
-        achData.useOnfido = !this.props.skipOnfido;
-        achData.policyID = this.props.policyId || '';
-        achData.plaidLinkToken = this.props.plaidLinkToken; // I think probably we won't have this until we start the Plaid flow but maybe...?
-        achData.isInSetup = !bankAccount || bankAccount.isInSetup();
-        achData.bankAccountInReview = bankAccount && bankAccount.isVerifying();
-        achData.domainLimi = 0;
-        achData.isDomainUsingExpensifyCard = false; // Maybe also needs to be a prop... not too sure.
-
-        // Unsure what the substeps are used for so far... pretty sure it's just to advance to the "login" step here and not used anywhere else
-        // https://github.com/Expensify/Web-Expensify/blob/896941794f68d7dce64466d83a3e86a5f8122e45/site/app/settings/reimbursement/bankAccountView.jsx#L356-L357
-        achData.subStep = this.props.subStep;
-
-        // If we're not in setup, it means we already have a withdrawal account and we're upgrading it to a business bank account.
-        // So let the user review all steps with all info prefilled and editable, unless a specific step was passed.
-        if (!achData.isInSetup) {
-            currentStep = this.props.currentStep; // Not sure if we need this as user's won't be upgrading these accounts...
-        }
-
-        // Temporary fix for Onfido flow. Can be removed by nkuoch after Sept 1 2020. - not sure if we need this or what this is about...
-        if (currentStep === 'ACHContractStep' && achData.useOnfido) {
-            const onfidoRes = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityOnfido');
-            const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
-            if (sdkToken && !achData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
-                currentStep = 'RequestorStep';
-            }
-        }
-
-        // Ensure we route the user to the correct step based on the status of their bank account
-        if (bankAccount && !currentStep) {
-            currentStep = bankAccount.isPending() || bankAccount.isVerifying() ? 'ValidationStep' : 'BankAccountStep';
-
-            // Again, not sure how much of this logic is needed right now as we shouldn't be handling any open accounts in E.cash yet...
-            if (bankAccount.isOpen()) {
-                if (bankAccount.needsToPassLatestChecks()) {
-                    // const hasTriedToUpgrade = bankAccount.getDateSigned() > (NVP.get('expensify_migration_2020_04_28_RunKycVerifications') || '2020-01-13');
-                    // currentStep = hasTriedToUpgrade ? 'ValidationStep' : 'CompanyStep';
-                    // achData.bankAccountInReview = hasTriedToUpgrade;
-                } else {
-                    // Not handling the EnableStep...
-                    // currentStep = 'EnableStep';
-                }
-            }
-        }
-
-        // If at this point we still don't have a current step, default to the BankAccountStep
-        if (!currentStep) {
-            currentStep = 'BankAccountStep';
-        }
-
-        this.setState({isLoading: false, currentStep, achData}, () => {
-            // @TODO this isn't really ideal - mostly doing this so we don't have to deviate too far from what is being migrated from Web-Secure for now.
-            this.goToStepID(currentStep, achData);
-        });
-    }
-
-    render() {
-        if (this.props.freePlanBankAccount.loading) {
-            return null;
-        }
-
-        const defaultCountry = this.getDefaultCountry();
+        const defaultCountry = achData.country || 'US'; // @TODO - In Web-Expensify this fallback refers to User.getIpCountry()
         const personalDetails = {firstName: this.props.personalDetails.firstName, lastName: this.props.personalDetails.lastName};
         const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, '@expensify.sms');
 
@@ -330,15 +74,13 @@ class ReimbursementAccountPage extends React.Component {
             defaultCountry,
             personalDetails,
             userHasPhonePrimaryEmail,
-            achData: this.state.achData,
-            steps: WITHDRAWAL_ACCOUNT_STEPS,
-            isPlaidDisabled: this.state.isPlaidDisabled,
+            achData,
         });
 
         // React.v.AddWithdrawalAccountForm mostly exists to
-        // 1. show a message about being throttled
-        // 2. show the step progress bar (that we're not using here)
-        // 3. block people who have primary phone logins from adding VBA
+        // 1. Show a message about being throttled
+        // 2. Show the step progress bar (that we're not using here)
+        // 3. Block people who have primary phone logins from adding VBA
         // https://github.com/Expensify/Web-Secure/blob/044c82affb78812a58b881a6d5ba026d91dace3b/site/app/dialogs/reimbursementAccount/addWithdrawalAccountForm.jsx#L56-L73
 
         if (userHasPhonePrimaryEmail) {
@@ -358,15 +100,16 @@ class ReimbursementAccountPage extends React.Component {
 
         // If we made it this far then we will render React.v.AddBankAccountForm with the isWithdrawal passed and the following params
         console.debug({
-            achData: this.state.achData,
+            achData,
             defaultCountry,
             preventCountryEdit: false, // Maybe we don't need to worry about this yet since there is no country selection?
             personalDetails,
             userHasPhonePrimaryEmail,
-            steps: WITHDRAWAL_ACCOUNT_STEPS,
-            isPlaidDisabled: this.state.isPlaidDisabled,
         });
 
+        // Everything we need to display UI-wise is pretty much in the achData at this point. We just need to call the correct actions in the right places when
+        // submitting a form or navigating back or forward.
+
         // This is where stuff gets fun... this form is used for both withdrawal and deposit accounts and displays the "step views" there are also some controls for
         // navigating to next and previous steps, error, and loading states etc. Errors + loaders are set with PubSub events. We will likely want to use Onyx for that
         // instead. One thing we are going to run into though is that the "stepped" view doesn't really work so great with react-navigation. But we can refactor it later

From 3319c162894e776dcc2e66ae982eedbc40366af0 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Thu, 10 Jun 2021 20:14:38 -1000
Subject: [PATCH 31/81] move everything into actions clean up temp view

---
 src/ONYXKEYS.js                       |   4 +-
 src/libs/actions/BankAccounts.js      |  14 +--
 src/pages/ReimbursementAccountPage.js | 135 ++------------------------
 3 files changed, 19 insertions(+), 134 deletions(-)

diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index b3271f8b3911..f585c8116a05 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -100,6 +100,6 @@ export default {
     // Object containing Wallet terms step state
     WALLET_TERMS: 'walletTerms',
 
-    // Stores information about the free plan bank account being set up
-    FREE_PLAN_BANK_ACCOUNT: 'freePlanBankAccount',
+    // Stores information about the active reimbursement account being set up
+    REIMBURSEMENT_ACCOUNT: 'reimbursementAccount',
 };
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 7cb8ad01f3ea..e136e468f010 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -273,7 +273,7 @@ function fetchUserWallet() {
 
 let previousACHData = {};
 Onyx.connect({
-    key: ONYXKEYS.FREE_PLAN_BANK_ACCOUNT,
+    key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
     callback: (val = {}) => {
         previousACHData = val.achData || {};
     },
@@ -297,14 +297,14 @@ function goToStepID(stepID, achData) {
         newACHData.subStep = 'manual';
     }
 
-    Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {achData: {...newACHData, ...achData, currentStep: stepID}});
+    Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...newACHData, ...achData, currentStep: stepID}});
 }
 
 /**
  * Fetch the bank account currently being set up by the user for the free plan if it exists.
  */
 function fetchFreePlanVerifiedBankAccount() {
-    Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: true});
+    Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
     let bankAccountID;
     let bankAccount;
     let kycVerificationsMigration;
@@ -410,11 +410,11 @@ function fetchFreePlanVerifiedBankAccount() {
                 currentStep = 'BankAccountStep';
             }
 
-            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {throttledDate});
+            Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {throttledDate});
             goToStepID(currentStep, achData);
         })
         .finally(() => {
-            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: false});
+            Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
         });
 }
 
@@ -481,7 +481,7 @@ function setFreePlanVerifiedBankAccountID(bankAccountID) {
  * @param {Object} [data]
  */
 function setupWithdrawalAccount(data) {
-    Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: true});
+    Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
 
     previousACHData = {...previousACHData, ...data};
     if (data && data.isSavings !== undefined) {
@@ -495,7 +495,7 @@ function setupWithdrawalAccount(data) {
 
     API.BankAccount_SetupWithdrawal(previousACHData)
         .finally((response) => {
-            Onyx.merge(ONYXKEYS.FREE_PLAN_BANK_ACCOUNT, {loading: false});
+            Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
 
             const currentStep = previousACHData.currentStep;
             let achData = response.achData;
diff --git a/src/pages/ReimbursementAccountPage.js b/src/pages/ReimbursementAccountPage.js
index da990bdf32c8..a69c70f7d342 100644
--- a/src/pages/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccountPage.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-unused-vars */
 /* eslint-disable max-len */
 /* eslint-disable react/no-unused-state */
 import moment from 'moment';
@@ -10,9 +11,10 @@ import PropTypes from 'prop-types';
 import ScreenWrapper from '../components/ScreenWrapper';
 import {fetchFreePlanVerifiedBankAccount} from '../libs/actions/BankAccounts';
 import ONYXKEYS from '../ONYXKEYS';
+import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator';
 
 const propTypes = {
-    freePlanBankAccount: PropTypes.shape({
+    reimbursementAccount: PropTypes.shape({
         loading: PropTypes.bool,
         throttledDate: PropTypes.string,
         achData: PropTypes.shape({
@@ -31,36 +33,23 @@ const propTypes = {
 };
 
 const defaultProps = {
-    freePlanBankAccount: {
+    reimbursementAccount: {
         loading: true,
     },
     personalDetails: {},
 };
 
 class ReimbursementAccountPage extends React.Component {
-    constructor(props) {
-        super(props);
-
-        this.state = {
-            bankAccountID: undefined,
-            isLoading: true,
-            achData: {},
-            isWithdrawal: true,
-            lastDataWithError: {},
-            currentStep: 'BankAccountStep',
-        };
-    }
-
     componentDidMount() {
         fetchFreePlanVerifiedBankAccount();
     }
 
     render() {
-        if (this.props.freePlanBankAccount.loading) {
-            return null;
+        if (this.props.reimbursementAccount.loading) {
+            return <FullScreenLoadingIndicator visible />;
         }
 
-        const {achData} = this.props.freePlanBankAccount;
+        const {achData} = this.props.reimbursementAccount;
 
         // In Web-Secure we check the policy to find out the defaultCountry. The policy is passed in here:
         // https://github.com/Expensify/Web-Expensify/blob/896941794f68d7dce64466d83a3e86a5f8122e45/site/app/policyEditor/policyEditorPage.jsx#L2169-L2171
@@ -69,27 +58,13 @@ class ReimbursementAccountPage extends React.Component {
         const personalDetails = {firstName: this.props.personalDetails.firstName, lastName: this.props.personalDetails.lastName};
         const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, '@expensify.sms');
 
-        // These are all the parameters passed to React.v.AddWithdrawalAccountForm
-        console.debug({
-            defaultCountry,
-            personalDetails,
-            userHasPhonePrimaryEmail,
-            achData,
-        });
-
-        // React.v.AddWithdrawalAccountForm mostly exists to
-        // 1. Show a message about being throttled
-        // 2. Show the step progress bar (that we're not using here)
-        // 3. Block people who have primary phone logins from adding VBA
-        // https://github.com/Expensify/Web-Secure/blob/044c82affb78812a58b881a6d5ba026d91dace3b/site/app/dialogs/reimbursementAccount/addWithdrawalAccountForm.jsx#L56-L73
-
         if (userHasPhonePrimaryEmail) {
             // @TODO message explaining that they need to make their primary login an email
             return null;
         }
 
         // See if they is throttled
-        const throttledDate = lodashGet(this.props, 'freePlanBankAccount.throttledDate');
+        const throttledDate = lodashGet(this.props, 'reimbursementAccount.throttledDate');
         if (throttledDate) {
             const throttledEnd = moment().add(24, 'hours');
             if (moment() < throttledEnd) {
@@ -98,98 +73,8 @@ class ReimbursementAccountPage extends React.Component {
             }
         }
 
-        // If we made it this far then we will render React.v.AddBankAccountForm with the isWithdrawal passed and the following params
-        console.debug({
-            achData,
-            defaultCountry,
-            preventCountryEdit: false, // Maybe we don't need to worry about this yet since there is no country selection?
-            personalDetails,
-            userHasPhonePrimaryEmail,
-        });
-
         // Everything we need to display UI-wise is pretty much in the achData at this point. We just need to call the correct actions in the right places when
         // submitting a form or navigating back or forward.
-
-        // This is where stuff gets fun... this form is used for both withdrawal and deposit accounts and displays the "step views" there are also some controls for
-        // navigating to next and previous steps, error, and loading states etc. Errors + loaders are set with PubSub events. We will likely want to use Onyx for that
-        // instead. One thing we are going to run into though is that the "stepped" view doesn't really work so great with react-navigation. But we can refactor it later
-        // to improve the UX and naively just swap the views for now with no transitions just to get something cooking in this big monolithic view.
-
-        // Submitting a form - there is a global submit method that will capture the input of whatever child view is rendered... we're not gonna do that because it's
-        // sort of a tough pattern to understand IMO.
-
-        // We also let a child view tell us whether it has a "nextStep" or not based on whether it has implemented a nextStep() method. This practice is also really
-        // strange IMO and hard to wrap one's head around. As an alternative I'd suggest that each view just fire off an action on submit. There is also something like a
-        // validate() class method interface that gets called on submit. We should just let each view handle it's own validation instead IMO to keep the logic in one spot.
-        // If something hasn't implemented the nextStep() method then we tell the controller (this component currently) to figure out what to do next + pass it the form
-        // values (which are also grabbed from a weird interface method). All of this stuff makes understanding the code extremely difficult so I want to basically follow
-        // this pattern instead of the interface/getFunctionFromDeepestView() style which is damn near impossible to reason about...
-
-        // Each view will:
-        // 1. Implement it's own validation
-        // 2. Implement it's own submit method
-        // 3. Store it's form values on state
-
-        // There are also "previousStep" methods that should now be implemented by each view and not called from a parent function but rather the view itself
-
-        // With all of that out of the way, here's a break down of each step in the Withdrawal account flow and which methods it is implementing...
-
-        /**
-         * BankAccountStep (React.v.BankAccountStep)
-         *    - This one is tricky because it also has it's own "sub steps" and even implemented a history stack - so our "magic" methods might be found on the sub steps
-         *    - AddBankCountry - this is a country selector that has implemented a nextStep() method so when the form is submitted it will call onCountryCurrencySubmit()
-         *    - AddAccountNumbersManually - This is basically the manual account adding flow and it has no magic methods - but it does have form values so on submit it will
-         *        call ReimbursementAccountPage.nextStep() which will call ReimbursementAccountPage.setupWithdrawalAccount() with those values
-         *    - PlaidBankForm -
-         *        - has a previousStep() implemented to either take us back to the `login` step if we are looking at the list of accounts or to call
-         *            ReimbursementAccountPage.previousStep() - I think one way we can improve this is to just get rid of the whole next/previous step concept as controlled by
-         *            ReimbursementAccountPage and instead just explicitly navigate to the next view from inside another one... it's all too magical!
-         *        - Similar to the BankAccountStep itself the PlaidBankForm also has it's own "steps" which are 'accounts' and 'login'
-         *            - PlaidLogin - offers the add method selection either plaid or manual
-         *              - plaid
-         *                  - Login with Plaid (or switch to manual mode under certain conditions)
-         *                  - Call getPlaidBankAccounts() which calls BankAccount_Get and sets the accounts then displays the list (or handle various errors and redirects)
-         *              - manual
-         *                  - this will send us to the manual step via "showCountryFields()" a method that literally will show the required information for a given country
-         *                    and not a selector for a country - but stuff like accountNumber, routingNumber, etc with the proper validation. If we call that function then we
-         *                    will see the AddAccountNumbersManually view.
-         *            - PlaidAccountList - let's us choose an account from the list if we went down the plaid road. We won't find any submit buttons in here since we are
-         *                still working with our magical functions yay! There is a nextStep() function in here that will be called on submit. It does some stuff and then we
-         *                call ReimbursementAccountPage.setupWithdrawlAccount().
-         *
-         * CompanyStep (React.v.CompanyStep)
-         *     - Infinitely easier to understand here. There's just a form and no magic methods at all so you know that all we will do is call getFormValues() via FormInState
-         *       mixin and then ReimbursementAccountPage.nextStep(). The only catch here is that there could be some validate() methods in FormInState which might not be obvious.
-         *
-         * RequestorStep (React.v.RequestorStep)
-         *     - This one is slightly tricky as well since we will only show the Onfido SDK junk if we have a token and we only get a token when we call VerificationAPI::verify()
-         *       with the RequestorStep and the first time we do that is when the user goes through React.v.GetRequestorIdentity
-         *     - So, here's the whole flow broken down:
-         *         - GetRequestorIdentity - User fills out the React.v.IdentityForm and GetRequestorIdentity.nextStep() is called on submit which does some validation stuff
-         *               before calling ReimbursementAccountPage.nextStep() - this will ultimately hit ExpectID::verifyIDExists() and then return a bunch of questions (maybe)
-         *         - AskRequestorIdentityQuestions - If we have questions in the response then we will have to answer them in this flow or we will use Onfido... I think...?
-         *             This view cycles through questions/answers and then calls ReimbursementAccountPage.nextStep() again at the end.
-         *         - Onfido SDK - this actually has no View in Web-Secure we just handle the callbacks from the SDK and once it's complete we
-         *             call ReimbursementAccountPage.completeOnfido() - which basically just calls ReimbursementAccountPage.setupWithdrawalAccount() again with the onfidoData.
-         *
-         * ACHContractStep (React.v.BeneficialOwnersStep)
-         *     - This is another easy one. Form values -> call nextStep() / validate -> ReimbursementAccountPage.nextStep()
-         *
-         * ValidationStep (React.v.ValidationStep)
-         *     - This view is easy to understand as it basically looks at different things like achData.bankAccountInReview, achData.state === PENDING or state === OPEN.
-         *     - Only if the account is PENDING do we asked the user to enter the 3 values then we call ReimbursementAccountPage.validateBankAccount() which is a separate
-         *         API from setupWithdrawalAccount() but once we successfully validate we will call setupWithdrawalAccount() again viw nextStep()
-         *     - The other two views will show messages and can't really be actioned on.
-         *
-         * EnableStep - We're killing this step so we don't need to worry about it. However, I think we might still need to handle it somehow in E.cash - but not entirely sure
-         *     we'll find out more when testing.
-         *
-         * As for the whole this.achData thing if feels like the pattern of:
-         *  - modififying the achData locally in the .then() of setupWithdrawalAccount() then "refreshing" the view
-         *
-         * Should be replaced with:
-         *  - action called setupWithdrawalAccount() that modifies an achData key in Onyx and a larger view to render the correct steps and subscribe to this key in a dumb way
-         */
         return (
             <ScreenWrapper>
                 <View />
@@ -201,8 +86,8 @@ class ReimbursementAccountPage extends React.Component {
 ReimbursementAccountPage.propTypes = propTypes;
 ReimbursementAccountPage.defaultProps = defaultProps;
 export default withOnyx({
-    freePlanBankAccount: {
-        key: ONYXKEYS.FREE_PLAN_BANK_ACCOUNT,
+    reimbursementAccount: {
+        key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
     },
     personalDetails: {
         key: ONYXKEYS.MY_PERSONAL_DETAILS,

From d61a5255e6d3edab112b26f1af73b5503216cf47 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 09:08:15 -1000
Subject: [PATCH 32/81] fix conflicts and rename some things

---
 src/ROUTES.js                                          |  2 +-
 src/libs/Navigation/AppNavigator/AuthScreens.js        |  7 -------
 .../Navigation/AppNavigator/ModalStackNavigators.js    |  8 --------
 src/libs/Navigation/linkingConfig.js                   |  7 +------
 src/pages/BusinessBankAccount/NewPage.js               | 10 +++++-----
 5 files changed, 7 insertions(+), 27 deletions(-)

diff --git a/src/ROUTES.js b/src/ROUTES.js
index 8fbb80f1853c..9669366fec88 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -9,7 +9,7 @@ const REPORT = 'r';
 
 export default {
     ADD_PERSONAL_BANK_ACCOUNT: 'add-personal-bank-account',
-    BANK_ACCOUNT_NEW: 'bank-account/new',
+    ADD_VERIFIED_BANK_ACCOUNT: 'add-verified-bank-account',
     HOME: '',
     SETTINGS: 'settings',
     SETTINGS_PROFILE: 'settings/profile',
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 7ea86bbe9906..7f17391134e8 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -49,7 +49,6 @@ import {
     NewChatModalStackNavigator,
     SettingsModalStackNavigator,
     EnablePaymentsStackNavigator,
-    BusinessBankAccountModalStackNavigator,
     AddPersonalBankAccountModalStackNavigator,
     ReimbursementAccountModalStackNavigator,
 } from './ModalStackNavigators';
@@ -283,12 +282,6 @@ class AuthScreens extends React.Component {
                     component={ReimbursementAccountModalStackNavigator}
                     listeners={modalScreenListeners}
                 />
-                <RootStack.Screen
-                    name="BusinessBankAccount"
-                    options={modalScreenOptions}
-                    component={BusinessBankAccountModalStackNavigator}
-                    listeners={modalScreenListeners}
-                />
             </RootStack.Navigator>
         );
     }
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 9e67d577745e..61ae2a78c7f4 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -159,13 +159,6 @@ const ReimbursementAccountModalStackNavigator = createModalStackNavigator([{
     name: 'ReimbursementAccount_Root',
 }]);
 
-const BusinessBankAccountModalStackNavigator = createModalStackNavigator([
-    {
-        Component: BusinessBankAccountNewPage,
-        name: 'BusinessBankAccount_New',
-    },
-]);
-
 export {
     IOUBillStackNavigator,
     IOURequestModalStackNavigator,
@@ -177,7 +170,6 @@ export {
     NewChatModalStackNavigator,
     SettingsModalStackNavigator,
     EnablePaymentsStackNavigator,
-    BusinessBankAccountModalStackNavigator,
     AddPersonalBankAccountModalStackNavigator,
     ReimbursementAccountModalStackNavigator,
 };
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index ebd73267b554..ae4518b4edc1 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -109,11 +109,6 @@ export default {
                     AddPersonalBankAccount_Root: ROUTES.ADD_PERSONAL_BANK_ACCOUNT,
                 },
             },
-            BusinessBankAccount: {
-                screens: {
-                    BusinessBankAccount_New: ROUTES.BANK_ACCOUNT_NEW,
-                },
-            },
             EnablePayments: {
                 screens: {
                     EnablePayments_Root: ROUTES.ENABLE_PAYMENTS,
@@ -121,7 +116,7 @@ export default {
             },
             ReimbursementAccount: {
                 screens: {
-                    ReimbursementAccount_Root: 'reimbursement-account',
+                    ReimbursementAccount_Root: ROUTES.ADD_VERIFIED_BANK_ACCOUNT,
                 },
             },
         },
diff --git a/src/pages/BusinessBankAccount/NewPage.js b/src/pages/BusinessBankAccount/NewPage.js
index 7e0bf0fd27c0..94303d938f54 100644
--- a/src/pages/BusinessBankAccount/NewPage.js
+++ b/src/pages/BusinessBankAccount/NewPage.js
@@ -39,7 +39,7 @@ class BusinessBankAccountNewPage extends React.Component {
         this.addManualAccount = this.addManualAccount.bind(this);
 
         this.state = {
-            // One of CONST.BANK_ACCOUNT.ADD_METHOD
+            // One of CONST.BANK_ACCOUNT.SETUP_TYPE
             bankAccountAddMethod: undefined,
             hasAcceptedTerms: false,
             routingNumber: '',
@@ -94,7 +94,7 @@ class BusinessBankAccountNewPage extends React.Component {
                                     icon={Bank}
                                     title={this.props.translate('bankAccount.logIntoYourBank')}
                                     onPress={() => {
-                                        this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.ADD_METHOD.PLAID});
+                                        this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID});
                                     }}
                                     shouldShowRightIcon
                                 />
@@ -102,7 +102,7 @@ class BusinessBankAccountNewPage extends React.Component {
                                     icon={Paycheck}
                                     title={this.props.translate('bankAccount.connectManually')}
                                     onPress={() => {
-                                        this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.ADD_METHOD.MANUAL});
+                                        this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL});
                                     }}
                                     shouldShowRightIcon
                                 />
@@ -125,7 +125,7 @@ class BusinessBankAccountNewPage extends React.Component {
                             </View>
                         </>
                     )}
-                    {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.ADD_METHOD.PLAID && (
+                    {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID && (
                         <AddPlaidBankAccount
                             text={this.props.translate('bankAccount.plaidBodyCopy')}
                             onSubmit={(args) => {
@@ -136,7 +136,7 @@ class BusinessBankAccountNewPage extends React.Component {
                             }}
                         />
                     )}
-                    {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.ADD_METHOD.MANUAL && (
+                    {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && (
                         <>
                             <View style={[styles.m5, styles.flex1]}>
                                 <Text style={[styles.mb5]}>

From e966390d81d4592fbe5568796e9f84b2d54eca3d Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 09:35:39 -1000
Subject: [PATCH 33/81] reorganize things a bit and fix incorrect API usage

---
 .../AppNavigator/ModalStackNavigators.js      |   3 +-
 src/libs/actions/BankAccounts.js              |  10 +-
 src/pages/BusinessBankAccount/NewPage.js      | 202 ------------------
 .../ReimbursementAccount/ACHContractStep.js   |   5 +
 .../ReimbursementAccount/BankAccountStep.js   | 178 +++++++++++++++
 src/pages/ReimbursementAccount/CompanyStep.js |   5 +
 .../ReimbursementAccountPage.js               |  46 +++-
 .../ReimbursementAccount/RequestorStep.js     |   5 +
 .../ReimbursementAccount/ValidationStep.js    |   5 +
 9 files changed, 245 insertions(+), 214 deletions(-)
 delete mode 100644 src/pages/BusinessBankAccount/NewPage.js
 create mode 100644 src/pages/ReimbursementAccount/ACHContractStep.js
 create mode 100644 src/pages/ReimbursementAccount/BankAccountStep.js
 create mode 100644 src/pages/ReimbursementAccount/CompanyStep.js
 rename src/pages/{ => ReimbursementAccount}/ReimbursementAccountPage.js (67%)
 create mode 100644 src/pages/ReimbursementAccount/RequestorStep.js
 create mode 100644 src/pages/ReimbursementAccount/ValidationStep.js

diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 61ae2a78c7f4..effa1e2decda 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -20,9 +20,8 @@ import SettingsAddSecondaryLoginPage from '../../../pages/settings/AddSecondaryL
 import IOUCurrencySelection from '../../../pages/iou/IOUCurrencySelection';
 import ReportParticipantsPage from '../../../pages/ReportParticipantsPage';
 import EnablePaymentsPage from '../../../pages/EnablePayments';
-import BusinessBankAccountNewPage from '../../../pages/BusinessBankAccount/NewPage';
 import AddPersonalBankAccountPage from '../../../pages/AddPersonalBankAccountPage';
-import ReimbursementAccountPage from '../../../pages/ReimbursementAccountPage';
+import ReimbursementAccountPage from '../../../pages/ReimbursementAccount/ReimbursementAccountPage';
 
 const defaultSubRouteOptions = {
     cardStyle: styles.navigationScreenCardStyle,
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index f80ab4677706..8674b34f6207 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -275,8 +275,8 @@ function fetchUserWallet() {
 let previousACHData = {};
 Onyx.connect({
     key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
-    callback: (val = {}) => {
-        previousACHData = val.achData || {};
+    callback: (val) => {
+        previousACHData = lodashGet(val, 'achData', {});
     },
 });
 
@@ -555,9 +555,9 @@ function setupWithdrawalAccount(data) {
                 if (currentStep === 'ACHContractStep') {
                     // Get an up-to-date bank account list so that we can allow the user to validate their newly
                     // generated bank account
-                    return API.get({returnValueList: 'bankAccountList'})
-                        .done((json) => {
-                            const bankAccountJSON = _.findWhere(json.bankAccountList, {
+                    return API.Get({returnValueList: 'bankAccountList'})
+                        .then((bankAccountListResponse) => {
+                            const bankAccountJSON = _.findWhere(bankAccountListResponse.bankAccountList, {
                                 bankAccountID: previousACHData.bankAccountID,
                             });
                             const bankAccount = new BankAccount(bankAccountJSON);
diff --git a/src/pages/BusinessBankAccount/NewPage.js b/src/pages/BusinessBankAccount/NewPage.js
deleted file mode 100644
index 94303d938f54..000000000000
--- a/src/pages/BusinessBankAccount/NewPage.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import _ from 'underscore';
-import React from 'react';
-import PropTypes from 'prop-types';
-import {View, Image} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import ScreenWrapper from '../../components/ScreenWrapper';
-import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
-import MenuItem from '../../components/MenuItem';
-import {Paycheck, Bank, Lock} from '../../components/Icon/Expensicons';
-import styles from '../../styles/styles';
-import TextLink from '../../components/TextLink';
-import Button from '../../components/Button';
-import Icon from '../../components/Icon';
-import colors from '../../styles/colors';
-import Navigation from '../../libs/Navigation/Navigation';
-import Permissions from '../../libs/Permissions';
-import CONST from '../../CONST';
-import TextInputWithLabel from '../../components/TextInputWithLabel';
-import AddPlaidBankAccount from '../../components/AddPlaidBankAccount';
-import ONYXKEYS from '../../ONYXKEYS';
-import CheckboxWithLabel from '../../components/CheckboxWithLabel';
-import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
-import compose from '../../libs/compose';
-import exampleCheckImage from '../../../assets/images/example-check-image.png';
-import Text from '../../components/Text';
-
-const propTypes = {
-    /** List of betas */
-    betas: PropTypes.arrayOf(PropTypes.string).isRequired,
-
-    ...withLocalizePropTypes,
-};
-
-class BusinessBankAccountNewPage extends React.Component {
-    constructor(props) {
-        super(props);
-
-        this.toggleTerms = this.toggleTerms.bind(this);
-        this.addManualAccount = this.addManualAccount.bind(this);
-
-        this.state = {
-            // One of CONST.BANK_ACCOUNT.SETUP_TYPE
-            bankAccountAddMethod: undefined,
-            hasAcceptedTerms: false,
-            routingNumber: '',
-            accountNumber: '',
-        };
-    }
-
-    toggleTerms() {
-        this.setState(prevState => ({
-            hasAcceptedTerms: !prevState.hasAcceptedTerms,
-        }));
-    }
-
-    /**
-     * @returns {Boolean}
-     */
-    canSubmitManually() {
-        return this.state.hasAcceptedTerms
-
-            // These are taken from BankCountry.js in Web-Secure
-            && CONST.BANK_ACCOUNT.REGEX.IBAN.test(this.state.accountNumber.trim())
-            && CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(this.state.routingNumber.trim());
-    }
-
-    addManualAccount() {
-        // @TODO call API to add account manually
-    }
-
-    render() {
-        if (!Permissions.canUseFreePlan(this.props.betas)) {
-            console.debug('Not showing new bank account page because user is not on free plan beta');
-            Navigation.dismissModal();
-            return null;
-        }
-
-        return (
-            <ScreenWrapper>
-                <View style={[styles.flex1, styles.justifyContentBetween]}>
-                    <HeaderWithCloseButton
-                        title={this.props.translate('bankAccount.addBankAccount')}
-                        onCloseButtonPress={Navigation.dismissModal}
-                        onBackButtonPress={() => this.setState({bankAccountAddMethod: undefined})}
-                        shouldShowBackButton={!_.isUndefined(this.state.bankAccountAddMethod)}
-                    />
-                    {!this.state.bankAccountAddMethod && (
-                        <>
-                            <View style={[styles.flex1]}>
-                                <Text style={[styles.mh5, styles.mb5]}>
-                                    {this.props.translate('bankAccount.toGetStarted')}
-                                </Text>
-                                <MenuItem
-                                    icon={Bank}
-                                    title={this.props.translate('bankAccount.logIntoYourBank')}
-                                    onPress={() => {
-                                        this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID});
-                                    }}
-                                    shouldShowRightIcon
-                                />
-                                <MenuItem
-                                    icon={Paycheck}
-                                    title={this.props.translate('bankAccount.connectManually')}
-                                    onPress={() => {
-                                        this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL});
-                                    }}
-                                    shouldShowRightIcon
-                                />
-                                <View style={[styles.m5, styles.flexRow, styles.justifyContentBetween]}>
-                                    <TextLink href="https://use.expensify.com/privacy">
-                                        {this.props.translate('common.privacy')}
-                                    </TextLink>
-                                    <View style={[styles.flexRow, styles.alignItemsCenter]}>
-                                        <TextLink
-                                            // eslint-disable-next-line max-len
-                                            href="https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/"
-                                        >
-                                            {this.props.translate('bankAccount.yourDataIsSecure')}
-                                        </TextLink>
-                                        <View style={[styles.ml1]}>
-                                            <Icon src={Lock} fill={colors.blue} />
-                                        </View>
-                                    </View>
-                                </View>
-                            </View>
-                        </>
-                    )}
-                    {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID && (
-                        <AddPlaidBankAccount
-                            text={this.props.translate('bankAccount.plaidBodyCopy')}
-                            onSubmit={(args) => {
-                                console.debug(args);
-                            }}
-                            onExitPlaid={() => {
-                                this.setState({bankAccountAddMethod: undefined});
-                            }}
-                        />
-                    )}
-                    {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && (
-                        <>
-                            <View style={[styles.m5, styles.flex1]}>
-                                <Text style={[styles.mb5]}>
-                                    {this.props.translate('bankAccount.checkHelpLine')}
-                                </Text>
-                                <Image
-                                    resizeMode="contain"
-                                    style={[styles.exampleCheckImage, styles.mb5]}
-                                    source={exampleCheckImage}
-                                />
-                                <TextInputWithLabel
-                                    placeholder={this.props.translate('bankAccount.routingNumber')}
-                                    keyboardType="number-pad"
-                                    value={this.state.routingNumber}
-                                    onChangeText={routingNumber => this.setState({routingNumber})}
-                                />
-                                <TextInputWithLabel
-                                    placeholder={this.props.translate('bankAccount.accountNumber')}
-                                    keyboardType="number-pad"
-                                    value={this.state.accountNumber}
-                                    onChangeText={accountNumber => this.setState({accountNumber})}
-                                />
-                                <CheckboxWithLabel
-                                    style={[styles.mb4, styles.mt5]}
-                                    isChecked={this.state.hasAcceptedTerms}
-                                    onPress={this.toggleTerms}
-                                    LabelComponent={() => (
-                                        <View style={[styles.flexRow, styles.alignItemsCenter]}>
-                                            <Text>
-                                                {this.props.translate('bankAccount.iAcceptThe')}
-                                            </Text>
-                                            <TextLink href="https://use.expensify.com/terms">
-                                                {`Expensify ${this.props.translate('common.termsOfService')}`}
-                                            </TextLink>
-                                        </View>
-                                    )}
-                                />
-                            </View>
-                            <Button
-                                success
-                                text={this.props.translate('common.saveAndContinue')}
-                                style={[styles.m5]}
-                                isDisabled={!this.canSubmitManually()}
-                                onPress={this.addManualAccount}
-                            />
-                        </>
-                    )}
-                </View>
-            </ScreenWrapper>
-        );
-    }
-}
-
-BusinessBankAccountNewPage.propTypes = propTypes;
-
-export default compose(
-    withOnyx({
-        betas: {
-            key: ONYXKEYS.BETAS,
-        },
-    }),
-    withLocalize,
-)(BusinessBankAccountNewPage);
diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js
new file mode 100644
index 000000000000..2434bd9e9f3e
--- /dev/null
+++ b/src/pages/ReimbursementAccount/ACHContractStep.js
@@ -0,0 +1,5 @@
+import React from 'react';
+import {View} from 'react-native';
+
+const ACHContractStep = () => <View />;
+export default ACHContractStep;
diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
new file mode 100644
index 000000000000..905257d1bd35
--- /dev/null
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -0,0 +1,178 @@
+import _ from 'underscore';
+import React from 'react';
+import {View, Image} from 'react-native';
+import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import MenuItem from '../../components/MenuItem';
+import {Paycheck, Bank, Lock} from '../../components/Icon/Expensicons';
+import styles from '../../styles/styles';
+import TextLink from '../../components/TextLink';
+import Button from '../../components/Button';
+import Icon from '../../components/Icon';
+import colors from '../../styles/colors';
+import Navigation from '../../libs/Navigation/Navigation';
+import CONST from '../../CONST';
+import TextInputWithLabel from '../../components/TextInputWithLabel';
+import AddPlaidBankAccount from '../../components/AddPlaidBankAccount';
+import CheckboxWithLabel from '../../components/CheckboxWithLabel';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import exampleCheckImage from '../../../assets/images/example-check-image.png';
+import Text from '../../components/Text';
+
+const propTypes = {
+    ...withLocalizePropTypes,
+};
+
+class BusinessBankAccountNewPage extends React.Component {
+    constructor(props) {
+        super(props);
+
+        this.toggleTerms = this.toggleTerms.bind(this);
+        this.addManualAccount = this.addManualAccount.bind(this);
+
+        this.state = {
+            // One of CONST.BANK_ACCOUNT.SETUP_TYPE
+            bankAccountAddMethod: undefined,
+            hasAcceptedTerms: false,
+            routingNumber: '',
+            accountNumber: '',
+        };
+    }
+
+    toggleTerms() {
+        this.setState(prevState => ({
+            hasAcceptedTerms: !prevState.hasAcceptedTerms,
+        }));
+    }
+
+    /**
+     * @returns {Boolean}
+     */
+    canSubmitManually() {
+        return this.state.hasAcceptedTerms
+
+            // These are taken from BankCountry.js in Web-Secure
+            && CONST.BANK_ACCOUNT.REGEX.IBAN.test(this.state.accountNumber.trim())
+            && CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(this.state.routingNumber.trim());
+    }
+
+    addManualAccount() {
+        // @TODO call API to add account manually
+    }
+
+    render() {
+        return (
+            <View style={[styles.flex1, styles.justifyContentBetween]}>
+                <HeaderWithCloseButton
+                    title={this.props.translate('bankAccount.addBankAccount')}
+                    onCloseButtonPress={Navigation.dismissModal}
+                    onBackButtonPress={() => this.setState({bankAccountAddMethod: undefined})}
+                    shouldShowBackButton={!_.isUndefined(this.state.bankAccountAddMethod)}
+                />
+                {!this.state.bankAccountAddMethod && (
+                    <>
+                        <View style={[styles.flex1]}>
+                            <Text style={[styles.mh5, styles.mb5]}>
+                                {this.props.translate('bankAccount.toGetStarted')}
+                            </Text>
+                            <MenuItem
+                                icon={Bank}
+                                title={this.props.translate('bankAccount.logIntoYourBank')}
+                                onPress={() => {
+                                    this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID});
+                                }}
+                                shouldShowRightIcon
+                            />
+                            <MenuItem
+                                icon={Paycheck}
+                                title={this.props.translate('bankAccount.connectManually')}
+                                onPress={() => {
+                                    this.setState({bankAccountAddMethod: CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL});
+                                }}
+                                shouldShowRightIcon
+                            />
+                            <View style={[styles.m5, styles.flexRow, styles.justifyContentBetween]}>
+                                <TextLink href="https://use.expensify.com/privacy">
+                                    {this.props.translate('common.privacy')}
+                                </TextLink>
+                                <View style={[styles.flexRow, styles.alignItemsCenter]}>
+                                    <TextLink
+                                        // eslint-disable-next-line max-len
+                                        href="https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/"
+                                    >
+                                        {this.props.translate('bankAccount.yourDataIsSecure')}
+                                    </TextLink>
+                                    <View style={[styles.ml1]}>
+                                        <Icon src={Lock} fill={colors.blue} />
+                                    </View>
+                                </View>
+                            </View>
+                        </View>
+                    </>
+                )}
+                {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID && (
+                    <AddPlaidBankAccount
+                        text={this.props.translate('bankAccount.plaidBodyCopy')}
+                        onSubmit={(args) => {
+                            console.debug(args);
+                        }}
+                        onExitPlaid={() => {
+                            this.setState({bankAccountAddMethod: undefined});
+                        }}
+                    />
+                )}
+                {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && (
+                    <>
+                        <View style={[styles.m5, styles.flex1]}>
+                            <Text style={[styles.mb5]}>
+                                {this.props.translate('bankAccount.checkHelpLine')}
+                            </Text>
+                            <Image
+                                resizeMode="contain"
+                                style={[styles.exampleCheckImage, styles.mb5]}
+                                source={exampleCheckImage}
+                            />
+                            <TextInputWithLabel
+                                placeholder={this.props.translate('bankAccount.routingNumber')}
+                                keyboardType="number-pad"
+                                value={this.state.routingNumber}
+                                onChangeText={routingNumber => this.setState({routingNumber})}
+                            />
+                            <TextInputWithLabel
+                                placeholder={this.props.translate('bankAccount.accountNumber')}
+                                keyboardType="number-pad"
+                                value={this.state.accountNumber}
+                                onChangeText={accountNumber => this.setState({accountNumber})}
+                            />
+                            <CheckboxWithLabel
+                                style={[styles.mb4, styles.mt5]}
+                                isChecked={this.state.hasAcceptedTerms}
+                                onPress={this.toggleTerms}
+                                LabelComponent={() => (
+                                    <View style={[styles.flexRow, styles.alignItemsCenter]}>
+                                        <Text>
+                                            {this.props.translate('bankAccount.iAcceptThe')}
+                                        </Text>
+                                        <TextLink href="https://use.expensify.com/terms">
+                                            {`Expensify ${this.props.translate('common.termsOfService')}`}
+                                        </TextLink>
+                                    </View>
+                                )}
+                            />
+                        </View>
+                        <Button
+                            success
+                            text={this.props.translate('common.saveAndContinue')}
+                            style={[styles.m5]}
+                            isDisabled={!this.canSubmitManually()}
+                            onPress={this.addManualAccount}
+                        />
+                    </>
+                )}
+            </View>
+        );
+    }
+}
+
+BusinessBankAccountNewPage.propTypes = propTypes;
+
+export default withLocalize(BusinessBankAccountNewPage);
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
new file mode 100644
index 000000000000..03a95de05476
--- /dev/null
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -0,0 +1,5 @@
+import React from 'react';
+import {View} from 'react-native';
+
+const CompanyStep = () => <View />;
+export default CompanyStep;
diff --git a/src/pages/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
similarity index 67%
rename from src/pages/ReimbursementAccountPage.js
rename to src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index a69c70f7d342..8a979e6fa8f9 100644
--- a/src/pages/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -8,17 +8,29 @@ import {withOnyx} from 'react-native-onyx';
 import Str from 'expensify-common/lib/str';
 import {View} from 'react-native';
 import PropTypes from 'prop-types';
-import ScreenWrapper from '../components/ScreenWrapper';
-import {fetchFreePlanVerifiedBankAccount} from '../libs/actions/BankAccounts';
-import ONYXKEYS from '../ONYXKEYS';
-import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator';
+import ScreenWrapper from '../../components/ScreenWrapper';
+import {fetchFreePlanVerifiedBankAccount} from '../../libs/actions/BankAccounts';
+import ONYXKEYS from '../../ONYXKEYS';
+import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator';
+
+// Steps
+import BankAccountStep from './BankAccountStep';
+import CompanyStep from './CompanyStep';
+import RequestorStep from './RequestorStep';
+import ACHContractStep from './ACHContractStep';
+import ValidationStep from './ValidationStep';
+import Navigation from '../../libs/Navigation/Navigation';
 
 const propTypes = {
+    /** List of betas */
+    betas: PropTypes.arrayOf(PropTypes.string).isRequired,
+
     reimbursementAccount: PropTypes.shape({
         loading: PropTypes.bool,
         throttledDate: PropTypes.string,
         achData: PropTypes.shape({
             country: PropTypes.string,
+            currentStep: PropTypes.string,
         }),
     }),
 
@@ -45,6 +57,12 @@ class ReimbursementAccountPage extends React.Component {
     }
 
     render() {
+        if (!Permissions.canUseFreePlan(this.props.betas)) {
+            console.debug('Not showing new bank account page because user is not on free plan beta');
+            Navigation.dismissModal();
+            return null;
+        }
+
         if (this.props.reimbursementAccount.loading) {
             return <FullScreenLoadingIndicator visible />;
         }
@@ -75,9 +93,24 @@ class ReimbursementAccountPage extends React.Component {
 
         // Everything we need to display UI-wise is pretty much in the achData at this point. We just need to call the correct actions in the right places when
         // submitting a form or navigating back or forward.
+        const currentStep = achData.currentStep;
         return (
             <ScreenWrapper>
-                <View />
+                {currentStep === 'BankAccountStep' && (
+                    <BankAccountStep />
+                )}
+                {currentStep === 'CompanyStep' && (
+                    <CompanyStep />
+                )}
+                {currentStep === 'RequestorStep' && (
+                    <RequestorStep />
+                )}
+                {currentStep === 'ACHContractStep' && (
+                    <ACHContractStep />
+                )}
+                {currentStep === 'ValidationStep' && (
+                    <ValidationStep />
+                )}
             </ScreenWrapper>
         );
     }
@@ -95,6 +128,9 @@ export default withOnyx({
     session: {
         key: ONYXKEYS.SESSION,
     },
+    betas: {
+        key: ONYXKEYS.BETAS,
+    },
 
     // @TODO we maybe need the user policyID + currency + default country + whether plaid is disabled ??
 })(ReimbursementAccountPage);
diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js
new file mode 100644
index 000000000000..350e3b58471c
--- /dev/null
+++ b/src/pages/ReimbursementAccount/RequestorStep.js
@@ -0,0 +1,5 @@
+import React from 'react';
+import {View} from 'react-native';
+
+const RequestorStep = () => <View />;
+export default RequestorStep;
diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js
new file mode 100644
index 000000000000..43628ea79677
--- /dev/null
+++ b/src/pages/ReimbursementAccount/ValidationStep.js
@@ -0,0 +1,5 @@
+import React from 'react';
+import {View} from 'react-native';
+
+const ValidationStep = () => <View />;
+export default ValidationStep;

From a7eaefe9a3c1ff2845829c0d34be354db98e4920 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 10:05:39 -1000
Subject: [PATCH 34/81] dont pass achData

---
 src/CONST.js                                  |  3 ++
 .../ReimbursementAccount/BankAccountStep.js   | 32 ++++++++++++++++---
 2 files changed, 30 insertions(+), 5 deletions(-)

diff --git a/src/CONST.js b/src/CONST.js
index a6a04f5e34fa..21eb2b474d5d 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -25,6 +25,9 @@ const CONST = {
             REQUESTOR_IDENTITY_ONFIDO: 'verifications.externalApiResponses.requestorIdentityOnfido',
             THROTTLED: 'verifications.throttled',
         },
+        FIELDS_TYPE: {
+            LOCAL: 'local',
+        },
         ONFIDO_RESPONSE: {
             SDK_TOKEN: 'apiResult.sdkToken',
             PASS: 'pass',
diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
index 905257d1bd35..98a5d40baf26 100644
--- a/src/pages/ReimbursementAccount/BankAccountStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -17,6 +17,7 @@ import CheckboxWithLabel from '../../components/CheckboxWithLabel';
 import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
 import exampleCheckImage from '../../../assets/images/example-check-image.png';
 import Text from '../../components/Text';
+import {setupWithdrawalAccount} from '../../libs/actions/BankAccounts';
 
 const propTypes = {
     ...withLocalizePropTypes,
@@ -28,11 +29,12 @@ class BusinessBankAccountNewPage extends React.Component {
 
         this.toggleTerms = this.toggleTerms.bind(this);
         this.addManualAccount = this.addManualAccount.bind(this);
+        this.addPlaidAccount = this.addPlaidAccount.bind(this);
 
         this.state = {
             // One of CONST.BANK_ACCOUNT.SETUP_TYPE
             bankAccountAddMethod: undefined,
-            hasAcceptedTerms: false,
+            hasAcceptedTerms: true,
             routingNumber: '',
             accountNumber: '',
         };
@@ -56,7 +58,29 @@ class BusinessBankAccountNewPage extends React.Component {
     }
 
     addManualAccount() {
-        // @TODO call API to add account manually
+        if (!this.state.hasAcceptedTerms) {
+            // @TODO don't let them submit
+            return;
+        }
+
+        setupWithdrawalAccount({
+            acceptTerms: this.state.hasAcceptedTerms,
+            accountNumber: this.state.accountNumber,
+            routingNumber: this.state.routingNumber,
+            setupType: CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL,
+
+            // Note: These are hardcoded as we're not supporting AU bank accounts for the free plan
+            country: CONST.COUNTRY.US,
+            currency: CONST.CURRENCY.USD,
+            fieldsType: CONST.BANK_ACCOUNT.FIELDS_TYPE.LOCAL,
+        });
+    }
+
+    /**
+     * @param {Object} params
+     */
+    addPlaidAccount(params) {
+        console.debug(params);
     }
 
     render() {
@@ -112,9 +136,7 @@ class BusinessBankAccountNewPage extends React.Component {
                 {this.state.bankAccountAddMethod === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID && (
                     <AddPlaidBankAccount
                         text={this.props.translate('bankAccount.plaidBodyCopy')}
-                        onSubmit={(args) => {
-                            console.debug(args);
-                        }}
+                        onSubmit={this.addPlaidAccount}
                         onExitPlaid={() => {
                             this.setState({bankAccountAddMethod: undefined});
                         }}

From 4bebfbb1ab2b6db9b62516a74328e4d8dcba015e Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 10:22:11 -1000
Subject: [PATCH 35/81] fix Permissions issue. rename goToStepID to
 goToWithdrawalStepID

---
 src/libs/actions/BankAccounts.js              | 24 ++++++++++---------
 src/pages/ReimbursementAccount/CompanyStep.js | 15 +++++++++++-
 .../ReimbursementAccountPage.js               |  1 +
 3 files changed, 28 insertions(+), 12 deletions(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 8674b34f6207..6a18710b0604 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -280,7 +280,7 @@ Onyx.connect({
     },
 });
 
-function goToStepID(stepID, achData) {
+function goToWithdrawalStepID(stepID, achData) {
     const newACHData = {...previousACHData};
 
     // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
@@ -412,7 +412,7 @@ function fetchFreePlanVerifiedBankAccount() {
             }
 
             Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {throttledDate});
-            goToStepID(currentStep, achData);
+            goToWithdrawalStepID(currentStep, achData);
         })
         .finally(() => {
             Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
@@ -461,13 +461,12 @@ function getIndexByStepID(stepID) {
  */
 function getNextStepID() {
     const nextStepIndex = Math.min(
-        getIndexByStepID(this.state.achData.currentStep) + 1,
+        getIndexByStepID(previousACHData.currentStep) + 1,
         WITHDRAWAL_ACCOUNT_STEPS.length - 1,
     );
     return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], 'BankAccountStep');
 }
 
-
 /**
  * @private
  * @param {Number} bankAccountID
@@ -495,18 +494,20 @@ function setupWithdrawalAccount(data) {
     let nextStep = previousACHData.currentStep;
 
     API.BankAccount_SetupWithdrawal(previousACHData)
-        .finally((response) => {
+        .then((response) => {
             Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
 
             const currentStep = previousACHData.currentStep;
-            let achData = response.achData;
+            let achData = lodashGet(response, 'achData', {});
             let error = lodashGet(achData, 'verifications.errorMessage');
 
             if (response.jsonCode === 200 && !error) {
                 // Save an NVP with the bankAccountID for this account. This is temporary since we are not showing lists
                 // of accounts yet and must have some kind of record of which account is the one the user is trying to
                 // set up for the free plan.
-                setFreePlanVerifiedBankAccountID(achData.bankAccountID);
+                if (achData.bankAccountID) {
+                    setFreePlanVerifiedBankAccountID(achData.bankAccountID);
+                }
 
                 // Show warning if another account already set up this bank account and promote share
                 if (response.existingOwners) {
@@ -529,7 +530,7 @@ function setupWithdrawalAccount(data) {
                         if (sdkToken && !previousACHData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
                             // Requestor Step still needs to run Onfido
                             achData.sdkToken = sdkToken;
-                            goToStepID('RequestorStep', achData);
+                            goToWithdrawalStepID('RequestorStep', achData);
                             return;
                         }
                     } else if (requestorResponse) {
@@ -546,7 +547,7 @@ function setupWithdrawalAccount(data) {
                         }
                         if (!_.isEmpty(questions)) {
                             achData.questions = questions;
-                            goToStepID('RequestorStep', achData);
+                            goToWithdrawalStepID('RequestorStep', achData);
                             return;
                         }
                     }
@@ -567,7 +568,7 @@ function setupWithdrawalAccount(data) {
                             achData.bankAccountInReview = needsToPassLatestChecks
                                 || achData.state === BankAccount.STATE.VERIFYING;
 
-                            goToStepID('ValidationStep', achData);
+                            goToWithdrawalStepID('ValidationStep', achData);
                         });
                 }
 
@@ -588,7 +589,7 @@ function setupWithdrawalAccount(data) {
             }
 
             // Go to next step
-            goToStepID(nextStep, achData);
+            goToWithdrawalStepID(nextStep, achData);
 
             if (error) {
                 // @TODO - Show the error for real
@@ -607,4 +608,5 @@ export {
     fetchUserWallet,
     fetchFreePlanVerifiedBankAccount,
     setupWithdrawalAccount,
+    goToWithdrawalStepID,
 };
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
index 03a95de05476..9ed1a3e90e6a 100644
--- a/src/pages/ReimbursementAccount/CompanyStep.js
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -1,5 +1,18 @@
 import React from 'react';
 import {View} from 'react-native';
+import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import {goToWithdrawalStepID} from '../../libs/actions/BankAccounts';
+import Navigation from '../../libs/Navigation/Navigation';
+
+const CompanyStep = () => (
+    <View>
+        <HeaderWithCloseButton
+            title="Company Information"
+            shouldShowBackButton
+            onBackButtonPress={() => goToWithdrawalStepID('BankAccountStep')}
+            onCloseButtonPress={Navigation.dismissModal}
+        />
+    </View>
+);
 
-const CompanyStep = () => <View />;
 export default CompanyStep;
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index 8a979e6fa8f9..a3cfaccb8481 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -12,6 +12,7 @@ import ScreenWrapper from '../../components/ScreenWrapper';
 import {fetchFreePlanVerifiedBankAccount} from '../../libs/actions/BankAccounts';
 import ONYXKEYS from '../../ONYXKEYS';
 import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator';
+import Permissions from '../../libs/Permissions';
 
 // Steps
 import BankAccountStep from './BankAccountStep';

From 76b67ce4ad89f6de1b4d594e0d507c86579a9388 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 11:20:40 -1000
Subject: [PATCH 36/81] use unmasked accountNumber

---
 src/libs/actions/BankAccounts.js              |  8 ++++++
 .../ReimbursementAccount/BankAccountStep.js   | 28 ++++++++++++++++++-
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 6a18710b0604..1f901dd3e5ac 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -493,6 +493,14 @@ function setupWithdrawalAccount(data) {
 
     let nextStep = previousACHData.currentStep;
 
+    // If we are setting up a Plaid account replace the accountNumber with the unmasked number
+    if (data.plaidAccountID) {
+        const unmaskedAccount = _.find(plaidBankAccounts, bankAccount => (
+            bankAccount.plaidAccountID === data.plaidAccountID
+        ));
+        previousACHData.accountNumber = unmaskedAccount.accountNumber;
+    }
+
     API.BankAccount_SetupWithdrawal(previousACHData)
         .then((response) => {
             Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
index 98a5d40baf26..2f02da864b0e 100644
--- a/src/pages/ReimbursementAccount/BankAccountStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -78,9 +78,35 @@ class BusinessBankAccountNewPage extends React.Component {
 
     /**
      * @param {Object} params
+     * @param {String} params.password
+     * @param {Object} params.account
+     * @param {String} params.account.bankName
+     * @param {Boolean} params.account.isSavings
+     * @param {String} params.account.addressName
+     * @param {String} params.account.ownershipType
+     * @param {String} params.account.accountNumber
+     * @param {String} params.account.routingNumber
+     * @param {String} params.account.plaidAccountID
      */
     addPlaidAccount(params) {
-        console.debug(params);
+        setupWithdrawalAccount({
+            acceptTerms: true,
+            setupType: CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID,
+
+            // Note: These are hardcoded as we're not supporting AU bank accounts for the free plan
+            country: CONST.COUNTRY.US,
+            currency: CONST.CURRENCY.USD,
+            fieldsType: CONST.BANK_ACCOUNT.FIELDS_TYPE.LOCAL,
+            password: params.password,
+            plaidAccessToken: params.plaidLinkToken,
+            accountNumber: params.account.accountNumber,
+            routingNumber: params.account.routingNumber,
+            plaidAccountID: params.account.plaidAccountID,
+            ownershipType: params.account.ownershipType,
+            isSavings: params.account.isSavings,
+            bankName: params.account.bankName,
+            addressName: params.account.addressName,
+        });
     }
 
     render() {

From d8e8120788e616e603fece0c17b32f55d83dad2e Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Fri, 11 Jun 2021 14:40:51 -0700
Subject: [PATCH 37/81] get optimistic addition and fallback remove working

---
 src/libs/actions/Policy.js | 63 ++++++++++++++++----------------------
 1 file changed, 26 insertions(+), 37 deletions(-)

diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 51fc68ffcbe9..a09673788be9 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -8,7 +8,7 @@ Onyx.connect({
     key: ONYXKEYS.COLLECTION.POLICY,
     callback: (val, key) => {
         if (val && key) {
-            allPolicies[key] = val;
+            allPolicies[key] = {...allPolicies[key], ...val};
         }
     },
 });
@@ -85,45 +85,34 @@ function getPolicyList() {
  * @param {String} policyID
  */
 function invite(login, welcomeNote, policyID) {
-    // Optimistically add the user to the policy
     const key = `${ONYXKEYS.COLLECTION.POLICY}${policyID}`;
-    const dataToStore = {};
-    dataToStore[key] = {
-        employeeList: allPolicies[key].employeeList.concat([login]),
-    };
 
-    Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, dataToStore)
-        .then(() => {
-            const policyDataWithoutLogin = {};
-            policyDataWithoutLogin[key] = {
-                employeeList: _.without(allPolicies[key].employeeList, login),
-            };
-            Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataWithoutLogin)
-                .then(() => {
-                   console.debug('done');
-                });
-        });
+    // Make a shallow copy to preserve original data, and concat the login
+    const policy = _.clone(allPolicies[key]);
+    policy.employeeList = [...policy.employeeList, login];
+
+    // Optimistically add the user to the policy
+    Onyx.set(key, policy);
 
-    // Policy_Employees_Merge({
-    //     employees: JSON.stringify([{email: login}]),
-    //     welcomeNote,
-    //     policyID,
-    // })
-    //     .then((data) => {
-    //         // Save data.personalDetails
-    //         if (data.jsonCode !== 200) {
-    //             // TODO: need to match personalDetails to data in PersonalDetails.formatPersonalDetails
-    //             Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[login]: data.personalDetails[login]});
-    //             return;
-    //         }
-    //
-    //         // If the operation failed, undo the optimistic addition
-    //         const policyDataWithoutLogin = {};
-    //         policyDataWithoutLogin[key] = {
-    //             employeeList: _.without(allPolicies[key].employeeList, login),
-    //         };
-    //         Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataWithoutLogin);
-    //     });
+    // Make the API call to merge the login into the policy
+    Policy_Employees_Merge({
+        employees: JSON.stringify([{email: login}]),
+        welcomeNote,
+        policyID,
+    })
+        .then((data) => {
+            // Save the personalDetails for the invited user in Onyx
+            if (data.jsonCode === 200) {
+                // TODO: need to match personalDetails to data in PersonalDetails.formatPersonalDetails
+                Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[login]: data.personalDetails[login]});
+                return;
+            }
+
+            // If the operation failed, undo the optimistic addition
+            const policyDataWithoutLogin = _.clone(allPolicies[key]);
+            policyDataWithoutLogin.employeeList = _.without(allPolicies[key].employeeList, login);
+            Onyx.set(key, policyDataWithoutLogin);
+        });
 }
 
 export {

From 2901551aca603888c7da8b8aa8471d5d1c7b9c25 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Fri, 11 Jun 2021 14:48:31 -0700
Subject: [PATCH 38/81] insert formatted personalDetails

---
 src/libs/actions/PersonalDetails.js | 1 +
 src/libs/actions/Policy.js          | 7 +++----
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index fff00ea6a2d7..fac49bf4fd60 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -303,6 +303,7 @@ NetworkConnection.onReconnect(fetchPersonalDetails);
 
 export {
     fetchPersonalDetails,
+    formatPersonalDetails,
     getFromReportParticipants,
     getDisplayName,
     getDefaultAvatar,
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index a09673788be9..e44f634c8706 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -2,6 +2,7 @@ import _ from 'underscore';
 import Onyx from 'react-native-onyx';
 import {GetPolicySummaryList, GetPolicyList, Policy_Employees_Merge} from '../API';
 import ONYXKEYS from '../../ONYXKEYS';
+import {formatPersonalDetails} from './PersonalDetails';
 
 const allPolicies = {};
 Onyx.connect({
@@ -102,9 +103,8 @@ function invite(login, welcomeNote, policyID) {
     })
         .then((data) => {
             // Save the personalDetails for the invited user in Onyx
-            if (data.jsonCode === 200) {
-                // TODO: need to match personalDetails to data in PersonalDetails.formatPersonalDetails
-                Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[login]: data.personalDetails[login]});
+            if (data.jsonCode === 200 && data.personalDetails) {
+                Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, formatPersonalDetails(data.personalDetails));
                 return;
             }
 
@@ -116,7 +116,6 @@ function invite(login, welcomeNote, policyID) {
 }
 
 export {
-    // eslint-disable-next-line import/prefer-default-export
     getPolicySummaries,
     getPolicyList,
     invite,

From c3b8007fd3c72ba93dae5dc9e49c807add98122a Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 11:49:06 -1000
Subject: [PATCH 39/81] clean up some TODOs

---
 src/CONST.js                                  |  3 ++
 src/ONYXKEYS.js                               |  1 -
 src/libs/actions/BankAccounts.js              |  5 ---
 .../ReimbursementAccount/BankAccountStep.js   |  2 +-
 .../ReimbursementAccountPage.js               | 36 +++++++++----------
 5 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/src/CONST.js b/src/CONST.js
index 21eb2b474d5d..0d090cd7fe61 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -6,6 +6,9 @@ const CONST = {
         IOS: 'https://apps.apple.com/us/app/expensify-cash/id1530278510',
         DESKTOP: 'https://expensify.cash/Expensify.cash.dmg',
     },
+    SMS: {
+        DOMAIN: '@expensify.sms',
+    },
     BANK_ACCOUNT: {
         STEP: {
             BANK_ACCOUNT: 'BankAccountStep',
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index f585c8116a05..f32362de4b68 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -75,7 +75,6 @@ export default {
 
     // Collection Keys
     COLLECTION: {
-        BANK_ACCOUNT: 'bankAccount_',
         REPORT: 'report_',
         REPORT_ACTIONS: 'reportActions_',
         REPORT_DRAFT_COMMENT: 'reportDraftComment_',
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 1f901dd3e5ac..0b174bc9f63c 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -352,10 +352,6 @@ function fetchFreePlanVerifiedBankAccount() {
             const achData = bankAccount ? bankAccount.toACHData() : {};
             achData.useOnfido = true;
             achData.policyID = '';
-
-            // @TODO I think probably we won't set the token until we actually ask to start the the Plaid flow
-            // but we could also get it here with another API call...
-            achData.plaidLinkToken = '';
             achData.isInSetup = !bankAccount || bankAccount.isInSetup();
             achData.bankAccountInReview = bankAccount && bankAccount.isVerifying();
             achData.domainLimit = 0;
@@ -600,7 +596,6 @@ function setupWithdrawalAccount(data) {
             goToWithdrawalStepID(nextStep, achData);
 
             if (error) {
-                // @TODO - Show the error for real
                 console.error(`Error setting up withdrawal account: ${error}`);
             }
         });
diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
index 2f02da864b0e..238b542fccaf 100644
--- a/src/pages/ReimbursementAccount/BankAccountStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -59,7 +59,7 @@ class BusinessBankAccountNewPage extends React.Component {
 
     addManualAccount() {
         if (!this.state.hasAcceptedTerms) {
-            // @TODO don't let them submit
+            console.error('Must accept terms before proceeding');
             return;
         }
 
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index a3cfaccb8481..c7b783fe6889 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -6,7 +6,7 @@ import lodashGet from 'lodash/get';
 import React from 'react';
 import {withOnyx} from 'react-native-onyx';
 import Str from 'expensify-common/lib/str';
-import {View} from 'react-native';
+import {View, Text} from 'react-native';
 import PropTypes from 'prop-types';
 import ScreenWrapper from '../../components/ScreenWrapper';
 import {fetchFreePlanVerifiedBankAccount} from '../../libs/actions/BankAccounts';
@@ -21,6 +21,7 @@ import RequestorStep from './RequestorStep';
 import ACHContractStep from './ACHContractStep';
 import ValidationStep from './ValidationStep';
 import Navigation from '../../libs/Navigation/Navigation';
+import CONST from '../../CONST';
 
 const propTypes = {
     /** List of betas */
@@ -69,26 +70,30 @@ class ReimbursementAccountPage extends React.Component {
         }
 
         const {achData} = this.props.reimbursementAccount;
-
-        // In Web-Secure we check the policy to find out the defaultCountry. The policy is passed in here:
-        // https://github.com/Expensify/Web-Expensify/blob/896941794f68d7dce64466d83a3e86a5f8122e45/site/app/policyEditor/policyEditorPage.jsx#L2169-L2171
-        // @TODO figure out whether we need to check the policy or not - not sure if it's necessary for V1 of Free Plan
-        const defaultCountry = achData.country || 'US'; // @TODO - In Web-Expensify this fallback refers to User.getIpCountry()
-        const personalDetails = {firstName: this.props.personalDetails.firstName, lastName: this.props.personalDetails.lastName};
-        const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, '@expensify.sms');
+        const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, CONST.SMS.DOMAIN);
 
         if (userHasPhonePrimaryEmail) {
-            // @TODO message explaining that they need to make their primary login an email
-            return null;
+            return (
+                <View>
+                    <Text>To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.</Text>
+                </View>
+            );
         }
 
-        // See if they is throttled
         const throttledDate = lodashGet(this.props, 'reimbursementAccount.throttledDate');
         if (throttledDate) {
             const throttledEnd = moment().add(24, 'hours');
             if (moment() < throttledEnd) {
-                // @TODO message explaining that the user has been throttled
-                return null;
+                return (
+                    <View>
+                        <Text>
+                            For security reasons, we&apos;re taking a break from bank account setup so you can double-check your company information. Please try again
+                            {' '}
+                            {throttledEnd.fromNow()}
+                            . Sorry!
+                        </Text>
+                    </View>
+                );
             }
         }
 
@@ -123,15 +128,10 @@ export default withOnyx({
     reimbursementAccount: {
         key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
     },
-    personalDetails: {
-        key: ONYXKEYS.MY_PERSONAL_DETAILS,
-    },
     session: {
         key: ONYXKEYS.SESSION,
     },
     betas: {
         key: ONYXKEYS.BETAS,
     },
-
-    // @TODO we maybe need the user policyID + currency + default country + whether plaid is disabled ??
 })(ReimbursementAccountPage);

From e31e101b3762a63c669bc32e5f4bd6012268b81f Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 12:03:39 -1000
Subject: [PATCH 40/81] fix up some CONSTs

---
 src/libs/actions/BankAccounts.js              | 80 ++++++++++---------
 .../ReimbursementAccountPage.js               | 40 ++++++----
 2 files changed, 68 insertions(+), 52 deletions(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 0b174bc9f63c..17ecfc2b6cd5 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -284,17 +284,17 @@ function goToWithdrawalStepID(stepID, achData) {
     const newACHData = {...previousACHData};
 
     // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
-    if (!newACHData.useOnfido && stepID === 'RequestorStep') {
+    if (!newACHData.useOnfido && stepID === CONST.BANK_ACCOUNT.STEP.REQUESTOR) {
         delete newACHData.questions;
         delete newACHData.answers;
-        if (lodashHas(achData, 'verifications.externalApiResponses')) {
+        if (lodashHas(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.EXTERNAL_API_RESPONSES)) {
             delete newACHData.verifications.externalApiResponses.requestorIdentityID;
             delete newACHData.verifications.externalApiResponses.requestorIdentityKBA;
         }
     }
 
     // When going from CompanyStep to BankAccountStep, show the manual form instead of Plaid
-    if (newACHData.currentStep === 'CompanyStep' && stepID === 'BankAccountStep') {
+    if (newACHData.currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && stepID === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) {
         newACHData.subStep = 'manual';
     }
 
@@ -373,18 +373,21 @@ function fetchFreePlanVerifiedBankAccount() {
 
             // Temporary fix for Onfido flow. Can be removed by nkuoch after Sept 1 2020.
             // @TODO not sure if we still need this or what this is about, but seems like maybe yes...
-            if (currentStep === 'ACHContractStep' && achData.useOnfido) {
-                const onfidoRes = lodashGet(achData, 'verifications.externalApiResponses.requestorIdentityOnfido');
-                const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
-                if (sdkToken && !achData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
-                    currentStep = 'RequestorStep';
+            if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT && achData.useOnfido) {
+                const onfidoRes = lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO);
+                const sdkToken = lodashGet(onfidoRes, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
+                if (sdkToken && !achData.isOnfidoSetupComplete
+                        && onfidoRes.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
+                ) {
+                    currentStep = CONST.BANK_ACCOUNT.STEP.REQUESTOR;
                 }
             }
 
             // Ensure we route the user to the correct step based on the status of their bank account
             if (bankAccount && !currentStep) {
-                currentStep = bankAccount.isPending()
-                    || bankAccount.isVerifying() ? 'ValidationStep' : 'BankAccountStep';
+                currentStep = bankAccount.isPending() || bankAccount.isVerifying()
+                    ? CONST.BANK_ACCOUNT.STEP.VALIDATION
+                    : CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT;
 
                 // @TODO Again, not sure how much of this logic is needed right now as we shouldn't be handling any
                 // open accounts in E.cash yet that need to pass any more checks or can be upgraded, but leaving in for
@@ -393,18 +396,19 @@ function fetchFreePlanVerifiedBankAccount() {
                     if (bankAccount.needsToPassLatestChecks()) {
                         const hasTriedToUpgrade = bankAccount.getDateSigned()
                             > (kycVerificationsMigration || '2020-01-13');
-                        currentStep = hasTriedToUpgrade ? 'ValidationStep' : 'CompanyStep';
+                        currentStep = hasTriedToUpgrade
+                            ? CONST.BANK_ACCOUNT.STEP.VALIDATION : CONST.BANK_ACCOUNT.STEP.COMPANY;
                         achData.bankAccountInReview = hasTriedToUpgrade;
                     } else {
                         // We're not going to have a EnableStep, but we can show this account as open if accessed
-                        currentStep = 'EnableStep';
+                        currentStep = CONST.BANK_ACCOUNT.STEP.ENABLE;
                     }
                 }
             }
 
             // If at this point we still don't have a current step, default to the BankAccountStep
             if (!currentStep) {
-                currentStep = 'BankAccountStep';
+                currentStep = CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT;
             }
 
             Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {throttledDate});
@@ -417,27 +421,27 @@ function fetchFreePlanVerifiedBankAccount() {
 
 const WITHDRAWAL_ACCOUNT_STEPS = [
     {
-        id: 'BankAccountStep',
+        id: CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT,
         title: 'Bank Account',
     },
     {
-        id: 'CompanyStep',
+        id: CONST.BANK_ACCOUNT.STEP.COMPANY,
         title: 'Company Information',
     },
     {
-        id: 'RequestorStep',
+        id: CONST.BANK_ACCOUNT.STEP.REQUESTOR,
         title: 'Requestor Information',
     },
     {
-        id: 'ACHContractStep',
+        id: CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT,
         title: 'Beneficial Owners',
     },
     {
-        id: 'ValidationStep',
+        id: CONST.BANK_ACCOUNT.STEP.VALIDATION,
         title: 'Validate',
     },
     {
-        id: 'EnableStep',
+        id: CONST.BANK_ACCOUNT.STEP.ENABLE,
         title: 'Enable',
     },
 ];
@@ -460,7 +464,7 @@ function getNextStepID() {
         getIndexByStepID(previousACHData.currentStep) + 1,
         WITHDRAWAL_ACCOUNT_STEPS.length - 1,
     );
-    return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], 'BankAccountStep');
+    return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT);
 }
 
 /**
@@ -484,7 +488,9 @@ function setupWithdrawalAccount(data) {
         previousACHData.isSavings = Boolean(data.isSavings);
     }
     if (!previousACHData.setupType) {
-        previousACHData.setupType = previousACHData.plaidAccountID ? 'plaid' : 'manual';
+        previousACHData.setupType = previousACHData.plaidAccountID
+            ? CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID
+            : CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL;
     }
 
     let nextStep = previousACHData.currentStep;
@@ -503,7 +509,7 @@ function setupWithdrawalAccount(data) {
 
             const currentStep = previousACHData.currentStep;
             let achData = lodashGet(response, 'achData', {});
-            let error = lodashGet(achData, 'verifications.errorMessage');
+            let error = lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.ERROR_MESSAGE);
 
             if (response.jsonCode === 200 && !error) {
                 // Save an NVP with the bankAccountID for this account. This is temporary since we are not showing lists
@@ -520,30 +526,32 @@ function setupWithdrawalAccount(data) {
                     return;
                 }
 
-                if (currentStep === 'RequestorStep') {
+                if (currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR) {
                     const requestorResponse = lodashGet(
                         achData,
-                        'verifications.externalApiResponses.requestorIdentityID',
+                        CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ID,
                     );
                     if (previousACHData.useOnfido) {
                         const onfidoRes = lodashGet(
                             achData,
-                            'verifications.externalApiResponses.requestorIdentityOnfido',
+                            CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO,
                         );
-                        const sdkToken = lodashGet(onfidoRes, 'apiResult.sdkToken');
-                        if (sdkToken && !previousACHData.isOnfidoSetupComplete && onfidoRes.status !== 'pass') {
+                        const sdkToken = lodashGet(onfidoRes, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
+                        if (sdkToken && !previousACHData.isOnfidoSetupComplete
+                                && onfidoRes.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
+                        ) {
                             // Requestor Step still needs to run Onfido
                             achData.sdkToken = sdkToken;
-                            goToWithdrawalStepID('RequestorStep', achData);
+                            goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.REQUESTOR, achData);
                             return;
                         }
                     } else if (requestorResponse) {
                         // Don't go to next step if Requestor Step needs to ask some questions
-                        let questions = lodashGet(requestorResponse, 'apiResult.questions.question') || [];
+                        let questions = lodashGet(requestorResponse, CONST.BANK_ACCOUNT.QUESTIONS.QUESTION) || [];
                         if (_.isEmpty(questions)) {
                             const differentiatorQuestion = lodashGet(
                                 requestorResponse,
-                                'apiResult.differentiator-question',
+                                CONST.BANK_ACCOUNT.QUESTIONS.DIFFERENTIATOR_QUESTION,
                             );
                             if (differentiatorQuestion) {
                                 questions = [differentiatorQuestion];
@@ -551,13 +559,13 @@ function setupWithdrawalAccount(data) {
                         }
                         if (!_.isEmpty(questions)) {
                             achData.questions = questions;
-                            goToWithdrawalStepID('RequestorStep', achData);
+                            goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.REQUESTOR, achData);
                             return;
                         }
                     }
                 }
 
-                if (currentStep === 'ACHContractStep') {
+                if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) {
                     // Get an up-to-date bank account list so that we can allow the user to validate their newly
                     // generated bank account
                     return API.Get({returnValueList: 'bankAccountList'})
@@ -572,12 +580,12 @@ function setupWithdrawalAccount(data) {
                             achData.bankAccountInReview = needsToPassLatestChecks
                                 || achData.state === BankAccount.STATE.VERIFYING;
 
-                            goToWithdrawalStepID('ValidationStep', achData);
+                            goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.VALIDATION, achData);
                         });
                 }
 
-                if ((currentStep === 'ValidationStep' && previousACHData.bankAccountInReview)
-                    || currentStep === 'EnableStep'
+                if ((currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && previousACHData.bankAccountInReview)
+                    || currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE
                 ) {
                     // Setup done!
                 } else {
@@ -587,7 +595,7 @@ function setupWithdrawalAccount(data) {
                 if (response.jsonCode === 666) {
                     error = response.message;
                 }
-                if (lodashGet(achData, 'verifications.throttled')) {
+                if (lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.THROTTLED)) {
                     achData.disableFields = true;
                 }
             }
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index c7b783fe6889..5d0a75374cd2 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -22,6 +22,8 @@ import ACHContractStep from './ACHContractStep';
 import ValidationStep from './ValidationStep';
 import Navigation from '../../libs/Navigation/Navigation';
 import CONST from '../../CONST';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import compose from '../../libs/compose';
 
 const propTypes = {
     /** List of betas */
@@ -44,6 +46,8 @@ const propTypes = {
     session: PropTypes.shape({
         email: PropTypes.string,
     }).isRequired,
+
+    ...withLocalizePropTypes,
 };
 
 const defaultProps = {
@@ -102,19 +106,19 @@ class ReimbursementAccountPage extends React.Component {
         const currentStep = achData.currentStep;
         return (
             <ScreenWrapper>
-                {currentStep === 'BankAccountStep' && (
+                {currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT && (
                     <BankAccountStep />
                 )}
-                {currentStep === 'CompanyStep' && (
+                {currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && (
                     <CompanyStep />
                 )}
-                {currentStep === 'RequestorStep' && (
+                {currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR && (
                     <RequestorStep />
                 )}
-                {currentStep === 'ACHContractStep' && (
+                {currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT && (
                     <ACHContractStep />
                 )}
-                {currentStep === 'ValidationStep' && (
+                {currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && (
                     <ValidationStep />
                 )}
             </ScreenWrapper>
@@ -124,14 +128,18 @@ class ReimbursementAccountPage extends React.Component {
 
 ReimbursementAccountPage.propTypes = propTypes;
 ReimbursementAccountPage.defaultProps = defaultProps;
-export default withOnyx({
-    reimbursementAccount: {
-        key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
-    },
-    session: {
-        key: ONYXKEYS.SESSION,
-    },
-    betas: {
-        key: ONYXKEYS.BETAS,
-    },
-})(ReimbursementAccountPage);
+
+export default compose(
+    withOnyx({
+        reimbursementAccount: {
+            key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
+        },
+        session: {
+            key: ONYXKEYS.SESSION,
+        },
+        betas: {
+            key: ONYXKEYS.BETAS,
+        },
+    }),
+    withLocalize,
+)(ReimbursementAccountPage);

From 25d16b83fab87a9dfd9cc35fbf8c38fa4ad0fd95 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Fri, 11 Jun 2021 15:04:25 -0700
Subject: [PATCH 41/81] pass default welcome if none is filled in

---
 src/pages/WorkspaceInvitePage.js | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 40702573c4fc..0360374bd818 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -47,11 +47,19 @@ class WorkspaceInvitePage extends React.Component {
             welcomeNote: '',
         };
 
+        this.getWelcomeNotePlaceholder = this.getWelcomeNotePlaceholder.bind(this);
         this.inviteUser = this.inviteUser.bind(this);
     }
 
     inviteUser() {
-        invite(this.state.emailOrPhone, this.state.welcomeNote, this.props.route.params.policyID);
+        invite(this.state.emailOrPhone, this.state.welcomeNote || this.getWelcomeNotePlaceholder(),
+            this.props.route.params.policyID);
+    }
+
+    getWelcomeNotePlaceholder() {
+        return this.props.translate('workspaceInvitePage.welcomeNote', {
+            workspaceName: this.props.policy.name,
+        });
     }
 
     render() {
@@ -84,9 +92,7 @@ class WorkspaceInvitePage extends React.Component {
                                 numberOfLines={10}
                                 multiline
                                 value={this.state.welcomeNote}
-                                placeholder={this.props.translate('workspaceInvitePage.welcomeNote', {
-                                    workspaceName: this.props.policy.name,
-                                })}
+                                placeholder={this.getWelcomeNotePlaceholder()}
                                 onChangeText={text => this.setState({welcomeNote: text})}
                             />
                         </View>

From 77cc488909e60b0496080a244b49a23d4c0e6a4a Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Fri, 11 Jun 2021 15:16:39 -0700
Subject: [PATCH 42/81] add privacy policy link

---
 src/pages/WorkspaceInvitePage.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 0360374bd818..361d8cd8fc9c 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -12,6 +12,7 @@ import Button from '../components/Button';
 import compose from '../libs/compose';
 import ONYXKEYS from '../ONYXKEYS';
 import {invite} from '../libs/actions/Policy';
+import TextLink from '../components/TextLink';
 
 const propTypes = {
     ...withLocalizePropTypes,
@@ -96,6 +97,9 @@ class WorkspaceInvitePage extends React.Component {
                                 onChangeText={text => this.setState({welcomeNote: text})}
                             />
                         </View>
+                        <TextLink href="https://use.expensify.com/privacy">
+                            {`${this.props.translate('common.privacyPolicy')} `}
+                        </TextLink>
                     </View>
                     <View style={styles.flexGrow0}>
                         <Button

From 36e5a971015620668def340825dc97330634fa16 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 12:18:54 -1000
Subject: [PATCH 43/81] fix propTypes and error messages

---
 src/languages/en.js                           |  2 ++
 src/languages/es.js                           |  2 ++
 .../ReimbursementAccountPage.js               | 24 +++++++++----------
 3 files changed, 16 insertions(+), 12 deletions(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index a9d4d55bc293..be6d645c92e0 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -270,6 +270,8 @@ export default {
         plaidBodyCopy: 'Give your employees an easier way to pay - and get paid back - for company expenses.',
         checkHelpLine: 'Your routing number and account number can be found on a check for the account.',
         iAcceptThe: 'I accept the ',
+        hasPhoneLoginError: 'To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.',
+        hasBeenThrottledError: ({fromNow}) => `For security reasons, we're taking a break from bank account setup so you can double-check your company information. Please try again ${fromNow}. Sorry!`,
     },
     addPersonalBankAccountPage: {
         enterPassword: 'Enter password',
diff --git a/src/languages/es.js b/src/languages/es.js
index 894d36489822..69d3ccc56ca0 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -265,6 +265,8 @@ export default {
         plaidBodyCopy: 'Ofrezca a sus empleados una forma más sencilla de pagar - y recuperar - los gastos de la empresa.',
         checkHelpLine: 'Su número de ruta y número de cuenta se pueden encontrar en un cheque para la cuenta.',
         iAcceptThe: 'Acepto los ',
+        hasPhoneLoginError: 'Para agregar una cuenta bancaria verificada, asegúrese de que su inicio de sesión principal sea un correo electrónico válido y vuelva a intentarlo. Puede agregar su número de teléfono como inicio de sesión secundario.',
+        hasBeenThrottledError: ({fromNow}) => `Por razones de seguridad, nos tomamos un descanso de la configuración de la cuenta bancaria para que pueda verificar la información de su empresa. Inténtalo de nuevo ${fromNow}. Lo siento!`,
     },
     addPersonalBankAccountPage: {
         enterPassword: 'Escribe una contraseña',
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index 5d0a75374cd2..2bc5c68aaa00 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -29,21 +29,25 @@ const propTypes = {
     /** List of betas */
     betas: PropTypes.arrayOf(PropTypes.string).isRequired,
 
+    /** ACH data for the withdrawal account actively being set up */
     reimbursementAccount: PropTypes.shape({
+        /** Whether we are loading the data via the API */
         loading: PropTypes.bool,
+
+        /** A date that indicates the user has been throttled */
         throttledDate: PropTypes.string,
+
+        /** Additional data for the account in setup */
         achData: PropTypes.shape({
-            country: PropTypes.string,
+
+            /** Step of the setup flow that we are on. Determines which view is presented. */
             currentStep: PropTypes.string,
         }),
     }),
 
-    personalDetails: PropTypes.shape({
-        firstName: PropTypes.string,
-        lastName: PropTypes.string,
-    }),
-
+    /** Current session for the user */
     session: PropTypes.shape({
+        /** User login */
         email: PropTypes.string,
     }).isRequired,
 
@@ -54,7 +58,6 @@ const defaultProps = {
     reimbursementAccount: {
         loading: true,
     },
-    personalDetails: {},
 };
 
 class ReimbursementAccountPage extends React.Component {
@@ -79,7 +82,7 @@ class ReimbursementAccountPage extends React.Component {
         if (userHasPhonePrimaryEmail) {
             return (
                 <View>
-                    <Text>To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.</Text>
+                    <Text>{this.props.translate('bankAccount.hasPhoneLoginError')}</Text>
                 </View>
             );
         }
@@ -91,10 +94,7 @@ class ReimbursementAccountPage extends React.Component {
                 return (
                     <View>
                         <Text>
-                            For security reasons, we&apos;re taking a break from bank account setup so you can double-check your company information. Please try again
-                            {' '}
-                            {throttledEnd.fromNow()}
-                            . Sorry!
+                            {this.props.translate('bankAccount.hasBeenThrottledError', {fromNow: throttledEnd.fromNow()})}
                         </Text>
                     </View>
                 );

From b96c2970285f88693222c3a79c3a6c2a3d9c8256 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 12:21:26 -1000
Subject: [PATCH 44/81] change BusinessBankAccountNewPage to BankAccountStep

---
 src/pages/ReimbursementAccount/BankAccountStep.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
index 238b542fccaf..8cfb895a4bc5 100644
--- a/src/pages/ReimbursementAccount/BankAccountStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -23,7 +23,7 @@ const propTypes = {
     ...withLocalizePropTypes,
 };
 
-class BusinessBankAccountNewPage extends React.Component {
+class BankAccountStep extends React.Component {
     constructor(props) {
         super(props);
 
@@ -221,6 +221,6 @@ class BusinessBankAccountNewPage extends React.Component {
     }
 }
 
-BusinessBankAccountNewPage.propTypes = propTypes;
+BankAccountStep.propTypes = propTypes;
 
-export default withLocalize(BusinessBankAccountNewPage);
+export default withLocalize(BankAccountStep);

From 7cee97ab69a24ad8448dadcd70e92e264d57c786 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Fri, 11 Jun 2021 15:23:32 -0700
Subject: [PATCH 45/81] style

---
 src/pages/WorkspaceInvitePage.js | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 361d8cd8fc9c..b7f6eabaa65d 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -52,17 +52,25 @@ class WorkspaceInvitePage extends React.Component {
         this.inviteUser = this.inviteUser.bind(this);
     }
 
-    inviteUser() {
-        invite(this.state.emailOrPhone, this.state.welcomeNote || this.getWelcomeNotePlaceholder(),
-            this.props.route.params.policyID);
-    }
-
+    /**
+     * Gets the welcome note default text
+     *
+     * @returns {Object}
+     */
     getWelcomeNotePlaceholder() {
         return this.props.translate('workspaceInvitePage.welcomeNote', {
             workspaceName: this.props.policy.name,
         });
     }
 
+    /**
+     * Handle the invite button click
+     */
+    inviteUser() {
+        invite(this.state.emailOrPhone, this.state.welcomeNote || this.getWelcomeNotePlaceholder(),
+            this.props.route.params.policyID);
+    }
+
     render() {
         return (
             <ScreenWrapper>

From 661ee343869c411e70bdb316085601dcf235fe72 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Fri, 11 Jun 2021 12:32:10 -1000
Subject: [PATCH 46/81] Fix comments style etc

---
 .../ReimbursementAccountPage.js               | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index 2bc5c68aaa00..4435525e47fd 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -1,6 +1,3 @@
-/* eslint-disable no-unused-vars */
-/* eslint-disable max-len */
-/* eslint-disable react/no-unused-state */
 import moment from 'moment';
 import lodashGet from 'lodash/get';
 import React from 'react';
@@ -24,6 +21,7 @@ import Navigation from '../../libs/Navigation/Navigation';
 import CONST from '../../CONST';
 import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
 import compose from '../../libs/compose';
+import styles from '../../styles/styles';
 
 const propTypes = {
     /** List of betas */
@@ -76,12 +74,11 @@ class ReimbursementAccountPage extends React.Component {
             return <FullScreenLoadingIndicator visible />;
         }
 
-        const {achData} = this.props.reimbursementAccount;
         const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, CONST.SMS.DOMAIN);
 
         if (userHasPhonePrimaryEmail) {
             return (
-                <View>
+                <View style={[styles.m5]}>
                     <Text>{this.props.translate('bankAccount.hasPhoneLoginError')}</Text>
                 </View>
             );
@@ -92,18 +89,20 @@ class ReimbursementAccountPage extends React.Component {
             const throttledEnd = moment().add(24, 'hours');
             if (moment() < throttledEnd) {
                 return (
-                    <View>
+                    <View style={[styles.m5]}>
                         <Text>
-                            {this.props.translate('bankAccount.hasBeenThrottledError', {fromNow: throttledEnd.fromNow()})}
+                            {this.props.translate('bankAccount.hasBeenThrottledError', {
+                                fromNow: throttledEnd.fromNow(),
+                            })}
                         </Text>
                     </View>
                 );
             }
         }
 
-        // Everything we need to display UI-wise is pretty much in the achData at this point. We just need to call the correct actions in the right places when
-        // submitting a form or navigating back or forward.
-        const currentStep = achData.currentStep;
+        // We grab the currentStep from the achData to determine which view to display. The SetupWithdrawalAccount flow
+        // allows us to continue the flow from various points depending on where the user left off.
+        const currentStep = this.props.reimbursementAccount.achData.currentStep;
         return (
             <ScreenWrapper>
                 {currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT && (

From eb6c3f55b082ffbe11df1655da88d2962e491502 Mon Sep 17 00:00:00 2001
From: Jasper Huang <jasper@expensify.com>
Date: Sat, 12 Jun 2021 14:12:56 +0800
Subject: [PATCH 47/81] Added language selector in the preferences page

---
 src/languages/en.js                   |  1 +
 src/languages/es.js                   |  3 +++
 src/pages/settings/PreferencesPage.js | 35 ++++++++++++++++++++++++---
 3 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index 80cea4bba2f5..c8fe6f64814d 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -212,6 +212,7 @@ export default {
         notifications: 'Notifications',
         receiveRelevantFeatureUpdatesAndExpensifyNews: 'Receive relevant feature updates and Expensify news',
         priorityMode: 'Priority Mode',
+        language: 'Language',
     },
     signInPage: {
         expensifyDotCash: 'Expensify.cash',
diff --git a/src/languages/es.js b/src/languages/es.js
index 894d36489822..a5f79dff2c00 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -207,6 +207,9 @@ export default {
         notifications: 'Notificaciones',
         receiveRelevantFeatureUpdatesAndExpensifyNews: 'Recibir noticias sobre Expensify y actualizaciones del producto',
         priorityMode: 'Modo Prioridad',
+
+        // TODO
+        language: '????',
     },
     signInPage: {
         expensifyDotCash: 'Expensify.cash',
diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js
index bf4f96ef289f..53af9f6698f4 100755
--- a/src/pages/settings/PreferencesPage.js
+++ b/src/pages/settings/PreferencesPage.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import {View} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
+import Onyx, {withOnyx} from 'react-native-onyx';
 import PropTypes from 'prop-types';
 
 import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
@@ -31,6 +31,9 @@ const propTypes = {
     }),
 
     ...withLocalizePropTypes,
+
+    // Indicates which locale the user currently has selected
+    preferredLocale: PropTypes.string.isRequired,
 };
 
 const defaultProps = {
@@ -38,7 +41,9 @@ const defaultProps = {
     user: {},
 };
 
-const PreferencesPage = ({priorityMode, user, translate}) => {
+const PreferencesPage = ({
+    priorityMode, user, translate, preferredLocale,
+}) => {
     const priorityModes = {
         default: {
             value: CONST.PRIORITY_MODE.DEFAULT,
@@ -52,6 +57,17 @@ const PreferencesPage = ({priorityMode, user, translate}) => {
         },
     };
 
+    const locales = {
+        default: {
+            value: 'en',
+            label: 'English',
+        },
+        es: {
+            value: 'es',
+            label: 'Spanish',
+        },
+    };
+
     return (
         <ScreenWrapper>
             <HeaderWithCloseButton
@@ -78,6 +94,7 @@ const PreferencesPage = ({priorityMode, user, translate}) => {
                             />
                         </View>
                     </View>
+
                     <Text style={[styles.formLabel]} numberOfLines={1}>
                         {translate('preferencesPage.priorityMode')}
                     </Text>
@@ -91,9 +108,21 @@ const PreferencesPage = ({priorityMode, user, translate}) => {
                             icon={() => <Icon src={DownArrow} />}
                         />
                     </View>
-                    <Text style={[styles.textLabel, styles.colorMuted]}>
+                    <Text style={[styles.textLabel, styles.colorMuted, styles.mb6]}>
                         {priorityModes[priorityMode].description}
                     </Text>
+
+                    <Text style={[styles.formLabel]} numberOfLines={1}>
+                        {translate('preferencesPage.language')}
+                    </Text>
+                    <View style={[styles.mb2]}>
+                        <Picker
+                            onChange={locale => Onyx.merge(ONYXKEYS.PREFERRED_LOCALE, locale)}
+                            items={Object.values(locales)}
+                            value={preferredLocale}
+                            icon={() => <Icon src={DownArrow} />}
+                        />
+                    </View>
                 </View>
             </View>
         </ScreenWrapper>

From 3294d9517e2860dfcf6e4ff16badaded0314a261 Mon Sep 17 00:00:00 2001
From: Jasper Huang <jasper@expensify.com>
Date: Sat, 12 Jun 2021 14:13:54 +0800
Subject: [PATCH 48/81] Added language selector in the preferences page

---
 src/pages/settings/PreferencesPage.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js
index 53af9f6698f4..0c6b7d13eca1 100755
--- a/src/pages/settings/PreferencesPage.js
+++ b/src/pages/settings/PreferencesPage.js
@@ -57,7 +57,7 @@ const PreferencesPage = ({
         },
     };
 
-    const locales = {
+    const localesToLanguages = {
         default: {
             value: 'en',
             label: 'English',
@@ -118,7 +118,7 @@ const PreferencesPage = ({
                     <View style={[styles.mb2]}>
                         <Picker
                             onChange={locale => Onyx.merge(ONYXKEYS.PREFERRED_LOCALE, locale)}
-                            items={Object.values(locales)}
+                            items={Object.values(localesToLanguages)}
                             value={preferredLocale}
                             icon={() => <Icon src={DownArrow} />}
                         />

From 845786dfbdc78efdf21b30bdf02d6dbff43a063f Mon Sep 17 00:00:00 2001
From: Jasper Huang <jasper@expensify.com>
Date: Sat, 12 Jun 2021 14:20:21 +0800
Subject: [PATCH 49/81] Remove whitespace

---
 src/pages/settings/PreferencesPage.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js
index 0c6b7d13eca1..a07b6b4fd2e7 100755
--- a/src/pages/settings/PreferencesPage.js
+++ b/src/pages/settings/PreferencesPage.js
@@ -94,7 +94,6 @@ const PreferencesPage = ({
                             />
                         </View>
                     </View>
-
                     <Text style={[styles.formLabel]} numberOfLines={1}>
                         {translate('preferencesPage.priorityMode')}
                     </Text>
@@ -111,7 +110,6 @@ const PreferencesPage = ({
                     <Text style={[styles.textLabel, styles.colorMuted, styles.mb6]}>
                         {priorityModes[priorityMode].description}
                     </Text>
-
                     <Text style={[styles.formLabel]} numberOfLines={1}>
                         {translate('preferencesPage.language')}
                     </Text>

From e85f5879f87db7a392e1960a8fa0f9f9b9e9f541 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Mon, 14 Jun 2021 07:16:19 -1000
Subject: [PATCH 50/81] fix new workspace modal

---
 src/libs/Navigation/AppNavigator/AuthScreens.js | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 5e619b7bf717..c63ae0e5e2de 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -283,6 +283,11 @@ class AuthScreens extends React.Component {
                     component={ReimbursementAccountModalStackNavigator}
                     listeners={modalScreenListeners}
                 />
+                <RootStack.Screen
+                    name="NewWorkspace"
+                    options={modalScreenOptions}
+                    component={NewWorkspaceStackNavigator}
+                />
             </RootStack.Navigator>
         );
     }

From d373ffb7e5c3f04b67adf1a760f7adee1ac17ef8 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Tue, 15 Jun 2021 03:49:29 +0530
Subject: [PATCH 51/81] refactor

---
 src/CONST.js                                 |  1 -
 src/components/Modal/BaseModal.js            |  6 ++--
 src/components/Modal/ModalPropTypes.js       |  4 +++
 src/components/Modal/index.js                |  8 ++---
 src/components/Popover/PopoverPropTypes.js   |  4 +--
 src/components/Popover/index.js              | 37 ++++++++++++++------
 src/components/PopoverWithMeasuredContent.js | 10 +++---
 src/pages/home/report/ReportActionItem.js    |  1 +
 src/styles/getModalStyles.js                 | 30 ----------------
 9 files changed, 45 insertions(+), 56 deletions(-)

diff --git a/src/CONST.js b/src/CONST.js
index c31207674f53..44d5b29519ed 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -84,7 +84,6 @@ const CONST = {
             BOTTOM_DOCKED: 'bottom_docked',
             POPOVER: 'popover',
             RIGHT_DOCKED: 'right_docked',
-            CONTEXT_MENU: 'context_menu',
         },
         ANCHOR_ORIGIN_VERTICAL: {
             TOP: 'top',
diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index e00433959566..6b0caf683376 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -58,8 +58,6 @@ class BaseModal extends PureComponent {
             shouldAddTopSafeAreaPadding,
             shouldAddBottomSafeAreaPadding,
             hideBackdrop,
-            hasBackdrop,
-            coverScreen,
         } = getModalStyles(
             this.props.type,
             {
@@ -95,8 +93,8 @@ class BaseModal extends PureComponent {
                 backdropColor={themeColors.modalBackdrop}
                 backdropOpacity={hideBackdrop ? 0 : 0.5}
                 backdropTransitionOutTiming={0}
-                hasBackdrop={hasBackdrop}
-                coverScreen={coverScreen}
+                hasBackdrop={this.props.fullscreen}
+                coverScreen={this.props.fullscreen}
                 style={modalStyle}
                 deviceHeight={this.props.windowHeight}
                 deviceWidth={this.props.windowWidth}
diff --git a/src/components/Modal/ModalPropTypes.js b/src/components/Modal/ModalPropTypes.js
index 2e62af163b6a..b0b554ff4c21 100644
--- a/src/components/Modal/ModalPropTypes.js
+++ b/src/components/Modal/ModalPropTypes.js
@@ -4,6 +4,9 @@ import CONST from '../../CONST';
 import {windowDimensionsPropTypes} from '../withWindowDimensions';
 
 const propTypes = {
+    /** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */
+    fullscreen: PropTypes.bool,
+
     /** Should we close modal on outside click */
     shouldCloseOnOutsideClick: PropTypes.bool,
 
@@ -55,6 +58,7 @@ const propTypes = {
 };
 
 const defaultProps = {
+    fullscreen: true,
     shouldCloseOnOutsideClick: false,
     shouldSetModalVisibility: true,
     onSubmit: null,
diff --git a/src/components/Modal/index.js b/src/components/Modal/index.js
index c3faef65332d..f74fdd6137dc 100644
--- a/src/components/Modal/index.js
+++ b/src/components/Modal/index.js
@@ -1,10 +1,9 @@
-import React, {PureComponent} from 'react';
+import React, {Component} from 'react';
 import withWindowDimensions from '../withWindowDimensions';
 import BaseModal from './BaseModal';
 import {propTypes, defaultProps} from './ModalPropTypes';
 
-
-class Modal extends PureComponent {
+class Modal extends Component {
     constructor(props) {
         super(props);
         this.closeOnOutsideClick = this.closeOnOutsideClick.bind(this);
@@ -24,7 +23,8 @@ class Modal extends PureComponent {
 
     closeOnOutsideClick(event) {
         if (this.props.isVisible
-            && this.baseModalRef && !this.baseModalRef.contains(event.target)
+            && this.baseModalRef
+            && !this.baseModalRef.contains(event.target)
             && this.props.shouldCloseOnOutsideClick) {
             this.props.onClose();
         }
diff --git a/src/components/Popover/PopoverPropTypes.js b/src/components/Popover/PopoverPropTypes.js
index e6f9c6856cfe..acc030ad80c0 100644
--- a/src/components/Popover/PopoverPropTypes.js
+++ b/src/components/Popover/PopoverPropTypes.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import {propTypes as modalPropTypes, defaultProps as defaultModalProps} from '../Modal/ModalPropTypes';
 
 const propTypes = {
-    ...(_.omit(modalPropTypes, 'popoverAnchorPosition')),
+    ...(_.omit(modalPropTypes, ['type', 'popoverAnchorPosition'])),
 
     /** The anchor position of the popover */
     anchorPosition: PropTypes.shape({
@@ -15,7 +15,7 @@ const propTypes = {
 };
 
 const defaultProps = {
-    ...(_.omit(defaultModalProps, 'popoverAnchorPosition')),
+    ...(_.omit(defaultModalProps, ['type', 'popoverAnchorPosition'])),
 
     // Anchor position is optional only because it is not relevant on mobile
     anchorPosition: {},
diff --git a/src/components/Popover/index.js b/src/components/Popover/index.js
index 9879d1d79a5f..cc1c7c534ec0 100644
--- a/src/components/Popover/index.js
+++ b/src/components/Popover/index.js
@@ -1,4 +1,5 @@
 import React from 'react';
+import {createPortal} from 'react-dom';
 import {propTypes, defaultProps} from './PopoverPropTypes';
 import CONST from '../../CONST';
 import Modal from '../Modal';
@@ -8,16 +9,32 @@ import withWindowDimensions from '../withWindowDimensions';
  * This is a convenience wrapper around the Modal component for a responsive Popover.
  * On small screen widths, it uses BottomDocked modal type, and a Popover type on wide screen widths.
  */
-const Popover = props => (
-    <Modal
-        type={props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.POPOVER}
-        popoverAnchorPosition={props.anchorPosition}
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        animationIn={props.isSmallScreenWidth ? undefined : props.animationIn}
-        animationOut={props.isSmallScreenWidth ? undefined : props.animationOut}
-    />
-);
+const Popover = (props) => {
+    if (!props.fullscreen && !props.isSmallScreenWidth) {
+        return createPortal(
+            <Modal
+                type={CONST.MODAL.MODAL_TYPE.POPOVER}
+                popoverAnchorPosition={props.anchorPosition}
+                // eslint-disable-next-line react/jsx-props-no-spreading
+                {...props}
+                animationIn={props.isSmallScreenWidth ? undefined : props.animationIn}
+                animationOut={props.isSmallScreenWidth ? undefined : props.animationOut}
+                shouldCloseOnOutsideClick
+            />,
+            document.body,
+        );
+    }
+    return (
+        <Modal
+            type={props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.POPOVER}
+            popoverAnchorPosition={props.anchorPosition}
+            // eslint-disable-next-line react/jsx-props-no-spreading
+            {...props}
+            animationIn={props.isSmallScreenWidth ? undefined : props.animationIn}
+            animationOut={props.isSmallScreenWidth ? undefined : props.animationOut}
+        />
+    );
+};
 
 Popover.propTypes = propTypes;
 Popover.defaultProps = defaultProps;
diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js
index e68bc1a6f649..82e83f3fa86e 100644
--- a/src/components/PopoverWithMeasuredContent.js
+++ b/src/components/PopoverWithMeasuredContent.js
@@ -2,7 +2,7 @@ import _ from 'underscore';
 import React, {Component} from 'react';
 import PropTypes from 'prop-types';
 import {View} from 'react-native';
-import ContextMenuPopover from './ContextMenuPopover';
+import Popover from './Popover';
 import {propTypes as popoverPropTypes, defaultProps as defaultPopoverProps} from './Popover/PopoverPropTypes';
 import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
 import CONST from '../CONST';
@@ -12,7 +12,7 @@ import {computeHorizontalShift, computeVerticalShift} from '../styles/getPopover
 const propTypes = {
     // All popover props except:
     // 1) anchorPosition (which is overridden for this component)
-    ...(_.omit(popoverPropTypes, ['type', 'anchorPosition'])),
+    ...(_.omit(popoverPropTypes, ['anchorPosition'])),
 
     /** The horizontal and vertical anchors points for the popover */
     anchorPosition: PropTypes.shape({
@@ -34,7 +34,7 @@ const propTypes = {
 };
 
 const defaultProps = {
-    ...(_.omit(defaultPopoverProps, ['type'])),
+    ...(_.omit(defaultPopoverProps)),
 
     // Default positioning of the popover
     anchorOrigin: {
@@ -149,13 +149,13 @@ class PopoverWithMeasuredContent extends Component {
         };
         return this.state.isContentMeasured
             ? (
-                <ContextMenuPopover
+                <Popover
                     // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this.props}
                     anchorPosition={shifedAnchorPosition}
                 >
                     {this.props.measureContent()}
-                </ContextMenuPopover>
+                </Popover>
             ) : (
 
                 /*
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index 9b8b7d6d0947..305d1b442c24 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -279,6 +279,7 @@ class ReportActionItem extends Component {
                     animationOutTiming={1}
                     measureContent={this.measureContent}
                     shouldSetModalVisibility={false}
+                    fullscreen={false}
                 >
                     <ReportActionContextMenu
                         isVisible
diff --git a/src/styles/getModalStyles.js b/src/styles/getModalStyles.js
index 69e126e3ee33..8921167c1cb1 100644
--- a/src/styles/getModalStyles.js
+++ b/src/styles/getModalStyles.js
@@ -17,8 +17,6 @@ export default (type, windowDimensions, popoverAnchorPosition = {}) => {
     let hideBackdrop = false;
     let shouldAddBottomSafeAreaPadding = false;
     let shouldAddTopSafeAreaPadding = false;
-    let coverScreen = true;
-    let hasBackdrop = true;
 
     switch (type) {
         case CONST.MODAL.MODAL_TYPE.CONFIRM:
@@ -133,32 +131,6 @@ export default (type, windowDimensions, popoverAnchorPosition = {}) => {
             animationIn = 'fadeIn';
             animationOut = 'fadeOut';
             break;
-        case CONST.MODAL.MODAL_TYPE.CONTEXT_MENU:
-            modalStyle = {
-                ...modalStyle,
-                ...popoverAnchorPosition,
-                ...{
-                    position: 'absolute',
-                    alignItems: 'center',
-                    justifyContent: 'flex-end',
-                },
-            };
-            modalContainerStyle = {
-                borderRadius: 12,
-                borderWidth: 1,
-                borderColor: themeColors.border,
-                justifyContent: 'center',
-                overflow: 'hidden',
-                boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.025)',
-            };
-
-            hideBackdrop = true;
-            swipeDirection = undefined;
-            animationIn = 'fadeIn';
-            animationOut = 'fadeOut';
-            coverScreen = false;
-            hasBackdrop = false;
-            break;
         case CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED:
             modalStyle = {
                 ...modalStyle,
@@ -212,7 +184,5 @@ export default (type, windowDimensions, popoverAnchorPosition = {}) => {
         hideBackdrop,
         shouldAddBottomSafeAreaPadding,
         shouldAddTopSafeAreaPadding,
-        coverScreen,
-        hasBackdrop,
     };
 };

From 1bce75ba6b57632cc3ac0eb9af16aad2a9ab9a62 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Tue, 15 Jun 2021 03:51:15 +0530
Subject: [PATCH 52/81] removed  unnecessary ContextMenuPopover

---
 src/components/ContextMenuPopover/index.js    | 28 -------------------
 .../ContextMenuPopover/index.native.js        | 23 ---------------
 2 files changed, 51 deletions(-)
 delete mode 100644 src/components/ContextMenuPopover/index.js
 delete mode 100644 src/components/ContextMenuPopover/index.native.js

diff --git a/src/components/ContextMenuPopover/index.js b/src/components/ContextMenuPopover/index.js
deleted file mode 100644
index 03d325b0e67e..000000000000
--- a/src/components/ContextMenuPopover/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react';
-import {createPortal} from 'react-dom';
-import {propTypes, defaultProps} from '../Popover/PopoverPropTypes';
-import withWindowDimensions from '../withWindowDimensions';
-import Popover from '../Popover';
-import CONST from '../../CONST';
-
-const ContextMenuPopover = props => (!props.isSmallScreenWidth ? createPortal(
-    <Popover
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        type={CONST.MODAL.MODAL_TYPE.CONTEXT_MENU}
-        shouldCloseOnOutsideClick
-    />,
-    document.body,
-) : (
-    <Popover
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
-    />
-));
-
-ContextMenuPopover.propTypes = propTypes;
-ContextMenuPopover.defaultProps = defaultProps;
-ContextMenuPopover.displayName = 'ContextMenuPopover';
-
-export default withWindowDimensions(ContextMenuPopover);
diff --git a/src/components/ContextMenuPopover/index.native.js b/src/components/ContextMenuPopover/index.native.js
deleted file mode 100644
index 6d278e9e321a..000000000000
--- a/src/components/ContextMenuPopover/index.native.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import {propTypes, defaultProps} from '../Popover/PopoverPropTypes';
-import withWindowDimensions from '../withWindowDimensions';
-import Popover from '../Popover';
-import CONST from '../../CONST';
-
-/*
- * This is a convenience wrapper around the Modal component for a responsive Popover.
- * On small screen widths, it uses BottomDocked modal type, and a Popover type on wide screen widths.
- */
-const ContextMenuPopover = props => (
-    <Popover
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
-    />
-);
-
-ContextMenuPopover.propTypes = propTypes;
-ContextMenuPopover.defaultProps = defaultProps;
-ContextMenuPopover.displayName = 'ContextMenuPopover';
-
-export default withWindowDimensions(ContextMenuPopover);

From d818277f5685b2294e110927eef28aee2c298540 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Tue, 15 Jun 2021 03:54:02 +0530
Subject: [PATCH 53/81] extra changes remvoed

---
 src/components/PopoverWithMeasuredContent.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js
index 82e83f3fa86e..bf67ba3a3327 100644
--- a/src/components/PopoverWithMeasuredContent.js
+++ b/src/components/PopoverWithMeasuredContent.js
@@ -34,7 +34,7 @@ const propTypes = {
 };
 
 const defaultProps = {
-    ...(_.omit(defaultPopoverProps)),
+    ...defaultPopoverProps,
 
     // Default positioning of the popover
     anchorOrigin: {

From 825d108c101262fe219cc9752a388b58113a522a Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Mon, 14 Jun 2021 12:34:03 -1000
Subject: [PATCH 54/81] make some requested changes

---
 src/libs/actions/BankAccounts.js                  | 2 +-
 src/libs/models/BankAccount.js                    | 2 +-
 src/pages/ReimbursementAccount/BankAccountStep.js | 5 -----
 src/pages/ReimbursementAccount/CompanyStep.js     | 3 ++-
 4 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 17ecfc2b6cd5..faa6314f2574 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -293,7 +293,7 @@ function goToWithdrawalStepID(stepID, achData) {
         }
     }
 
-    // When going from CompanyStep to BankAccountStep, show the manual form instead of Plaid
+    // When going back to the BankAccountStep from the Company Step
     if (newACHData.currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && stepID === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) {
         newACHData.subStep = 'manual';
     }
diff --git a/src/libs/models/BankAccount.js b/src/libs/models/BankAccount.js
index cfecacb67af6..c2a68747e85b 100644
--- a/src/libs/models/BankAccount.js
+++ b/src/libs/models/BankAccount.js
@@ -69,7 +69,7 @@ class BankAccount {
     }
 
     /**
-     * Get all user emails having access to this bank account
+     * Get all user emails that have access to this bank account
      * @return {String[]}
      */
     getSharees() {
diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
index 8cfb895a4bc5..8abc2595f820 100644
--- a/src/pages/ReimbursementAccount/BankAccountStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -58,11 +58,6 @@ class BankAccountStep extends React.Component {
     }
 
     addManualAccount() {
-        if (!this.state.hasAcceptedTerms) {
-            console.error('Must accept terms before proceeding');
-            return;
-        }
-
         setupWithdrawalAccount({
             acceptTerms: this.state.hasAcceptedTerms,
             accountNumber: this.state.accountNumber,
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
index 9ed1a3e90e6a..232670b2585a 100644
--- a/src/pages/ReimbursementAccount/CompanyStep.js
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import {View} from 'react-native';
 import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import CONST from '../../CONST';
 import {goToWithdrawalStepID} from '../../libs/actions/BankAccounts';
 import Navigation from '../../libs/Navigation/Navigation';
 
@@ -9,7 +10,7 @@ const CompanyStep = () => (
         <HeaderWithCloseButton
             title="Company Information"
             shouldShowBackButton
-            onBackButtonPress={() => goToWithdrawalStepID('BankAccountStep')}
+            onBackButtonPress={() => goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT)}
             onCloseButtonPress={Navigation.dismissModal}
         />
     </View>

From c900d39c956df390dda2a25ea3747c6beee9fc4c Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Mon, 14 Jun 2021 13:23:23 -1000
Subject: [PATCH 55/81] Make requested changes

---
 src/CONST.js                                      |  1 +
 src/languages/es.js                               |  2 +-
 src/libs/Navigation/AppNavigator/AuthScreens.js   |  9 +++++----
 src/pages/ReimbursementAccount/BankAccountStep.js | 10 ++++++----
 4 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/src/CONST.js b/src/CONST.js
index 0d090cd7fe61..ef7f2109060e 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -11,6 +11,7 @@ const CONST = {
     },
     BANK_ACCOUNT: {
         STEP: {
+            // In the order they appear in the VBA flow
             BANK_ACCOUNT: 'BankAccountStep',
             COMPANY: 'CompanyStep',
             REQUESTOR: 'RequestorStep',
diff --git a/src/languages/es.js b/src/languages/es.js
index ca70af2d2211..00e53087b9d2 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -266,7 +266,7 @@ export default {
         checkHelpLine: 'Su número de ruta y número de cuenta se pueden encontrar en un cheque para la cuenta.',
         iAcceptThe: 'Acepto los ',
         hasPhoneLoginError: 'Para agregar una cuenta bancaria verificada, asegúrese de que su inicio de sesión principal sea un correo electrónico válido y vuelva a intentarlo. Puede agregar su número de teléfono como inicio de sesión secundario.',
-        hasBeenThrottledError: ({fromNow}) => `Por razones de seguridad, nos tomamos un descanso de la configuración de la cuenta bancaria para que pueda verificar la información de su empresa. Inténtalo de nuevo ${fromNow}. Lo siento!`,
+        hasBeenThrottledError: ({fromNow}) => `Por razones de seguridad, nos tomamos un descanso de la configuración de la cuenta bancaria para que pueda verificar la información de su empresa. Inténtalo de nuevo ${fromNow}. ¡Lo siento!`,
     },
     addPersonalBankAccountPage: {
         enterPassword: 'Escribe una contraseña',
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index c63ae0e5e2de..349311267c6c 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -278,15 +278,16 @@ class AuthScreens extends React.Component {
                     listeners={modalScreenListeners}
                 />
                 <RootStack.Screen
-                    name="ReimbursementAccount"
+                    name="NewWorkspace"
                     options={modalScreenOptions}
-                    component={ReimbursementAccountModalStackNavigator}
+                    component={NewWorkspaceStackNavigator}
                     listeners={modalScreenListeners}
                 />
                 <RootStack.Screen
-                    name="NewWorkspace"
+                    name="ReimbursementAccount"
                     options={modalScreenOptions}
-                    component={NewWorkspaceStackNavigator}
+                    component={ReimbursementAccountModalStackNavigator}
+                    listeners={modalScreenListeners}
                 />
             </RootStack.Navigator>
         );
diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
index 8abc2595f820..edfaa3bf3987 100644
--- a/src/pages/ReimbursementAccount/BankAccountStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -88,10 +88,7 @@ class BankAccountStep extends React.Component {
             acceptTerms: true,
             setupType: CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID,
 
-            // Note: These are hardcoded as we're not supporting AU bank accounts for the free plan
-            country: CONST.COUNTRY.US,
-            currency: CONST.CURRENCY.USD,
-            fieldsType: CONST.BANK_ACCOUNT.FIELDS_TYPE.LOCAL,
+            // Params passed via the Plaid callback when an account is selected
             password: params.password,
             plaidAccessToken: params.plaidLinkToken,
             accountNumber: params.account.accountNumber,
@@ -101,6 +98,11 @@ class BankAccountStep extends React.Component {
             isSavings: params.account.isSavings,
             bankName: params.account.bankName,
             addressName: params.account.addressName,
+
+            // Note: These are hardcoded as we're not supporting AU bank accounts for the free plan
+            country: CONST.COUNTRY.US,
+            currency: CONST.CURRENCY.USD,
+            fieldsType: CONST.BANK_ACCOUNT.FIELDS_TYPE.LOCAL,
         });
     }
 

From 1b1e21091741cad5de42ae951c6af6fa317f5ea5 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Mon, 14 Jun 2021 14:14:57 -1000
Subject: [PATCH 56/81] Make some requested changes

---
 src/libs/actions/BankAccounts.js              | 48 +++++++++++--------
 src/pages/ReimbursementAccount/CompanyStep.js |  4 +-
 2 files changed, 31 insertions(+), 21 deletions(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index faa6314f2574..baea5c4c0edc 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -280,7 +280,13 @@ Onyx.connect({
     },
 });
 
-function goToWithdrawalStepID(stepID, achData) {
+/**
+ * Navigate to a specific step in the VBA flow
+ *
+ * @param {String} stepID
+ * @param {Object} achData
+ */
+function goToWithdrawalAccountSetupStep(stepID, achData) {
     const newACHData = {...previousACHData};
 
     // If we go back to Requestor Step, reset any validation and previously answered questions from expectID.
@@ -347,8 +353,8 @@ function fetchFreePlanVerifiedBankAccount() {
             const throttledDate = lodashGet(response.nameValuePairs, CONST.NVP.ACH_DATA_THROTTLED, '');
 
             // Next we'll build the achData and save it to Onyx
-            // If the user already is already setting up a bank account we will want to continue the flow for them
-            let currentStep;
+            // If the user is already setting up a bank account we will continue the flow for them
+            let currentStep = previousACHData.currentStep;
             const achData = bankAccount ? bankAccount.toACHData() : {};
             achData.useOnfido = true;
             achData.policyID = '';
@@ -374,10 +380,10 @@ function fetchFreePlanVerifiedBankAccount() {
             // Temporary fix for Onfido flow. Can be removed by nkuoch after Sept 1 2020.
             // @TODO not sure if we still need this or what this is about, but seems like maybe yes...
             if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT && achData.useOnfido) {
-                const onfidoRes = lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO);
-                const sdkToken = lodashGet(onfidoRes, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
+                const onfidoResponse = lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO);
+                const sdkToken = lodashGet(onfidoResponse, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
                 if (sdkToken && !achData.isOnfidoSetupComplete
-                        && onfidoRes.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
+                    && onfidoResponse.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
                 ) {
                     currentStep = CONST.BANK_ACCOUNT.STEP.REQUESTOR;
                 }
@@ -400,7 +406,9 @@ function fetchFreePlanVerifiedBankAccount() {
                             ? CONST.BANK_ACCOUNT.STEP.VALIDATION : CONST.BANK_ACCOUNT.STEP.COMPANY;
                         achData.bankAccountInReview = hasTriedToUpgrade;
                     } else {
-                        // We're not going to have a EnableStep, but we can show this account as open if accessed
+                        // In Expensify.cash we do not show a specific view for the EnableStep since we will enable the
+                        // Expensify card automatically. However, we will still handle that step and show the Validate
+                        // view.
                         currentStep = CONST.BANK_ACCOUNT.STEP.ENABLE;
                     }
                 }
@@ -412,7 +420,7 @@ function fetchFreePlanVerifiedBankAccount() {
             }
 
             Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {throttledDate});
-            goToWithdrawalStepID(currentStep, achData);
+            goToWithdrawalAccountSetupStep(currentStep, achData);
         })
         .finally(() => {
             Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
@@ -448,6 +456,7 @@ const WITHDRAWAL_ACCOUNT_STEPS = [
 
 /**
  * Get step position in the array
+ * @private
  * @param {String} stepID
  * @return {Number}
  */
@@ -472,7 +481,7 @@ function getNextStepID() {
  * @param {Number} bankAccountID
  */
 function setFreePlanVerifiedBankAccountID(bankAccountID) {
-    API.SetNameValuePair({name: 'expensify_freePlanBankAccountID', value: bankAccountID});
+    API.SetNameValuePair({name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, value: bankAccountID});
 }
 
 /**
@@ -484,7 +493,7 @@ function setupWithdrawalAccount(data) {
     Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
 
     previousACHData = {...previousACHData, ...data};
-    if (data && data.isSavings !== undefined) {
+    if (data && !_.isUndefined(data.isSavings)) {
         previousACHData.isSavings = Boolean(data.isSavings);
     }
     if (!previousACHData.setupType) {
@@ -532,17 +541,17 @@ function setupWithdrawalAccount(data) {
                         CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ID,
                     );
                     if (previousACHData.useOnfido) {
-                        const onfidoRes = lodashGet(
+                        const onfidoResponse = lodashGet(
                             achData,
                             CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO,
                         );
-                        const sdkToken = lodashGet(onfidoRes, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
+                        const sdkToken = lodashGet(onfidoResponse, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
                         if (sdkToken && !previousACHData.isOnfidoSetupComplete
-                                && onfidoRes.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
+                                && onfidoResponse.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
                         ) {
                             // Requestor Step still needs to run Onfido
                             achData.sdkToken = sdkToken;
-                            goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.REQUESTOR, achData);
+                            goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR, achData);
                             return;
                         }
                     } else if (requestorResponse) {
@@ -559,7 +568,7 @@ function setupWithdrawalAccount(data) {
                         }
                         if (!_.isEmpty(questions)) {
                             achData.questions = questions;
-                            goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.REQUESTOR, achData);
+                            goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR, achData);
                             return;
                         }
                     }
@@ -568,7 +577,7 @@ function setupWithdrawalAccount(data) {
                 if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) {
                     // Get an up-to-date bank account list so that we can allow the user to validate their newly
                     // generated bank account
-                    return API.Get({returnValueList: 'bankAccountList'})
+                    API.Get({returnValueList: 'bankAccountList'})
                         .then((bankAccountListResponse) => {
                             const bankAccountJSON = _.findWhere(bankAccountListResponse.bankAccountList, {
                                 bankAccountID: previousACHData.bankAccountID,
@@ -580,8 +589,9 @@ function setupWithdrawalAccount(data) {
                             achData.bankAccountInReview = needsToPassLatestChecks
                                 || achData.state === BankAccount.STATE.VERIFYING;
 
-                            goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.VALIDATION, achData);
+                            goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.VALIDATION, achData);
                         });
+                    return;
                 }
 
                 if ((currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && previousACHData.bankAccountInReview)
@@ -601,7 +611,7 @@ function setupWithdrawalAccount(data) {
             }
 
             // Go to next step
-            goToWithdrawalStepID(nextStep, achData);
+            goToWithdrawalAccountSetupStep(nextStep, achData);
 
             if (error) {
                 console.error(`Error setting up withdrawal account: ${error}`);
@@ -619,5 +629,5 @@ export {
     fetchUserWallet,
     fetchFreePlanVerifiedBankAccount,
     setupWithdrawalAccount,
-    goToWithdrawalStepID,
+    goToWithdrawalAccountSetupStep,
 };
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
index 232670b2585a..8b8dc52f38f1 100644
--- a/src/pages/ReimbursementAccount/CompanyStep.js
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -2,7 +2,7 @@ import React from 'react';
 import {View} from 'react-native';
 import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
 import CONST from '../../CONST';
-import {goToWithdrawalStepID} from '../../libs/actions/BankAccounts';
+import {goToWithdrawalAccountSetupStep} from '../../libs/actions/BankAccounts';
 import Navigation from '../../libs/Navigation/Navigation';
 
 const CompanyStep = () => (
@@ -10,7 +10,7 @@ const CompanyStep = () => (
         <HeaderWithCloseButton
             title="Company Information"
             shouldShowBackButton
-            onBackButtonPress={() => goToWithdrawalStepID(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT)}
+            onBackButtonPress={() => goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT)}
             onCloseButtonPress={Navigation.dismissModal}
         />
     </View>

From cc62bd200d7f75b2cb1abaa741f7ec7759779592 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 18:22:24 -0700
Subject: [PATCH 57/81] add welcomeNote label

---
 src/languages/en.js              | 1 +
 src/languages/es.js              | 1 +
 src/pages/WorkspaceInvitePage.js | 3 +++
 3 files changed, 5 insertions(+)

diff --git a/src/languages/en.js b/src/languages/en.js
index 3c50e06b9d9b..792e7f168e15 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -349,6 +349,7 @@ export default {
         invite: {
             invitePeople: 'Invite People',
             invitePeoplePrompt: 'Invite a colleague to your workspace.',
+            personalMessagePrompt: 'Add a Personal Message (Optional)',
             enterEmailOrPhone: 'Email or Phone',
             welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
         },
diff --git a/src/languages/es.js b/src/languages/es.js
index 31c21fef1de2..ecbeb2d30a94 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -303,6 +303,7 @@ export default {
         invite: {
             invitePeople: 'Invite People',
             invitePeoplePrompt: 'Invite a colleague to your workspace.',
+            personalMessagePrompt: 'Add a Personal Message (Optional)',
             enterEmailOrPhone: 'Email or Phone',
             welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
         },
diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 5c427188cdd4..7deb91009851 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -93,6 +93,9 @@ class WorkspaceInvitePage extends React.Component {
                                 onChangeText={text => this.setState({emailOrPhone: text})}
                             />
                         </View>
+                        <Text style={[styles.textP]}>
+                            {this.props.translate('workspace.invite.personalMessagePrompt')}
+                        </Text>
                         <View style={styles.mb6}>
                             <TextInput
                                 autoCompleteType="off"

From a6d715b4997d1a9cf531e4eae68cdd7b56f3208c Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 18:26:49 -0700
Subject: [PATCH 58/81] match styles from mockup

---
 src/pages/WorkspaceInvitePage.js | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/WorkspaceInvitePage.js
index 7deb91009851..2adf986b66c3 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/WorkspaceInvitePage.js
@@ -84,19 +84,21 @@ class WorkspaceInvitePage extends React.Component {
                             {this.props.translate('workspace.invite.invitePeoplePrompt')}
                         </Text>
                         <View style={styles.mb6}>
+                            <Text style={[styles.textP, styles.mb2]}>
+                                {this.props.translate('workspace.invite.enterEmailOrPhone')}
+                            </Text>
                             <TextInput
                                 autoCompleteType="off"
                                 autoCorrect={false}
                                 style={[styles.textInput]}
                                 value={this.state.emailOrPhone}
-                                placeholder={this.props.translate('workspace.invite.enterEmailOrPhone')}
                                 onChangeText={text => this.setState({emailOrPhone: text})}
                             />
                         </View>
-                        <Text style={[styles.textP]}>
-                            {this.props.translate('workspace.invite.personalMessagePrompt')}
-                        </Text>
                         <View style={styles.mb6}>
+                            <Text style={[styles.textP, styles.mb2]}>
+                                {this.props.translate('workspace.invite.personalMessagePrompt')}
+                            </Text>
                             <TextInput
                                 autoCompleteType="off"
                                 autoCorrect={false}
@@ -109,7 +111,7 @@ class WorkspaceInvitePage extends React.Component {
                             />
                         </View>
                         <TextLink href="https://use.expensify.com/privacy">
-                            {`${this.props.translate('common.privacyPolicy')} `}
+                            {`${this.props.translate('common.privacy')} `}
                         </TextLink>
                     </View>
                     <View style={styles.flexGrow0}>

From 7e5937faa6bfd03e7ca00a8218f3289c25a10d53 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 18:32:07 -0700
Subject: [PATCH 59/81] move page into workspace folder

---
 .../AppNavigator/ModalStackNavigators.js      |  2 +-
 .../{ => workspace}/WorkspaceInvitePage.js    | 22 +++++++++----------
 2 files changed, 12 insertions(+), 12 deletions(-)
 rename src/pages/{ => workspace}/WorkspaceInvitePage.js (88%)

diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index b02a4810c279..4c57b1edb53e 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -22,7 +22,7 @@ import ReportParticipantsPage from '../../../pages/ReportParticipantsPage';
 import EnablePaymentsPage from '../../../pages/EnablePayments';
 import BusinessBankAccountNewPage from '../../../pages/BusinessBankAccount/NewPage';
 import AddPersonalBankAccountPage from '../../../pages/AddPersonalBankAccountPage';
-import WorkspaceInvitePage from '../../../pages/WorkspaceInvitePage';
+import WorkspaceInvitePage from '../../../pages/workspace/WorkspaceInvitePage';
 import NewWorkspacePage from '../../../pages/workspace/NewWorkspacePage';
 
 const defaultSubRouteOptions = {
diff --git a/src/pages/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
similarity index 88%
rename from src/pages/WorkspaceInvitePage.js
rename to src/pages/workspace/WorkspaceInvitePage.js
index 2adf986b66c3..8dc09b80aadf 100644
--- a/src/pages/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -2,17 +2,17 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import {TextInput, View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
-import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
-import ScreenWrapper from '../components/ScreenWrapper';
-import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
-import Navigation from '../libs/Navigation/Navigation';
-import styles from '../styles/styles';
-import Text from '../components/Text';
-import Button from '../components/Button';
-import compose from '../libs/compose';
-import ONYXKEYS from '../ONYXKEYS';
-import {invite} from '../libs/actions/Policy';
-import TextLink from '../components/TextLink';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import ScreenWrapper from '../../components/ScreenWrapper';
+import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import Navigation from '../../libs/Navigation/Navigation';
+import styles from '../../styles/styles';
+import Text from '../../components/Text';
+import Button from '../../components/Button';
+import compose from '../../libs/compose';
+import ONYXKEYS from '../../ONYXKEYS';
+import {invite} from '../../libs/actions/Policy';
+import TextLink from '../../components/TextLink';
 
 const propTypes = {
     ...withLocalizePropTypes,

From e8f13000daa9d6f56432d5add192c5a8ff9f1194 Mon Sep 17 00:00:00 2001
From: Jasper Huang <jasper@expensify.com>
Date: Tue, 15 Jun 2021 09:46:00 +0800
Subject: [PATCH 60/81] Update Spanish translation

---
 src/languages/es.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/languages/es.js b/src/languages/es.js
index a5f79dff2c00..cab80c882670 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -207,9 +207,7 @@ export default {
         notifications: 'Notificaciones',
         receiveRelevantFeatureUpdatesAndExpensifyNews: 'Recibir noticias sobre Expensify y actualizaciones del producto',
         priorityMode: 'Modo Prioridad',
-
-        // TODO
-        language: '????',
+        language: 'Idioma',
     },
     signInPage: {
         expensifyDotCash: 'Expensify.cash',

From 59ab4880c895433055e9002ff5a9046319339b72 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 19:50:50 -0700
Subject: [PATCH 61/81] show error on invite failure

---
 src/languages/en.js                        |  1 +
 src/languages/es.js                        |  1 +
 src/libs/actions/Policy.js                 | 16 ++++++++++++++++
 src/pages/workspace/WorkspaceInvitePage.js |  1 +
 4 files changed, 19 insertions(+)

diff --git a/src/languages/en.js b/src/languages/en.js
index 792e7f168e15..627f6e5cf1b2 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -351,6 +351,7 @@ export default {
             invitePeoplePrompt: 'Invite a colleague to your workspace.',
             personalMessagePrompt: 'Add a Personal Message (Optional)',
             enterEmailOrPhone: 'Email or Phone',
+            growlMessageOnFailure: 'An error occurred inviting the user to the workspace, please try again',
             welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
         },
     },
diff --git a/src/languages/es.js b/src/languages/es.js
index ecbeb2d30a94..aa11fd0cdfa3 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -305,6 +305,7 @@ export default {
             invitePeoplePrompt: 'Invite a colleague to your workspace.',
             personalMessagePrompt: 'Add a Personal Message (Optional)',
             enterEmailOrPhone: 'Email or Phone',
+            growlMessageOnFailure: 'An error occurred inviting the user to the workspace, please try again',
             welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
         },
     },
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index e44f634c8706..3cc7fa99cd39 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -3,6 +3,9 @@ import Onyx from 'react-native-onyx';
 import {GetPolicySummaryList, GetPolicyList, Policy_Employees_Merge} from '../API';
 import ONYXKEYS from '../../ONYXKEYS';
 import {formatPersonalDetails} from './PersonalDetails';
+import Growl from '../Growl';
+import CONST from '../../CONST';
+import {translate} from '../translate';
 
 const allPolicies = {};
 Onyx.connect({
@@ -14,6 +17,16 @@ Onyx.connect({
     },
 });
 
+let translateLocal = (phrase, variables) => translate(CONST.DEFAULT_LOCALE, phrase, variables);
+Onyx.connect({
+    key: ONYXKEYS.PREFERRED_LOCALE,
+    callback: (preferredLocale) => {
+        if (preferredLocale) {
+            translateLocal = (phrase, variables) => translate(preferredLocale, phrase, variables);
+        }
+    },
+});
+
 /**
  * Takes a full policy summary that is returned from the policySummaryList and simplifies it so we are only storing
  * the pieces of data that we need to in Onyx
@@ -112,6 +125,9 @@ function invite(login, welcomeNote, policyID) {
             const policyDataWithoutLogin = _.clone(allPolicies[key]);
             policyDataWithoutLogin.employeeList = _.without(allPolicies[key].employeeList, login);
             Onyx.set(key, policyDataWithoutLogin);
+
+            // Show the user feedback that the addition failed
+            Growl.show(translateLocal('workspace.invite.growlMessageOnFailure'), CONST.GROWL.ERROR, 3000);
         });
 }
 
diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index 8dc09b80aadf..92e4ac4bf8ac 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -69,6 +69,7 @@ class WorkspaceInvitePage extends React.Component {
     inviteUser() {
         invite(this.state.emailOrPhone, this.state.welcomeNote || this.getWelcomeNotePlaceholder(),
             this.props.route.params.policyID);
+        Navigation.goBack();
     }
 
     render() {

From ff4f09587da756d972682b036079591cd8284208 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 20:49:08 -0700
Subject: [PATCH 62/81] more style updates

---
 src/pages/workspace/WorkspaceInvitePage.js | 8 +++++++-
 src/styles/styles.js                       | 2 +-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index 92e4ac4bf8ac..7ab91d2ef94b 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -13,6 +13,8 @@ import compose from '../../libs/compose';
 import ONYXKEYS from '../../ONYXKEYS';
 import {invite} from '../../libs/actions/Policy';
 import TextLink from '../../components/TextLink';
+import getEmailKeyboardType from '../../libs/getEmailKeyboardType';
+import themeColors from '../../styles/themes/default';
 
 const propTypes = {
     ...withLocalizePropTypes,
@@ -89,10 +91,12 @@ class WorkspaceInvitePage extends React.Component {
                                 {this.props.translate('workspace.invite.enterEmailOrPhone')}
                             </Text>
                             <TextInput
-                                autoCompleteType="off"
+                                autoCompleteType="email"
                                 autoCorrect={false}
+                                autoCapitalize="none"
                                 style={[styles.textInput]}
                                 value={this.state.emailOrPhone}
+                                keyboardType={getEmailKeyboardType()}
                                 onChangeText={text => this.setState({emailOrPhone: text})}
                             />
                         </View>
@@ -105,9 +109,11 @@ class WorkspaceInvitePage extends React.Component {
                                 autoCorrect={false}
                                 style={[styles.textInput, styles.workspaceInviteWelcome]}
                                 numberOfLines={10}
+                                textAlignVertical="top"
                                 multiline
                                 value={this.state.welcomeNote}
                                 placeholder={this.getWelcomeNotePlaceholder()}
+                                placeholderTextColor={themeColors.placeholderText}
                                 onChangeText={text => this.setState({welcomeNote: text})}
                             />
                         </View>
diff --git a/src/styles/styles.js b/src/styles/styles.js
index f8ecb265b128..8e5dccda69b6 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1532,7 +1532,7 @@ const styles = {
     },
 
     workspaceInviteWelcome: {
-        minHeight: 200,
+        minHeight: 150,
     },
 };
 

From f34c715c5f3690662f10f1ec1e4ab71b9570e17c Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 21:00:26 -0700
Subject: [PATCH 63/81] fix textInput spacing on mobile

---
 src/pages/workspace/WorkspaceInvitePage.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index 7ab91d2ef94b..6bc7cd20a279 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -107,7 +107,7 @@ class WorkspaceInvitePage extends React.Component {
                             <TextInput
                                 autoCompleteType="off"
                                 autoCorrect={false}
-                                style={[styles.textInput, styles.workspaceInviteWelcome]}
+                                style={[styles.textInput, styles.workspaceInviteWelcome, styles.mb6]}
                                 numberOfLines={10}
                                 textAlignVertical="top"
                                 multiline
@@ -116,10 +116,10 @@ class WorkspaceInvitePage extends React.Component {
                                 placeholderTextColor={themeColors.placeholderText}
                                 onChangeText={text => this.setState({welcomeNote: text})}
                             />
+                            <TextLink href="https://use.expensify.com/privacy">
+                                {`${this.props.translate('common.privacy')} `}
+                            </TextLink>
                         </View>
-                        <TextLink href="https://use.expensify.com/privacy">
-                            {`${this.props.translate('common.privacy')} `}
-                        </TextLink>
                     </View>
                     <View style={styles.flexGrow0}>
                         <Button

From 409f66ac7982a130f3d48dea3815139cbcd2a868 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 21:10:01 -0700
Subject: [PATCH 64/81] simplify privacy link

---
 src/pages/workspace/WorkspaceInvitePage.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index 6bc7cd20a279..5c0e713e774d 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -117,7 +117,7 @@ class WorkspaceInvitePage extends React.Component {
                                 onChangeText={text => this.setState({welcomeNote: text})}
                             />
                             <TextLink href="https://use.expensify.com/privacy">
-                                {`${this.props.translate('common.privacy')} `}
+                                {this.props.translate('common.privacy')}
                             </TextLink>
                         </View>
                     </View>

From 466a9bc9b9e1f2e8fe2e23fa61dee5c80d507908 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 21:43:43 -0700
Subject: [PATCH 65/81] improve error messages

---
 src/languages/en.js                        | 3 ++-
 src/languages/es.js                        | 3 ++-
 src/libs/actions/Policy.js                 | 7 ++++++-
 src/pages/workspace/WorkspaceInvitePage.js | 8 ++++++++
 4 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index 627f6e5cf1b2..b665cd6ca3e2 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -351,7 +351,8 @@ export default {
             invitePeoplePrompt: 'Invite a colleague to your workspace.',
             personalMessagePrompt: 'Add a Personal Message (Optional)',
             enterEmailOrPhone: 'Email or Phone',
-            growlMessageOnFailure: 'An error occurred inviting the user to the workspace, please try again',
+            pleaseEnterValidLogin: 'Please ensure the email or phone number is valid (e.g. +15005550006).',
+            genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.',
             welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
         },
     },
diff --git a/src/languages/es.js b/src/languages/es.js
index aa11fd0cdfa3..2431531e76e7 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -305,7 +305,8 @@ export default {
             invitePeoplePrompt: 'Invite a colleague to your workspace.',
             personalMessagePrompt: 'Add a Personal Message (Optional)',
             enterEmailOrPhone: 'Email or Phone',
-            growlMessageOnFailure: 'An error occurred inviting the user to the workspace, please try again',
+            pleaseEnterValidLogin: 'Please ensure the email or phone number is valid (e.g. +15005550006).',
+            genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.',
             welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
         },
     },
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 3cc7fa99cd39..4979f98a8a4b 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -127,7 +127,12 @@ function invite(login, welcomeNote, policyID) {
             Onyx.set(key, policyDataWithoutLogin);
 
             // Show the user feedback that the addition failed
-            Growl.show(translateLocal('workspace.invite.growlMessageOnFailure'), CONST.GROWL.ERROR, 3000);
+            let errorMessage = translateLocal('workspace.invite.genericFailureMessage');
+            if (data.jsonCode === 402) {
+                errorMessage += ' ' + translateLocal('workspace.invite.pleaseEnterValidLogin');
+            }
+
+            Growl.show(errorMessage, CONST.GROWL.ERROR, 5000);
         });
 }
 
diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index 5c0e713e774d..d9625f3a83d2 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -15,6 +15,9 @@ import {invite} from '../../libs/actions/Policy';
 import TextLink from '../../components/TextLink';
 import getEmailKeyboardType from '../../libs/getEmailKeyboardType';
 import themeColors from '../../styles/themes/default';
+import Growl from '../../libs/Growl';
+import CONST from '../../CONST';
+import Str from 'expensify-common/lib/str';
 
 const propTypes = {
     ...withLocalizePropTypes,
@@ -69,6 +72,11 @@ class WorkspaceInvitePage extends React.Component {
      * Handle the invite button click
      */
     inviteUser() {
+        if (!Str.isValidEmail(this.state.emailOrPhone) && !Str.isValidPhone(this.state.emailOrPhone)) {
+            Growl.show(this.props.translate('workspace.invite.pleaseEnterValidLogin'), CONST.GROWL.ERROR, 5000);
+            return;
+        }
+
         invite(this.state.emailOrPhone, this.state.welcomeNote || this.getWelcomeNotePlaceholder(),
             this.props.route.params.policyID);
         Navigation.goBack();

From c787e0179236aeb18e3f0211fec80bcf6c0be3e6 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 21:55:05 -0700
Subject: [PATCH 66/81] fix style

---
 src/libs/actions/Policy.js                 | 2 +-
 src/pages/workspace/WorkspaceInvitePage.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 4979f98a8a4b..ee7e42b00540 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -129,7 +129,7 @@ function invite(login, welcomeNote, policyID) {
             // Show the user feedback that the addition failed
             let errorMessage = translateLocal('workspace.invite.genericFailureMessage');
             if (data.jsonCode === 402) {
-                errorMessage += ' ' + translateLocal('workspace.invite.pleaseEnterValidLogin');
+                errorMessage += ` ${translateLocal('workspace.invite.pleaseEnterValidLogin')}`;
             }
 
             Growl.show(errorMessage, CONST.GROWL.ERROR, 5000);
diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index d9625f3a83d2..adf1316e2502 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import {TextInput, View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
+import Str from 'expensify-common/lib/str';
 import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
 import ScreenWrapper from '../../components/ScreenWrapper';
 import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
@@ -17,7 +18,6 @@ import getEmailKeyboardType from '../../libs/getEmailKeyboardType';
 import themeColors from '../../styles/themes/default';
 import Growl from '../../libs/Growl';
 import CONST from '../../CONST';
-import Str from 'expensify-common/lib/str';
 
 const propTypes = {
     ...withLocalizePropTypes,

From 4fe596a3f9621a4c1e7c199a5a6c1c339f82951f Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 23:30:59 -0700
Subject: [PATCH 67/81] translate to spanish

---
 src/languages/es.js | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/languages/es.js b/src/languages/es.js
index 2431531e76e7..8fe7b6bf0686 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -301,13 +301,13 @@ export default {
             requestCall: 'Concertar una llamada',
         },
         invite: {
-            invitePeople: 'Invite People',
-            invitePeoplePrompt: 'Invite a colleague to your workspace.',
-            personalMessagePrompt: 'Add a Personal Message (Optional)',
-            enterEmailOrPhone: 'Email or Phone',
-            pleaseEnterValidLogin: 'Please ensure the email or phone number is valid (e.g. +15005550006).',
-            genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.',
-            welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
+            invitePeople: 'Invitar a la gente',
+            invitePeoplePrompt: 'Invita a un colega a tu espacio de trabajo.',
+            personalMessagePrompt: 'Agregar un mensaje personal (Opcional)',
+            enterEmailOrPhone: 'Email o teléfono',
+            pleaseEnterValidLogin: 'Asegúrese de que el correo electrónico o el número de teléfono sean válidos (e.g. +15005550006).',
+            genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..',
+            welcomeNote: ({workspaceName}) => `¡Has sido invitado a la ${workspaceName} Espacio de trabajo! Descargue la aplicación móvil Expensify para comenzar a rastrear sus gastos.`,
         },
     },
 };

From 5be04c020ef52446e64cec52a2e89f822efd30d5 Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 23:46:17 -0700
Subject: [PATCH 68/81] address PR review comments

---
 src/libs/actions/Policy.js                 | 8 ++++----
 src/pages/workspace/WorkspaceInvitePage.js | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index ee7e42b00540..28dde23271bc 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -47,7 +47,7 @@ function getSimplifiedPolicyObject(fullPolicy) {
  * @param {Object} fullPolicy
  * @returns {Object}
  */
-function getSimplifiedMemberList(fullPolicy) {
+function getSimplifiedEmployeeListObject(fullPolicy) {
     const employeeListEmails = _.chain(fullPolicy.value.employeeList)
         .pluck('email')
         .flatten()
@@ -84,7 +84,7 @@ function getPolicyList() {
             if (data.jsonCode === 200) {
                 const policyDataToStore = _.reduce(data.policyList, (memo, policy) => ({
                     ...memo,
-                    [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: getSimplifiedMemberList(policy),
+                    [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: getSimplifiedEmployeeListObject(policy),
                 }), {});
                 Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataToStore);
             }
@@ -103,7 +103,7 @@ function invite(login, welcomeNote, policyID) {
 
     // Make a shallow copy to preserve original data, and concat the login
     const policy = _.clone(allPolicies[key]);
-    policy.employeeList = [...policy.employeeList, login];
+    policy.employeeList.push(login);
 
     // Optimistically add the user to the policy
     Onyx.set(key, policy);
@@ -116,7 +116,7 @@ function invite(login, welcomeNote, policyID) {
     })
         .then((data) => {
             // Save the personalDetails for the invited user in Onyx
-            if (data.jsonCode === 200 && data.personalDetails) {
+            if (data.jsonCode === 200) {
                 Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, formatPersonalDetails(data.personalDetails));
                 return;
             }
diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index adf1316e2502..b406b5f454e2 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -53,7 +53,7 @@ class WorkspaceInvitePage extends React.Component {
             welcomeNote: '',
         };
 
-        this.getWelcomeNotePlaceholder = this.getWelcomeNotePlaceholder.bind(this);
+        //this.getWelcomeNotePlaceholder = this.getWelcomeNotePlaceholder.bind(this);
         this.inviteUser = this.inviteUser.bind(this);
     }
 
@@ -87,7 +87,7 @@ class WorkspaceInvitePage extends React.Component {
             <ScreenWrapper>
                 <HeaderWithCloseButton
                     title={this.props.translate('workspace.invite.invitePeople')}
-                    onCloseButtonPress={() => Navigation.dismissModal()}
+                    onCloseButtonPress={Navigation.dismissModal}
                 />
                 <View style={[styles.p5, styles.flex1, styles.overflowAuto]}>
                     <View style={styles.flexGrow1}>

From ae3fc4a08f4700d871b39d826d3a8570a948d8aa Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Mon, 14 Jun 2021 23:52:29 -0700
Subject: [PATCH 69/81] remove commented out line

---
 src/pages/workspace/WorkspaceInvitePage.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index b406b5f454e2..7637ee9947ea 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -53,7 +53,6 @@ class WorkspaceInvitePage extends React.Component {
             welcomeNote: '',
         };
 
-        //this.getWelcomeNotePlaceholder = this.getWelcomeNotePlaceholder.bind(this);
         this.inviteUser = this.inviteUser.bind(this);
     }
 

From 9998549bba021b9b4ed5735418251ddca34aa3d7 Mon Sep 17 00:00:00 2001
From: Rajat Parashar <parasharrajat@users.noreply.github.com>
Date: Tue, 15 Jun 2021 20:22:10 +0530
Subject: [PATCH 70/81] fix: propTypes

---
 src/components/Modal/BaseModal.js | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index 6b0caf683376..24e576e611a3 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -1,15 +1,28 @@
 import React, {PureComponent} from 'react';
 import {View} from 'react-native';
+import PropTypes from 'prop-types';
 import ReactNativeModal from 'react-native-modal';
 import {SafeAreaInsetsContext} from 'react-native-safe-area-context';
 
 import KeyboardShortcut from '../../libs/KeyboardShortcut';
 import styles, {getModalPaddingStyles, getSafeAreaPadding} from '../../styles/styles';
 import themeColors from '../../styles/themes/default';
-import {propTypes, defaultProps} from './ModalPropTypes';
+import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './ModalPropTypes';
 import getModalStyles from '../../styles/getModalStyles';
 import {setModalVisibility} from '../../libs/actions/Modal';
 
+const propTypes = {
+    ...modalPropTypes,
+
+    /** The ref to the modal container */
+    forwardedRef: PropTypes.func,
+};
+
+const defaultProps = {
+    ...modalDefaultProps,
+    forwardedRef: () => {},
+};
+
 class BaseModal extends PureComponent {
     constructor(props) {
         super(props);
@@ -129,7 +142,7 @@ class BaseModal extends PureComponent {
                                     ...modalContainerStyle,
                                     ...modalPaddingStyles,
                                 }}
-                                ref={this.props.forwardRef}
+                                ref={this.props.forwardedRef}
                             >
                                 {this.props.children}
                             </View>
@@ -146,5 +159,5 @@ BaseModal.defaultProps = defaultProps;
 BaseModal.displayName = 'BaseModal';
 export default React.forwardRef((props, ref) => (
     // eslint-disable-next-line react/jsx-props-no-spreading
-    <BaseModal {...props} forwardRef={ref} />
+    <BaseModal {...props} forwardedRef={ref} />
 ));

From 2aacbc9aed19c82bcbb3e274236a9d56ef728913 Mon Sep 17 00:00:00 2001
From: Ionatan Wiznia <ionatan@expensify.com>
Date: Tue, 15 Jun 2021 10:12:58 -0600
Subject: [PATCH 71/81] Add missing spanish tranlsation

---
 src/languages/es.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/languages/es.js b/src/languages/es.js
index a49bb131d0ad..d7562cad2001 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -114,6 +114,7 @@ export default {
         requestMoney: 'Pedir Dinero',
         pay: 'Pagar',
         viewDetails: 'Ver detalles',
+        settleExpensify: 'Pagar con Expensify',
         settleElsewhere: 'Voy a pagar de otra forma',
         decline: 'Rechazar',
         settlePaypalMe: 'Pagar con PayPal.me',

From a48ddb5e5620e68f421bfa381559caddb620ac21 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Tue, 15 Jun 2021 08:03:57 -1000
Subject: [PATCH 72/81] use promiseAllSettled

---
 src/libs/actions/BankAccounts.js | 74 ++++++++++++++++----------------
 1 file changed, 36 insertions(+), 38 deletions(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index baea5c4c0edc..03bea9652e0f 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -7,6 +7,7 @@ import CONST from '../../CONST';
 import ONYXKEYS from '../../ONYXKEYS';
 import * as API from '../API';
 import BankAccount from '../models/BankAccount';
+import promiseAllSettled from '../promiseAllSettled';
 
 /**
  * Gets the Plaid Link token used to initialize the Plaid SDK
@@ -312,45 +313,42 @@ function goToWithdrawalAccountSetupStep(stepID, achData) {
  */
 function fetchFreePlanVerifiedBankAccount() {
     Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
-    let bankAccountID;
-    let bankAccount;
-    let kycVerificationsMigration;
-    API.Get({
-        returnValueList: 'nameValuePairs',
-        name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
-    })
-        .then((response) => {
-            bankAccountID = lodashGet(response.nameValuePairs, CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, '');
-
-            if (!bankAccountID) {
-                return Promise.resolve({});
-            }
-
-            return API.Get({
-                returnValueList: 'nameValuePairs',
-                name: 'expensify_migration_2020_04_28_RunKycVerifications',
-            });
-        })
-        .then((response) => {
-            kycVerificationsMigration = lodashGet(
-                response.nameValuePairs,
-                'expensify_migration_2020_04_28_RunKycVerifications',
-                '',
+    promiseAllSettled([
+        API.Get({
+            returnValueList: 'nameValuePairs',
+            name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
+        }),
+        API.Get({
+            returnValueList: 'nameValuePairs',
+            name: 'expensify_migration_2020_04_28_RunKycVerifications',
+        }),
+        API.Get({
+            returnValueList: 'nameValuePairs',
+            name: CONST.NVP.ACH_DATA_THROTTLED,
+        }),
+        API.Get({returnValueList: 'bankAccountList'}),
+    ])
+        .then(([
+            freePlanBankAccountIDResponse,
+            kycVerificationsMigrationResponse,
+            achDataThrottledResponse,
+            bankAccountListResponse,
+        ]) => {
+            const bankAccountID = lodashGet(freePlanBankAccountIDResponse, [
+                'value', 'nameValuePairs', CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
+            ], '');
+            const kycVerificationsMigration = lodashGet(kycVerificationsMigrationResponse, [
+                'value', 'nameValuePairs', 'expensify_migration_2020_04_28_RunKycVerifications',
+            ], '');
+            const throttledDate = lodashGet(achDataThrottledResponse, [
+                'value', 'nameValuePairs', CONST.NVP.ACH_DATA_THROTTLED,
+            ], '');
+            const bankAccountJSON = _.find(
+                lodashGet(bankAccountListResponse, ['value', 'bankAccountList'], []), account => (
+                    account.bankAccountID === bankAccountID
+                ),
             );
-            return API.Get({returnValueList: 'bankAccountList'});
-        })
-        .then((response) => {
-            const bankAccountJSON = _.find(response.bankAccountList, account => (
-                account.bankAccountID === bankAccountID
-            ));
-            bankAccount = bankAccountJSON ? new BankAccount(bankAccountJSON) : null;
-            return API.Get({
-                returnValueList: 'nameValuePairs',
-                name: CONST.NVP.ACH_DATA_THROTTLED,
-            });
-        })
-        .then((response) => {
-            const throttledDate = lodashGet(response.nameValuePairs, CONST.NVP.ACH_DATA_THROTTLED, '');
+            const bankAccount = bankAccountJSON ? new BankAccount(bankAccountJSON) : null;
 
             // Next we'll build the achData and save it to Onyx
             // If the user is already setting up a bank account we will continue the flow for them

From 635bebf30d4e68334c442a505708ee0652a70976 Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Tue, 15 Jun 2021 08:12:11 -1000
Subject: [PATCH 73/81] add growl for errors

---
 src/libs/actions/BankAccounts.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 03bea9652e0f..2034d37f54cc 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -8,6 +8,7 @@ import ONYXKEYS from '../../ONYXKEYS';
 import * as API from '../API';
 import BankAccount from '../models/BankAccount';
 import promiseAllSettled from '../promiseAllSettled';
+import Growl from '../Growl';
 
 /**
  * Gets the Plaid Link token used to initialize the Plaid SDK
@@ -612,7 +613,7 @@ function setupWithdrawalAccount(data) {
             goToWithdrawalAccountSetupStep(nextStep, achData);
 
             if (error) {
-                console.error(`Error setting up withdrawal account: ${error}`);
+                Growl.show(`Error setting up account: ${error}`, CONST.GROWL.ERROR, 5000);
             }
         });
 }

From b4086631b32b83b06d0ea00362b7d36a5da9eb7b Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Tue, 15 Jun 2021 08:13:53 -1000
Subject: [PATCH 74/81] fix comment

---
 src/libs/actions/BankAccounts.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 2034d37f54cc..fde8be7c0c24 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -301,7 +301,7 @@ function goToWithdrawalAccountSetupStep(stepID, achData) {
         }
     }
 
-    // When going back to the BankAccountStep from the Company Step
+    // When going back to the BankAccountStep from the Company Step, show the manual form instead of Plaid
     if (newACHData.currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && stepID === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) {
         newACHData.subStep = 'manual';
     }

From 43924eb55440deadf48d90ed031b42907959870b Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Tue, 15 Jun 2021 09:47:11 -1000
Subject: [PATCH 75/81] Add optional stepToOpen parameter

---
 src/ROUTES.js                                 |  2 +-
 .../ReimbursementAccountPage.js               | 37 ++++++++++++++++++-
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/src/ROUTES.js b/src/ROUTES.js
index 8af98287e5d8..78a5c5ec5153 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -9,7 +9,7 @@ const REPORT = 'r';
 
 export default {
     ADD_PERSONAL_BANK_ACCOUNT: 'add-personal-bank-account',
-    ADD_VERIFIED_BANK_ACCOUNT: 'add-verified-bank-account',
+    ADD_VERIFIED_BANK_ACCOUNT: 'add-verified-bank-account/:stepToOpen?',
     HOME: '',
     SETTINGS: 'settings',
     SETTINGS_PROFILE: 'settings/profile',
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index 4435525e47fd..872a4a2cf2c7 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -49,6 +49,15 @@ const propTypes = {
         email: PropTypes.string,
     }).isRequired,
 
+    /** Route object from navigation */
+    route: PropTypes.shape({
+        /** Params that are passed into the route */
+        params: PropTypes.shape({
+            /** A step to navigate to if we need to drop the user into a specific point in the flow */
+            stepToOpen: PropTypes.string,
+        }),
+    }),
+
     ...withLocalizePropTypes,
 };
 
@@ -56,6 +65,11 @@ const defaultProps = {
     reimbursementAccount: {
         loading: true,
     },
+    route: {
+        params: {
+            stepToOpen: '',
+        },
+    },
 };
 
 class ReimbursementAccountPage extends React.Component {
@@ -63,6 +77,24 @@ class ReimbursementAccountPage extends React.Component {
         fetchFreePlanVerifiedBankAccount();
     }
 
+    /**
+     * @returns {String}
+     */
+    getStepToOpen() {
+        switch (lodashGet(this.props.route, ['params', 'stepToOpen'])) {
+            case 'company':
+                return CONST.BANK_ACCOUNT.STEP.COMPANY;
+            case 'requestor':
+                return CONST.BANK_ACCOUNT.STEP.REQUESTOR;
+            case 'ach-contract':
+                return CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT;
+            case 'validation':
+                return CONST.BANK_ACCOUNT.STEP.VALIDATION;
+            default:
+                return '';
+        }
+    }
+
     render() {
         if (!Permissions.canUseFreePlan(this.props.betas)) {
             console.debug('Not showing new bank account page because user is not on free plan beta');
@@ -101,8 +133,9 @@ class ReimbursementAccountPage extends React.Component {
         }
 
         // We grab the currentStep from the achData to determine which view to display. The SetupWithdrawalAccount flow
-        // allows us to continue the flow from various points depending on where the user left off.
-        const currentStep = this.props.reimbursementAccount.achData.currentStep;
+        // allows us to continue the flow from various points depending on where the user left off. We can also
+        // specify a specific step to navigate to by using route params.
+        const currentStep = this.getStepToOpen() || this.props.reimbursementAccount.achData.currentStep;
         return (
             <ScreenWrapper>
                 {currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT && (

From f843ffcef6c6cfafc3e69e5a1bd611f37ca16d7a Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Tue, 15 Jun 2021 09:48:47 -1000
Subject: [PATCH 76/81] better name

---
 src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index 872a4a2cf2c7..945b996d4560 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -80,7 +80,7 @@ class ReimbursementAccountPage extends React.Component {
     /**
      * @returns {String}
      */
-    getStepToOpen() {
+    getStepToOpenFromRouteParams() {
         switch (lodashGet(this.props.route, ['params', 'stepToOpen'])) {
             case 'company':
                 return CONST.BANK_ACCOUNT.STEP.COMPANY;
@@ -135,7 +135,7 @@ class ReimbursementAccountPage extends React.Component {
         // We grab the currentStep from the achData to determine which view to display. The SetupWithdrawalAccount flow
         // allows us to continue the flow from various points depending on where the user left off. We can also
         // specify a specific step to navigate to by using route params.
-        const currentStep = this.getStepToOpen() || this.props.reimbursementAccount.achData.currentStep;
+        const currentStep = this.getStepToOpenFromRouteParams() || this.props.reimbursementAccount.achData.currentStep;
         return (
             <ScreenWrapper>
                 {currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT && (

From 9972d32fe8ca87255464ac497660d5c9cb06460b Mon Sep 17 00:00:00 2001
From: Marc Glasser <marc.aaron.glasser@gmail.com>
Date: Tue, 15 Jun 2021 10:56:48 -1000
Subject: [PATCH 77/81] use bank-account/personal

---
 src/ROUTES.js                                              | 4 ++--
 src/libs/Navigation/linkingConfig.js                       | 4 ++--
 src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 6 ++++--
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/ROUTES.js b/src/ROUTES.js
index 78a5c5ec5153..36ec577dff39 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -8,8 +8,8 @@ import {addTrailingForwardSlash} from './libs/Url';
 const REPORT = 'r';
 
 export default {
-    ADD_PERSONAL_BANK_ACCOUNT: 'add-personal-bank-account',
-    ADD_VERIFIED_BANK_ACCOUNT: 'add-verified-bank-account/:stepToOpen?',
+    BANK_ACCOUNT: 'bank-account/:stepToOpen?',
+    BANK_ACCOUNT_PERSONAL: 'bank-account/personal',
     HOME: '',
     SETTINGS: 'settings',
     SETTINGS_PROFILE: 'settings/profile',
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index e18b0b75ae1c..5d316f9d3773 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -106,7 +106,7 @@ export default {
             },
             AddPersonalBankAccount: {
                 screens: {
-                    AddPersonalBankAccount_Root: ROUTES.ADD_PERSONAL_BANK_ACCOUNT,
+                    AddPersonalBankAccount_Root: ROUTES.BANK_ACCOUNT_PERSONAL,
                 },
             },
             EnablePayments: {
@@ -116,7 +116,7 @@ export default {
             },
             ReimbursementAccount: {
                 screens: {
-                    ReimbursementAccount_Root: ROUTES.ADD_VERIFIED_BANK_ACCOUNT,
+                    ReimbursementAccount_Root: ROUTES.BANK_ACCOUNT,
                 },
             },
             NewWorkspace: {
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index 945b996d4560..bc8af0e484f6 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -82,13 +82,15 @@ class ReimbursementAccountPage extends React.Component {
      */
     getStepToOpenFromRouteParams() {
         switch (lodashGet(this.props.route, ['params', 'stepToOpen'])) {
+            case 'new':
+                return CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT;
             case 'company':
                 return CONST.BANK_ACCOUNT.STEP.COMPANY;
             case 'requestor':
                 return CONST.BANK_ACCOUNT.STEP.REQUESTOR;
-            case 'ach-contract':
+            case 'contract':
                 return CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT;
-            case 'validation':
+            case 'validate':
                 return CONST.BANK_ACCOUNT.STEP.VALIDATION;
             default:
                 return '';

From 2b0e57511f140973519c4cf109a4f235e47b3e4f Mon Sep 17 00:00:00 2001
From: Joe Gambino <joe@expensify.com>
Date: Tue, 15 Jun 2021 14:52:34 -0700
Subject: [PATCH 78/81] don't modify original data

---
 src/libs/actions/Policy.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 28dde23271bc..15867a448c5f 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -103,7 +103,7 @@ function invite(login, welcomeNote, policyID) {
 
     // Make a shallow copy to preserve original data, and concat the login
     const policy = _.clone(allPolicies[key]);
-    policy.employeeList.push(login);
+    policy.employeeList = [...policy.employeeList, login];
 
     // Optimistically add the user to the policy
     Onyx.set(key, policy);

From e4cfc48f3f3864e3fa9ea3af75320dc6ec52ba98 Mon Sep 17 00:00:00 2001
From: Jasper Huang <jasper@expensify.com>
Date: Wed, 16 Jun 2021 10:42:06 +0800
Subject: [PATCH 79/81] Translations for the options in the language selector

---
 src/languages/en.js                   | 4 ++++
 src/languages/es.js                   | 4 ++++
 src/pages/settings/PreferencesPage.js | 4 ++--
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index c8fe6f64814d..46ff13782bae 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -213,6 +213,10 @@ export default {
         receiveRelevantFeatureUpdatesAndExpensifyNews: 'Receive relevant feature updates and Expensify news',
         priorityMode: 'Priority Mode',
         language: 'Language',
+        languages: {
+            english: 'English',
+            spanish: 'Spanish',
+        },
     },
     signInPage: {
         expensifyDotCash: 'Expensify.cash',
diff --git a/src/languages/es.js b/src/languages/es.js
index cab80c882670..444571be281a 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -208,6 +208,10 @@ export default {
         receiveRelevantFeatureUpdatesAndExpensifyNews: 'Recibir noticias sobre Expensify y actualizaciones del producto',
         priorityMode: 'Modo Prioridad',
         language: 'Idioma',
+        languages: {
+            english: 'Inglés',
+            spanish: 'Español',
+        },
     },
     signInPage: {
         expensifyDotCash: 'Expensify.cash',
diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js
index a07b6b4fd2e7..a398b4bca495 100755
--- a/src/pages/settings/PreferencesPage.js
+++ b/src/pages/settings/PreferencesPage.js
@@ -60,11 +60,11 @@ const PreferencesPage = ({
     const localesToLanguages = {
         default: {
             value: 'en',
-            label: 'English',
+            label: translate('preferencesPage.languages.english'),
         },
         es: {
             value: 'es',
-            label: 'Spanish',
+            label: translate('preferencesPage.languages.spanish'),
         },
     };
 

From 3ea5b6775996ec3df56594950fe872e6be9a473e Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Wed, 16 Jun 2021 15:21:07 +0200
Subject: [PATCH 80/81] Add latest bank + paycheck icons from design team

---
 assets/images/bank.svg     |  9 +++++++--
 assets/images/paycheck.svg | 23 +++++++++++++++++++----
 2 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/assets/images/bank.svg b/assets/images/bank.svg
index 4e17ec42c591..3c03e528d731 100644
--- a/assets/images/bank.svg
+++ b/assets/images/bank.svg
@@ -1,3 +1,8 @@
-<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M18.125 5H16.5L10.25 1.5C10 1.375 9.875 1.375 9.625 1.5L3.5 5H1.875C1.5 5 1.25 5.25 1.25 5.625V6.875C1.25 7.25 1.5 7.5 1.875 7.5H2.5V16.25H1.875C1.5 16.25 1.25 16.5 1.25 16.875V18.125C1.25 18.5 1.5 18.75 1.875 18.75H18.125C18.5 18.75 18.75 18.5 18.75 18.125V16.875C18.75 16.5 18.5 16.25 18.125 16.25H17.5V7.5H18.125C18.5 7.5 18.75 7.25 18.75 6.875V5.625C18.75 5.25 18.5 5 18.125 5ZM8.75 16.25H5V7.5H8.75V16.25ZM15 16.25H11.25V7.5H15V16.25Z" fill="black"/>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
+<path d="M18.1,5h-1.6l-6.2-3.5c-0.2-0.1-0.4-0.1-0.6,0L3.5,5H1.9C1.5,5,1.2,5.2,1.2,5.6v1.2c0,0.4,0.2,0.6,0.6,0.6h0.6v8.8H1.9
+	c-0.4,0-0.6,0.2-0.6,0.6v1.2c0,0.4,0.2,0.6,0.6,0.6h16.2c0.4,0,0.6-0.2,0.6-0.6v-1.2c0-0.4-0.2-0.6-0.6-0.6h-0.6V7.5h0.6
+	c0.4,0,0.6-0.2,0.6-0.6V5.6C18.8,5.2,18.5,5,18.1,5z M8.8,16.2H5V7.5h3.8V16.2z M15,16.2h-3.8V7.5H15V16.2z"/>
 </svg>
diff --git a/assets/images/paycheck.svg b/assets/images/paycheck.svg
index 231545aec85d..228faf7cee7e 100644
--- a/assets/images/paycheck.svg
+++ b/assets/images/paycheck.svg
@@ -1,5 +1,20 @@
-<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.56407 11.5479C4.75922 11.5251 4.90125 11.484 4.99018 11.4247C5.14581 11.3196 5.22362 11.1381 5.22362 10.8801C5.22362 10.6838 5.15075 10.5285 5.005 10.4144C4.91855 10.3482 4.77157 10.2797 4.56407 10.2089V11.5479Z" fill="black"/>
-<path d="M4.21206 7.96918C3.99715 7.97374 3.83782 8.0274 3.73407 8.13014C3.63032 8.23059 3.57845 8.36872 3.57845 8.54452C3.57845 8.7363 3.65626 8.88813 3.81188 9C3.89834 9.06164 4.03173 9.11872 4.21206 9.17123V7.96918Z" fill="black"/>
-<path fill-rule="evenodd" clip-rule="evenodd" d="M1 3C0.447715 3 0 3.44772 0 4V15.75C0 16.3023 0.447716 16.75 1 16.75H19C19.5523 16.75 20 16.3023 20 15.75V4C20 3.44772 19.5523 3 19 3H1ZM4.57148 13H4.21206V12.3185C3.72543 12.2683 3.3623 12.1701 3.12269 12.024C2.70028 11.7614 2.49278 11.3139 2.50019 10.6815H3.50063C3.53522 10.9692 3.58339 11.1621 3.64514 11.2603C3.74148 11.4132 3.93045 11.5126 4.21206 11.5582V10.0959L3.91193 10.0137C3.44011 9.88584 3.10663 9.70776 2.91148 9.47945C2.71881 9.25114 2.62247 8.97603 2.62247 8.65411C2.62247 8.44178 2.65952 8.24886 2.73363 8.07534C2.81021 7.90183 2.91643 7.75228 3.05229 7.62671C3.22767 7.46461 3.42282 7.35274 3.63773 7.2911C3.76865 7.25228 3.9601 7.22374 4.21206 7.20548V6.75H4.56407V7.21233C4.96672 7.24201 5.28291 7.33333 5.51264 7.4863C5.93011 7.73059 6.14625 8.12671 6.16107 8.67466H5.18657C5.1668 8.47146 5.12975 8.32306 5.07541 8.22945C4.98154 8.06735 4.81109 7.97831 4.56407 7.96233V9.26712C5.15198 9.45434 5.54722 9.61986 5.74978 9.7637C6.08326 10.0034 6.25 10.3413 6.25 10.7774C6.25 11.3527 6.0215 11.7705 5.56451 12.0308C5.28538 12.1906 4.95436 12.2842 4.57148 12.3116V13ZM8.75 8C8.75 7.65482 9.02982 7.375 9.375 7.375H16.875C17.2202 7.375 17.5 7.65482 17.5 8C17.5 8.34518 17.2202 8.625 16.875 8.625H9.375C9.02982 8.625 8.75 8.34518 8.75 8ZM9.375 11.125C9.02982 11.125 8.75 11.4048 8.75 11.75C8.75 12.0952 9.02982 12.375 9.375 12.375H14.375C14.7202 12.375 15 12.0952 15 11.75C15 11.4048 14.7202 11.125 14.375 11.125H9.375Z" fill="black"/>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill-rule:evenodd;clip-rule:evenodd;}
+</style>
+<g>
+	<path class="st0" d="M4.6,10.2v1.3c0.2,0,0.3-0.1,0.4-0.1c0.2-0.1,0.2-0.3,0.2-0.5c0-0.2-0.1-0.4-0.2-0.5
+		C4.9,10.3,4.8,10.3,4.6,10.2z"/>
+	<path class="st0" d="M3.6,8.5c0,0.2,0.1,0.3,0.2,0.5C3.9,9.1,4,9.1,4.2,9.2V8C4,8,3.8,8,3.7,8.1C3.6,8.2,3.6,8.4,3.6,8.5z"/>
+	<path class="st0" d="M19,3H1C0.4,3,0,3.4,0,4v11.8c0,0.6,0.4,1,1,1h18c0.6,0,1-0.4,1-1V4C20,3.4,19.6,3,19,3z M5.6,12
+		c-0.3,0.2-0.6,0.3-1,0.3V13H4.2v-0.7c-0.5-0.1-0.8-0.1-1.1-0.3c-0.4-0.3-0.6-0.7-0.6-1.3h1c0,0.3,0.1,0.5,0.1,0.6
+		c0.1,0.2,0.3,0.3,0.6,0.3v-1.5L3.9,10c-0.5-0.1-0.8-0.3-1-0.5C2.7,9.3,2.6,9,2.6,8.7c0-0.2,0-0.4,0.1-0.6c0.1-0.2,0.2-0.3,0.3-0.4
+		c0.2-0.2,0.4-0.3,0.6-0.3c0.1,0,0.3-0.1,0.6-0.1V6.8h0.4v0.5c0.4,0,0.7,0.1,0.9,0.3c0.4,0.2,0.6,0.6,0.6,1.2h-1
+		c0-0.2-0.1-0.4-0.1-0.4C5,8.1,4.8,8,4.6,8v1.3c0.6,0.2,1,0.4,1.2,0.5c0.3,0.2,0.5,0.6,0.5,1C6.2,11.4,6,11.8,5.6,12z M14.4,12.4h-5
+		c-0.3,0-0.6-0.3-0.6-0.6s0.3-0.6,0.6-0.6h5c0.3,0,0.6,0.3,0.6,0.6S14.7,12.4,14.4,12.4z M16.9,8.6H9.4C9,8.6,8.8,8.3,8.8,8
+		S9,7.4,9.4,7.4h7.5c0.3,0,0.6,0.3,0.6,0.6S17.2,8.6,16.9,8.6z"/>
+</g>
 </svg>

From ac731594f13d4e1a648327f470fef7e2f90f7951 Mon Sep 17 00:00:00 2001
From: OSBotify <runner@Mac-1623861199669.local>
Date: Wed, 16 Jun 2021 17:27:56 +0000
Subject: [PATCH 81/81] Update version to 1.0.69-1

---
 android/app/build.gradle          | 4 ++--
 ios/ExpensifyCash/Info.plist      | 2 +-
 ios/ExpensifyCashTests/Info.plist | 2 +-
 package-lock.json                 | 2 +-
 package.json                      | 2 +-
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/android/app/build.gradle b/android/app/build.gradle
index c6b82037bc1e..998a94a53af2 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -148,8 +148,8 @@ android {
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
         multiDexEnabled rootProject.ext.multiDexEnabled
-        versionCode 1001006900
-        versionName "1.0.69-0"
+        versionCode 1001006901
+        versionName "1.0.69-1"
     }
     splits {
         abi {
diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist
index d6d672516ab5..91c6aeb7c910 100644
--- a/ios/ExpensifyCash/Info.plist
+++ b/ios/ExpensifyCash/Info.plist
@@ -30,7 +30,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>1.0.69.0</string>
+	<string>1.0.69.1</string>
 	<key>ITSAppUsesNonExemptEncryption</key>
 	<false/>
 	<key>LSApplicationQueriesSchemes</key>
diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist
index 4778d76565bf..853d1c7274a8 100644
--- a/ios/ExpensifyCashTests/Info.plist
+++ b/ios/ExpensifyCashTests/Info.plist
@@ -19,6 +19,6 @@
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>1.0.69.0</string>
+	<string>1.0.69.1</string>
 </dict>
 </plist>
diff --git a/package-lock.json b/package-lock.json
index 465e4be01d16..7548a564be2b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "expensify.cash",
-  "version": "1.0.69-0",
+  "version": "1.0.69-1",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 03c308f506a8..73b74fffb289 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "expensify.cash",
-  "version": "1.0.69-0",
+  "version": "1.0.69-1",
   "author": "Expensify, Inc.",
   "homepage": "https://expensify.cash",
   "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
