From 25b29b626e573662c18520ecf34dbe8c6d4977a1 Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Thu, 16 Nov 2023 07:31:38 +0100 Subject: [PATCH 01/10] fix --- .../ReimbursementAccountPage.js | 677 +++++++++--------- 1 file changed, 339 insertions(+), 338 deletions(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 35fa1261f9dc..bf4f1a1f4e42 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -1,18 +1,18 @@ import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; import ReimbursementAccountLoadingIndicator from '@components/ReimbursementAccountLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; -import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import Text from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import usePrevious from '@hooks/usePrevious'; import compose from '@libs/compose'; import getPlaidOAuthReceivedRedirectURI from '@libs/getPlaidOAuthReceivedRedirectURI'; import BankAccount from '@libs/models/BankAccount'; @@ -39,6 +39,9 @@ const propTypes = { /** Plaid SDK token to use to initialize the widget */ plaidLinkToken: PropTypes.string, + /** Plaid SDK current event */ + plaidCurrentEvent: PropTypes.string, + /** ACH data for the withdrawal account actively being set up */ reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, @@ -48,6 +51,11 @@ const propTypes = { /** The token required to initialize the Onfido SDK */ onfidoToken: PropTypes.string, + /** Policy values needed in the component */ + policy: PropTypes.shape({ + name: PropTypes.string, + }), + /** Indicated whether the report data is loading */ isLoadingReportData: PropTypes.bool, @@ -57,9 +65,6 @@ const propTypes = { isLoading: PropTypes.bool, }), - /** Information about the network */ - network: networkPropTypes.isRequired, - /** Current session for the user */ session: PropTypes.shape({ /** User login */ @@ -75,15 +80,15 @@ const propTypes = { policyID: PropTypes.string, }), }), - - ...withLocalizePropTypes, }; const defaultProps = { reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, reimbursementAccountDraft: {}, onfidoToken: '', + policy: {}, plaidLinkToken: '', + plaidCurrentEvent: '', isLoadingReportData: false, account: {}, session: { @@ -97,99 +102,89 @@ const defaultProps = { }, }; -class ReimbursementAccountPage extends React.Component { - constructor(props) { - super(props); - this.continue = this.continue.bind(this); - this.getDefaultStateForField = this.getDefaultStateForField.bind(this); - this.goBack = this.goBack.bind(this); - this.requestorStepRef = React.createRef(); - - // The first time we open this page, props.reimbursementAccount is either not available in Onyx - // or only partial data loaded where props.reimbursementAccount.achData.currentStep is not available - // Calculating shouldShowContinueSetupButton on first page open doesn't make sense, and we should recalculate - // it once we get the response from the server the first time in componentDidUpdate. - const hasACHDataBeenLoaded = - this.props.reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && _.has(this.props.reimbursementAccount, 'achData.currentStep'); - this.state = { - hasACHDataBeenLoaded, - shouldShowContinueSetupButton: hasACHDataBeenLoaded ? this.getShouldShowContinueSetupButtonInitialValue() : false, - }; - } +const ROUTE_NAMES = { + COMPANY: 'company', + PERSONAL_INFORMATION: 'personal-information', + CONTRACT: 'contract', + VALIDATE: 'validate', + ENABLE: 'enable', + NEW: 'new', +}; - componentDidMount() { - this.fetchData(); +/** + * We can pass stepToOpen in the URL to force which step to show. + * Mainly needed when user finished the flow in verifying state, and Ops ask them to modify some fields from a specific step. + * @param {Object} route + * @returns {String} + */ +function getStepToOpenFromRouteParams(route) { + + switch (lodashGet(route, ['params', 'stepToOpen'], '')) { + case ROUTE_NAMES.NEW: + return CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + case ROUTE_NAMES.COMPANY: + return CONST.BANK_ACCOUNT.STEP.COMPANY; + case ROUTE_NAMES.PERSONAL_INFORMATION: + return CONST.BANK_ACCOUNT.STEP.REQUESTOR; + case ROUTE_NAMES.CONTRACT: + return CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT; + case ROUTE_NAMES.VALIDATE: + return CONST.BANK_ACCOUNT.STEP.VALIDATION; + case ROUTE_NAMES.ENABLE: + return CONST.BANK_ACCOUNT.STEP.ENABLE; + default: + return ''; } +} - componentDidUpdate(prevProps) { - if (prevProps.network.isOffline && !this.props.network.isOffline && prevProps.reimbursementAccount.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - this.fetchData(); - } - if (!this.state.hasACHDataBeenLoaded) { - // If the ACHData has not been loaded yet, and we are seeing the default data for props.reimbursementAccount - // We don't need to do anything yet - if (this.props.reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && !this.props.reimbursementAccount.isLoading) { - // If we are here, it is because this is the first time we load the ACHData from the server and - // this.props.reimbursementAccount.isLoading just changed to false. From now on, it makes sense to run the code - // below updating states and the route, and this will happen in the next react lifecycle. - this.setState({ - shouldShowContinueSetupButton: this.getShouldShowContinueSetupButtonInitialValue(), - hasACHDataBeenLoaded: true, - }); - } - return; - } - - if ( - prevProps.reimbursementAccount.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && - this.props.reimbursementAccount.pendingAction !== prevProps.reimbursementAccount.pendingAction - ) { - // We are here after the user tried to delete the bank account. We will want to set - // this.state.shouldShowContinueSetupButton to `false` if the bank account was deleted. - this.setState({shouldShowContinueSetupButton: this.hasInProgressVBBA()}); - } - - const currentStep = lodashGet(this.props.reimbursementAccount, 'achData.currentStep') || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; - - if (this.state.shouldShowContinueSetupButton) { - // If we are showing the "Continue with setup" / "Start over" buttons: - // - We don't want to update the route in case the user reloads the page. If we update the route and the user reloads, we will - // take the user to the step set in the route and skip chosing the options. - // - We don't want to clear possible errors because we want to allow the user to clear them clicking the X - return; - } +function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, policy, account, isLoadingReportData, session, plaidLinkToken, plaidCurrentEvent, reimbursementAccountDraft}) { + // The SetupWithdrawalAccount flow allows us to continue the flow from various points depending on where the + // user left off. This view will refer to the achData as the single source of truth to determine which route to + // display. We can also specify a specific route to navigate to via route params when the component first + // mounts which will set the achData.currentStep after the account data is fetched and overwrite the logical + // next step. + const achData = lodashGet(reimbursementAccount, 'achData', {}); - // Update the data that is returned from back-end to draft value - const draftStep = this.props.reimbursementAccount.draftStep; - if (draftStep) { - BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsForStep(draftStep))); - } - - const currentStepRouteParam = this.getStepToOpenFromRouteParams(); - if (currentStepRouteParam === currentStep) { - // The route is showing the correct step, no need to update the route param or clear errors. - return; - } - if (currentStepRouteParam !== '') { - // When we click "Connect bank account", we load the page without the current step param, if there - // was an error when we tried to disconnect or start over, we want the user to be able to see the error, - // so we don't clear it. We only want to clear the errors if we are moving between steps. - BankAccounts.hideBankAccountErrors(); + /** + * @param {String} currentStep + * @returns {String} + */ + function getRouteForCurrentStep(currentStep) { + switch (currentStep) { + case CONST.BANK_ACCOUNT.STEP.COMPANY: + return ROUTE_NAMES.COMPANY; + case CONST.BANK_ACCOUNT.STEP.REQUESTOR: + return ROUTE_NAMES.PERSONAL_INFORMATION; + case CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT: + return ROUTE_NAMES.CONTRACT; + case CONST.BANK_ACCOUNT.STEP.VALIDATION: + return ROUTE_NAMES.VALIDATE; + case CONST.BANK_ACCOUNT.STEP.ENABLE: + return ROUTE_NAMES.ENABLE; + case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: + default: + return ROUTE_NAMES.NEW; } - - // When the step changes we will navigate to update the route params. This is mostly cosmetic as we only use - // the route params when the component first mounts to jump to a specific route instead of picking up where the - // user left off in the flow. - const backTo = lodashGet(this.props.route.params, 'backTo'); - const policyId = lodashGet(this.props.route.params, 'policyID'); - Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(this.getRouteForCurrentStep(currentStep), policyId, backTo)); } - componentWillUnmount() { - BankAccounts.clearReimbursementAccount(); + /** + * @param {Array} fieldNames + * + * @returns {*} + */ + function getBankAccountFields(fieldNames) { + return { + ..._.pick(lodashGet(reimbursementAccount, 'achData'), ...fieldNames), + }; } - getFieldsForStep(step) { + /** + * Returns selected bank account fields based on field names provided. + * + * @param {string} step + * @returns {Array} + */ + function getFieldsForStep(step) { switch (step) { case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: return ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']; @@ -214,129 +209,149 @@ class ReimbursementAccountPage extends React.Component { } } + // Update the data that is returned from back-end to draft value + const draftStep = reimbursementAccount.draftStep; + if (draftStep) { + BankAccounts.updateReimbursementAccountDraft(getBankAccountFields(getFieldsForStep(draftStep))); + } + /** - * @param {Array} fieldNames - * - * @returns {*} + * Returns true if a VBBA exists in any state other than OPEN or LOCKED + * @returns {Boolean} */ - getBankAccountFields(fieldNames) { - return { - ..._.pick(lodashGet(this.props.reimbursementAccount, 'achData'), ...fieldNames), - }; + function hasInProgressVBBA() { + return achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN && achData.state !== BankAccount.STATE.LOCKED; } - /* * Calculates the state used to show the "Continue with setup" view. If a bank account setup is already in progress and * no specific further step was passed in the url we'll show the workspace bank account reset modal if the user wishes to start over */ - getShouldShowContinueSetupButtonInitialValue() { - if (!this.hasInProgressVBBA()) { + function getShouldShowContinueSetupButtonInitialValue() { + if (!hasInProgressVBBA()) { // Since there is no VBBA in progress, we won't need to show the component ContinueBankAccountSetup return false; } - const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); - return achData.state === BankAccount.STATE.PENDING || _.contains([CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, ''], this.getStepToOpenFromRouteParams()); - } - - /** - * @param {String} fieldName - * @param {*} defaultValue - * - * @returns {*} - */ - getDefaultStateForField(fieldName, defaultValue = '') { - return lodashGet(this.props.reimbursementAccount, ['achData', fieldName], defaultValue); + return achData.state === BankAccount.STATE.PENDING || _.contains([CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, ''], getStepToOpenFromRouteParams(route)); } - /** - * We can pass stepToOpen in the URL to force which step to show. - * Mainly needed when user finished the flow in verifying state, and Ops ask them to modify some fields from a specific step. - * @returns {String} - */ - 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 'personal-information': - return CONST.BANK_ACCOUNT.STEP.REQUESTOR; - case 'contract': - return CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT; - case 'validate': - return CONST.BANK_ACCOUNT.STEP.VALIDATION; - case 'enable': - return CONST.BANK_ACCOUNT.STEP.ENABLE; - default: - return ''; - } - } - - /** - * @param {String} currentStep - * @returns {String} - */ - getRouteForCurrentStep(currentStep) { - switch (currentStep) { - case CONST.BANK_ACCOUNT.STEP.COMPANY: - return 'company'; - case CONST.BANK_ACCOUNT.STEP.REQUESTOR: - return 'personal-information'; - case CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT: - return 'contract'; - case CONST.BANK_ACCOUNT.STEP.VALIDATION: - return 'validate'; - case CONST.BANK_ACCOUNT.STEP.ENABLE: - return 'enable'; - case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: - default: - return 'new'; - } - } - - /** - * Returns true if a VBBA exists in any state other than OPEN or LOCKED - * @returns {Boolean} - */ - hasInProgressVBBA() { - const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); - return achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN && achData.state !== BankAccount.STATE.LOCKED; - } + // When this page is first opened, `reimbursementAccount` prop might not yet be fully loaded from Onyx + // or could be partially loaded such that `reimbursementAccount.achData.currentStep` is unavailable. + // Calculating `shouldShowContinueSetupButton` immediately on initial render doesn't make sense as + // it relies on complete data. Thus, we should wait to calculate it until we have received + // the full `reimbursementAccount` data from the server. This logic is handled within the useEffect hook, + // which acts similarly to `componentDidUpdate` when the `reimbursementAccount` dependency changes. + const [hasACHDataBeenLoaded, setHasACHDataBeenLoaded] = useState( + reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && _.has(reimbursementAccount, 'achData.currentStep'), + ); + + const [shouldShowContinueSetupButton, setShouldShowContinueSetupButton] = useState(hasACHDataBeenLoaded ? getShouldShowContinueSetupButtonInitialValue() : false); + + const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + const policyName = lodashGet(policy, 'name', ''); + const policyID = lodashGet(route.params, 'policyID', ''); + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + const requestorStepRef = useRef(null); + const prevReimbursementAccount = usePrevious(reimbursementAccount); + const prevIsOffline = usePrevious(isOffline); /** * Retrieve verified business bank account currently being set up. * @param {boolean} ignoreLocalCurrentStep Pass true if you want the last "updated" view (from db), not the last "viewed" view (from onyx). */ - fetchData(ignoreLocalCurrentStep) { + function fetchData(ignoreLocalCurrentStep) { // Show loader right away, as optimisticData might be set only later in case multiple calls are in the queue BankAccounts.setReimbursementAccountLoading(true); // We can specify a step to navigate to by using route params when the component mounts. // We want to use the same stepToOpen variable when the network state changes because we can be redirected to a different step when the account refreshes. - const stepToOpen = this.getStepToOpenFromRouteParams(); - const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); + const stepToOpen = getStepToOpenFromRouteParams(route); const subStep = achData.subStep || ''; const localCurrentStep = achData.currentStep || ''; BankAccounts.openReimbursementAccountPage(stepToOpen, subStep, ignoreLocalCurrentStep ? '' : localCurrentStep); } - continue() { - this.setState({ - shouldShowContinueSetupButton: false, - }); - this.fetchData(true); - } + useEffect( + () => { + fetchData(); + return () => { + BankAccounts.clearReimbursementAccount(); + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); // The empty dependency array ensures this runs only once after the component mounts. + + useEffect( + () => { + // Check for network change from offline to online + if (prevIsOffline && !isOffline && prevReimbursementAccount && prevReimbursementAccount.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + fetchData(); + } + + if (!hasACHDataBeenLoaded) { + if (reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && !reimbursementAccount.isLoading) { + setShouldShowContinueSetupButton(getShouldShowContinueSetupButtonInitialValue()); + setHasACHDataBeenLoaded(true); + } + return; + } + + if ( + prevReimbursementAccount && + prevReimbursementAccount.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && + reimbursementAccount.pendingAction !== prevReimbursementAccount.pendingAction + ) { + setShouldShowContinueSetupButton(hasInProgressVBBA()); + } + + if (shouldShowContinueSetupButton) { + return; + } + + const currentStepRouteParam = getStepToOpenFromRouteParams(route); + if (currentStepRouteParam === currentStep) { + // The route is showing the correct step, no need to update the route param or clear errors. + return; + } + if (currentStepRouteParam !== '') { + // When we click "Connect bank account", we load the page without the current step param, if there + // was an error when we tried to disconnect or start over, we want the user to be able to see the error, + // so we don't clear it. We only want to clear the errors if we are moving between steps. + BankAccounts.hideBankAccountErrors(); + } + + const backTo = lodashGet(route.params, 'backTo'); + const policyId = lodashGet(route.params, 'policyID'); + + Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(getRouteForCurrentStep(currentStep), policyId, backTo)); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [isOffline, reimbursementAccount, route, hasACHDataBeenLoaded, shouldShowContinueSetupButton], + ); + + const continueFunction = () => { + setShouldShowContinueSetupButton(false); + fetchData(true); + }; + + /** + * @param {String} fieldName + * @param {String} defaultValue + * + * @returns {String} + */ + const getDefaultStateForField = (fieldName, defaultValue = '') => lodashGet(reimbursementAccount, ['achData', fieldName], defaultValue); - goBack() { - const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); - const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + const goBack = () => { const subStep = achData.subStep; - const shouldShowOnfido = this.props.onfidoToken && !achData.isOnfidoSetupComplete; - const backTo = lodashGet(this.props.route.params, 'backTo', ROUTES.HOME); + const shouldShowOnfido = onfidoToken && !achData.isOnfidoSetupComplete; + const backTo = lodashGet(route.params, 'backTo', ROUTES.HOME); + switch (currentStep) { case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: - if (this.hasInProgressVBBA()) { - this.setState({shouldShowContinueSetupButton: true}); + if (hasInProgressVBBA()) { + setShouldShowContinueSetupButton(true); } if (subStep) { BankAccounts.setBankAccountSubStep(null); @@ -345,9 +360,11 @@ class ReimbursementAccountPage extends React.Component { Navigation.goBack(backTo); } break; + case CONST.BANK_ACCOUNT.STEP.COMPANY: BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, {subStep: CONST.BANK_ACCOUNT.SUBSTEP.MANUAL}); break; + case CONST.BANK_ACCOUNT.STEP.REQUESTOR: if (shouldShowOnfido) { BankAccounts.clearOnfidoToken(); @@ -355,182 +372,167 @@ class ReimbursementAccountPage extends React.Component { BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.COMPANY); } break; + case CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT: BankAccounts.clearOnfidoToken(); BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); break; + case CONST.BANK_ACCOUNT.STEP.VALIDATION: if (_.contains([BankAccount.STATE.VERIFYING, BankAccount.STATE.SETUP], achData.state)) { BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT); - } else if (!this.props.network.isOffline && achData.state === BankAccount.STATE.PENDING) { - this.setState({ - shouldShowContinueSetupButton: true, - }); + } else if (!isOffline && achData.state === BankAccount.STATE.PENDING) { + setShouldShowContinueSetupButton(true); } else { Navigation.goBack(backTo); } break; + default: Navigation.goBack(backTo); } + }; + + if (_.isEmpty(policy) || !PolicyUtils.isPolicyAdmin(policy)) { + return ( + + Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} + subtitleKey={_.isEmpty(policy) ? undefined : 'workspace.common.notAuthorized'} + /> + + ); } - render() { - // The SetupWithdrawalAccount flow allows us to continue the flow from various points depending on where the - // user left off. This view will refer to the achData as the single source of truth to determine which route to - // display. We can also specify a specific route to navigate to via route params when the component first - // mounts which will set the achData.currentStep after the account data is fetched and overwrite the logical - // next step. - const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); - const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; - const policyName = lodashGet(this.props.policy, 'name'); - const policyID = lodashGet(this.props.route.params, 'policyID'); - - if (_.isEmpty(this.props.policy) || !PolicyUtils.isPolicyAdmin(this.props.policy)) { - return ( - - Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} - subtitleKey={_.isEmpty(this.props.policy) ? undefined : 'workspace.common.notAuthorized'} - /> - - ); - } - const isLoading = - (this.props.isLoadingReportData || this.props.account.isLoading || this.props.reimbursementAccount.isLoading) && - (!this.props.plaidCurrentEvent || this.props.plaidCurrentEvent === CONST.BANK_ACCOUNT.PLAID.EVENTS_NAME.EXIT); - - // Prevent the full-page blocking offline view from being displayed for these steps if the device goes offline. - const shouldShowOfflineLoader = !( - this.props.network.isOffline && - _.contains([CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, CONST.BANK_ACCOUNT.STEP.COMPANY, CONST.BANK_ACCOUNT.STEP.REQUESTOR, CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT], currentStep) + const isLoading = (isLoadingReportData || account.isLoading || reimbursementAccount.isLoading) && (!plaidCurrentEvent || plaidCurrentEvent === CONST.BANK_ACCOUNT.PLAID.EVENTS_NAME.EXIT); + const shouldShowOfflineLoader = !( + isOffline && _.contains([CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, CONST.BANK_ACCOUNT.STEP.COMPANY, CONST.BANK_ACCOUNT.STEP.REQUESTOR, CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT], currentStep) + ); + + // Show loading indicator when page is first time being opened and props.reimbursementAccount yet to be loaded from the server + // or when data is being loaded. Don't show the loading indicator if we're offline and restarted the bank account setup process + // On Android, when we open the app from the background, Onfido activity gets destroyed, so we need to reopen it. + if ((!hasACHDataBeenLoaded || isLoading) && shouldShowOfflineLoader && (shouldReopenOnfido || !requestorStepRef.current)) { + const isSubmittingVerificationsData = _.contains([CONST.BANK_ACCOUNT.STEP.COMPANY, CONST.BANK_ACCOUNT.STEP.REQUESTOR, CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT], currentStep); + return ( + ); + } - // Show loading indicator when page is first time being opened and props.reimbursementAccount yet to be loaded from the server - // or when data is being loaded. Don't show the loading indicator if we're offline and restarted the bank account setup process - // On Android, when we open the app from the background, Onfido activity gets destroyed, so we need to reopen it. - if ((!this.state.hasACHDataBeenLoaded || isLoading) && shouldShowOfflineLoader && (shouldReopenOnfido || !this.requestorStepRef.current)) { - const isSubmittingVerificationsData = _.contains([CONST.BANK_ACCOUNT.STEP.COMPANY, CONST.BANK_ACCOUNT.STEP.REQUESTOR, CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT], currentStep); - return ( - - ); - } - - let errorText; - const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, CONST.SMS.DOMAIN); - const throttledDate = lodashGet(this.props.reimbursementAccount, 'throttledDate'); - const hasUnsupportedCurrency = lodashGet(this.props.policy, 'outputCurrency', '') !== CONST.CURRENCY.USD; - - if (userHasPhonePrimaryEmail) { - errorText = this.props.translate('bankAccount.hasPhoneLoginError'); - } else if (throttledDate) { - errorText = this.props.translate('bankAccount.hasBeenThrottledError'); - } else if (hasUnsupportedCurrency) { - errorText = this.props.translate('bankAccount.hasCurrencyError'); - } - - if (errorText) { - return ( - - Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} - /> - - {errorText} - - - ); - } + let errorText; + const userHasPhonePrimaryEmail = Str.endsWith(session.email, CONST.SMS.DOMAIN); + const throttledDate = lodashGet(reimbursementAccount, 'throttledDate', ''); + const hasUnsupportedCurrency = lodashGet(policy, 'outputCurrency', '') !== CONST.CURRENCY.USD; + + if (userHasPhonePrimaryEmail) { + errorText = translate('bankAccount.hasPhoneLoginError'); + } else if (throttledDate) { + errorText = translate('bankAccount.hasBeenThrottledError'); + } else if (hasUnsupportedCurrency) { + errorText = translate('bankAccount.hasCurrencyError'); + } - if (this.state.shouldShowContinueSetupButton) { - return ( - { - Navigation.goBack(lodashGet(this.props.route.params, 'backTo', ROUTES.HOME)); - }} + if (errorText) { + return ( + + Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} /> - ); - } + + {errorText} + + + ); + } - if (currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) { - return ( - - ); - } + if (shouldShowContinueSetupButton) { + return ( + { + Navigation.goBack(lodashGet(route.params, 'backTo', ROUTES.HOME)); + }} + /> + ); + } - if (currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY) { - return ( - - ); - } + if (currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) { + return ( + + ); + } - if (currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR) { - const shouldShowOnfido = this.props.onfidoToken && !achData.isOnfidoSetupComplete; - return ( - - ); - } + if (currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY) { + return ( + + ); + } - if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) { - return ( - - ); - } + if (currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR) { + const shouldShowOnfido = onfidoToken && !achData.isOnfidoSetupComplete; + return ( + + ); + } - if (currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION) { - return ( - - ); - } + if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) { + return ( + + ); + } - if (currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE) { - return ( - - ); - } + if (currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE) { + return ( + + ); } } @@ -539,7 +541,6 @@ ReimbursementAccountPage.defaultProps = defaultProps; ReimbursementAccountPage.displayName = 'ReimbursementAccountPage'; export default compose( - withNetwork(), withOnyx({ reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, @@ -566,6 +567,6 @@ export default compose( key: ONYXKEYS.ACCOUNT, }, }), - withLocalize, withPolicy, )(ReimbursementAccountPage); + From 868a1adc4a8a73e079f53b055d20c315fbee68f7 Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Thu, 16 Nov 2023 14:45:34 +0100 Subject: [PATCH 02/10] visual updates --- .../ReimbursementAccountPage.js | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 98ac77be2339..ede3164262b8 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -138,11 +138,13 @@ function getStepToOpenFromRouteParams(route) { } function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, policy, account, isLoadingReportData, session, plaidLinkToken, plaidCurrentEvent, reimbursementAccountDraft}) { - // The SetupWithdrawalAccount flow allows us to continue the flow from various points depending on where the - // user left off. This view will refer to the achData as the single source of truth to determine which route to - // display. We can also specify a specific route to navigate to via route params when the component first - // mounts which will set the achData.currentStep after the account data is fetched and overwrite the logical - // next step. + /** + The SetupWithdrawalAccount flow allows us to continue the flow from various points depending on where the + user left off. This view will refer to the achData as the single source of truth to determine which route to + display. We can also specify a specific route to navigate to via route params when the component first + mounts which will set the achData.currentStep after the account data is fetched and overwrite the logical + next step. + */ const achData = lodashGet(reimbursementAccount, 'achData', {}); /** @@ -170,7 +172,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol /** * @param {Array} fieldNames * - * @returns {*} + * @returns {Object} */ function getBankAccountFields(fieldNames) { return { @@ -234,12 +236,14 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol return achData.state === BankAccount.STATE.PENDING || _.contains([CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, ''], getStepToOpenFromRouteParams(route)); } - // When this page is first opened, `reimbursementAccount` prop might not yet be fully loaded from Onyx - // or could be partially loaded such that `reimbursementAccount.achData.currentStep` is unavailable. - // Calculating `shouldShowContinueSetupButton` immediately on initial render doesn't make sense as - // it relies on complete data. Thus, we should wait to calculate it until we have received - // the full `reimbursementAccount` data from the server. This logic is handled within the useEffect hook, - // which acts similarly to `componentDidUpdate` when the `reimbursementAccount` dependency changes. + /** + When this page is first opened, `reimbursementAccount` prop might not yet be fully loaded from Onyx + or could be partially loaded such that `reimbursementAccount.achData.currentStep` is unavailable. + Calculating `shouldShowContinueSetupButton` immediately on initial render doesn't make sense as + it relies on complete data. Thus, we should wait to calculate it until we have received + the full `reimbursementAccount` data from the server. This logic is handled within the useEffect hook, + which acts similarly to `componentDidUpdate` when the `reimbursementAccount` dependency changes. + */ const [hasACHDataBeenLoaded, setHasACHDataBeenLoaded] = useState( reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && _.has(reimbursementAccount, 'achData.currentStep'), ); @@ -568,4 +572,4 @@ export default compose( }, }), withPolicy, -)(ReimbursementAccountPage); \ No newline at end of file +)(ReimbursementAccountPage); From 5dce9d3315e3292b59ee22f3c285ec6504ded1f6 Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Thu, 16 Nov 2023 14:53:50 +0100 Subject: [PATCH 03/10] prettier ran --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index ede3164262b8..00e181cdbff1 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -118,7 +118,6 @@ const ROUTE_NAMES = { * @returns {String} */ function getStepToOpenFromRouteParams(route) { - switch (lodashGet(route, ['params', 'stepToOpen'], '')) { case ROUTE_NAMES.NEW: return CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; From 5a68e6f39edc5f992b38397c69e0bf667c06a7ab Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Fri, 17 Nov 2023 11:42:09 +0100 Subject: [PATCH 04/10] pr cosmetics --- .../ReimbursementAccountPage.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 00e181cdbff1..79dde48471fb 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -9,7 +9,7 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ReimbursementAccountLoadingIndicator from '@components/ReimbursementAccountLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; -import Text from '@components/TextInput'; +import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; @@ -136,6 +136,28 @@ function getStepToOpenFromRouteParams(route) { } } +/** + * @param {String} currentStep + * @returns {String} + */ +function getRouteForCurrentStep(currentStep) { + switch (currentStep) { + case CONST.BANK_ACCOUNT.STEP.COMPANY: + return ROUTE_NAMES.COMPANY; + case CONST.BANK_ACCOUNT.STEP.REQUESTOR: + return ROUTE_NAMES.PERSONAL_INFORMATION; + case CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT: + return ROUTE_NAMES.CONTRACT; + case CONST.BANK_ACCOUNT.STEP.VALIDATION: + return ROUTE_NAMES.VALIDATE; + case CONST.BANK_ACCOUNT.STEP.ENABLE: + return ROUTE_NAMES.ENABLE; + case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: + default: + return ROUTE_NAMES.NEW; + } +} + function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, policy, account, isLoadingReportData, session, plaidLinkToken, plaidCurrentEvent, reimbursementAccountDraft}) { /** The SetupWithdrawalAccount flow allows us to continue the flow from various points depending on where the @@ -146,28 +168,6 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol */ const achData = lodashGet(reimbursementAccount, 'achData', {}); - /** - * @param {String} currentStep - * @returns {String} - */ - function getRouteForCurrentStep(currentStep) { - switch (currentStep) { - case CONST.BANK_ACCOUNT.STEP.COMPANY: - return ROUTE_NAMES.COMPANY; - case CONST.BANK_ACCOUNT.STEP.REQUESTOR: - return ROUTE_NAMES.PERSONAL_INFORMATION; - case CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT: - return ROUTE_NAMES.CONTRACT; - case CONST.BANK_ACCOUNT.STEP.VALIDATION: - return ROUTE_NAMES.VALIDATE; - case CONST.BANK_ACCOUNT.STEP.ENABLE: - return ROUTE_NAMES.ENABLE; - case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: - default: - return ROUTE_NAMES.NEW; - } - } - /** * @param {Array} fieldNames * From 76b1a29c9cc702b6450caad4cdde3399904297cf Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Fri, 1 Dec 2023 10:03:42 +0100 Subject: [PATCH 05/10] Update src/pages/ReimbursementAccount/ReimbursementAccountPage.js Co-authored-by: wentao --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 79dde48471fb..26e9bd4275b0 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -293,7 +293,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol } if (!hasACHDataBeenLoaded) { - if (reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && !reimbursementAccount.isLoading) { + if (reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && reimbursementAccount.isLoading === false) { setShouldShowContinueSetupButton(getShouldShowContinueSetupButtonInitialValue()); setHasACHDataBeenLoaded(true); } From 2ef98f8589553e7dd8aca4bf8caa83a9dea5bdc5 Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Mon, 4 Dec 2023 07:05:19 +0100 Subject: [PATCH 06/10] Update src/pages/ReimbursementAccount/ReimbursementAccountPage.js Co-authored-by: wentao --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 26e9bd4275b0..e058d91307d6 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -524,6 +524,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol ); } From 46fa2659f817bda19b6a800f80e8290db4f42815 Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Mon, 4 Dec 2023 07:06:12 +0100 Subject: [PATCH 07/10] Update src/pages/ReimbursementAccount/ReimbursementAccountPage.js Co-authored-by: wentao --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index e058d91307d6..c9ddfdb1beb4 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -252,6 +252,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; const policyName = lodashGet(policy, 'name', ''); const policyID = lodashGet(route.params, 'policyID', ''); + const styles = useThemeStyles(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); const requestorStepRef = useRef(null); From 17ac67717eac4073b3894b292ae51f85f3f0fcf3 Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Mon, 4 Dec 2023 07:06:52 +0100 Subject: [PATCH 08/10] Update src/pages/ReimbursementAccount/ReimbursementAccountPage.js Co-authored-by: wentao --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index c9ddfdb1beb4..c2adec7b9bef 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -448,7 +448,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol subtitle={policyName} onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} /> - + {errorText} From b57985c9f25c1418ff11e5ac6ba655acc524281d Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Mon, 4 Dec 2023 07:15:53 +0100 Subject: [PATCH 09/10] merge missings --- 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 c2adec7b9bef..f89318c183f1 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -13,6 +13,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; +import useThemeStyles from '@styles/useThemeStyles'; import compose from '@libs/compose'; import getPlaidOAuthReceivedRedirectURI from '@libs/getPlaidOAuthReceivedRedirectURI'; import BankAccount from '@libs/models/BankAccount'; @@ -20,7 +21,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import shouldReopenOnfido from '@libs/shouldReopenOnfido'; import withPolicy from '@pages/workspace/withPolicy'; -import styles from '@styles/styles'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -448,7 +448,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol subtitle={policyName} onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} /> - + {errorText} From a1a578e4207673ea6942769eaf4f6223593629f5 Mon Sep 17 00:00:00 2001 From: keisyrzk Date: Mon, 4 Dec 2023 07:28:28 +0100 Subject: [PATCH 10/10] linter & prettier --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index f89318c183f1..1b6a188c521b 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -13,7 +13,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; -import useThemeStyles from '@styles/useThemeStyles'; import compose from '@libs/compose'; import getPlaidOAuthReceivedRedirectURI from '@libs/getPlaidOAuthReceivedRedirectURI'; import BankAccount from '@libs/models/BankAccount'; @@ -21,6 +20,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import shouldReopenOnfido from '@libs/shouldReopenOnfido'; import withPolicy from '@pages/workspace/withPolicy'; +import useThemeStyles from '@styles/useThemeStyles'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS';