diff --git a/android/app/build.gradle b/android/app/build.gradle index ba464059c8cd..b3b5dc98ed40 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -156,8 +156,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001025701 - versionName "1.2.57-1" + versionCode 1001025703 + versionName "1.2.57-3" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 3736f104de4f..8f2664e30eea 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.2.57.1 + 1.2.57.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index fac84a1d426b..d2dab9ff6531 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.2.57.1 + 1.2.57.3 diff --git a/package-lock.json b/package-lock.json index 97ffe26fdc11..e15a8fc2f5e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.2.57-1", + "version": "1.2.57-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.2.57-1", + "version": "1.2.57-3", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -48,7 +48,7 @@ "metro-config": "^0.71.3", "moment": "^2.29.4", "moment-timezone": "^0.5.31", - "onfido-sdk-ui": "^10.3.0", + "onfido-sdk-ui": "10.3.0", "process": "^0.11.10", "prop-types": "^15.7.2", "pusher-js": "^7.0.6", @@ -69,7 +69,7 @@ "react-native-image-picker": "^4.10.2", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.35", + "react-native-onyx": "1.0.36", "react-native-pdf": "^6.6.2", "react-native-performance": "^4.0.0", "react-native-permissions": "^3.0.1", @@ -33561,9 +33561,9 @@ } }, "node_modules/onfido-sdk-ui": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/onfido-sdk-ui/-/onfido-sdk-ui-10.3.1.tgz", - "integrity": "sha512-dALXSCVNT9Tkqnp3pMHCRUc10DFPKpSoWlwHbraGttavhipuHOyyhKfunUkRce2axfvYJ/soL2MFEnaYCy8KvQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/onfido-sdk-ui/-/onfido-sdk-ui-10.3.0.tgz", + "integrity": "sha512-53Yr9s9fb3heMPserJiJY8+Yf5XvRSBVXhswylhlosrZivr5ygEeweteSmdb/ICm2qpqYA8MAVNgzRKnFaImIA==", "dependencies": { "@onfido/active-video-capture": "^0.22.1", "@onfido/opencv": "^1.0.0", @@ -35628,9 +35628,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.35.tgz", - "integrity": "sha512-HQDSM0c2ADb54NoSQdxqeJOhViICB9HwE8aMEB62AdHkRw6crCoIX7iSIF0ewbZ2A/hbX3frewWq8AUh3AyMvA==", + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.36.tgz", + "integrity": "sha512-7kMdj27dk4q9bD37Jg5t9lwQSR8Cjr/9KZPKBUvWhwCwzUeg9YmPaZfu6O22eiUPo9gk4Y5L2j/mQMmXq4wc/w==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -68370,9 +68370,9 @@ } }, "onfido-sdk-ui": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/onfido-sdk-ui/-/onfido-sdk-ui-10.3.1.tgz", - "integrity": "sha512-dALXSCVNT9Tkqnp3pMHCRUc10DFPKpSoWlwHbraGttavhipuHOyyhKfunUkRce2axfvYJ/soL2MFEnaYCy8KvQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/onfido-sdk-ui/-/onfido-sdk-ui-10.3.0.tgz", + "integrity": "sha512-53Yr9s9fb3heMPserJiJY8+Yf5XvRSBVXhswylhlosrZivr5ygEeweteSmdb/ICm2qpqYA8MAVNgzRKnFaImIA==", "requires": { "@onfido/active-video-capture": "^0.22.1", "@onfido/opencv": "^1.0.0", @@ -70042,9 +70042,9 @@ } }, "react-native-onyx": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.35.tgz", - "integrity": "sha512-HQDSM0c2ADb54NoSQdxqeJOhViICB9HwE8aMEB62AdHkRw6crCoIX7iSIF0ewbZ2A/hbX3frewWq8AUh3AyMvA==", + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.36.tgz", + "integrity": "sha512-7kMdj27dk4q9bD37Jg5t9lwQSR8Cjr/9KZPKBUvWhwCwzUeg9YmPaZfu6O22eiUPo9gk4Y5L2j/mQMmXq4wc/w==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", diff --git a/package.json b/package.json index 56543f10e630..674ae9f755af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.2.57-1", + "version": "1.2.57-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -79,7 +79,7 @@ "metro-config": "^0.71.3", "moment": "^2.29.4", "moment-timezone": "^0.5.31", - "onfido-sdk-ui": "^10.3.0", + "onfido-sdk-ui": "10.3.0", "process": "^0.11.10", "prop-types": "^15.7.2", "pusher-js": "^7.0.6", @@ -100,7 +100,7 @@ "react-native-image-picker": "^4.10.2", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.35", + "react-native-onyx": "1.0.36", "react-native-pdf": "^6.6.2", "react-native-performance": "^4.0.0", "react-native-permissions": "^3.0.1", diff --git a/src/CONST.js b/src/CONST.js index 9c05e4d716aa..a2eaae0efe49 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -481,6 +481,7 @@ const CONST = { DECIMAL_PAD: 'decimal-pad', VISIBLE_PASSWORD: 'visible-password', EMAIL_ADDRESS: 'email-address', + ASCII_CAPABLE: 'ascii-capable', }, ATTACHMENT_SOURCE_ATTRIBUTE: 'data-expensify-source', diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 77c3f0396cbc..8caa4b2997c4 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -79,6 +79,9 @@ export default { // Plaid data (access tokens, bank accounts ...) PLAID_DATA: 'plaidData', + // If we disabled Plaid because of too many attempts + IS_PLAID_DISABLED: 'isPlaidDisabled', + // Token needed to initialize Plaid link PLAID_LINK_TOKEN: 'plaidLinkToken', diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index e71e42e1d15a..0a2117d1ca7c 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -10,7 +10,6 @@ import withWindowDimensions from './withWindowDimensions'; import Permissions from '../libs/Permissions'; import PopoverMenu from './PopoverMenu'; import paypalMeDataPropTypes from './paypalMeDataPropTypes'; -import * as BankAccounts from '../libs/actions/BankAccounts'; const propTypes = { isVisible: PropTypes.bool.isRequired, @@ -50,7 +49,6 @@ const AddPaymentMethodMenu = props => ( text: props.translate('common.bankAccount'), icon: Expensicons.Bank, onSelected: () => { - BankAccounts.clearPlaid(); props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT); }, }, diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 386eea4c325c..992190e6b725 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -5,8 +5,8 @@ import { View, } from 'react-native'; import PropTypes from 'prop-types'; -import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import Log from '../libs/Log'; import PlaidLink from './PlaidLink'; import * as BankAccounts from '../libs/actions/BankAccounts'; @@ -16,7 +16,7 @@ import themeColors from '../styles/themes/default'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Picker from './Picker'; -import plaidDataPropTypes from '../pages/ReimbursementAccount/plaidDataPropTypes'; +import {plaidDataPropTypes} from '../pages/ReimbursementAccount/plaidDataPropTypes'; import Text from './Text'; import getBankIcon from './Icon/BankIcons'; import Icon from './Icon'; @@ -24,7 +24,7 @@ import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlocking const propTypes = { /** Contains plaid data */ - plaidData: plaidDataPropTypes, + plaidData: plaidDataPropTypes.isRequired, /** Selected account ID from the Picker associated with the end of the Plaid flow */ selectedPlaidAccountID: PropTypes.string, @@ -57,14 +57,6 @@ const propTypes = { }; const defaultProps = { - plaidData: { - bankName: '', - plaidAccessToken: '', - bankAccounts: [], - isLoading: false, - error: '', - errors: {}, - }, selectedPlaidAccountID: '', plaidLinkToken: '', onExitPlaid: () => {}, @@ -85,7 +77,9 @@ class AddPlaidBankAccount extends React.Component { componentDidMount() { // If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken - if ((this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) || !_.isEmpty(this.props.plaidData)) { + if ((this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) + || !_.isEmpty(lodashGet(this.props.plaidData, 'bankAccounts')) + || !_.isEmpty(lodashGet(this.props.plaidData, 'errors'))) { return; } @@ -106,20 +100,22 @@ class AddPlaidBankAccount extends React.Component { } render() { - const plaidBankAccounts = lodashGet(this.props.plaidData, 'bankAccounts', []); + const plaidBankAccounts = lodashGet(this.props.plaidData, 'bankAccounts') || []; const token = this.getPlaidLinkToken(); const options = _.map(plaidBankAccounts, account => ({ value: account.plaidAccountID, label: `${account.addressName} ${account.mask}`, })); const {icon, iconSize} = getBankIcon(); - const plaidDataErrorMessage = !_.isEmpty(this.props.plaidData.errors) ? _.chain(this.props.plaidData.errors).values().first().value() : this.props.plaidData.error; + const plaidErrors = lodashGet(this.props.plaidData, 'errors'); + const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : ''; + const bankName = lodashGet(this.props.plaidData, 'bankName'); // Plaid Link view if (!plaidBankAccounts.length) { return ( - {this.props.plaidData.isLoading && ( + {lodashGet(this.props.plaidData, 'isLoading') && ( @@ -129,13 +125,12 @@ class AddPlaidBankAccount extends React.Component { {plaidDataErrorMessage} )} - {Boolean(token) && ( + {Boolean(token) && !bankName && ( { Log.info('[PlaidLink] Success!'); BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, this.props.allowDebit); - BankAccounts.updatePlaidData({institution: metadata.institution}); }} onError={(error) => { Log.hmmm('[PlaidLink] Error: ', error.message); @@ -163,7 +158,7 @@ class AddPlaidBankAccount extends React.Component { height={iconSize} width={iconSize} /> - {this.props.plaidData.bankName} + {bankName} {this.props.options.length > 1 ? ( this.props.onPress(event, this.state.selectedItem.value)} + onButtonPress={event => this.props.onPress(event, selectedItem.value)} onDropdownPress={() => { this.setMenuVisibility(true); }} @@ -72,7 +72,7 @@ class ButtonWithMenu extends PureComponent { isDisabled={this.props.isDisabled} style={[styles.w100]} isLoading={this.props.isLoading} - text={selectedItemText} + text={selectedItem.text} onPress={event => this.props.onPress(event, this.props.options[0].value)} pressOnEnter /> @@ -84,10 +84,10 @@ class ButtonWithMenu extends PureComponent { onItemSelected={() => this.setMenuVisibility(false)} anchorPosition={styles.createMenuPositionRightSidepane} headerText={this.props.menuHeaderText} - menuItems={_.map(this.props.options, item => ({ + menuItems={_.map(this.props.options, (item, index) => ({ ...item, onSelected: () => { - this.setState({selectedItem: item}); + this.setState({selectedItemIndex: index}); }, }))} /> diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js index 6f37202cfadb..e1e03c36cdfa 100644 --- a/src/components/DatePicker/index.ios.js +++ b/src/components/DatePicker/index.ios.js @@ -39,6 +39,8 @@ class DatePicker extends React.Component { * @param {Event} event */ showPicker(event) { + this.initialValue = this.state.selectedDate; + // Opens the popover only after the keyboard is hidden to avoid a "blinking" effect where the keyboard was on iOS // See https://github.com/Expensify/App/issues/14084 for more context if (!this.props.isKeyboardShown) { @@ -50,7 +52,6 @@ class DatePicker extends React.Component { listener.remove(); }); Keyboard.dismiss(); - this.initialValue = this.state.selectedDate; event.preventDefault(); } diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index b5bc9df18b69..53baab7d64fa 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -101,16 +101,6 @@ class IOUConfirmationList extends Component { ...participant, selected: true, })); - this.splitOrRequestOptions = [{ - text: props.translate(props.hasMultipleParticipants ? 'iou.split' : 'iou.request', { - amount: props.numberFormat( - props.iouAmount, - {style: 'currency', currency: props.iou.selectedCurrencyCode}, - ), - }), - value: props.hasMultipleParticipants ? CONST.IOU.IOU_TYPE.SPLIT : CONST.IOU.IOU_TYPE.REQUEST, - }]; - this.state = { participants: formattedParticipants, }; @@ -119,6 +109,22 @@ class IOUConfirmationList extends Component { this.confirm = this.confirm.bind(this); } + /** + * Get the confirmation button options + * @returns {Array} + */ + getSplitOrRequestOptions() { + return [{ + text: this.props.translate(this.props.hasMultipleParticipants ? 'iou.split' : 'iou.request', { + amount: this.props.numberFormat( + this.props.iouAmount, + {style: 'currency', currency: this.props.iou.selectedCurrencyCode}, + ), + }), + value: this.props.hasMultipleParticipants ? CONST.IOU.IOU_TYPE.SPLIT : CONST.IOU.IOU_TYPE.REQUEST, + }]; + } + /** * Get selected participants * @returns {Array} @@ -309,7 +315,7 @@ class IOUConfirmationList extends Component { this.confirm(value)} - options={this.splitOrRequestOptions} + options={this.getSplitOrRequestOptions()} /> )} /> diff --git a/src/components/ReimbursementAccountLoadingIndicator.js b/src/components/ReimbursementAccountLoadingIndicator.js index 2a7a6bfffde6..d3a55e826f3e 100644 --- a/src/components/ReimbursementAccountLoadingIndicator.js +++ b/src/components/ReimbursementAccountLoadingIndicator.js @@ -10,11 +10,15 @@ import Navigation from '../libs/Navigation/Navigation'; import ScreenWrapper from './ScreenWrapper'; import FullScreenLoadingIndicator from './FullscreenLoadingIndicator'; import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView'; +import compose from '../libs/compose'; +import {withNetwork} from './OnyxProvider'; const propTypes = { /** Whether the user is submitting verifications data */ isSubmittingVerificationsData: PropTypes.bool.isRequired, + /** Method to trigger when pressing back button of the header */ + onBackButtonPress: PropTypes.func.isRequired, ...withLocalizePropTypes, }; @@ -23,6 +27,8 @@ const ReimbursementAccountLoadingIndicator = props => ( {props.isSubmittingVerificationsData ? ( @@ -49,4 +55,8 @@ const ReimbursementAccountLoadingIndicator = props => ( ReimbursementAccountLoadingIndicator.propTypes = propTypes; ReimbursementAccountLoadingIndicator.displayName = 'ReimbursementAccountLoadingIndicator'; -export default withLocalize(ReimbursementAccountLoadingIndicator); +export default compose( + withLocalize, + withNetwork(), +)(ReimbursementAccountLoadingIndicator); + diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js index 273bae8a8107..a2af6fbbf049 100644 --- a/src/components/RoomNameInput/index.native.js +++ b/src/components/RoomNameInput/index.native.js @@ -30,7 +30,7 @@ class RoomNameInput extends Component { } render() { - const keyboardType = getOperatingSystem() === CONST.OS.IOS ? 'default' : 'visible-password'; + const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; return ( ); } diff --git a/src/libs/ReimbursementAccountUtils.js b/src/libs/ReimbursementAccountUtils.js index 4074f1e8ab6c..6c11b72fb5c4 100644 --- a/src/libs/ReimbursementAccountUtils.js +++ b/src/libs/ReimbursementAccountUtils.js @@ -1,4 +1,3 @@ -import _ from 'underscore'; import lodashGet from 'lodash/get'; import * as BankAccounts from './actions/BankAccounts'; import FormHelper from './FormHelper'; @@ -15,28 +14,16 @@ const clearErrors = (props, paths) => formHelper.clearErrors(props, paths); /** * Get the default state for input fields in the VBA flow * - * @param {Object} props + * @param {Object} reimbursementAccountDraft + * @param {Object} reimbursementAccount * @param {String} fieldName * @param {*} defaultValue * * @returns {*} */ -function getDefaultStateForField(props, fieldName, defaultValue = '') { - return lodashGet(props, ['reimbursementAccountDraft', fieldName]) - || lodashGet(props, ['reimbursementAccount', 'achData', fieldName], defaultValue); -} - -/** - * @param {Object} props - * @param {Array} fieldNames - * - * @returns {*} - */ -function getBankAccountFields(props, fieldNames) { - return { - ..._.pick(lodashGet(props, 'reimbursementAccount.achData'), ...fieldNames), - ..._.pick(props.reimbursementAccountDraft, ...fieldNames), - }; +function getDefaultStateForField(reimbursementAccountDraft, reimbursementAccount, fieldName, defaultValue = '') { + return lodashGet(reimbursementAccountDraft, fieldName) + || lodashGet(reimbursementAccount, ['achData', fieldName], defaultValue); } /** @@ -56,5 +43,4 @@ export { clearError, clearErrors, getErrorText, - getBankAccountFields, }; diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index cc438b605c35..bb67b2fb39b6 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -50,24 +50,27 @@ function getSortedReportActions(reportActions, shouldSortInDescendingOrder = fal if (!_.isArray(reportActions)) { throw new Error(`ReportActionsUtils.getSortedReportActions requires an array, received ${typeof reportActions}`); } - const invertedMultiplier = shouldSortInDescendingOrder ? -1 : 1; - reportActions.sort((first, second) => { - // First sort by timestamp - if (first.created !== second.created) { - return (first.created < second.created ? -1 : 1) * invertedMultiplier; - } - - // Then by action type, ensuring that `CREATED` actions always come first if they have the same timestamp as another action type - if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) { - return ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ? -1 : 1) * invertedMultiplier; - } - // Then fallback on reportActionID as the final sorting criteria. It is a random number, - // but using this will ensure that the order of reportActions with the same created time and action type - // will be consistent across all users and devices - return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier; - }); - return reportActions; + const invertedMultiplier = shouldSortInDescendingOrder ? -1 : 1; + return _.chain(reportActions) + .compact() + .sort((first, second) => { + // First sort by timestamp + if (first.created !== second.created) { + return (first.created < second.created ? -1 : 1) * invertedMultiplier; + } + + // Then by action type, ensuring that `CREATED` actions always come first if they have the same timestamp as another action type + if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) { + return ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ? -1 : 1) * invertedMultiplier; + } + + // Then fallback on reportActionID as the final sorting criteria. It is a random number, + // but using this will ensure that the order of reportActions with the same created time and action type + // will be consistent across all users and devices + return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier; + }) + .value(); } /** diff --git a/src/libs/RoomNameInputUtils.js b/src/libs/RoomNameInputUtils.js index 3940996ec3fa..15b85f9f651a 100644 --- a/src/libs/RoomNameInputUtils.js +++ b/src/libs/RoomNameInputUtils.js @@ -8,7 +8,10 @@ import CONST from '../CONST'; */ function modifyRoomName(roomName) { const modifiedRoomNameWithoutHash = roomName - .replace(/ /g, '-'); + .replace(/ /g, '-') + + // Replaces the smart dash on iOS devices with two hyphens + .replace(/—/g, '--'); return `${CONST.POLICY.ROOM_PREFIX}${modifiedRoomNameWithoutHash}`; } diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 3c1593e864c5..b6aea981e5f6 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -4,6 +4,9 @@ import * as API from '../API'; import ONYXKEYS from '../../ONYXKEYS'; import * as Localize from '../Localize'; import DateUtils from '../DateUtils'; +import * as PlaidDataProps from '../../pages/ReimbursementAccount/plaidDataPropTypes'; +import Navigation from '../Navigation/Navigation'; +import ROUTES from '../../ROUTES'; export { goToWithdrawalAccountSetupStep, @@ -28,17 +31,23 @@ export { acceptWalletTerms, } from './Wallet'; -function clearPersonalBankAccount() { - Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {}); -} - function clearPlaid() { - Onyx.set(ONYXKEYS.PLAID_DATA, {}); Onyx.set(ONYXKEYS.PLAID_LINK_TOKEN, ''); + + return Onyx.set(ONYXKEYS.PLAID_DATA, PlaidDataProps.plaidDataDefaultProps); } -function updatePlaidData(plaidData) { - Onyx.merge(ONYXKEYS.PLAID_DATA, plaidData); +function openPlaidView() { + clearPlaid().then(() => this.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID)); +} + +function openPersonalBankAccountSetupView() { + clearPlaid().then(() => Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT)); +} + +function clearPersonalBankAccount() { + clearPlaid(); + Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {}); } function clearOnfidoToken() { @@ -341,7 +350,7 @@ function updateBeneficialOwnersForBankAccount(params) { /** * Create the bank account with manually entered data. * - * @param {String} [bankAccountID] + * @param {number} [bankAccountID] * @param {String} [accountNumber] * @param {String} [routingNumber] * @param {String} [plaidMask] @@ -387,14 +396,15 @@ export { clearOnfidoToken, clearPersonalBankAccount, clearPlaid, + openPlaidView, connectBankAccountManually, connectBankAccountWithPlaid, deletePaymentBankAccount, + openPersonalBankAccountSetupView, openReimbursementAccountPage, updateBeneficialOwnersForBankAccount, updateCompanyInformationForBankAccount, updatePersonalInformationForBankAccount, - updatePlaidData, openWorkspaceView, validateBankAccount, verifyIdentityForBankAccount, diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js index 66b593a328da..043556260c7b 100644 --- a/src/libs/actions/PaymentMethods.js +++ b/src/libs/actions/PaymentMethods.js @@ -1,6 +1,5 @@ import _ from 'underscore'; import {createRef} from 'react'; -import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; @@ -10,7 +9,6 @@ import * as Localize from '../Localize'; import Navigation from '../Navigation/Navigation'; import * as CardUtils from '../CardUtils'; import * as User from './User'; -import * as store from './ReimbursementAccount/store'; import ROUTES from '../../ROUTES'; function deletePayPalMe() { @@ -37,19 +35,6 @@ function continueSetup() { kycWallRef.current.continue(); } -/** - * Clears local reimbursement account if it doesn't exist in bankAccounts - * @param {Object[]} bankAccounts - */ -function cleanLocalReimbursementData(bankAccounts) { - const bankAccountID = lodashGet(store.getReimbursementAccountInSetup(), 'bankAccountID'); - - // We check if the bank account list doesn't have the reimbursementAccount - if (!_.find(bankAccounts, bankAccount => bankAccount.bankAccountID === bankAccountID)) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: null, shouldShowResetModal: false}); - } -} - function openPaymentsPage() { const onyxData = { optimisticData: [ @@ -359,7 +344,6 @@ export { resetWalletTransferData, saveWalletTransferAccountTypeAndID, saveWalletTransferMethodType, - cleanLocalReimbursementData, hasPaymentMethodError, clearDeletePaymentMethodError, clearAddPaymentMethodError, diff --git a/src/libs/actions/Plaid.js b/src/libs/actions/Plaid.js index 393b5bbc9961..0785a1fd9b3e 100644 --- a/src/libs/actions/Plaid.js +++ b/src/libs/actions/Plaid.js @@ -2,6 +2,7 @@ import getPlaidLinkTokenParameters from '../getPlaidLinkTokenParameters'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; import CONST from '../../CONST'; +import * as PlaidDataProps from '../../pages/ReimbursementAccount/plaidDataPropTypes'; /** * Gets the Plaid Link token used to initialize the Plaid SDK @@ -12,7 +13,23 @@ function openPlaidBankLogin(allowDebit, bankAccountID) { const params = getPlaidLinkTokenParameters(); params.allowDebit = allowDebit; params.bankAccountID = bankAccountID; - API.read('OpenPlaidBankLogin', params); + const optimisticData = [{ + onyxMethod: CONST.ONYX.METHOD.SET, + key: ONYXKEYS.PLAID_DATA, + value: {...PlaidDataProps.plaidDataDefaultProps, isLoading: true}, + }, { + onyxMethod: CONST.ONYX.METHOD.SET, + key: ONYXKEYS.PLAID_LINK_TOKEN, + value: '', + }, { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, + value: { + plaidAccountID: '', + }, + }]; + + API.read('OpenPlaidBankLogin', params, {optimisticData}); } /** diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 58c70231e29d..f46285a76c67 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -923,10 +923,8 @@ function createWorkspace(ownerEmail = '', makeMeAdmin = false, policyName = '', /** * * @param {string} policyID - * @param {string} subStep The sub step in first step of adding withdrawal bank account - * @param {*} localCurrentStep The locally stored current step of adding a withdrawal bank account */ -function openWorkspaceReimburseView(policyID, subStep, localCurrentStep) { +function openWorkspaceReimburseView(policyID) { if (!policyID) { Log.warn('openWorkspaceReimburseView invalid params', {policyID}); return; @@ -952,7 +950,7 @@ function openWorkspaceReimburseView(policyID, subStep, localCurrentStep) { ], }; - API.read('OpenWorkspaceReimburseView', {policyID, subStep, localCurrentStep}, onyxData); + API.read('OpenWorkspaceReimburseView', {policyID}, onyxData); } function openWorkspaceMembersPage(policyID, clientMemberEmails) { diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js index 40dc5835ab0a..bcac825a630a 100644 --- a/src/libs/actions/ReimbursementAccount/index.js +++ b/src/libs/actions/ReimbursementAccount/index.js @@ -12,9 +12,12 @@ export { } from './errors'; /** - * Set the current sub step in first step of adding withdrawal bank account + * Set the current sub step in first step of adding withdrawal bank account: + * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually + * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber + * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid * - * @param {String} subStep - One of {CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL, CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID, null} + * @param {String} subStep */ function setBankAccountSubStep(subStep) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); diff --git a/src/libs/actions/ReimbursementAccount/navigation.js b/src/libs/actions/ReimbursementAccount/navigation.js index f695137764fa..312479838c9f 100644 --- a/src/libs/actions/ReimbursementAccount/navigation.js +++ b/src/libs/actions/ReimbursementAccount/navigation.js @@ -1,95 +1,16 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; -import * as store from './store'; -import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import ROUTES from '../../../ROUTES'; import Navigation from '../../Navigation/Navigation'; -const WITHDRAWAL_ACCOUNT_STEPS = [ - { - 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', - }, - { - id: CONST.BANK_ACCOUNT.STEP.ENABLE, - title: 'Enable', - }, -]; - -/** - * Get step position in the array - * @private - * @param {String} stepID - * @return {Number} - */ -function getIndexByStepID(stepID) { - return _.findIndex(WITHDRAWAL_ACCOUNT_STEPS, step => step.id === stepID); -} - -/** - * Get next step ID - * @param {String} [stepID] - * @return {String} - */ -function getNextStepID(stepID) { - const nextStepIndex = Math.min( - getIndexByStepID(stepID || store.getReimbursementAccountInSetup().currentStep) + 1, - WITHDRAWAL_ACCOUNT_STEPS.length - 1, - ); - return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT); -} - -/** - * @param {Object} achData - * @returns {String} - */ -function getNextStepToComplete(achData) { - if (achData.currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR && !achData.isOnfidoSetupComplete) { - return CONST.BANK_ACCOUNT.STEP.REQUESTOR; - } - - return getNextStepID(achData.currentStep); -} - /** * Navigate to a specific step in the VBA flow * * @param {String} stepID - * @param {Object} achData + * @param {Object} newAchData */ -function goToWithdrawalAccountSetupStep(stepID, achData) { - const newACHData = {...store.getReimbursementAccountInSetup()}; - - // If we go back to Requestor Step, reset any validation and previously answered questions from expectID. - if (!newACHData.useOnfido && stepID === CONST.BANK_ACCOUNT.STEP.REQUESTOR) { - delete newACHData.questions; - delete newACHData.answers; - } - - // 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 = CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL; - } - - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...newACHData, ...achData, currentStep: stepID}}); +function goToWithdrawalAccountSetupStep(stepID, newAchData) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...newAchData, currentStep: stepID}}); } /** @@ -101,7 +22,5 @@ function navigateToBankAccountRoute() { export { goToWithdrawalAccountSetupStep, - getNextStepToComplete, - getNextStepID, navigateToBankAccountRoute, }; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js index 24a7919fe824..4255b79d5d22 100644 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js @@ -1,13 +1,15 @@ -import lodashGet from 'lodash/get'; +import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import * as store from './store'; import * as API from '../../API'; +import * as PlaidDataProps from '../../../pages/ReimbursementAccount/plaidDataPropTypes'; +import * as ReimbursementAccountProps from '../../../pages/ReimbursementAccount/reimbursementAccountPropTypes'; /** * Reset user's reimbursement account. This will delete the bank account. + * @param {number} bankAccountID */ -function resetFreePlanBankAccount() { - const bankAccountID = lodashGet(store.getReimbursementAccountInSetup(), 'bankAccountID'); +function resetFreePlanBankAccount(bankAccountID) { if (!bankAccountID) { throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); } @@ -28,27 +30,24 @@ function resetFreePlanBankAccount() { value: '', }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.PLAID_DATA, - value: {}, + value: PlaidDataProps.plaidDataDefaultProps, }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.PLAID_LINK_TOKEN, value: '', }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - achData: {}, - shouldShowResetModal: false, - }, + value: ReimbursementAccountProps.reimbursementAccountDefaultProps, }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - value: null, + value: {}, }, ], }); diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 3cbb5a2b0150..78faeaa3e3a9 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -163,6 +163,27 @@ function unsubscribeFromReportChannel(reportID) { Pusher.unsubscribe(pusherChannelName); } +const defaultNewActionSubscriber = { + reportID: '', + callback: () => {}, +}; + +let newActionSubscriber = defaultNewActionSubscriber; + +/** + * Enables the Report actions file to let the ReportActionsView know that a new comment has arrived in realtime for the current report + * + * @param {String} reportID + * @param {Function} callback + * @returns {Function} + */ +function subscribeToNewActionEvent(reportID, callback) { + newActionSubscriber = {callback, reportID}; + return () => { + newActionSubscriber = defaultNewActionSubscriber; + }; +} + /** * Add up to two report actions to a report. This method can be called for the following situations: * @@ -265,6 +286,12 @@ function addActions(reportID, text = '', file) { API.write(commandName, parameters, { optimisticData, }); + + // Notify the ReportActionsView that a new comment has arrived + if (reportID === newActionSubscriber.reportID) { + const isFromCurrentUser = lastAction.actorAccountID === currentUserAccountID; + newActionSubscriber.callback(isFromCurrentUser, lastAction.reportActionID); + } } /** @@ -1065,33 +1092,13 @@ function setIsComposerFullSize(reportID, isComposerFullSize) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${reportID}`, isComposerFullSize); } -const defaultNewActionSubscriber = { - reportID: '', - callback: () => {}, -}; - -let newActionSubscriber = defaultNewActionSubscriber; - -/** - * Enables the Report actions file to let the ReportActionsView know that a new comment has arrived in realtime for the current report - * - * @param {String} reportID - * @param {Function} callback - * @returns {Function} - */ -function subscribeToNewActionEvent(reportID, callback) { - newActionSubscriber = {callback, reportID}; - return () => { - newActionSubscriber = defaultNewActionSubscriber; - }; -} - /** * @param {String} reportID * @param {Object} action */ function showReportActionNotification(reportID, action) { if (ReportActionsUtils.isDeletedAction(action)) { + Log.info('[LOCAL_NOTIFICATION] Skipping notification because the action was deleted', false, {reportID, action}); return; } @@ -1115,7 +1122,7 @@ function showReportActionNotification(reportID, action) { // If we are currently viewing this report do not show a notification. if (reportID === Navigation.getReportIDFromRoute() && Visibility.isVisible()) { - Log.info('[LOCAL_NOTIFICATION] No notification because it was a comment for the current report'); + Log.info('[LOCAL_NOTIFICATION] No notification because it was a comment for the current report', false, {currentReport: Navigation.getReportIDFromRoute(), reportID, action}); return; } @@ -1126,7 +1133,7 @@ function showReportActionNotification(reportID, action) { // Don't show a notification if no comment exists if (!_.some(action.message, f => f.type === 'COMMENT')) { - Log.info('[LOCAL_NOTIFICATION] No notification because no comments exist for the current report'); + Log.info('[LOCAL_NOTIFICATION] No notification because no comments exist for the current action'); return; } diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 784a23a51320..09e7d6873084 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -18,6 +18,7 @@ import * as Link from './Link'; import * as SequentialQueue from '../Network/SequentialQueue'; import PusherUtils from '../PusherUtils'; import * as Report from './Report'; +import * as ReportActionsUtils from '../ReportActionsUtils'; let currentUserAccountID = ''; Onyx.connect({ @@ -264,12 +265,9 @@ function triggerNotifications(onyxUpdates) { } const reportID = update.key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); - const reportAction = _.chain(update.value) - .values() - .compact() - .first() - .value(); - Report.showReportActionNotification(reportID, reportAction); + const reportActions = _.values(update.value); + const sortedReportActions = ReportActionsUtils.getSortedReportActions(reportActions); + Report.showReportActionNotification(reportID, _.last(sortedReportActions)); }); } diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js index 5cf294a47cfe..d755d06e345f 100644 --- a/src/pages/AddPersonalBankAccountPage.js +++ b/src/pages/AddPersonalBankAccountPage.js @@ -15,11 +15,15 @@ import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; import Form from '../components/Form'; import ROUTES from '../ROUTES'; +import * as PlaidDataProps from './ReimbursementAccount/plaidDataPropTypes'; import ConfirmationPage from '../components/ConfirmationPage'; const propTypes = { ...withLocalizePropTypes, + /** Contains plaid data */ + plaidData: PlaidDataProps.plaidDataPropTypes, + /** The details about the Personal bank account we are adding saved in Onyx */ personalBankAccount: PropTypes.shape({ /** An error message to display to the user */ @@ -37,6 +41,7 @@ const propTypes = { }; const defaultProps = { + plaidData: PlaidDataProps.plaidDataDefaultProps, personalBankAccount: { error: '', shouldShowSuccess: false, @@ -93,7 +98,10 @@ class AddPersonalBankAccountPage extends React.Component { description={this.props.translate('addPersonalBankAccountPage.successMessage')} shouldShowButton buttonText={this.props.translate('common.continue')} - onButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PAYMENTS)} + onButtonPress={() => { + BankAccounts.clearPersonalBankAccount(); + Navigation.navigate(ROUTES.SETTINGS_PAYMENTS); + }} /> ) : (
{ this.setState({selectedPlaidAccountID}); }} + plaidData={this.props.plaidData} onExitPlaid={Navigation.goBack} receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()} selectedPlaidAccountID={this.state.selectedPlaidAccountID} diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index f6e21955b9b6..86aa55217a7d 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -3,34 +3,27 @@ import lodashGet from 'lodash/get'; import React from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import * as store from '../../libs/actions/ReimbursementAccount/store'; import Text from '../../components/Text'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import styles from '../../styles/styles'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import TextLink from '../../components/TextLink'; import IdentityForm from './IdentityForm'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import Navigation from '../../libs/Navigation/Navigation'; import CONST from '../../CONST'; import * as ValidationUtils from '../../libs/ValidationUtils'; -import ONYXKEYS from '../../ONYXKEYS'; -import compose from '../../libs/compose'; import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; import ReimbursementAccountForm from './ReimbursementAccountForm'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { + ...StepPropTypes, + /** Name of the company */ companyName: PropTypes.string.isRequired, - - ...withLocalizePropTypes, - - /** Bank account currently in setup */ - // eslint-disable-next-line react/no-unused-prop-types - reimbursementAccount: reimbursementAccountPropTypes.isRequired, }; class ACHContractStep extends React.Component { @@ -41,11 +34,11 @@ class ACHContractStep extends React.Component { this.submit = this.submit.bind(this); this.state = { - ownsMoreThan25Percent: ReimbursementAccountUtils.getDefaultStateForField(props, 'ownsMoreThan25Percent', false), - hasOtherBeneficialOwners: ReimbursementAccountUtils.getDefaultStateForField(props, 'hasOtherBeneficialOwners', false), - acceptTermsAndConditions: ReimbursementAccountUtils.getDefaultStateForField(props, 'acceptTermsAndConditions', false), - certifyTrueInformation: ReimbursementAccountUtils.getDefaultStateForField(props, 'certifyTrueInformation', false), - beneficialOwners: ReimbursementAccountUtils.getDefaultStateForField(props, 'beneficialOwners', []), + ownsMoreThan25Percent: props.getDefaultStateForField('ownsMoreThan25Percent', false), + hasOtherBeneficialOwners: props.getDefaultStateForField('hasOtherBeneficialOwners', false), + acceptTermsAndConditions: props.getDefaultStateForField('acceptTermsAndConditions', false), + certifyTrueInformation: props.getDefaultStateForField('certifyTrueInformation', false), + beneficialOwners: props.getDefaultStateForField('beneficialOwners', []), }; // These fields need to be filled out in order to submit the form (doesn't include IdentityForm fields) @@ -143,7 +136,7 @@ class ACHContractStep extends React.Component { return; } - const bankAccountID = lodashGet(store.getReimbursementAccountInSetup(), 'bankAccountID'); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; // If they did not select that there are other beneficial owners, then we need to clear out the array here. The // reason we do it here is that if they filled out several beneficial owners, but then toggled the checkbox, we @@ -169,15 +162,12 @@ class ACHContractStep extends React.Component { render() { return ( - <> + { - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} + onBackButtonPress={this.props.onBackButtonPress} shouldShowGetAssistanceButton guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT} shouldShowBackButton @@ -289,20 +279,10 @@ class ACHContractStep extends React.Component { errorText={this.getErrorText('certifyTrueInformation')} /> - + ); } } ACHContractStep.propTypes = propTypes; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, - }), -)(ACHContractStep); +export default withLocalize(ACHContractStep); diff --git a/src/pages/ReimbursementAccount/BankAccountManualStep.js b/src/pages/ReimbursementAccount/BankAccountManualStep.js index 1a0679b3fe0e..991f52e5c857 100644 --- a/src/pages/ReimbursementAccount/BankAccountManualStep.js +++ b/src/pages/ReimbursementAccount/BankAccountManualStep.js @@ -1,7 +1,6 @@ import React from 'react'; import {Image, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; import * as BankAccounts from '../../libs/actions/BankAccounts'; @@ -11,22 +10,17 @@ import TextInput from '../../components/TextInput'; import styles from '../../styles/styles'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import TextLink from '../../components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as ValidationUtils from '../../libs/ValidationUtils'; -import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import exampleCheckImage from './exampleCheckImage'; import Form from '../../components/Form'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; import shouldDelayFocus from '../../libs/shouldDelayFocus'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - onBack: PropTypes.func, - ...withLocalizePropTypes, -}; - -const defaultProps = { - onBack: () => {}, + ...StepPropTypes, }; class BankAccountManualStep extends React.Component { @@ -62,25 +56,25 @@ class BankAccountManualStep extends React.Component { submit(values) { BankAccounts.connectBankAccountManually( - ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0), + lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, values.accountNumber, values.routingNumber, - ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidMask'), + this.props.getDefaultStateForField('plaidMask'), ); } render() { - const shouldDisableInputs = Boolean(ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID')); + const shouldDisableInputs = Boolean(lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID')); return ( - <> + )} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'acceptTerms', false)} + defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} shouldSaveDraft /> - +
); } } BankAccountManualStep.propTypes = propTypes; -BankAccountManualStep.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - // Needed to retrieve errorFields - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, - }), -)(BankAccountManualStep); +export default withLocalize(BankAccountManualStep); diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index 4db9bc2103f3..45ef47dd4686 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -7,31 +7,34 @@ import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import Navigation from '../../libs/Navigation/Navigation'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import AddPlaidBankAccount from '../../components/AddPlaidBankAccount'; import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; import Form from '../../components/Form'; import styles from '../../styles/styles'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import * as PlaidDataProps from './plaidDataPropTypes'; +import StepPropTypes from './StepPropTypes'; const propTypes = { + ...StepPropTypes, + + /** Contains plaid data */ + plaidData: PlaidDataProps.plaidDataPropTypes, + /** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */ receivedRedirectURI: PropTypes.string, /** During the OAuth flow we need to use the plaidLink token that we initially connected with */ plaidLinkOAuthToken: PropTypes.string, - - onBack: PropTypes.func, - - ...withLocalizePropTypes, }; const defaultProps = { + plaidData: PlaidDataProps.plaidDataDefaultProps, receivedRedirectURI: null, plaidLinkOAuthToken: '', - onBack: () => {}, }; class BankAccountPlaidStep extends React.Component { @@ -42,7 +45,7 @@ class BankAccountPlaidStep extends React.Component { submit() { const selectedPlaidBankAccount = _.findWhere(lodashGet(this.props.plaidData, 'bankAccounts', []), { - plaidAccountID: ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidAccountID'), + plaidAccountID: this.props.getDefaultStateForField('plaidAccountID'), }); const bankAccountData = { @@ -50,29 +53,29 @@ class BankAccountPlaidStep extends React.Component { accountNumber: selectedPlaidBankAccount.accountNumber, plaidMask: selectedPlaidBankAccount.mask, isSavings: selectedPlaidBankAccount.isSavings, - bankName: this.props.plaidData.bankName, + bankName: lodashGet(this.props.plaidData, 'bankName') || '', plaidAccountID: selectedPlaidBankAccount.plaidAccountID, - plaidAccessToken: this.props.plaidData.plaidAccessToken, + plaidAccessToken: lodashGet(this.props.plaidData, 'plaidAccessToken') || '', }; ReimbursementAccount.updateReimbursementAccountDraft(bankAccountData); - const bankAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; BankAccounts.connectBankAccountWithPlaid(bankAccountID, bankAccountData); } render() { - const bankAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0); - const selectedPlaidAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidAccountID', ''); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; + const selectedPlaidAccountID = this.props.getDefaultStateForField('plaidAccountID', ''); return ( - <> +
{ ReimbursementAccount.updateReimbursementAccountDraft({plaidAccountID}); }} + plaidData={this.props.plaidData} onExitPlaid={() => BankAccounts.setBankAccountSubStep(null)} receivedRedirectURI={this.props.receivedRedirectURI} plaidLinkOAuthToken={this.props.plaidLinkOAuthToken} @@ -97,7 +101,7 @@ class BankAccountPlaidStep extends React.Component { selectedPlaidAccountID={selectedPlaidAccountID} /> - +
); } } @@ -107,9 +111,6 @@ BankAccountPlaidStep.defaultProps = defaultProps; export default compose( withLocalize, withOnyx({ - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, plaidData: { key: ONYXKEYS.PLAID_DATA, }, diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 3bd4d00617a3..b92b322dc0e2 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -16,7 +16,7 @@ import Icon from '../../components/Icon'; import colors from '../../styles/colors'; import Navigation from '../../libs/Navigation/Navigation'; import CONST from '../../CONST'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import Text from '../../components/Text'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import ONYXKEYS from '../../ONYXKEYS'; @@ -27,12 +27,11 @@ import getPlaidDesktopMessage from '../../libs/getPlaidDesktopMessage'; import CONFIG from '../../CONFIG'; import ROUTES from '../../ROUTES'; import Button from '../../components/Button'; -import plaidDataPropTypes from './plaidDataPropTypes'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - /** Contains plaid data */ - plaidData: plaidDataPropTypes, + ...StepPropTypes, /** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */ receivedRedirectURI: PropTypes.string, @@ -40,36 +39,21 @@ const propTypes = { /** During the OAuth flow we need to use the plaidLink token that we initially connected with */ plaidLinkOAuthToken: PropTypes.string, - /** Once the user has selected a sub step, clicking on back button should redirect to the continue button screen. */ - /** As such, we need to expose this handler */ - onSubStepBack: PropTypes.func, - - /** The bank account currently in setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccount: reimbursementAccountPropTypes, - /** Object with various information about the user */ user: PropTypes.shape({ /** Is the user account validated? */ validated: PropTypes.bool, }), - - ...withLocalizePropTypes, }; const defaultProps = { receivedRedirectURI: null, plaidLinkOAuthToken: '', - plaidData: { - isPlaidDisabled: false, - }, - onSubStepBack: () => {}, - reimbursementAccount: {}, user: {}, }; const BankAccountStep = (props) => { - let subStep = lodashGet(props, 'reimbursementAccount.achData.subStep', ''); + let subStep = lodashGet(props.reimbursementAccount, 'achData.subStep', ''); const shouldReinitializePlaidLink = props.plaidLinkOAuthToken && props.receivedRedirectURI && subStep !== CONST.BANK_ACCOUNT.SUBSTEP.MANUAL; if (shouldReinitializePlaidLink) { subStep = CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; @@ -78,107 +62,114 @@ const BankAccountStep = (props) => { const bankAccountRoute = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}${ROUTES.BANK_ACCOUNT}`; if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL) { - return ; + return ( + + ); } if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID) { - return ; + return ( + + ); } return ( - - { - // If we have a subStep then we will remove otherwise we will go back - if (subStep) { - BankAccounts.setBankAccountSubStep(null); - return; - } - Navigation.goBack(); - }} - shouldShowGetAssistanceButton - guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT} - shouldShowBackButton - /> - -
- - {props.translate('bankAccount.toGetStarted')} - - {plaidDesktopMessage && ( - - - {props.translate(plaidDesktopMessage)} - + + + + +
+ + {props.translate('bankAccount.toGetStarted')} - )} -
- {!props.user.validated && ( - - - - {props.translate('bankAccount.validateAccountError')} - - - )} - - - {props.translate('common.privacy')} - - Linking.openURL('https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/')} - > - - - {props.translate('bankAccount.yourDataIsSecure')} - - - - + {props.error && ( + + {props.error} + + )} + + BankAccounts.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL)} + shouldShowRightIcon + wrapperStyle={[styles.cardMenuItem]} + /> + +
+ {!props.user.validated && ( + + + + {props.translate('bankAccount.validateAccountError')} + - -
- -
+ )} + + + {props.translate('common.privacy')} + + Linking.openURL('https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/')} + > + + + {props.translate('bankAccount.yourDataIsSecure')} + + + + + + + + + + + ); }; @@ -192,11 +183,8 @@ export default compose( user: { key: ONYXKEYS.USER, }, - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - plaidData: { - key: ONYXKEYS.PLAID_DATA, + isPlaidDisabled: { + key: ONYXKEYS.IS_PLAID_DISABLED, }, }), )(BankAccountStep); diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index f53d480b9480..9309a31701e1 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -16,28 +16,19 @@ import styles from '../../styles/styles'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import TextLink from '../../components/TextLink'; import StatePicker from '../../components/StatePicker'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as ValidationUtils from '../../libs/ValidationUtils'; import * as LoginUtils from '../../libs/LoginUtils'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import Picker from '../../components/Picker'; import AddressForm from './AddressForm'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; -import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; import Form from '../../components/Form'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - /** The bank account currently in setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, - - /** The draft values of the bank account being setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccountDraft: reimbursementAccountDraftPropTypes.isRequired, - - ...withLocalizePropTypes, + ...StepPropTypes, }; class CompanyStep extends React.Component { @@ -56,6 +47,18 @@ class CompanyStep extends React.Component { BankAccounts.resetReimbursementAccount(); } + /** + * @param {Array} fieldNames + * + * @returns {*} + */ + getBankAccountFields(fieldNames) { + return { + ..._.pick(lodashGet(this.props.reimbursementAccount, 'achData'), ...fieldNames), + ..._.pick(this.props.reimbursementAccountDraft, ...fieldNames), + }; + } + /** * @param {Object} values - form input values passed by the Form component * @returns {Object} - Object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2} @@ -118,8 +121,10 @@ class CompanyStep extends React.Component { submit(values) { const bankAccount = { + bankAccountID: lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, + // Fields from BankAccount step - ...ReimbursementAccountUtils.getBankAccountFields(this.props, ['bankAccountID', 'routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), + ...this.getBankAccountFields(['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), // Fields from Company step ...values, @@ -132,19 +137,19 @@ class CompanyStep extends React.Component { } render() { - const bankAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0); - const shouldDisableCompanyName = bankAccountID && ReimbursementAccountUtils.getDefaultStateForField(this.props, 'companyName'); - const shouldDisableCompanyTaxID = bankAccountID && ReimbursementAccountUtils.getDefaultStateForField(this.props, 'companyTaxID'); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; + const shouldDisableCompanyName = bankAccountID && this.props.getDefaultStateForField('companyName'); + const shouldDisableCompanyTaxID = bankAccountID && this.props.getDefaultStateForField('companyTaxID'); return ( - <> + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT)} + onBackButtonPress={this.props.onBackButtonPress} onCloseButtonPress={Navigation.dismissModal} />
@@ -203,7 +208,7 @@ class CompanyStep extends React.Component { keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} disabled={shouldDisableCompanyTaxID} placeholder={this.props.translate('companyStep.taxIDNumberPlaceholder')} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'companyTaxID')} + defaultValue={this.props.getDefaultStateForField('companyTaxID')} shouldSaveDraft /> @@ -212,7 +217,7 @@ class CompanyStep extends React.Component { label={this.props.translate('companyStep.companyType')} items={_.map(this.props.translate('companyStep.incorporationTypes'), (label, value) => ({value, label}))} placeholder={{value: '', label: '-'}} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'incorporationType')} + defaultValue={this.props.getDefaultStateForField('incorporationType')} shouldSaveDraft /> @@ -221,7 +226,7 @@ class CompanyStep extends React.Component { inputID="incorporationDate" label={this.props.translate('companyStep.incorporationDate')} placeholder={this.props.translate('companyStep.incorporationDatePlaceholder')} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'incorporationDate')} + defaultValue={this.props.getDefaultStateForField('incorporationDate')} shouldSaveDraft /> @@ -229,13 +234,13 @@ class CompanyStep extends React.Component { ( <> {`${this.props.translate('companyStep.confirmCompanyIsNot')} `} @@ -251,7 +256,7 @@ class CompanyStep extends React.Component { shouldSaveDraft /> - +
); } } @@ -261,13 +266,6 @@ CompanyStep.propTypes = propTypes; export default compose( withLocalize, withOnyx({ - // Needed to retrieve errorFields - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js index 12c9e5c743df..76b490239c07 100644 --- a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js +++ b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import {ScrollView} from 'react-native'; import _ from 'underscore'; -import * as BankAccounts from '../../libs/actions/BankAccounts'; import * as Expensicons from '../../components/Icon/Expensicons'; import * as Illustrations from '../../components/Icon/Illustrations'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; @@ -19,11 +18,14 @@ import ScreenWrapper from '../../components/ScreenWrapper'; import Section from '../../components/Section'; import Text from '../../components/Text'; import withPolicy from '../workspace/withPolicy'; -import WorkspaceResetBankAccountModal from '../workspace/WorkspaceResetBankAccountModal'; const propTypes = { + /** Callback to continue to the next step of the setup */ continue: PropTypes.func.isRequired, + /** Callback to start over the setup */ + startOver: PropTypes.func.isRequired, + /** Policy values needed in the component */ policy: PropTypes.shape({ name: PropTypes.string, @@ -33,7 +35,7 @@ const propTypes = { }; const ContinueBankAccountSetup = props => ( - + ( - ); diff --git a/src/pages/ReimbursementAccount/EnableStep.js b/src/pages/ReimbursementAccount/EnableStep.js index 4293a5c2a74f..329f8c61cc9d 100644 --- a/src/pages/ReimbursementAccount/EnableStep.js +++ b/src/pages/ReimbursementAccount/EnableStep.js @@ -1,6 +1,7 @@ import React from 'react'; -import {View, ScrollView} from 'react-native'; +import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; @@ -13,17 +14,18 @@ import Button from '../../components/Button'; import * as Expensicons from '../../components/Icon/Expensicons'; import MenuItem from '../../components/MenuItem'; import getBankIcon from '../../components/Icon/BankIcons'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import userPropTypes from '../settings/userPropTypes'; import Section from '../../components/Section'; import * as Illustrations from '../../components/Icon/Illustrations'; -import * as BankAccounts from '../../libs/actions/BankAccounts'; import * as Link from '../../libs/actions/Link'; import * as User from '../../libs/actions/User'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import * as BankAccounts from '../../libs/actions/ReimbursementAccount'; const propTypes = { /** Bank account currently in setup */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, /* Onyx Props */ user: userPropTypes.isRequired, @@ -33,17 +35,17 @@ const propTypes = { const EnableStep = (props) => { const isUsingExpensifyCard = props.user.isUsingExpensifyCard; - const reimbursementAccount = props.reimbursementAccount.achData || {}; - const {icon, iconSize} = getBankIcon(reimbursementAccount.bankName); - const formattedBankAccountNumber = reimbursementAccount.accountNumber + const achData = lodashGet(props.reimbursementAccount, 'achData') || {}; + const {icon, iconSize} = getBankIcon(achData.bankName); + const formattedBankAccountNumber = achData.accountNumber ? `${props.translate('paymentMethodList.accountLastFour')} ${ - reimbursementAccount.accountNumber.slice(-4) + achData.accountNumber.slice(-4) }` : ''; - const bankName = reimbursementAccount.addressName; + const bankName = achData.addressName; return ( - + { )} - + ); }; @@ -110,9 +112,6 @@ EnableStep.propTypes = propTypes; export default compose( withLocalize, withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, user: { key: ONYXKEYS.USER, }, diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js index dcab1ec499c8..c6c266f1a7d8 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js @@ -2,13 +2,10 @@ import _ from 'underscore'; import React from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; -import compose from '../../libs/compose'; -import ONYXKEYS from '../../ONYXKEYS'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import FormAlertWithSubmitButton from '../../components/FormAlertWithSubmitButton'; import FormScrollView from '../../components/FormScrollView'; import * as BankAccounts from '../../libs/actions/BankAccounts'; @@ -16,7 +13,7 @@ import * as ErrorUtils from '../../libs/ErrorUtils'; const propTypes = { /** Data for the bank account actively being set up */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, /** Called when the form is submitted */ onSubmit: PropTypes.func.isRequired, @@ -26,11 +23,6 @@ const propTypes = { }; const defaultProps = { - reimbursementAccount: { - isLoading: false, - errors: {}, - errorFields: {}, - }, buttonText: '', hideSubmitButton: false, }; @@ -77,11 +69,4 @@ class ReimbursementAccountForm extends React.Component { ReimbursementAccountForm.propTypes = propTypes; ReimbursementAccountForm.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), -)(ReimbursementAccountForm); +export default withLocalize(ReimbursementAccountForm); diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index c0e60c126d8f..17c630677ab1 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -19,9 +19,6 @@ import getPlaidOAuthReceivedRedirectURI from '../../libs/getPlaidOAuthReceivedRe import Text from '../../components/Text'; import {withNetwork} from '../../components/OnyxProvider'; import networkPropTypes from '../../components/networkPropTypes'; -import * as store from '../../libs/actions/ReimbursementAccount/store'; - -// Steps import BankAccountStep from './BankAccountStep'; import CompanyStep from './CompanyStep'; import ContinueBankAccountSetup from './ContinueBankAccountSetup'; @@ -31,15 +28,23 @@ import ACHContractStep from './ACHContractStep'; import EnableStep from './EnableStep'; import ROUTES from '../../ROUTES'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import WorkspaceResetBankAccountModal from '../workspace/WorkspaceResetBankAccountModal'; +import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; +import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; const propTypes = { /** Plaid SDK token to use to initialize the widget */ plaidLinkToken: PropTypes.string, /** ACH data for the withdrawal account actively being set up */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, + + /** The draft values of the bank account being setup */ + reimbursementAccountDraft: reimbursementAccountDraftPropTypes, + + /** The token required to initialize the Onfido SDK */ + onfidoToken: PropTypes.string, /** Information about the network */ network: networkPropTypes.isRequired, @@ -63,9 +68,9 @@ const propTypes = { }; const defaultProps = { - reimbursementAccount: { - isLoading: true, - }, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, + reimbursementAccountDraft: {}, + onfidoToken: '', plaidLinkToken: '', route: { params: { @@ -78,11 +83,13 @@ 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); + const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); + const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN && achData.state !== BankAccount.STATE.LOCKED; - const achData = lodashGet(this.props, 'reimbursementAccount.achData', {}); - const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN; this.state = { - shouldShowContinueSetupButton: hasInProgressVBBA, + shouldHideContinueSetupButton: !hasInProgressVBBA, }; } @@ -94,17 +101,8 @@ class ReimbursementAccountPage extends React.Component { if (prevProps.network.isOffline && !this.props.network.isOffline) { this.fetchData(); } - const currentStep = lodashGet( - this.props, - 'reimbursementAccount.achData.currentStep', - CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, - ); - const previousStep = lodashGet( - prevProps, - 'reimbursementAccount.achData.currentStep', - CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, - ); - + const currentStep = lodashGet(this.props.reimbursementAccount, 'achData.currentStep') || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + const previousStep = lodashGet(prevProps.reimbursementAccount, 'achData.currentStep') || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; if (currentStep === previousStep) { return; } @@ -117,6 +115,18 @@ class ReimbursementAccountPage extends React.Component { } /** + * @param {String} fieldName + * @param {*} defaultValue + * + * @returns {*} + */ + getDefaultStateForField(fieldName, defaultValue = '') { + return ReimbursementAccountUtils.getDefaultStateForField(this.props.reimbursementAccountDraft, this.props.reimbursementAccount, fieldName, defaultValue); + } + + /** + * 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() { @@ -160,20 +170,71 @@ class ReimbursementAccountPage extends React.Component { } } - fetchData() { + /** + * 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) { // 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 reimbursementAccount = store.getReimbursementAccountInSetup(); - const subStep = reimbursementAccount.subStep || ''; - const localCurrentStep = reimbursementAccount.currentStep || ''; - BankAccounts.openReimbursementAccountPage(stepToOpen, subStep, localCurrentStep); + const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); + const subStep = achData.subStep || ''; + const localCurrentStep = achData.currentStep || ''; + BankAccounts.openReimbursementAccountPage(stepToOpen, subStep, ignoreLocalCurrentStep ? '' : localCurrentStep); } continue() { this.setState({ - shouldShowContinueSetupButton: false, + shouldHideContinueSetupButton: true, }); + this.fetchData(true); + } + + goBack() { + const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); + const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + const subStep = achData.subStep; + const shouldShowOnfido = this.props.onfidoToken && !achData.isOnfidoSetupComplete; + const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN && achData.state !== BankAccount.STATE.LOCKED; + switch (currentStep) { + case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: + if (hasInProgressVBBA) { + this.setState({shouldHideContinueSetupButton: false}); + } + if (subStep) { + BankAccounts.setBankAccountSubStep(null); + } else { + Navigation.goBack(); + } + 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(); + } else { + 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 (achData.state === BankAccount.STATE.PENDING) { + this.setState({ + shouldHideContinueSetupButton: false, + }); + } else { + Navigation.goBack(); + } + break; + default: Navigation.goBack(); + } } render() { @@ -182,8 +243,9 @@ class ReimbursementAccountPage extends React.Component { // 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 achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + if (this.props.reimbursementAccount.isLoading) { const isSubmittingVerificationsData = _.contains([ CONST.BANK_ACCOUNT.STEP.COMPANY, @@ -193,15 +255,33 @@ class ReimbursementAccountPage extends React.Component { return ( ); } - const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN; - if (hasInProgressVBBA && this.state.shouldShowContinueSetupButton) { + if (this.props.reimbursementAccount.shouldShowResetModal && Boolean(achData.bankAccountID)) { + return ( + + ); + } + + // Show the "Continue with setup" button if a bank account setup is already in progress and no specific further step was passed in the url + if (!this.state.shouldHideContinueSetupButton + && Boolean(achData.bankAccountID) + && achData.state !== BankAccount.STATE.OPEN + && achData.state !== BankAccount.STATE.LOCKED + && ( + _.contains([CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, ''], this.getStepToOpenFromRouteParams()) + || achData.state === BankAccount.STATE.PENDING + )) { return ( { + this.setState({shouldHideContinueSetupButton: true}); + BankAccounts.requestResetFreePlanBankAccount(); + }} /> ); } @@ -217,7 +297,7 @@ class ReimbursementAccountPage extends React.Component { ); } - const throttledDate = lodashGet(this.props, 'reimbursementAccount.throttledDate'); + const throttledDate = lodashGet(this.props.reimbursementAccount, 'throttledDate'); if (throttledDate) { errorComponent = ( @@ -239,33 +319,70 @@ class ReimbursementAccountPage extends React.Component { ); } - return ( - - {currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT && ( - (hasInProgressVBBA ? this.setState({shouldShowContinueSetupButton: true}) : BankAccounts.setBankAccountSubStep(null))} - /> - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE && ( - - )} - - - ); + + if (currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR) { + const shouldShowOnfido = this.props.onfidoToken && !achData.isOnfidoSetupComplete; + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE) { + return ( + + ); + } } } @@ -278,12 +395,18 @@ export default compose( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, + reimbursementAccountDraft: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, + }, session: { key: ONYXKEYS.SESSION, }, plaidLinkToken: { key: ONYXKEYS.PLAID_LINK_TOKEN, }, + onfidoToken: { + key: ONYXKEYS.ONFIDO_TOKEN, + }, }), withLocalize, )(ReimbursementAccountPage); diff --git a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js index 60399a905aca..f03a3a2ad0a2 100644 --- a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js +++ b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js @@ -2,35 +2,26 @@ import React from 'react'; import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import Onfido from '../../components/Onfido'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; import Growl from '../../libs/Growl'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; -import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; import CONST from '../../CONST'; import FullPageOfflineBlockingView from '../../components/BlockingViews/FullPageOfflineBlockingView'; +import StepPropTypes from './StepPropTypes'; +import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; +import Navigation from '../../libs/Navigation/Navigation'; +import ScreenWrapper from '../../components/ScreenWrapper'; const propTypes = { - /** Bank account currently in setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, - - /** The draft values of the bank account being setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccountDraft: reimbursementAccountDraftPropTypes.isRequired, + ...StepPropTypes, /** The token required to initialize the Onfido SDK */ onfidoToken: PropTypes.string.isRequired, - - /** A callback to call once the user completes the Onfido flow */ - onComplete: PropTypes.func.isRequired, - - ...withLocalizePropTypes, }; const defaultProps = {}; @@ -43,34 +34,46 @@ class RequestorOnfidoStep extends React.Component { submit(onfidoData) { BankAccounts.verifyIdentityForBankAccount( - ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0), + lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, onfidoData, ); - this.props.onComplete(); + + BankAccounts.updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); } render() { return ( - - - { - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} - onError={() => { - // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. - Growl.error(this.props.translate('onfidoStep.genericError'), 10000); - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} - onSuccess={(onfidoData) => { - this.submit(onfidoData); - }} - /> - - + + + + + { + BankAccounts.clearOnfidoToken(); + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }} + onError={() => { + // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. + Growl.error(this.props.translate('onfidoStep.genericError'), 10000); + BankAccounts.clearOnfidoToken(); + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }} + onSuccess={(onfidoData) => { + this.submit(onfidoData); + }} + /> + + + ); } } @@ -81,14 +84,8 @@ RequestorOnfidoStep.defaultProps = defaultProps; export default compose( withLocalize, withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, onfidoToken: { key: ONYXKEYS.ONFIDO_TOKEN, }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, }), )(RequestorOnfidoStep); diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index b821f3d1b354..6823eedddfe3 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -1,11 +1,10 @@ import React from 'react'; -import lodashGet from 'lodash/get'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import moment from 'moment'; import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; import TextLink from '../../components/TextLink'; @@ -15,55 +14,25 @@ import Text from '../../components/Text'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import IdentityForm from './IdentityForm'; import * as ValidationUtils from '../../libs/ValidationUtils'; -import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; import RequestorOnfidoStep from './RequestorOnfidoStep'; import Form from '../../components/Form'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - /** The bank account currently in setup */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, - - /** The token required to initialize the Onfido SDK */ - onfidoToken: PropTypes.string, - - ...withLocalizePropTypes, -}; + ...StepPropTypes, -const defaultProps = { - onfidoToken: '', + /** If we should show Onfido flow */ + shouldShowOnfido: PropTypes.bool.isRequired, }; class RequestorStep extends React.Component { constructor(props) { super(props); - this.getDefaultStateForField = this.getDefaultStateForField.bind(this); this.validate = this.validate.bind(this); this.submit = this.submit.bind(this); - this.setOnfidoAsComplete = this.setOnfidoAsComplete.bind(this); - - this.state = { - isOnfidoSetupComplete: lodashGet(props, ['achData', 'isOnfidoSetupComplete'], false), - }; - } - - /** - * Update state to indicate that the user has completed the Onfido verification process - */ - setOnfidoAsComplete() { - this.setState({isOnfidoSetupComplete: true}); - } - - /** - * Get default value from reimbursementAccount or achData - * @param {String} fieldName - * @param {*} defaultValue - * @returns {String} - */ - getDefaultStateForField(fieldName, defaultValue) { - return lodashGet(this.props, ['reimbursementAccount', 'achData', fieldName], defaultValue); } /** @@ -122,7 +91,7 @@ class RequestorStep extends React.Component { submit(values) { const payload = { - bankAccountID: this.getDefaultStateForField('bankAccountID', 0), + bankAccountID: lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, ...values, dob: moment(values.dob).format(CONST.DATE.MOMENT_FORMAT_STRING), }; @@ -131,134 +100,119 @@ class RequestorStep extends React.Component { } render() { - const achData = this.props.reimbursementAccount.achData; - const shouldShowOnfido = achData.useOnfido && this.props.onfidoToken && !this.state.isOnfidoSetupComplete; + if (this.props.shouldShowOnfido) { + return ( + + ); + } return ( - <> + { - if (shouldShowOnfido) { - BankAccounts.clearOnfidoToken(); - } else { - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.COMPANY); - } - }} + onBackButtonPress={this.props.onBackButtonPress} onCloseButtonPress={Navigation.dismissModal} /> - {shouldShowOnfido ? ( - + {this.props.translate('requestorStep.subtitle')} + + + {`${this.props.translate('requestorStep.learnMore')}`} + + {' | '} + + {`${this.props.translate('requestorStep.isMyDataSafe')}`} + + + + ( + + + {this.props.translate('requestorStep.isControllingOfficer')} + + + )} + style={[styles.mt4]} + shouldSaveDraft /> - ) : ( -
- {this.props.translate('requestorStep.subtitle')} - - - {`${this.props.translate('requestorStep.learnMore')}`} - - {' | '} - - {`${this.props.translate('requestorStep.isMyDataSafe')}`} - - - - ( - - - {this.props.translate('requestorStep.isControllingOfficer')} - - - )} - style={[styles.mt4]} - shouldSaveDraft - /> - - {this.props.translate('requestorStep.onFidoConditions')} - - {this.props.translate('onfidoStep.facialScan')} - - {', '} - - {this.props.translate('common.privacy')} - - {` ${this.props.translate('common.and')} `} - - {this.props.translate('common.termsOfService')} - - - - )} - + + {this.props.translate('requestorStep.onFidoConditions')} + + {this.props.translate('onfidoStep.facialScan')} + + {', '} + + {this.props.translate('common.privacy')} + + {` ${this.props.translate('common.and')} `} + + {this.props.translate('common.termsOfService')} + + + +
); } } RequestorStep.propTypes = propTypes; -RequestorStep.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - onfidoToken: { - key: ONYXKEYS.ONFIDO_TOKEN, - }, - }), -)(RequestorStep); +export default withLocalize(RequestorStep); diff --git a/src/pages/ReimbursementAccount/StepPropTypes.js b/src/pages/ReimbursementAccount/StepPropTypes.js new file mode 100644 index 000000000000..dc9734c6ecf7 --- /dev/null +++ b/src/pages/ReimbursementAccount/StepPropTypes.js @@ -0,0 +1,20 @@ +import PropTypes from 'prop-types'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; +import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; +import {withLocalizePropTypes} from '../../components/withLocalize'; + +export default { + /** The bank account currently in setup */ + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, + + /** The draft values of the bank account being setup */ + reimbursementAccountDraft: reimbursementAccountDraftPropTypes.isRequired, + + /** Goes to the previous step */ + onBackButtonPress: PropTypes.func.isRequired, + + /** Get a field value from Onyx reimbursementAccountDraft or reimbursementAccount */ + getDefaultStateForField: PropTypes.func.isRequired, + + ...withLocalizePropTypes, +}; diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js index 7458f4271339..282c1c0fa974 100644 --- a/src/pages/ReimbursementAccount/ValidationStep.js +++ b/src/pages/ReimbursementAccount/ValidationStep.js @@ -1,9 +1,9 @@ import lodashGet from 'lodash/get'; import React from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; import _ from 'underscore'; +import PropTypes from 'prop-types'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import * as BankAccounts from '../../libs/actions/BankAccounts'; @@ -15,10 +15,9 @@ import Text from '../../components/Text'; import BankAccount from '../../libs/models/BankAccount'; import TextLink from '../../components/TextLink'; import ONYXKEYS from '../../ONYXKEYS'; -import compose from '../../libs/compose'; import * as ValidationUtils from '../../libs/ValidationUtils'; import EnableStep from './EnableStep'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import Form from '../../components/Form'; import * as Expensicons from '../../components/Icon/Expensicons'; import * as Illustrations from '../../components/Icon/Illustrations'; @@ -31,15 +30,9 @@ const propTypes = { ...withLocalizePropTypes, /** Bank account currently in setup */ - reimbursementAccount: reimbursementAccountPropTypes, -}; + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, -const defaultProps = { - reimbursementAccount: { - errorFields: {}, - errors: {}, - maxAttemptsReached: false, - }, + onBackButtonPress: PropTypes.func.isRequired, }; class ValidationStep extends React.Component { @@ -107,14 +100,14 @@ class ValidationStep extends React.Component { } render() { - const state = lodashGet(this.props, 'reimbursementAccount.achData.state'); + const state = lodashGet(this.props.reimbursementAccount, 'achData.state'); // If a user tries to navigate directly to the validate page we'll show them the EnableStep if (state === BankAccount.STATE.OPEN) { return ; } - const maxAttemptsReached = lodashGet(this.props, 'reimbursementAccount.maxAttemptsReached'); + const maxAttemptsReached = lodashGet(this.props.reimbursementAccount, 'maxAttemptsReached'); const isVerifying = !maxAttemptsReached && state === BankAccount.STATE.VERIFYING; return ( @@ -123,7 +116,7 @@ class ValidationStep extends React.Component { title={isVerifying ? this.props.translate('validationStep.headerTitle') : this.props.translate('workspace.common.testTransactions')} stepCounter={{step: 5, total: 5}} onCloseButtonPress={Navigation.dismissModal} - onBackButtonPress={() => Navigation.goBack()} + onBackButtonPress={this.props.onBackButtonPress} shouldShowGetAssistanceButton guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT} shouldShowBackButton @@ -219,13 +212,5 @@ class ValidationStep extends React.Component { } ValidationStep.propTypes = propTypes; -ValidationStep.defaultProps = defaultProps; - -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), -)(ValidationStep); + +export default withLocalize(ValidationStep); diff --git a/src/pages/ReimbursementAccount/plaidDataPropTypes.js b/src/pages/ReimbursementAccount/plaidDataPropTypes.js index ae4f3b896b4c..e8db5c087551 100644 --- a/src/pages/ReimbursementAccount/plaidDataPropTypes.js +++ b/src/pages/ReimbursementAccount/plaidDataPropTypes.js @@ -1,14 +1,17 @@ import PropTypes from 'prop-types'; -export default PropTypes.shape({ +const plaidDataPropTypes = PropTypes.shape({ /** Whether we are fetching the bank accounts from the API */ isLoading: PropTypes.bool, - /** Error message */ - error: PropTypes.string, + /** Any additional error message to show */ + errors: PropTypes.objectOf(PropTypes.string), - /** Whether we should prevent the user from connecting with Plaid */ - isPlaidDisabled: PropTypes.bool, + /** Name of the bank */ + bankName: PropTypes.string, + + /** Access token returned by Plaid once the user has logged into their bank. This token can be used along with internal credentials to query for Plaid Balance or Assets */ + plaidAccessToken: PropTypes.string, /** List of plaid bank accounts */ bankAccounts: PropTypes.arrayOf(PropTypes.shape({ @@ -34,3 +37,14 @@ export default PropTypes.shape({ plaidAccessToken: PropTypes.string, })), }); + +const plaidDataDefaultProps = { + bankName: '', + plaidAccessToken: '', + bankAccounts: [], + isLoading: false, + error: '', + errors: {}, +}; + +export {plaidDataPropTypes, plaidDataDefaultProps}; diff --git a/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js b/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js index e077b8668cce..abac3c89a80e 100644 --- a/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js +++ b/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; +import BankAccount from '../../libs/models/BankAccount'; -export default PropTypes.shape({ +const reimbursementAccountPropTypes = PropTypes.shape({ /** Whether we are loading the data via the API */ isLoading: PropTypes.bool, @@ -33,3 +34,16 @@ export default PropTypes.shape({ /** Any additional error message to show */ errors: PropTypes.objectOf(PropTypes.string), }); + +const reimbursementAccountDefaultProps = { + achData: { + state: BankAccount.STATE.SETUP, + }, + isLoading: false, + errorFields: {}, + errors: {}, + maxAttemptsReached: false, + shouldShowResetModal: false, +}; + +export {reimbursementAccountPropTypes, reimbursementAccountDefaultProps}; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 25aba06a5f78..6fb9b2fec4f8 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -208,6 +208,12 @@ class ReportActionsView extends React.Component { this.openReportIfNecessary(); } + // If the report is unread, we want to check if the number of actions has decreased. If so, then it seems that one of them was deleted. In this case, if the deleted action was the + // one marking the unread point, we need to recalculate which action should be the unread marker. + if (ReportUtils.isUnread(this.props.report) && ReportActionsUtils.filterReportActionsForDisplay(prevProps.reportActions).length > this.sortedAndFilteredReportActions.length) { + this.setState({newMarkerReportActionID: ReportUtils.getNewMarkerReportActionID(this.props.report, this.sortedAndFilteredReportActions)}); + } + // When the user navigates to the LHN the ReportActionsView doesn't unmount and just remains hidden. // The next time we navigate to the same report (e.g. by swiping or tapping the LHN row) we want the new marker to clear. const didSidebarOpen = !prevProps.isDrawerOpen && this.props.isDrawerOpen; diff --git a/src/pages/settings/Payments/ChooseTransferAccountPage.js b/src/pages/settings/Payments/ChooseTransferAccountPage.js index 99d0b1fe2623..665b69b822ed 100644 --- a/src/pages/settings/Payments/ChooseTransferAccountPage.js +++ b/src/pages/settings/Payments/ChooseTransferAccountPage.js @@ -15,6 +15,7 @@ import compose from '../../../libs/compose'; import ONYXKEYS from '../../../ONYXKEYS'; import walletTransferPropTypes from './walletTransferPropTypes'; import styles from '../../../styles/styles'; +import * as BankAccounts from '../../../libs/actions/BankAccounts'; const propTypes = { /** Wallet transfer propTypes */ @@ -52,7 +53,7 @@ const ChooseTransferAccountPage = (props) => { Navigation.navigate(ROUTES.SETTINGS_ADD_DEBIT_CARD); return; } - Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); + BankAccounts.openPersonalBankAccountSetupView(); }; return ( diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js index 4279fb928b04..df20970bf278 100644 --- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js @@ -215,7 +215,7 @@ class BasePaymentsPage extends React.Component { } if (paymentType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); + BankAccounts.openPersonalBankAccountSetupView(); return; } diff --git a/src/pages/workspace/WorkspacePageWithSections.js b/src/pages/workspace/WorkspacePageWithSections.js index 89b2a4ec1950..eb5d17c0a3c2 100644 --- a/src/pages/workspace/WorkspacePageWithSections.js +++ b/src/pages/workspace/WorkspacePageWithSections.js @@ -14,7 +14,7 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import ONYXKEYS from '../../ONYXKEYS'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import BankAccount from '../../libs/models/BankAccount'; -import reimbursementAccountPropTypes from '../ReimbursementAccount/reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from '../ReimbursementAccount/reimbursementAccountPropTypes'; import userPropTypes from '../settings/userPropTypes'; import withPolicy from './withPolicy'; import {withNetwork} from '../../components/OnyxProvider'; @@ -42,7 +42,7 @@ const propTypes = { /** From Onyx */ /** Bank account attached to free plan */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, /** User Data from Onyx */ user: userPropTypes, @@ -70,7 +70,7 @@ const propTypes = { const defaultProps = { children: () => {}, user: {}, - reimbursementAccount: {}, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, footer: null, guidesCallTaskID: '', shouldUseScrollView: false, diff --git a/src/pages/workspace/WorkspaceResetBankAccountModal.js b/src/pages/workspace/WorkspaceResetBankAccountModal.js index 63464ae7f6ee..835bb43ee9cd 100644 --- a/src/pages/workspace/WorkspaceResetBankAccountModal.js +++ b/src/pages/workspace/WorkspaceResetBankAccountModal.js @@ -1,39 +1,26 @@ -import _ from 'underscore'; import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; -import {withOnyx} from 'react-native-onyx'; import ConfirmModal from '../../components/ConfirmModal'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import reimbursementAccountPropTypes from '../ReimbursementAccount/reimbursementAccountPropTypes'; -import compose from '../../libs/compose'; -import ONYXKEYS from '../../ONYXKEYS'; -import bankAccountPropTypes from '../../components/bankAccountPropTypes'; +import * as ReimbursementAccountProps from '../ReimbursementAccount/reimbursementAccountPropTypes'; import Text from '../../components/Text'; import styles from '../../styles/styles'; import BankAccount from '../../libs/models/BankAccount'; const propTypes = { /** Reimbursement account data */ - reimbursementAccount: reimbursementAccountPropTypes, - - /** List of bank accounts */ - bankAccountList: PropTypes.objectOf(bankAccountPropTypes), + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, ...withLocalizePropTypes, }; -const defaultProps = { - reimbursementAccount: {}, - bankAccountList: {}, -}; - const WorkspaceResetBankAccountModal = (props) => { - const isInOpenState = lodashGet(props.reimbursementAccount, 'achData.state') === BankAccount.STATE.OPEN; - const bankAccountID = lodashGet(props.reimbursementAccount, 'achData.bankAccountID'); - const account = _.find(props.bankAccountList, bankAccount => bankAccount.bankAccountID === bankAccountID); - const bankShortName = account ? `${lodashGet(account, 'addressName', '')} ${lodashGet(account, 'accountNumber', '').slice(-4)}` : ''; + const achData = lodashGet(props.reimbursementAccount, 'achData') || {}; + const isInOpenState = achData.state === BankAccount.STATE.OPEN; + const bankAccountID = achData.bankAccountID; + const bankShortName = `${achData.addressName || ''} ${(achData.accountNumber || '').slice(-4)}`; + return ( { ) : props.translate('workspace.bankAccount.clearProgress')} danger onCancel={BankAccounts.cancelResetFreePlanBankAccount} - onConfirm={() => BankAccounts.resetFreePlanBankAccount()} + onConfirm={() => BankAccounts.resetFreePlanBankAccount(bankAccountID)} shouldShowCancelButton - isVisible={lodashGet(props.reimbursementAccount, 'shouldShowResetModal', false)} + isVisible /> ); }; WorkspaceResetBankAccountModal.displayName = 'WorkspaceResetBankAccountModal'; WorkspaceResetBankAccountModal.propTypes = propTypes; -WorkspaceResetBankAccountModal.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - bankAccountList: { - key: ONYXKEYS.BANK_ACCOUNT_LIST, - initWithStoredValues: false, - }, - }), -)(WorkspaceResetBankAccountModal); +export default withLocalize(WorkspaceResetBankAccountModal); diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js index d6d940c4e09f..20f443bf5671 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js @@ -12,7 +12,7 @@ import Section from '../../../components/Section'; import * as Link from '../../../libs/actions/Link'; import Button from '../../../components/Button'; import BankAccount from '../../../libs/models/BankAccount'; -import reimbursementAccountPropTypes from '../../ReimbursementAccount/reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from '../../ReimbursementAccount/reimbursementAccountPropTypes'; import * as ReimbursementAccount from '../../../libs/actions/ReimbursementAccount'; import networkPropTypes from '../../../components/networkPropTypes'; import CONST from '../../../CONST'; @@ -24,7 +24,7 @@ const propTypes = { }).isRequired, /** Bank account attached to free plan */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, /** Information about the network */ network: networkPropTypes.isRequired, diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index 68369a2d8b14..f6f8367d210e 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -18,7 +18,7 @@ import compose from '../../../libs/compose'; import * as Policy from '../../../libs/actions/Policy'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; -import reimbursementAccountPropTypes from '../../ReimbursementAccount/reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from '../../ReimbursementAccount/reimbursementAccountPropTypes'; import getPermittedDecimalSeparator from '../../../libs/getPermittedDecimalSeparator'; import {withNetwork} from '../../../components/OnyxProvider'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; @@ -53,7 +53,7 @@ const propTypes = { /** From Onyx */ /** Bank account attached to free plan */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, /** Information about the network */ network: networkPropTypes.isRequired, @@ -62,7 +62,7 @@ const propTypes = { }; const defaultProps = { - reimbursementAccount: {isLoading: true}, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, }; class WorkspaceReimburseView extends React.Component { @@ -179,14 +179,11 @@ class WorkspaceReimburseView extends React.Component { } fetchData() { - const subStep = this.props.reimbursementAccount.subStep || ''; - const localCurrentStep = this.props.reimbursementAccount.currentStep || ''; - // Instead of setting the reimbursement account loading within the optimistic data of the API command, use a separate action so that the Onyx value is updated right away. // openWorkspaceReimburseView uses API.read which will not make the request until all WRITE requests in the sequential queue have finished responding, so there would be a delay in // updating Onyx with the optimistic data. BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(this.props.policy.id, subStep, localCurrentStep); + Policy.openWorkspaceReimburseView(this.props.policy.id); } debounceUpdateOnCursorMove(event) { diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index 6bd1477f6760..b2c10c890f75 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -18,6 +18,10 @@ import LocalNotification from '../../src/libs/Notification/LocalNotification'; import * as Report from '../../src/libs/actions/Report'; import * as CollectionUtils from '../../src/libs/CollectionUtils'; import DateUtils from '../../src/libs/DateUtils'; +import * as User from '../../src/libs/actions/User'; +import * as Pusher from '../../src/libs/Pusher/pusher'; +import PusherConnectionManager from '../../src/libs/PusherConnectionManager'; +import CONFIG from '../../src/CONFIG'; jest.mock('../../src/libs/Notification/LocalNotification'); @@ -33,6 +37,14 @@ beforeAll(() => { jest.setTimeout(30000); Linking.setInitialURL('https://new.expensify.com/r/1'); appSetup(); + + // Connect to Pusher + PusherConnectionManager.init(); + Pusher.init({ + appKey: CONFIG.PUSHER.APP_KEY, + cluster: CONFIG.PUSHER.CLUSTER, + authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`, + }); }); /** @@ -127,6 +139,10 @@ function signInAndGetAppWithUnreadChat() { return TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A'); }) + .then(() => { + User.subscribeToUserEvents(); + return waitForPromisesToResolve(); + }) .then(() => { const MOMENT_TEN_MINUTES_AGO = moment().subtract(10, 'minutes'); reportAction3CreatedDate = MOMENT_TEN_MINUTES_AGO.clone().add(30, 'seconds').format(MOMENT_FORMAT); @@ -184,8 +200,11 @@ function signInAndGetAppWithUnreadChat() { .then(() => renderedApp); } -describe.skip('Unread Indicators', () => { - afterEach(() => Onyx.clear()); +describe('Unread Indicators', () => { + afterEach(() => { + jest.clearAllMocks(); + Onyx.clear(); + }); it('Display bold in the LHN for unread chat and new line indicator above the chat message when we navigate to it', () => { let renderedApp; @@ -283,43 +302,60 @@ describe.skip('Unread Indicators', () => { const NEW_REPORT_ID = '2'; const NEW_REPORT_CREATED_MOMENT = moment().subtract(5, 'seconds'); const NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT = NEW_REPORT_CREATED_MOMENT.add(1, 'seconds'); - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${NEW_REPORT_ID}`, { - reportID: NEW_REPORT_ID, - reportName: CONST.REPORT.DEFAULT_REPORT_NAME, - maxSequenceNumber: 1, - lastReadSequenceNumber: 0, - lastReadTime: '', - lastActionCreated: DateUtils.getDBTime(NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.utc().valueOf()), - lastMessageText: 'Comment 1', - participants: [USER_C_EMAIL], - }); - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${NEW_REPORT_ID}`, { - 0: { - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, - automatic: false, - sequenceNumber: 0, - created: NEW_REPORT_CREATED_MOMENT.format(MOMENT_FORMAT), - reportActionID: NumberUtils.rand64(), + + const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${USER_A_ACCOUNT_ID}${CONFIG.PUSHER.SUFFIX}`); + channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${NEW_REPORT_ID}`, + value: { + reportID: NEW_REPORT_ID, + reportName: CONST.REPORT.DEFAULT_REPORT_NAME, + maxSequenceNumber: 1, + lastReadSequenceNumber: 0, + lastReadTime: '', + lastActionCreated: DateUtils.getDBTime(NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.utc().valueOf()), + lastMessageText: 'Comment 1', + participants: [USER_C_EMAIL], + }, }, - 1: { - actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, - actorEmail: USER_C_EMAIL, - actorAccountID: USER_C_ACCOUNT_ID, - person: [{type: 'TEXT', style: 'strong', text: 'User C'}], - sequenceNumber: 1, - created: NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.format(MOMENT_FORMAT), - message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}], - reportActionID: NumberUtils.rand64(), + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${NEW_REPORT_ID}`, + value: { + 0: { + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + automatic: false, + sequenceNumber: 0, + created: NEW_REPORT_CREATED_MOMENT.format(MOMENT_FORMAT), + reportActionID: NumberUtils.rand64(), + }, + 1: { + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + actorEmail: USER_C_EMAIL, + actorAccountID: USER_C_ACCOUNT_ID, + person: [{type: 'TEXT', style: 'strong', text: 'User C'}], + sequenceNumber: 1, + created: NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.format(MOMENT_FORMAT), + message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}], + reportActionID: NumberUtils.rand64(), + }, + }, + shouldNotify: true, }, - }); - Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, { - [USER_C_EMAIL]: TestHelper.buildPersonalDetails(USER_C_EMAIL, USER_C_ACCOUNT_ID, 'C'), - }); + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS, + value: { + [USER_C_EMAIL]: TestHelper.buildPersonalDetails(USER_C_EMAIL, USER_C_ACCOUNT_ID, 'C'), + }, + }, + ]); return waitForPromisesToResolve(); }) .then(() => { - // Verify notification was created as the new message that has arrived is very recent - expect(LocalNotification.showCommentNotification.mock.calls).toHaveLength(1); + // Verify notification was created + expect(LocalNotification.showCommentNotification).toBeCalled(); // // Navigate back to the sidebar return navigateToSidebar(renderedApp);