From f5c7bef128a9fb83151812f724648df5c7cb13a4 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 14 Dec 2021 16:39:30 -0700 Subject: [PATCH 01/69] create form and actions --- src/components/ExpensiForm.js | 145 ++++++++++++++++++ src/libs/actions/ExpensiForm.js | 18 +++ .../ReimbursementAccount/BankAccountStep.js | 1 - 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/components/ExpensiForm.js create mode 100644 src/libs/actions/ExpensiForm.js diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js new file mode 100644 index 000000000000..222f8b132282 --- /dev/null +++ b/src/components/ExpensiForm.js @@ -0,0 +1,145 @@ +import React from 'react'; +import _ from 'underscore'; +import PropTypes from 'prop-types'; +import * as FormAction from '../libs/actions/ExpensiForm'; + +const propTypes = { + name: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + + // eslint-disable-next-line react/forbid-prop-types + defaultValues: PropTypes.object, + + validate: PropTypes.func.isRequired, + saveDraft: PropTypes.bool, +}; + +const defaultProps = { + defaultValues: {}, + saveDraft: true, +}; + +class ExpensiForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + defaultValues: this.props.defaultValues, + errors: {}, + }; + this.inputRefs = React.createRef(); + this.inputRefs.current = {}; + + this.getFormValues = this.getFormValues.bind(this); + this.saveDraft = this.saveDraft.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.validate = this.validate.bind(this); + this.clearInputErrors = this.clearInputErrors.bind(this); + } + + getFormValues() { + const formData = {}; + _.each(_.keys(this.inputRefs.current), (key) => { + if (key && key !== 'undefined') { + return; + } + formData[key] = this.inputRefs.current[key].value; + }); + return formData; + } + + // TODO: Skip saving draft for sensitive inputs, e.g. passwords + // Should be called onChange + saveDraft(draft) { + if (!this.props.saveDraft) { + return; + } + FormAction.saveFormDraft(this.props.name, {draft}); + } + + // Should be called onBlur and onSubmit + validate(field) { + const values = this.getFormValues(); + // validate takes in form values and returns errors object in the format + // how do we handle multiple errors in this case??? + // {username: 'form.errors.required', name: 'form.errors.tooShort', ...} + + // We check if we are trying to validate a single field or the entire form + const errors = this.props.validate(values); + if (field) { + this.setState({errors: errors[field]}); + } else { + this.setState({errors}); + } + } + + // Should be called onFocus + clearInputErrors(field) { + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [field]: undefined, + }, + })); + } + + onSubmit(submit) { + const values = this.getFormValues(); + this.validate(values); + if (!_.isEmpty(this.state.errors)) { + return; + } + FormAction.setLoading(this.props.name, true); + + submit(values).then(() => { + FormAction.setLoading(this.props.name, false); + }); + } + + render() { + const childrenWrapperWithProps = children => ( + React.Children.map(children, (child) => { + // Do nothing if child is not a valid React element + if (!React.isValidElement(child)) { + return child; + } + + // Depth first traversal of the render tree as the form element is likely to be the last node + if (child.props.children) { + child = React.cloneElement(child, { + children: childrenWrapperWithProps(child.props.children), + }); + } + + // We check if the component has the EXPENSIFORM static property enabled, + // as we don't want to pass form props to non form components, e.g. View, Text, etc + if (!child.type.EXPENSIFORM) { + return child; + } + + // We clone the child passing down all form props + const inputRef = node => this.inputRefs.current[child.props.name] = node; + return React.cloneElement(child, { + ref: inputRef, + saveDraft: this.saveDraft, + validate: this.validate, + clearInputErrors: this.clearInputErrors, + onSubmit: this.onSubmit, + defaultValue: this.state.defaultValues[child.props.name], + error: this.state.errors[child.props.name], + }); + }) + ); + + return ( + <> + {childrenWrapperWithProps(this.props.children)} + + ); + } +} + +ExpensiForm.propTypes = propTypes; +ExpensiForm.defaultProps = defaultProps; + +export default ExpensiForm; \ No newline at end of file diff --git a/src/libs/actions/ExpensiForm.js b/src/libs/actions/ExpensiForm.js new file mode 100644 index 000000000000..8a1eed80c8e8 --- /dev/null +++ b/src/libs/actions/ExpensiForm.js @@ -0,0 +1,18 @@ +import Onyx from 'react-native-onyx'; + +/** + * @param {String} formName + * @param {Object} draft + */ +function saveFormDraft(formName, draft) { + Onyx.merge(formName, draft); +} + +function setLoading(formName, state) { + Onyx.merge(formName, {loading: state}); +} + +export { + saveFormDraft, + setLoading, +}; diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 84f582cf2800..7a1330033638 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -99,7 +99,6 @@ class BankAccountStep extends React.Component { } BankAccounts.setBankAccountFormValidationErrors(errors); - return _.size(errors) === 0; } /** From 5d25971b8a62db01dca3cd25959fb9d4617063cf Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 14 Dec 2021 18:05:39 -0700 Subject: [PATCH 02/69] refactor BankAccountStep and ExpensiTextInput --- src/components/ExpensiForm.js | 10 +-- .../ExpensiTextInput/BaseExpensiTextInput.js | 9 ++- src/components/ExpensiTextInput/index.js | 1 + .../ReimbursementAccount/BankAccountStep.js | 70 +++++++------------ 4 files changed, 39 insertions(+), 51 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 222f8b132282..a9521d60c75d 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -40,9 +40,6 @@ class ExpensiForm extends React.Component { getFormValues() { const formData = {}; _.each(_.keys(this.inputRefs.current), (key) => { - if (key && key !== 'undefined') { - return; - } formData[key] = this.inputRefs.current[key].value; }); return formData; @@ -67,7 +64,12 @@ class ExpensiForm extends React.Component { // We check if we are trying to validate a single field or the entire form const errors = this.props.validate(values); if (field) { - this.setState({errors: errors[field]}); + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [field]: errors[field] + } + })); } else { this.setState({errors}); } diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 9394a8d9f88d..949111d6edc4 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -79,12 +79,14 @@ class BaseExpensiTextInput extends Component { if (this.props.onFocus) { this.props.onFocus(event); } this.setState({isFocused: true}); this.activateLabel(); + this.props.clearInputErrors(this.props.name); } onBlur(event) { if (this.props.onBlur) { this.props.onBlur(event); } this.setState({isFocused: false}); this.deactivateLabel(); + this.props.validate(this.props.name); } /** @@ -95,6 +97,7 @@ class BaseExpensiTextInput extends Component { */ setValue(value) { this.value = value; + this.props.saveDraft({[this.props.name]: value}); Str.result(this.props.onChangeText, value); this.activateLabel(); } @@ -156,7 +159,7 @@ class BaseExpensiTextInput extends Component { style={[ styles.expensiTextInputContainer, this.state.isFocused && styles.borderColorFocus, - (this.props.hasError || this.props.errorText) && styles.borderColorDanger, + (this.props.hasError || this.props.errorText || this.props.error) && styles.borderColorDanger, ]} > {hasLabel ? ( @@ -209,9 +212,9 @@ class BaseExpensiTextInput extends Component { - {!_.isEmpty(this.props.errorText) && ( + {!_.isEmpty(this.props.errorText || this.props.error) && ( - {this.props.errorText} + {this.props.errorText || this.props.error} )} diff --git a/src/components/ExpensiTextInput/index.js b/src/components/ExpensiTextInput/index.js index 06bc5a9de525..783a4c53fba2 100644 --- a/src/components/ExpensiTextInput/index.js +++ b/src/components/ExpensiTextInput/index.js @@ -12,6 +12,7 @@ const ExpensiTextInput = forwardRef((props, ref) => ( /> )); +ExpensiTextInput.EXPENSIFORM = true; ExpensiTextInput.propTypes = propTypes; ExpensiTextInput.defaultProps = defaultProps; ExpensiTextInput.displayName = 'ExpensiTextInput'; diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 7a1330033638..b06cb829187b 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -28,6 +28,8 @@ import WorkspaceSection from '../workspace/WorkspaceSection'; import * as ValidationUtils from '../../libs/ValidationUtils'; import * as Illustrations from '../../components/Icon/Illustrations'; +import ExpensiForm from '../../components/ExpensiForm'; + const propTypes = { /** Bank account currently in setup */ // eslint-disable-next-line react/no-unused-prop-types @@ -57,19 +59,9 @@ class BankAccountStep extends React.Component { this.state = { // One of CONST.BANK_ACCOUNT.SETUP_TYPE hasAcceptedTerms: ReimbursementAccountUtils.getDefaultStateForField(props, 'acceptTerms', true), - routingNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'routingNumber'), - accountNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'accountNumber'), - }; - - // Keys in this.errorTranslationKeys are associated to inputs, they are a subset of the keys found in this.state - this.errorTranslationKeys = { - routingNumber: 'bankAccount.error.routingNumber', - accountNumber: 'bankAccount.error.accountNumber', + // routingNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'routingNumber'), + // accountNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'accountNumber'), }; - - this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); - this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); - this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); } toggleTerms() { @@ -84,46 +76,32 @@ class BankAccountStep extends React.Component { /** * @returns {Boolean} */ - validate() { + validate(values) { const errors = {}; // These are taken from BankCountry.js in Web-Secure - if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(this.state.accountNumber.trim())) { + if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(values.accountNumber.trim())) { errors.accountNumber = true; } - if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(this.state.routingNumber.trim()) || !ValidationUtils.isValidRoutingNumber(this.state.routingNumber.trim())) { - errors.routingNumber = true; + if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(values.routingNumber.trim()) || !ValidationUtils.isValidRoutingNumber(values.routingNumber.trim())) { + errors.routingNumber = 'reimbursementAccount.errors.routingNumber'; } - if (!this.state.hasAcceptedTerms) { + if (!values.hasAcceptedTerms) { errors.hasAcceptedTerms = true; } BankAccounts.setBankAccountFormValidationErrors(errors); + return errors; } - /** - * Clear the error associated to inputKey if found and store the inputKey new value in the state. - * - * @param {String} inputKey - * @param {String} value - */ - clearErrorAndSetValue(inputKey, value) { - const newState = {[inputKey]: value}; - this.setState(newState); - BankAccounts.updateReimbursementAccountDraft(newState); - this.clearError(inputKey); - } - - addManualAccount() { + addManualAccount(values) { if (!this.validate()) { BankAccounts.showBankAccountErrorModal(); return; } BankAccounts.setupWithdrawalAccount({ - acceptTerms: this.state.hasAcceptedTerms, - accountNumber: this.state.accountNumber, - routingNumber: this.state.routingNumber, + ...values, setupType: CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL, // Note: These are hardcoded as we're not supporting AU bank accounts for the free plan @@ -255,8 +233,10 @@ class BankAccountStep extends React.Component { /> )} {subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && ( - {this.props.translate('bankAccount.checkHelpLine')} @@ -267,21 +247,23 @@ class BankAccountStep extends React.Component { source={exampleCheckImage(this.props.preferredLocale)} /> this.clearErrorAndSetValue('routingNumber', value)} + // value={this.state.routingNumber} + // onChangeText={value => this.clearErrorAndSetValue('routingNumber', value)} disabled={shouldDisableInputs} - errorText={this.getErrorText('routingNumber')} + // errorText={this.getErrorText('routingNumber')} /> this.clearErrorAndSetValue('accountNumber', value)} + // value={this.state.accountNumber} + // onChangeText={value => this.clearErrorAndSetValue('accountNumber', value)} disabled={shouldDisableInputs} - errorText={this.getErrorText('accountNumber')} + // errorText={this.getErrorText('accountNumber')} /> )} - hasError={this.getErrors().hasAcceptedTerms} + // hasError={this.getErrors().hasAcceptedTerms} /> - + )} ); From 5fdb65a213a256c79fd891032be5b52087c770f6 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 15 Dec 2021 17:42:55 -0700 Subject: [PATCH 03/69] add onsubmit and other handlers --- src/components/ExpensiForm.js | 49 +++++++++++++------ src/components/ExpensifyButton.js | 6 ++- .../setupWithdrawalAccount.js | 11 +++-- .../ReimbursementAccount/BankAccountStep.js | 37 +++++++++----- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index a9521d60c75d..277b930a659d 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -2,6 +2,8 @@ import React from 'react'; import _ from 'underscore'; import PropTypes from 'prop-types'; import * as FormAction from '../libs/actions/ExpensiForm'; +import {ScrollView, View} from 'react-native'; +import styles from '../styles/styles'; const propTypes = { name: PropTypes.string.isRequired, @@ -9,6 +11,8 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types defaultValues: PropTypes.object, + // eslint-disable-next-line react/forbid-prop-types + serverErrors: PropTypes.object, validate: PropTypes.func.isRequired, saveDraft: PropTypes.bool, @@ -16,6 +20,7 @@ const propTypes = { const defaultProps = { defaultValues: {}, + serverErrors: {}, saveDraft: true, }; @@ -24,8 +29,10 @@ class ExpensiForm extends React.Component { super(props); this.state = { + isLoading: false, defaultValues: this.props.defaultValues, errors: {}, + serverErrors: {}, }; this.inputRefs = React.createRef(); this.inputRefs.current = {}; @@ -35,6 +42,8 @@ class ExpensiForm extends React.Component { this.onSubmit = this.onSubmit.bind(this); this.validate = this.validate.bind(this); this.clearInputErrors = this.clearInputErrors.bind(this); + this.setLoading = this.setLoading.bind(this); + this.setServerError = this.setServerError.bind(this); } getFormValues() { @@ -45,21 +54,18 @@ class ExpensiForm extends React.Component { return formData; } - // TODO: Skip saving draft for sensitive inputs, e.g. passwords - // Should be called onChange saveDraft(draft) { if (!this.props.saveDraft) { return; } - FormAction.saveFormDraft(this.props.name, {draft}); + FormAction.saveFormDraft(`${this.props.name}_draft`, {draft}); } - // Should be called onBlur and onSubmit validate(field) { const values = this.getFormValues(); // validate takes in form values and returns errors object in the format - // how do we handle multiple errors in this case??? // {username: 'form.errors.required', name: 'form.errors.tooShort', ...} + // how do we handle multiple errors in this case??? // We check if we are trying to validate a single field or the entire form const errors = this.props.validate(values); @@ -73,6 +79,7 @@ class ExpensiForm extends React.Component { } else { this.setState({errors}); } + return errors; } // Should be called onFocus @@ -85,17 +92,21 @@ class ExpensiForm extends React.Component { })); } - onSubmit(submit) { + setLoading(value) { + this.setState({isLoading: value}) + } + + setServerError(value) { + this.setState({serverError: value}); + } + + onSubmit() { const values = this.getFormValues(); - this.validate(values); - if (!_.isEmpty(this.state.errors)) { + const errors = this.validate(); + if (!_.isEmpty(errors)) { return; } - FormAction.setLoading(this.props.name, true); - - submit(values).then(() => { - FormAction.setLoading(this.props.name, false); - }); + this.props.onSubmit(values, {setLoading: this.setLoading, setServerError: this.setServerError}) } render() { @@ -129,13 +140,23 @@ class ExpensiForm extends React.Component { onSubmit: this.onSubmit, defaultValue: this.state.defaultValues[child.props.name], error: this.state.errors[child.props.name], + isLoading: this.state.isLoading, }); }) ); return ( <> - {childrenWrapperWithProps(this.props.children)} + + {/* Form elements */} + + {childrenWrapperWithProps(this.props.children)} + + ); } diff --git a/src/components/ExpensifyButton.js b/src/components/ExpensifyButton.js index 15fd3909390f..3c2633e0c7bb 100644 --- a/src/components/ExpensifyButton.js +++ b/src/components/ExpensifyButton.js @@ -110,7 +110,8 @@ class ExpensifyButton extends Component { if (this.props.isDisabled || this.props.isLoading) { return; } - this.props.onPress(); + // this.props.onPress(); + this.props.onSubmit(); }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true); } @@ -169,7 +170,7 @@ class ExpensifyButton extends Component { render() { return ( { @@ -318,10 +319,12 @@ function setupWithdrawalAccount(params) { // Go to next step navigation.goToWithdrawalAccountSetupStep(getNextStep(updatedACHData), responseACHData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); + // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); + setLoading(false); }) .catch((response) => { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}}); + // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}}); + setLoading(false); console.error(response.stack); errors.showBankAccountErrorModal(Localize.translateLocal('common.genericErrorMessage')); }); diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index b06cb829187b..c1d26806e691 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -29,6 +29,7 @@ import * as ValidationUtils from '../../libs/ValidationUtils'; import * as Illustrations from '../../components/Icon/Illustrations'; import ExpensiForm from '../../components/ExpensiForm'; +import ExpensifyButton from '../../components/ExpensifyButton'; const propTypes = { /** Bank account currently in setup */ @@ -81,24 +82,24 @@ class BankAccountStep extends React.Component { // These are taken from BankCountry.js in Web-Secure if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(values.accountNumber.trim())) { - errors.accountNumber = true; + errors.accountNumber = 'reimbursementAccount.errors.accountNumber'; } if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(values.routingNumber.trim()) || !ValidationUtils.isValidRoutingNumber(values.routingNumber.trim())) { errors.routingNumber = 'reimbursementAccount.errors.routingNumber'; } - if (!values.hasAcceptedTerms) { - errors.hasAcceptedTerms = true; - } + // if (!values.hasAcceptedTerms) { + // errors.hasAcceptedTerms = true; + // } - BankAccounts.setBankAccountFormValidationErrors(errors); + // BankAccounts.setBankAccountFormValidationErrors(errors); return errors; } - addManualAccount(values) { - if (!this.validate()) { - BankAccounts.showBankAccountErrorModal(); - return; - } + addManualAccount(values, setLoading) { + // if (!this.validate()) { + // BankAccounts.showBankAccountErrorModal(); + // return; + // } BankAccounts.setupWithdrawalAccount({ ...values, @@ -108,7 +109,7 @@ class BankAccountStep extends React.Component { country: CONST.COUNTRY.US, currency: CONST.CURRENCY.USD, fieldsType: CONST.BANK_ACCOUNT.FIELDS_TYPE.LOCAL, - }); + }, setLoading); } /** @@ -235,8 +236,10 @@ class BankAccountStep extends React.Component { {subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && ( {this.props.translate('bankAccount.checkHelpLine')} @@ -281,6 +284,14 @@ class BankAccountStep extends React.Component { )} // hasError={this.getErrors().hasAcceptedTerms} /> + )} @@ -298,7 +309,7 @@ export default compose( key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, + key: `${ONYXKEYS.REIMBURSEMENT_ACCOUNT}_draft`, }, user: { key: ONYXKEYS.USER, From 6ac6d0e9552f738a96a2c1a1ce4eed6fcb7360e1 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 15 Dec 2021 18:40:51 -0700 Subject: [PATCH 04/69] save progress --- src/components/ExpensiForm.js | 9 +- src/components/ExpensiFormSubmit.js | 119 ++++++++++++++++++ .../ReimbursementAccount/BankAccountStep.js | 5 +- 3 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 src/components/ExpensiFormSubmit.js diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 277b930a659d..db08f7c5b763 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -77,7 +77,7 @@ class ExpensiForm extends React.Component { } })); } else { - this.setState({errors}); + this.setState({serverErrors: 'There were errors submitting the form', errors}); } return errors; } @@ -116,19 +116,21 @@ class ExpensiForm extends React.Component { if (!React.isValidElement(child)) { return child; } - + console.log('here 1', child?.type?.displayName) // Depth first traversal of the render tree as the form element is likely to be the last node if (child.props.children) { child = React.cloneElement(child, { children: childrenWrapperWithProps(child.props.children), }); } - + console.log('here 2', child?.type?.displayName) // We check if the component has the EXPENSIFORM static property enabled, // as we don't want to pass form props to non form components, e.g. View, Text, etc + console.log(child.type) if (!child.type.EXPENSIFORM) { return child; } + console.log('here 3', child?.type?.displayName) // We clone the child passing down all form props const inputRef = node => this.inputRefs.current[child.props.name] = node; @@ -140,6 +142,7 @@ class ExpensiForm extends React.Component { onSubmit: this.onSubmit, defaultValue: this.state.defaultValues[child.props.name], error: this.state.errors[child.props.name], + serverError: this.state.serverError, isLoading: this.state.isLoading, }); }) diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js new file mode 100644 index 000000000000..23d8a714c54c --- /dev/null +++ b/src/components/ExpensiFormSubmit.js @@ -0,0 +1,119 @@ +import _ from 'underscore'; +import React from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import styles from '../styles/styles'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import colors from '../styles/colors'; +import ExpensifyButton from './ExpensifyButton'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import TextLink from './TextLink'; +import ExpensifyText from './ExpensifyText'; +import RenderHTML from './RenderHTML'; + +const propTypes = { + // /** Whether the button is disabled */ + // isDisabled: PropTypes.bool, + + // /** Submit function */ + // onSubmit: PropTypes.func.isRequired, + + // /** Text for the button */ + // buttonText: PropTypes.string.isRequired, + + // /** Callback fired when the "fix the errors" link is pressed */ + // onFixTheErrorsLinkPressed: PropTypes.func.isRequired, + + // /** Error message to display above button */ + // message: PropTypes.string, + + // /** Whether message is in html format */ + // isMessageHtml: PropTypes.bool, + + // /** Styles for container element */ + // containerStyles: PropTypes.arrayOf(PropTypes.object), + + // /** Is the button in a loading state */ + // isLoading: PropTypes.bool, + + // ...withLocalizePropTypes, +}; + +const defaultProps = { + message: '', + isDisabled: false, + isMessageHtml: false, + containerStyles: [], + isLoading: false, +}; + +const FormAlertWithSubmitButton = (props) => { + /** + * @returns {React.Component} + */ + function getAlertPrompt() { + let error = ''; + + if (!_.isEmpty(props.message)) { + if (props.isMessageHtml) { + error = ( + ${props.message}`} /> + ); + } else { + error = ( + {props.message} + ); + } + } else { + error = ( + <> + + {`${props.translate('common.please')} `} + + + {props.translate('common.fixTheErrors')} + + + {` ${props.translate('common.inTheFormBeforeContinuing')}.`} + + + ); + } + + return ( + + {error} + + ); + } + console.log(props) + return ( + + {props.serverErrors && ( + + + {getAlertPrompt()} + + )} + + + ); +}; + +FormAlertWithSubmitButton.EXPENSIFORM = true; +FormAlertWithSubmitButton.propTypes = propTypes; +FormAlertWithSubmitButton.defaultProps = defaultProps; +FormAlertWithSubmitButton.displayName = 'FormAlertWithSubmitButton'; + +export default withLocalize(FormAlertWithSubmitButton); diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index c1d26806e691..b6087ca7cbce 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -30,6 +30,7 @@ import * as Illustrations from '../../components/Icon/Illustrations'; import ExpensiForm from '../../components/ExpensiForm'; import ExpensifyButton from '../../components/ExpensifyButton'; +import ExpensiFormSubmit from '../../components/ExpensiFormSubmit'; const propTypes = { /** Bank account currently in setup */ @@ -284,10 +285,10 @@ class BankAccountStep extends React.Component { )} // hasError={this.getErrors().hasAcceptedTerms} /> - Date: Thu, 16 Dec 2021 12:54:40 -0700 Subject: [PATCH 05/69] fix props usage in ExpensifyButton and ExpensiTextInput --- ios/Podfile.lock | 4 ++-- .../ExpensiTextInput/BaseExpensiTextInput.js | 12 +++++++++--- src/components/ExpensifyButton.js | 11 ++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a8781483c7cc..05b229c33406 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -506,7 +506,7 @@ PODS: - React-perflogger (= 0.64.1) - rn-fetch-blob (0.12.0): - React-Core - - RNBootSplash (3.2.6): + - RNBootSplash (3.2.0): - React-Core - RNCAsyncStorage (1.15.5): - React-Core @@ -920,7 +920,7 @@ SPEC CHECKSUMS: React-runtimeexecutor: ff951a0c241bfaefc4940a3f1f1a229e7cb32fa6 ReactCommon: bedc99ed4dae329c4fcf128d0c31b9115e5365ca rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba - RNBootSplash: 00f99e3c05fb44af3613e6088406de4be0a8eca3 + RNBootSplash: 3123ba68fe44d8be09a014e89cc8f0f55b68a521 RNCAsyncStorage: 8324611026e8dc3706f829953aa6e3899f581589 RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3 RNCMaskedView: 138134c4d8a9421b4f2bf39055a79aa05c2d47b1 diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 949111d6edc4..247c5cdddd0f 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -79,14 +79,18 @@ class BaseExpensiTextInput extends Component { if (this.props.onFocus) { this.props.onFocus(event); } this.setState({isFocused: true}); this.activateLabel(); - this.props.clearInputErrors(this.props.name); + if (this.props.cleclearInputErrors) { + this.props.clearInputErrors(this.props.name); + } } onBlur(event) { if (this.props.onBlur) { this.props.onBlur(event); } this.setState({isFocused: false}); this.deactivateLabel(); - this.props.validate(this.props.name); + if (this.props.validate) { + this.props.validate(this.props.name); + } } /** @@ -97,7 +101,9 @@ class BaseExpensiTextInput extends Component { */ setValue(value) { this.value = value; - this.props.saveDraft({[this.props.name]: value}); + if (this.props.saveDraft) { + this.props.saveDraft({[this.props.name]: value}); + } Str.result(this.props.onChangeText, value); this.activateLabel(); } diff --git a/src/components/ExpensifyButton.js b/src/components/ExpensifyButton.js index 3c2633e0c7bb..7982d20703e9 100644 --- a/src/components/ExpensifyButton.js +++ b/src/components/ExpensifyButton.js @@ -96,6 +96,7 @@ class ExpensifyButton extends Component { this.additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style]; this.renderContent = this.renderContent.bind(this); + this.onPress = this.onPress.bind(this); } componentDidMount() { @@ -167,10 +168,18 @@ class ExpensifyButton extends Component { return textComponent; } + onPress() { + if (this.props.onSubmit) { + this.props.onSubmit(); + } else { + this.props.onPress(); + } + } + render() { return ( Date: Thu, 16 Dec 2021 13:25:02 -0700 Subject: [PATCH 06/69] read static property for wrapped component --- src/components/ExpensiForm.js | 9 ++++----- src/components/ExpensiFormSubmit.js | 4 ++-- src/components/withLocalize.js | 2 +- src/pages/ReimbursementAccount/BankAccountStep.js | 7 +++++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index db08f7c5b763..f828c6c91f1d 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -58,7 +58,7 @@ class ExpensiForm extends React.Component { if (!this.props.saveDraft) { return; } - FormAction.saveFormDraft(`${this.props.name}_draft`, {draft}); + FormAction.saveFormDraft(`${this.props.name}_draft`, {...draft}); } validate(field) { @@ -116,21 +116,20 @@ class ExpensiForm extends React.Component { if (!React.isValidElement(child)) { return child; } - console.log('here 1', child?.type?.displayName) + // Depth first traversal of the render tree as the form element is likely to be the last node if (child.props.children) { child = React.cloneElement(child, { children: childrenWrapperWithProps(child.props.children), }); } - console.log('here 2', child?.type?.displayName) + // We check if the component has the EXPENSIFORM static property enabled, // as we don't want to pass form props to non form components, e.g. View, Text, etc - console.log(child.type) if (!child.type.EXPENSIFORM) { return child; } - console.log('here 3', child?.type?.displayName) + // We clone the child passing down all form props const inputRef = node => this.inputRefs.current[child.props.name] = node; diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js index 23d8a714c54c..de14f7953612 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormSubmit.js @@ -90,9 +90,9 @@ const FormAlertWithSubmitButton = (props) => { ); } - console.log(props) + return ( - + {props.serverErrors && ( diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 6f885ef36f40..918c6c98ce1f 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -144,7 +144,7 @@ export default function withLocalize(WrappedComponent) { )); WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; - + WithLocalize.EXPENSIFORM = WrappedComponent.EXPENSIFORM; return WithLocalize; } diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index b6087ca7cbce..705857e134bb 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -37,6 +37,8 @@ const propTypes = { // eslint-disable-next-line react/no-unused-prop-types reimbursementAccount: reimbursementAccountPropTypes.isRequired, + reimbursementAccountDraft: PropTypes.object, + /** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */ receivedRedirectURI: PropTypes.string, @@ -49,6 +51,7 @@ const propTypes = { const defaultProps = { receivedRedirectURI: null, plaidLinkOAuthToken: '', + reimbursementAccountDraft: {}, }; class BankAccountStep extends React.Component { @@ -96,7 +99,7 @@ class BankAccountStep extends React.Component { return errors; } - addManualAccount(values, setLoading) { + addManualAccount(values, {setLoading}) { // if (!this.validate()) { // BankAccounts.showBankAccountErrorModal(); // return; @@ -237,7 +240,7 @@ class BankAccountStep extends React.Component { {subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && ( Date: Thu, 16 Dec 2021 14:32:47 -0700 Subject: [PATCH 07/69] add serverError scroll --- src/components/ExpensiForm.js | 38 +++++++++++++------ src/components/ExpensiFormSubmit.js | 7 ++-- .../setupWithdrawalAccount.js | 8 ++-- .../ReimbursementAccount/BankAccountStep.js | 4 +- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index f828c6c91f1d..e47e2a348977 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -12,7 +12,10 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types defaultValues: PropTypes.object, // eslint-disable-next-line react/forbid-prop-types - serverErrors: PropTypes.object, + serverError: PropTypes.shape({ + firstErrorToFix: PropTypes.ref, + message: PropTypes.string, + }), validate: PropTypes.func.isRequired, saveDraft: PropTypes.bool, @@ -20,7 +23,10 @@ const propTypes = { const defaultProps = { defaultValues: {}, - serverErrors: {}, + serverError: { + firstErrorToFix: null, + message: '', + }, saveDraft: true, }; @@ -32,7 +38,7 @@ class ExpensiForm extends React.Component { isLoading: false, defaultValues: this.props.defaultValues, errors: {}, - serverErrors: {}, + serverError: {}, }; this.inputRefs = React.createRef(); this.inputRefs.current = {}; @@ -69,15 +75,22 @@ class ExpensiForm extends React.Component { // We check if we are trying to validate a single field or the entire form const errors = this.props.validate(values); - if (field) { - this.setState(prevState => ({ - errors: { - ...prevState.errors, - [field]: errors[field] - } - })); - } else { - this.setState({serverErrors: 'There were errors submitting the form', errors}); + if (!_.isEmpty(errors)) { + if (field) { + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [field]: errors[field] + } + })); + } else { + this.setState({ + serverError: { + firstErrorToFix: this.inputRefs.current[_.keys(errors)[0]] + }, + errors, + }); + } } return errors; } @@ -132,6 +145,7 @@ class ExpensiForm extends React.Component { // We clone the child passing down all form props + // We should only pass refs to class components! const inputRef = node => this.inputRefs.current[child.props.name] = node; return React.cloneElement(child, { ref: inputRef, diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js index de14f7953612..4cdfeff28053 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormSubmit.js @@ -55,7 +55,8 @@ const FormAlertWithSubmitButton = (props) => { function getAlertPrompt() { let error = ''; - if (!_.isEmpty(props.message)) { + // TODO: Check html messages from server errors + if (!_.isEmpty(props.serverError.message)) { if (props.isMessageHtml) { error = ( ${props.message}`} /> @@ -73,7 +74,7 @@ const FormAlertWithSubmitButton = (props) => { props.serverError.firstErrorToFix.focus()} > {props.translate('common.fixTheErrors')} @@ -93,7 +94,7 @@ const FormAlertWithSubmitButton = (props) => { return ( - {props.serverErrors && ( + {!_.isEmpty(props.serverError) && ( {getAlertPrompt()} diff --git a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js index 3259b2331439..c88ed418fe14 100644 --- a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js +++ b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js @@ -285,9 +285,9 @@ function checkDataAndMaybeStayOnRequestorStep(achData, nextStep) { * @param {Boolean} [params.certifyTrueInformation] * @param {Array} [params.beneficialOwners] */ -function setupWithdrawalAccount(params, setLoading) { +function setupWithdrawalAccount(params, formFunctions) { // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true, errorModalMessage: '', errors: null}); - setLoading(true); + formFunctions.setLoading(true); const updatedACHData = mergeParamsWithLocalACHData(params); API.BankAccount_SetupWithdrawal(updatedACHData) .then((response) => { @@ -320,11 +320,11 @@ function setupWithdrawalAccount(params, setLoading) { // Go to next step navigation.goToWithdrawalAccountSetupStep(getNextStep(updatedACHData), responseACHData); // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); - setLoading(false); + formFunctions.setLoading(false); }) .catch((response) => { // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}}); - setLoading(false); + formFunctions.addressZipCodesetLoading(false); console.error(response.stack); errors.showBankAccountErrorModal(Localize.translateLocal('common.genericErrorMessage')); }); diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 705857e134bb..81160d76c24f 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -99,7 +99,7 @@ class BankAccountStep extends React.Component { return errors; } - addManualAccount(values, {setLoading}) { + addManualAccount(values, formFunctions) { // if (!this.validate()) { // BankAccounts.showBankAccountErrorModal(); // return; @@ -113,7 +113,7 @@ class BankAccountStep extends React.Component { country: CONST.COUNTRY.US, currency: CONST.CURRENCY.USD, fieldsType: CONST.BANK_ACCOUNT.FIELDS_TYPE.LOCAL, - }, setLoading); + }, formFunctions); } /** From 0abd92371a41f9438385ad935a6e7e0e6d7a4fab Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 29 Dec 2021 12:38:40 -0700 Subject: [PATCH 08/69] move serverError to errors --- src/components/ExpensiForm.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index e47e2a348977..05fceb8609df 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -11,11 +11,6 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types defaultValues: PropTypes.object, - // eslint-disable-next-line react/forbid-prop-types - serverError: PropTypes.shape({ - firstErrorToFix: PropTypes.ref, - message: PropTypes.string, - }), validate: PropTypes.func.isRequired, saveDraft: PropTypes.bool, @@ -23,10 +18,6 @@ const propTypes = { const defaultProps = { defaultValues: {}, - serverError: { - firstErrorToFix: null, - message: '', - }, saveDraft: true, }; @@ -38,10 +29,10 @@ class ExpensiForm extends React.Component { isLoading: false, defaultValues: this.props.defaultValues, errors: {}, - serverError: {}, }; this.inputRefs = React.createRef(); this.inputRefs.current = {}; + this.firstErrorToFix = null; this.getFormValues = this.getFormValues.bind(this); this.saveDraft = this.saveDraft.bind(this); @@ -84,12 +75,8 @@ class ExpensiForm extends React.Component { } })); } else { - this.setState({ - serverError: { - firstErrorToFix: this.inputRefs.current[_.keys(errors)[0]] - }, - errors, - }); + this.setState({errors}); + this.firstErrorToFix = this.inputRefs.current[_.keys(errors)[0]]; } } return errors; @@ -110,7 +97,12 @@ class ExpensiForm extends React.Component { } setServerError(value) { - this.setState({serverError: value}); + this.setState(prevState => ({ + errors: { + ...prevState.errors, + serverError: value, + }, + })); } onSubmit() { @@ -155,7 +147,7 @@ class ExpensiForm extends React.Component { onSubmit: this.onSubmit, defaultValue: this.state.defaultValues[child.props.name], error: this.state.errors[child.props.name], - serverError: this.state.serverError, + serverError: this.state.errors.serverError, isLoading: this.state.isLoading, }); }) From bbc0d73548120942633be33ceb38fdf769a43657 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 29 Dec 2021 12:41:38 -0700 Subject: [PATCH 09/69] update usage of ExpensifyButton and ExpensifyText --- src/components/ExpensiFormSubmit.js | 16 ++++++++-------- .../ReimbursementAccount/BankAccountStep.js | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js index 4cdfeff28053..ba61e2a10525 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormSubmit.js @@ -6,10 +6,10 @@ import styles from '../styles/styles'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import colors from '../styles/colors'; -import ExpensifyButton from './ExpensifyButton'; +import Button from './Button'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import TextLink from './TextLink'; -import ExpensifyText from './ExpensifyText'; +import Text from './Text'; import RenderHTML from './RenderHTML'; const propTypes = { @@ -63,24 +63,24 @@ const FormAlertWithSubmitButton = (props) => { ); } else { error = ( - {props.message} + {props.message} ); } } else { error = ( <> - + {`${props.translate('common.please')} `} - + props.serverError.firstErrorToFix.focus()} > {props.translate('common.fixTheErrors')} - + {` ${props.translate('common.inTheFormBeforeContinuing')}.`} - + ); } @@ -100,7 +100,7 @@ const FormAlertWithSubmitButton = (props) => { {getAlertPrompt()} )} - Date: Wed, 29 Dec 2021 13:23:05 -0700 Subject: [PATCH 10/69] update alerts --- src/components/ExpensiForm.js | 27 ++++++++++--------- src/components/ExpensiFormSubmit.js | 13 +++++---- .../ExpensiTextInput/BaseExpensiTextInput.js | 2 +- .../ReimbursementAccount/BankAccountStep.js | 6 ++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 05fceb8609df..00d22bd0fc45 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -29,10 +29,10 @@ class ExpensiForm extends React.Component { isLoading: false, defaultValues: this.props.defaultValues, errors: {}, + alert: {}, }; this.inputRefs = React.createRef(); this.inputRefs.current = {}; - this.firstErrorToFix = null; this.getFormValues = this.getFormValues.bind(this); this.saveDraft = this.saveDraft.bind(this); @@ -40,7 +40,7 @@ class ExpensiForm extends React.Component { this.validate = this.validate.bind(this); this.clearInputErrors = this.clearInputErrors.bind(this); this.setLoading = this.setLoading.bind(this); - this.setServerError = this.setServerError.bind(this); + this.setFormAlert = this.setFormAlert.bind(this); } getFormValues() { @@ -75,8 +75,12 @@ class ExpensiForm extends React.Component { } })); } else { - this.setState({errors}); - this.firstErrorToFix = this.inputRefs.current[_.keys(errors)[0]]; + this.setState({ + errors, + alert: { + firstErrorToFix: this.inputRefs.current[_.keys(errors)[0]], + } + }); } } return errors; @@ -96,13 +100,8 @@ class ExpensiForm extends React.Component { this.setState({isLoading: value}) } - setServerError(value) { - this.setState(prevState => ({ - errors: { - ...prevState.errors, - serverError: value, - }, - })); + setFormAlert(serverError) { + this.setState({alert: {serverError}}); } onSubmit() { @@ -111,7 +110,7 @@ class ExpensiForm extends React.Component { if (!_.isEmpty(errors)) { return; } - this.props.onSubmit(values, {setLoading: this.setLoading, setServerError: this.setServerError}) + this.props.onSubmit(values, {setLoading: this.setLoading, setFormAlert: this.setFormAlert}) } render() { @@ -135,6 +134,8 @@ class ExpensiForm extends React.Component { return child; } + // TODO Should we have a separate static property for the submit button and pass loading state / onSubmit only to that component? + // We clone the child passing down all form props // We should only pass refs to class components! @@ -147,7 +148,7 @@ class ExpensiForm extends React.Component { onSubmit: this.onSubmit, defaultValue: this.state.defaultValues[child.props.name], error: this.state.errors[child.props.name], - serverError: this.state.errors.serverError, + alert: this.state.alert, isLoading: this.state.isLoading, }); }) diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js index ba61e2a10525..ebdebed3581f 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormSubmit.js @@ -55,15 +55,14 @@ const FormAlertWithSubmitButton = (props) => { function getAlertPrompt() { let error = ''; - // TODO: Check html messages from server errors - if (!_.isEmpty(props.serverError.message)) { - if (props.isMessageHtml) { + if (!_.isEmpty(props.alert.serverError)) { + if (props.alert.serverError.isMessageHtml) { error = ( - ${props.message}`} /> + ${props.alert.serverError.message}`} /> ); } else { error = ( - {props.message} + {props.alert.serverError.message} ); } } else { @@ -74,7 +73,7 @@ const FormAlertWithSubmitButton = (props) => { props.serverError.firstErrorToFix.focus()} + onPress={() => props.alert.firstErrorToFix.focus()} > {props.translate('common.fixTheErrors')} @@ -94,7 +93,7 @@ const FormAlertWithSubmitButton = (props) => { return ( - {!_.isEmpty(props.serverError) && ( + {!_.isEmpty(props.alert) && ( {getAlertPrompt()} diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 247c5cdddd0f..8593734d80c6 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -79,7 +79,7 @@ class BaseExpensiTextInput extends Component { if (this.props.onFocus) { this.props.onFocus(event); } this.setState({isFocused: true}); this.activateLabel(); - if (this.props.cleclearInputErrors) { + if (this.props.clearInputErrors) { this.props.clearInputErrors(this.props.name); } } diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 5b4451f29803..2d5e140535c0 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -83,13 +83,13 @@ class BankAccountStep extends React.Component { validate(values) { const errors = {}; + if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(values.routingNumber.trim()) || !ValidationUtils.isValidRoutingNumber(values.routingNumber.trim())) { + errors.routingNumber = 'reimbursementAccount.errors.routingNumber'; + } // These are taken from BankCountry.js in Web-Secure if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(values.accountNumber.trim())) { errors.accountNumber = 'reimbursementAccount.errors.accountNumber'; } - if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(values.routingNumber.trim()) || !ValidationUtils.isValidRoutingNumber(values.routingNumber.trim())) { - errors.routingNumber = 'reimbursementAccount.errors.routingNumber'; - } // if (!values.hasAcceptedTerms) { // errors.hasAcceptedTerms = true; // } From 31a9087fbcc1fbbc820d43a08c68cdc68c05774f Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 29 Dec 2021 13:56:40 -0700 Subject: [PATCH 11/69] refactor submit to set alerts --- src/components/ExpensiForm.js | 4 ++-- src/components/ExpensiFormSubmit.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 00d22bd0fc45..f4920e548f58 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -100,8 +100,8 @@ class ExpensiForm extends React.Component { this.setState({isLoading: value}) } - setFormAlert(serverError) { - this.setState({alert: {serverError}}); + setFormAlert(alert) { + this.setState({alert}); } onSubmit() { diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js index ebdebed3581f..de11583f4698 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormSubmit.js @@ -55,14 +55,14 @@ const FormAlertWithSubmitButton = (props) => { function getAlertPrompt() { let error = ''; - if (!_.isEmpty(props.alert.serverError)) { - if (props.alert.serverError.isMessageHtml) { + if (!_.isEmpty(props.alert.message)) { + if (props.alert.isMessageHtml) { error = ( - ${props.alert.serverError.message}`} /> + ${props.alert.message}`} /> ); } else { error = ( - {props.alert.serverError.message} + {props.alert.message} ); } } else { @@ -114,6 +114,6 @@ const FormAlertWithSubmitButton = (props) => { FormAlertWithSubmitButton.EXPENSIFORM = true; FormAlertWithSubmitButton.propTypes = propTypes; FormAlertWithSubmitButton.defaultProps = defaultProps; -FormAlertWithSubmitButton.displayName = 'FormAlertWithSubmitButton'; +FormAlertWithSubmitButton.displayName = 'ExpensiFormSubmit'; export default withLocalize(FormAlertWithSubmitButton); From c8269e2c635e9694a391fcab047c2cb9e02a68a3 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 29 Dec 2021 14:15:45 -0700 Subject: [PATCH 12/69] update expensiformsubmit prop types --- src/components/ExpensiFormSubmit.js | 35 +++++++++++++---------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js index de11583f4698..5d5123cb36ef 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormSubmit.js @@ -13,31 +13,26 @@ import Text from './Text'; import RenderHTML from './RenderHTML'; const propTypes = { - // /** Whether the button is disabled */ - // isDisabled: PropTypes.bool, + /** Submit function */ + onSubmit: PropTypes.func.isRequired, - // /** Submit function */ - // onSubmit: PropTypes.func.isRequired, + /** Text for the button */ + buttonText: PropTypes.string.isRequired, - // /** Text for the button */ - // buttonText: PropTypes.string.isRequired, + /** Styles for container element */ + containerStyles: PropTypes.arrayOf(PropTypes.object), - // /** Callback fired when the "fix the errors" link is pressed */ - // onFixTheErrorsLinkPressed: PropTypes.func.isRequired, + /** Is the button in a loading state */ + isLoading: PropTypes.bool, - // /** Error message to display above button */ - // message: PropTypes.string, + /** Is the button in a loading state */ + alert: PropTypes.shape({ + firstErrorToFix: PropTypes.func, + message: PropTypes.string, + isMessageHtml: PropTypes.bool, + }), - // /** Whether message is in html format */ - // isMessageHtml: PropTypes.bool, - - // /** Styles for container element */ - // containerStyles: PropTypes.arrayOf(PropTypes.object), - - // /** Is the button in a loading state */ - // isLoading: PropTypes.bool, - - // ...withLocalizePropTypes, + ...withLocalizePropTypes, }; const defaultProps = { From 0eb5cc49a53d248af54e920c2e7e9740d85edded Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 29 Dec 2021 14:17:10 -0700 Subject: [PATCH 13/69] remove default values from state in expensiform --- src/components/ExpensiForm.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index f4920e548f58..75b256f4703f 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -27,7 +27,6 @@ class ExpensiForm extends React.Component { this.state = { isLoading: false, - defaultValues: this.props.defaultValues, errors: {}, alert: {}, }; @@ -146,7 +145,7 @@ class ExpensiForm extends React.Component { validate: this.validate, clearInputErrors: this.clearInputErrors, onSubmit: this.onSubmit, - defaultValue: this.state.defaultValues[child.props.name], + defaultValue: this.props.defaultValues[child.props.name], error: this.state.errors[child.props.name], alert: this.state.alert, isLoading: this.state.isLoading, From daeb792de00f5c5c3d7c585dd30c07897650906b Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 3 Jan 2022 12:53:24 -0700 Subject: [PATCH 14/69] remove createRef, rename static property, add server error handling --- ios/Podfile.lock | 4 ++-- src/components/Button.js | 2 +- src/components/ExpensiForm.js | 18 ++++++++---------- src/components/ExpensiFormSubmit.js | 16 ++++------------ src/components/ExpensiTextInput/index.js | 2 +- .../ExpensiTextInput/index.native.js | 1 + src/components/withLocalize.js | 2 +- .../setupWithdrawalAccount.js | 11 +++++------ 8 files changed, 23 insertions(+), 33 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 05b229c33406..a8781483c7cc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -506,7 +506,7 @@ PODS: - React-perflogger (= 0.64.1) - rn-fetch-blob (0.12.0): - React-Core - - RNBootSplash (3.2.0): + - RNBootSplash (3.2.6): - React-Core - RNCAsyncStorage (1.15.5): - React-Core @@ -920,7 +920,7 @@ SPEC CHECKSUMS: React-runtimeexecutor: ff951a0c241bfaefc4940a3f1f1a229e7cb32fa6 ReactCommon: bedc99ed4dae329c4fcf128d0c31b9115e5365ca rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba - RNBootSplash: 3123ba68fe44d8be09a014e89cc8f0f55b68a521 + RNBootSplash: 00f99e3c05fb44af3613e6088406de4be0a8eca3 RNCAsyncStorage: 8324611026e8dc3706f829953aa6e3899f581589 RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3 RNCMaskedView: 138134c4d8a9421b4f2bf39055a79aa05c2d47b1 diff --git a/src/components/Button.js b/src/components/Button.js index 813a436a5b90..9138aa2d1853 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -215,7 +215,7 @@ class Button extends Component { } } -Button.EXPENSIFORM = true; +Button.EXPENSIFORM_COMPATIBLE_INPUT = true; Button.propTypes = propTypes; Button.defaultProps = defaultProps; diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 75b256f4703f..3167be4849a5 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -30,8 +30,8 @@ class ExpensiForm extends React.Component { errors: {}, alert: {}, }; - this.inputRefs = React.createRef(); - this.inputRefs.current = {}; + + this.inputRefs = {}; this.getFormValues = this.getFormValues.bind(this); this.saveDraft = this.saveDraft.bind(this); @@ -44,8 +44,8 @@ class ExpensiForm extends React.Component { getFormValues() { const formData = {}; - _.each(_.keys(this.inputRefs.current), (key) => { - formData[key] = this.inputRefs.current[key].value; + _.each(_.keys(this.inputRefs), (key) => { + formData[key] = this.inputRefs[key].value; }); return formData; } @@ -77,7 +77,7 @@ class ExpensiForm extends React.Component { this.setState({ errors, alert: { - firstErrorToFix: this.inputRefs.current[_.keys(errors)[0]], + firstErrorToFix: this.inputRefs[_.keys(errors)[0]], } }); } @@ -127,20 +127,18 @@ class ExpensiForm extends React.Component { }); } - // We check if the component has the EXPENSIFORM static property enabled, + // We check if the component has the EXPENSIFORM_COMPATIBLE_INPUT static property enabled, // as we don't want to pass form props to non form components, e.g. View, Text, etc - if (!child.type.EXPENSIFORM) { + if (!child.type.EXPENSIFORM_COMPATIBLE_INPUT) { return child; } // TODO Should we have a separate static property for the submit button and pass loading state / onSubmit only to that component? - // We clone the child passing down all form props // We should only pass refs to class components! - const inputRef = node => this.inputRefs.current[child.props.name] = node; return React.cloneElement(child, { - ref: inputRef, + ref: node => this.inputRefs[child.props.name] = node, saveDraft: this.saveDraft, validate: this.validate, clearInputErrors: this.clearInputErrors, diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormSubmit.js index 5d5123cb36ef..3acf660d89d3 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormSubmit.js @@ -29,7 +29,6 @@ const propTypes = { alert: PropTypes.shape({ firstErrorToFix: PropTypes.func, message: PropTypes.string, - isMessageHtml: PropTypes.bool, }), ...withLocalizePropTypes, @@ -38,7 +37,6 @@ const propTypes = { const defaultProps = { message: '', isDisabled: false, - isMessageHtml: false, containerStyles: [], isLoading: false, }; @@ -51,15 +49,9 @@ const FormAlertWithSubmitButton = (props) => { let error = ''; if (!_.isEmpty(props.alert.message)) { - if (props.alert.isMessageHtml) { - error = ( - ${props.alert.message}`} /> - ); - } else { - error = ( - {props.alert.message} - ); - } + error = ( + ${props.alert.message}`} /> + ); } else { error = ( <> @@ -106,7 +98,7 @@ const FormAlertWithSubmitButton = (props) => { ); }; -FormAlertWithSubmitButton.EXPENSIFORM = true; +FormAlertWithSubmitButton.EXPENSIFORM_COMPATIBLE_INPUT = true; FormAlertWithSubmitButton.propTypes = propTypes; FormAlertWithSubmitButton.defaultProps = defaultProps; FormAlertWithSubmitButton.displayName = 'ExpensiFormSubmit'; diff --git a/src/components/ExpensiTextInput/index.js b/src/components/ExpensiTextInput/index.js index 783a4c53fba2..6cd37e4e3bd5 100644 --- a/src/components/ExpensiTextInput/index.js +++ b/src/components/ExpensiTextInput/index.js @@ -12,7 +12,7 @@ const ExpensiTextInput = forwardRef((props, ref) => ( /> )); -ExpensiTextInput.EXPENSIFORM = true; +ExpensiTextInput.EXPENSIFORM_COMPATIBLE_INPUT = true; ExpensiTextInput.propTypes = propTypes; ExpensiTextInput.defaultProps = defaultProps; ExpensiTextInput.displayName = 'ExpensiTextInput'; diff --git a/src/components/ExpensiTextInput/index.native.js b/src/components/ExpensiTextInput/index.native.js index 3d1b0257e279..96693b495872 100644 --- a/src/components/ExpensiTextInput/index.native.js +++ b/src/components/ExpensiTextInput/index.native.js @@ -16,6 +16,7 @@ const ExpensiTextInput = forwardRef((props, ref) => ( /> )); +ExpensiTextInput.EXPENSIFORM_COMPATIBLE_INPUT = true; ExpensiTextInput.propTypes = propTypes; ExpensiTextInput.defaultProps = defaultProps; ExpensiTextInput.displayName = 'ExpensiTextInput'; diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 918c6c98ce1f..a069001a92e9 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -144,7 +144,7 @@ export default function withLocalize(WrappedComponent) { )); WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; - WithLocalize.EXPENSIFORM = WrappedComponent.EXPENSIFORM; + WithLocalize.EXPENSIFORM_COMPATIBLE_INPUT = WrappedComponent.EXPENSIFORM_COMPATIBLE_INPUT; return WithLocalize; } diff --git a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js index 47406c1fe107..c4771d49c066 100644 --- a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js +++ b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js @@ -75,7 +75,7 @@ function getNextStep(updatedACHData) { * @param {String} verificationsError * @param {Object} updatedACHData */ -function showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData) { +function showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData, formFunctions) { let error = verificationsError; let isErrorHTML = false; const responseACHData = lodashGet(response, 'achData', {}); @@ -100,8 +100,8 @@ function showSetupWithdrawalAccountErrors(response, verificationsError, updatedA } if (error) { - errors.showBankAccountFormValidationError(error); - errors.showBankAccountErrorModal(error, isErrorHTML); + formFunctions.setFormAlert({message: response.htmlMessage, isMessageHtml: isErrorHTML}); + formFunctions.setLoading(false); } const nextStep = response.jsonCode === 200 && !error ? getNextStep(updatedACHData) : updatedACHData.currentStep; @@ -213,8 +213,8 @@ function setupWithdrawalAccount(params, formFunctions) { const currentStep = updatedACHData.currentStep; const responseACHData = lodashGet(response, 'achData', {}); const verificationsError = lodashGet(responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.ERROR_MESSAGE); - if (response.jsonCode !== 200 || verificationsError) { - showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData); + if (response.jsonCode === 200 || verificationsError) { + showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData, formFunctions); return; } @@ -259,7 +259,6 @@ function setupWithdrawalAccount(params, formFunctions) { }) .catch((response) => { // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}}); - formFunctions.addressZipCodesetLoading(false); console.error(response.stack); errors.showBankAccountErrorModal(Localize.translateLocal('common.genericErrorMessage')); formFunctions.setLoading(false); From e80402826f16df030a5e4f3404009356d15a1f83 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 3 Jan 2022 15:07:06 -0700 Subject: [PATCH 15/69] rename submit component --- src/components/ExpensiForm.js | 44 ++++++++++--------- ...> ExpensiFormFormAlertWithSubmitButton.js} | 14 +++--- .../ExpensiTextInput/BaseExpensiTextInput.js | 8 ++-- .../baseExpensiTextInputPropTypes.js | 4 -- .../ReimbursementAccount/BankAccountStep.js | 9 ++-- 5 files changed, 40 insertions(+), 39 deletions(-) rename src/components/{ExpensiFormSubmit.js => ExpensiFormFormAlertWithSubmitButton.js} (86%) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 3167be4849a5..48ae255c7115 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -36,7 +36,8 @@ class ExpensiForm extends React.Component { this.getFormValues = this.getFormValues.bind(this); this.saveDraft = this.saveDraft.bind(this); this.onSubmit = this.onSubmit.bind(this); - this.validate = this.validate.bind(this); + this.validateForm = this.validateForm.bind(this); + this.validateField = this.validateField.bind(this); this.clearInputErrors = this.clearInputErrors.bind(this); this.setLoading = this.setLoading.bind(this); this.setFormAlert = this.setFormAlert.bind(this); @@ -57,7 +58,19 @@ class ExpensiForm extends React.Component { FormAction.saveFormDraft(`${this.props.name}_draft`, {...draft}); } - validate(field) { + validateField(fieldName) { + const fieldError = this.props.validate({[fieldName]: this.inputRefs[fieldName].value})[fieldName]; + if (fieldError) { + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [fieldName]: fieldError, + } + })); + }; + } + + validateForm() { const values = this.getFormValues(); // validate takes in form values and returns errors object in the format // {username: 'form.errors.required', name: 'form.errors.tooShort', ...} @@ -66,21 +79,12 @@ class ExpensiForm extends React.Component { // We check if we are trying to validate a single field or the entire form const errors = this.props.validate(values); if (!_.isEmpty(errors)) { - if (field) { - this.setState(prevState => ({ - errors: { - ...prevState.errors, - [field]: errors[field] - } - })); - } else { - this.setState({ - errors, - alert: { - firstErrorToFix: this.inputRefs[_.keys(errors)[0]], - } - }); - } + this.setState({ + errors, + alert: { + firstErrorToFix: this.inputRefs[_.keys(errors)[0]], + } + }); } return errors; } @@ -105,7 +109,7 @@ class ExpensiForm extends React.Component { onSubmit() { const values = this.getFormValues(); - const errors = this.validate(); + const errors = this.validateForm(); if (!_.isEmpty(errors)) { return; } @@ -140,11 +144,11 @@ class ExpensiForm extends React.Component { return React.cloneElement(child, { ref: node => this.inputRefs[child.props.name] = node, saveDraft: this.saveDraft, - validate: this.validate, + validateField: this.validateField, clearInputErrors: this.clearInputErrors, onSubmit: this.onSubmit, defaultValue: this.props.defaultValues[child.props.name], - error: this.state.errors[child.props.name], + errorText: this.state.errors[child.props.name], alert: this.state.alert, isLoading: this.state.isLoading, }); diff --git a/src/components/ExpensiFormSubmit.js b/src/components/ExpensiFormFormAlertWithSubmitButton.js similarity index 86% rename from src/components/ExpensiFormSubmit.js rename to src/components/ExpensiFormFormAlertWithSubmitButton.js index 3acf660d89d3..8cd20f5460ee 100644 --- a/src/components/ExpensiFormSubmit.js +++ b/src/components/ExpensiFormFormAlertWithSubmitButton.js @@ -27,7 +27,7 @@ const propTypes = { /** Is the button in a loading state */ alert: PropTypes.shape({ - firstErrorToFix: PropTypes.func, + firstErrorToFix: PropTypes.object, message: PropTypes.string, }), @@ -41,7 +41,7 @@ const defaultProps = { isLoading: false, }; -const FormAlertWithSubmitButton = (props) => { +const ExpensiFormFormAlertWithSubmitButton = (props) => { /** * @returns {React.Component} */ @@ -98,9 +98,9 @@ const FormAlertWithSubmitButton = (props) => { ); }; -FormAlertWithSubmitButton.EXPENSIFORM_COMPATIBLE_INPUT = true; -FormAlertWithSubmitButton.propTypes = propTypes; -FormAlertWithSubmitButton.defaultProps = defaultProps; -FormAlertWithSubmitButton.displayName = 'ExpensiFormSubmit'; +ExpensiFormFormAlertWithSubmitButton.EXPENSIFORM_COMPATIBLE_INPUT = true; +ExpensiFormFormAlertWithSubmitButton.propTypes = propTypes; +ExpensiFormFormAlertWithSubmitButton.defaultProps = defaultProps; +ExpensiFormFormAlertWithSubmitButton.displayName = 'ExpensiFormSubmit'; -export default withLocalize(FormAlertWithSubmitButton); +export default withLocalize(ExpensiFormFormAlertWithSubmitButton); diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 8593734d80c6..f5137c37176c 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -88,8 +88,8 @@ class BaseExpensiTextInput extends Component { if (this.props.onBlur) { this.props.onBlur(event); } this.setState({isFocused: false}); this.deactivateLabel(); - if (this.props.validate) { - this.props.validate(this.props.name); + if (this.props.validateField) { + this.props.validateField(this.props.name); } } @@ -218,9 +218,9 @@ class BaseExpensiTextInput extends Component { - {!_.isEmpty(this.props.errorText || this.props.error) && ( + {!_.isEmpty(this.props.errorText) && ( - {this.props.errorText || this.props.error} + {this.props.errorText} )} diff --git a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js index 6023b6d31d9c..8271cb5b3d36 100644 --- a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js +++ b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js @@ -19,9 +19,6 @@ const propTypes = { /** Error text to display */ errorText: PropTypes.string, - /** Should the input be styled for errors */ - hasError: PropTypes.bool, - /** Customize the ExpensiTextInput container */ containerStyles: PropTypes.arrayOf(PropTypes.object), @@ -39,7 +36,6 @@ const defaultProps = { name: '', errorText: '', placeholder: '', - hasError: false, containerStyles: [], inputStyle: [], autoFocus: false, diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 2d5e140535c0..8df367770a98 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -1,4 +1,5 @@ import _ from 'underscore'; +import lodashGet from 'lodash/get'; import React from 'react'; import {View, Image, ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -29,7 +30,7 @@ import * as ValidationUtils from '../../libs/ValidationUtils'; import * as Illustrations from '../../components/Icon/Illustrations'; import ExpensiForm from '../../components/ExpensiForm'; -import ExpensiFormSubmit from '../../components/ExpensiFormSubmit'; +import ExpensiFormFormAlertWithSubmitButton from '../../components/ExpensiFormFormAlertWithSubmitButton'; const propTypes = { /** Bank account currently in setup */ @@ -83,11 +84,11 @@ class BankAccountStep extends React.Component { validate(values) { const errors = {}; - if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(values.routingNumber.trim()) || !ValidationUtils.isValidRoutingNumber(values.routingNumber.trim())) { + if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(lodashGet(values, 'routingNumber', '').trim()) || !ValidationUtils.isValidRoutingNumber(lodashGet(values, 'routingNumber', ''))) { errors.routingNumber = 'reimbursementAccount.errors.routingNumber'; } // These are taken from BankCountry.js in Web-Secure - if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(values.accountNumber.trim())) { + if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(lodashGet(values, 'accountNumber', '').trim())) { errors.accountNumber = 'reimbursementAccount.errors.accountNumber'; } // if (!values.hasAcceptedTerms) { @@ -288,7 +289,7 @@ class BankAccountStep extends React.Component { )} // hasError={this.getErrors().hasAcceptedTerms} /> - Date: Mon, 3 Jan 2022 15:18:29 -0700 Subject: [PATCH 16/69] add EXPENSIFORM_SUBMIT_INPUT static propery --- src/components/ExpensiForm.js | 14 +++++++++----- .../ExpensiFormFormAlertWithSubmitButton.js | 2 +- src/components/withLocalize.js | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 48ae255c7115..b44fc29f1b7b 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -133,11 +133,18 @@ class ExpensiForm extends React.Component { // We check if the component has the EXPENSIFORM_COMPATIBLE_INPUT static property enabled, // as we don't want to pass form props to non form components, e.g. View, Text, etc - if (!child.type.EXPENSIFORM_COMPATIBLE_INPUT) { + if (!child.type.EXPENSIFORM_COMPATIBLE_INPUT && !child.type.EXPENSIFORM_SUBMIT_INPUT) { return child; } - // TODO Should we have a separate static property for the submit button and pass loading state / onSubmit only to that component? + // // We clone the child passing down all submit input props + if (child.type.EXPENSIFORM_SUBMIT_INPUT) { + return React.cloneElement(child, { + onSubmit: this.onSubmit, + alert: this.state.alert, + isLoading: this.state.isLoading, + }); + } // We clone the child passing down all form props // We should only pass refs to class components! @@ -146,11 +153,8 @@ class ExpensiForm extends React.Component { saveDraft: this.saveDraft, validateField: this.validateField, clearInputErrors: this.clearInputErrors, - onSubmit: this.onSubmit, defaultValue: this.props.defaultValues[child.props.name], errorText: this.state.errors[child.props.name], - alert: this.state.alert, - isLoading: this.state.isLoading, }); }) ); diff --git a/src/components/ExpensiFormFormAlertWithSubmitButton.js b/src/components/ExpensiFormFormAlertWithSubmitButton.js index 8cd20f5460ee..8cc1e4647d9a 100644 --- a/src/components/ExpensiFormFormAlertWithSubmitButton.js +++ b/src/components/ExpensiFormFormAlertWithSubmitButton.js @@ -98,7 +98,7 @@ const ExpensiFormFormAlertWithSubmitButton = (props) => { ); }; -ExpensiFormFormAlertWithSubmitButton.EXPENSIFORM_COMPATIBLE_INPUT = true; +ExpensiFormFormAlertWithSubmitButton.EXPENSIFORM_SUBMIT_INPUT = true; ExpensiFormFormAlertWithSubmitButton.propTypes = propTypes; ExpensiFormFormAlertWithSubmitButton.defaultProps = defaultProps; ExpensiFormFormAlertWithSubmitButton.displayName = 'ExpensiFormSubmit'; diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index a069001a92e9..c655b52c4bb1 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -145,6 +145,7 @@ export default function withLocalize(WrappedComponent) { WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; WithLocalize.EXPENSIFORM_COMPATIBLE_INPUT = WrappedComponent.EXPENSIFORM_COMPATIBLE_INPUT; + WithLocalize.EXPENSIFORM_SUBMIT_INPUT = WrappedComponent.EXPENSIFORM_SUBMIT_INPUT; return WithLocalize; } From 047a2c5bc7fed8ed35947d305922e12df05b14a4 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 4 Jan 2022 12:14:14 -0700 Subject: [PATCH 17/69] fix tests --- src/components/ExpensiForm.js | 55 ++++++++++--------- .../ExpensiTextInput/BaseExpensiTextInput.js | 10 ++-- .../baseExpensiTextInputPropTypes.js | 3 + .../ReimbursementAccount/BankAccountStep.js | 3 +- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index b44fc29f1b7b..6bbc4f06456f 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import * as FormAction from '../libs/actions/ExpensiForm'; import {ScrollView, View} from 'react-native'; import styles from '../styles/styles'; +import {withOnyx} from 'react-native-onyx'; const propTypes = { name: PropTypes.string.isRequired, @@ -13,12 +14,12 @@ const propTypes = { defaultValues: PropTypes.object, validate: PropTypes.func.isRequired, - saveDraft: PropTypes.bool, + shouldSaveDraft: PropTypes.bool, }; const defaultProps = { defaultValues: {}, - saveDraft: true, + shouldSaveDraft: true, }; class ExpensiForm extends React.Component { @@ -37,7 +38,7 @@ class ExpensiForm extends React.Component { this.saveDraft = this.saveDraft.bind(this); this.onSubmit = this.onSubmit.bind(this); this.validateForm = this.validateForm.bind(this); - this.validateField = this.validateField.bind(this); + this.validateInput = this.validateInput.bind(this); this.clearInputErrors = this.clearInputErrors.bind(this); this.setLoading = this.setLoading.bind(this); this.setFormAlert = this.setFormAlert.bind(this); @@ -52,22 +53,20 @@ class ExpensiForm extends React.Component { } saveDraft(draft) { - if (!this.props.saveDraft) { + if (!this.props.shouldSaveDraft) { return; } FormAction.saveFormDraft(`${this.props.name}_draft`, {...draft}); } - validateField(fieldName) { - const fieldError = this.props.validate({[fieldName]: this.inputRefs[fieldName].value})[fieldName]; - if (fieldError) { - this.setState(prevState => ({ - errors: { - ...prevState.errors, - [fieldName]: fieldError, - } - })); - }; + validateInput(inputName) { + const inputError = this.props.validate({[inputName]: this.inputRefs[inputName].value})[inputName]; + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [inputName]: inputError, + } + })); } validateForm() { @@ -75,26 +74,24 @@ class ExpensiForm extends React.Component { // validate takes in form values and returns errors object in the format // {username: 'form.errors.required', name: 'form.errors.tooShort', ...} // how do we handle multiple errors in this case??? - - // We check if we are trying to validate a single field or the entire form const errors = this.props.validate(values); + const alert = {}; if (!_.isEmpty(errors)) { - this.setState({ - errors, - alert: { - firstErrorToFix: this.inputRefs[_.keys(errors)[0]], - } - }); + alert['firstErrorToFix'] = this.inputRefs[_.keys(errors)[0]]; } + this.setState({ + errors, + alert, + }); return errors; } // Should be called onFocus - clearInputErrors(field) { + clearInputErrors(inputName) { this.setState(prevState => ({ errors: { ...prevState.errors, - [field]: undefined, + [inputName]: undefined, }, })); } @@ -151,9 +148,9 @@ class ExpensiForm extends React.Component { return React.cloneElement(child, { ref: node => this.inputRefs[child.props.name] = node, saveDraft: this.saveDraft, - validateField: this.validateField, + validateInput: this.validateInput, clearInputErrors: this.clearInputErrors, - defaultValue: this.props.defaultValues[child.props.name], + defaultValue: this.props.draft[child.props.name], errorText: this.state.errors[child.props.name], }); }) @@ -179,4 +176,8 @@ class ExpensiForm extends React.Component { ExpensiForm.propTypes = propTypes; ExpensiForm.defaultProps = defaultProps; -export default ExpensiForm; \ No newline at end of file +export default withOnyx({ + draft: { + key: name => name, + } +})(ExpensiForm);; \ No newline at end of file diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index f5137c37176c..10880c9f6e21 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -79,17 +79,14 @@ class BaseExpensiTextInput extends Component { if (this.props.onFocus) { this.props.onFocus(event); } this.setState({isFocused: true}); this.activateLabel(); - if (this.props.clearInputErrors) { - this.props.clearInputErrors(this.props.name); - } } onBlur(event) { if (this.props.onBlur) { this.props.onBlur(event); } this.setState({isFocused: false}); this.deactivateLabel(); - if (this.props.validateField) { - this.props.validateField(this.props.name); + if (this.props.validateInput) { + this.props.validateInput(this.props.name); } } @@ -100,8 +97,9 @@ class BaseExpensiTextInput extends Component { * @memberof BaseExpensiTextInput */ setValue(value) { + console.log(value) this.value = value; - if (this.props.saveDraft) { + if (this.props.saveDraft && this.props.shouldSaveDraft) { this.props.saveDraft({[this.props.name]: value}); } Str.result(this.props.onChangeText, value); diff --git a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js index 8271cb5b3d36..3af5a0e2b196 100644 --- a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js +++ b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js @@ -29,6 +29,8 @@ const propTypes = { /** Should the input auto focus? */ autoFocus: PropTypes.bool, + + shouldSaveDraft: PropTypes.bool, }; const defaultProps = { @@ -47,6 +49,7 @@ const defaultProps = { value: undefined, defaultValue: undefined, forceActiveLabel: false, + shouldSaveDraft: true, }; export {propTypes, defaultProps}; diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 8df367770a98..79b0f37ee8a9 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -241,7 +241,7 @@ class BankAccountStep extends React.Component { {subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && ( this.clearErrorAndSetValue('accountNumber', value)} disabled={shouldDisableInputs} + shouldSaveDraft={false} // errorText={this.getErrorText('accountNumber')} /> Date: Wed, 5 Jan 2022 10:01:25 -0700 Subject: [PATCH 18/69] move alert and submit component --- src/components/ExpensiForm.js | 10 +++++++++- src/components/ExpensiFormFormAlertWithSubmitButton.js | 3 +-- src/pages/ReimbursementAccount/BankAccountStep.js | 9 +-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 6bbc4f06456f..13901953e7ac 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -5,6 +5,7 @@ import * as FormAction from '../libs/actions/ExpensiForm'; import {ScrollView, View} from 'react-native'; import styles from '../styles/styles'; import {withOnyx} from 'react-native-onyx'; +import ExpensiFormFormAlertWithSubmitButton from './ExpensiFormFormAlertWithSubmitButton'; const propTypes = { name: PropTypes.string.isRequired, @@ -114,6 +115,7 @@ class ExpensiForm extends React.Component { } render() { + console.log(this.props.draft) const childrenWrapperWithProps = children => ( React.Children.map(children, (child) => { // Do nothing if child is not a valid React element @@ -166,6 +168,12 @@ class ExpensiForm extends React.Component { {/* Form elements */} {childrenWrapperWithProps(this.props.children)} + @@ -178,6 +186,6 @@ ExpensiForm.defaultProps = defaultProps; export default withOnyx({ draft: { - key: name => name, + key: ({name}) => `${name}_draft`, } })(ExpensiForm);; \ No newline at end of file diff --git a/src/components/ExpensiFormFormAlertWithSubmitButton.js b/src/components/ExpensiFormFormAlertWithSubmitButton.js index 8cc1e4647d9a..97a51fb79018 100644 --- a/src/components/ExpensiFormFormAlertWithSubmitButton.js +++ b/src/components/ExpensiFormFormAlertWithSubmitButton.js @@ -98,9 +98,8 @@ const ExpensiFormFormAlertWithSubmitButton = (props) => { ); }; -ExpensiFormFormAlertWithSubmitButton.EXPENSIFORM_SUBMIT_INPUT = true; ExpensiFormFormAlertWithSubmitButton.propTypes = propTypes; ExpensiFormFormAlertWithSubmitButton.defaultProps = defaultProps; -ExpensiFormFormAlertWithSubmitButton.displayName = 'ExpensiFormSubmit'; +ExpensiFormFormAlertWithSubmitButton.displayName = 'ExpensiFormFormAlertWithSubmitButton'; export default withLocalize(ExpensiFormFormAlertWithSubmitButton); diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 79b0f37ee8a9..590de2b23320 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -244,6 +244,7 @@ class BankAccountStep extends React.Component { // defaultValues={this.props.reimbursementAccountDraft} validate={this.validate} onSubmit={this.addManualAccount} + buttonText={'Save & continue'} style={[styles.flex1, styles.mh5]} > @@ -290,14 +291,6 @@ class BankAccountStep extends React.Component { )} // hasError={this.getErrors().hasAcceptedTerms} /> - )} From d5ac5171c0a279b457230a379fc6e8096c383fea Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jan 2022 12:12:48 -0700 Subject: [PATCH 19/69] revert changes --- src/components/Button.js | 15 +-- .../ExpensiTextInput/BaseExpensiTextInput.js | 9 +- .../baseExpensiTextInputPropTypes.js | 6 +- src/components/ExpensiTextInput/index.js | 1 - .../ExpensiTextInput/index.native.js | 1 - src/components/withLocalize.js | 2 - .../setupWithdrawalAccount.js | 21 ++-- .../ReimbursementAccount/BankAccountStep.js | 100 ++++++++++-------- 8 files changed, 70 insertions(+), 85 deletions(-) diff --git a/src/components/Button.js b/src/components/Button.js index 9138aa2d1853..b0990f2ad911 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -96,7 +96,6 @@ class Button extends Component { this.additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style]; this.renderContent = this.renderContent.bind(this); - this.onPress = this.onPress.bind(this); } componentDidMount() { @@ -111,8 +110,7 @@ class Button extends Component { if (this.props.isDisabled || this.props.isLoading) { return; } - // this.props.onPress(); - this.props.onSubmit(); + this.props.onPress(); }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true); } @@ -168,18 +166,10 @@ class Button extends Component { return textComponent; } - onPress() { - if (this.props.onSubmit) { - this.props.onSubmit(); - } else { - this.props.onPress(); - } - } - render() { return ( {hasLabel ? ( diff --git a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js index 3af5a0e2b196..4d4986b9291b 100644 --- a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js +++ b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js @@ -19,6 +19,9 @@ const propTypes = { /** Error text to display */ errorText: PropTypes.string, + /** Should the input be styled for errors */ + hasError: PropTypes.bool, + /** Customize the ExpensiTextInput container */ containerStyles: PropTypes.arrayOf(PropTypes.object), @@ -29,8 +32,6 @@ const propTypes = { /** Should the input auto focus? */ autoFocus: PropTypes.bool, - - shouldSaveDraft: PropTypes.bool, }; const defaultProps = { @@ -49,7 +50,6 @@ const defaultProps = { value: undefined, defaultValue: undefined, forceActiveLabel: false, - shouldSaveDraft: true, }; export {propTypes, defaultProps}; diff --git a/src/components/ExpensiTextInput/index.js b/src/components/ExpensiTextInput/index.js index 6cd37e4e3bd5..06bc5a9de525 100644 --- a/src/components/ExpensiTextInput/index.js +++ b/src/components/ExpensiTextInput/index.js @@ -12,7 +12,6 @@ const ExpensiTextInput = forwardRef((props, ref) => ( /> )); -ExpensiTextInput.EXPENSIFORM_COMPATIBLE_INPUT = true; ExpensiTextInput.propTypes = propTypes; ExpensiTextInput.defaultProps = defaultProps; ExpensiTextInput.displayName = 'ExpensiTextInput'; diff --git a/src/components/ExpensiTextInput/index.native.js b/src/components/ExpensiTextInput/index.native.js index 96693b495872..3d1b0257e279 100644 --- a/src/components/ExpensiTextInput/index.native.js +++ b/src/components/ExpensiTextInput/index.native.js @@ -16,7 +16,6 @@ const ExpensiTextInput = forwardRef((props, ref) => ( /> )); -ExpensiTextInput.EXPENSIFORM_COMPATIBLE_INPUT = true; ExpensiTextInput.propTypes = propTypes; ExpensiTextInput.defaultProps = defaultProps; ExpensiTextInput.displayName = 'ExpensiTextInput'; diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index c655b52c4bb1..2763a5357b2a 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -144,8 +144,6 @@ export default function withLocalize(WrappedComponent) { )); WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; - WithLocalize.EXPENSIFORM_COMPATIBLE_INPUT = WrappedComponent.EXPENSIFORM_COMPATIBLE_INPUT; - WithLocalize.EXPENSIFORM_SUBMIT_INPUT = WrappedComponent.EXPENSIFORM_SUBMIT_INPUT; return WithLocalize; } diff --git a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js index c4771d49c066..2f11ee6514b5 100644 --- a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js +++ b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js @@ -75,7 +75,7 @@ function getNextStep(updatedACHData) { * @param {String} verificationsError * @param {Object} updatedACHData */ -function showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData, formFunctions) { +function showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData) { let error = verificationsError; let isErrorHTML = false; const responseACHData = lodashGet(response, 'achData', {}); @@ -100,8 +100,8 @@ function showSetupWithdrawalAccountErrors(response, verificationsError, updatedA } if (error) { - formFunctions.setFormAlert({message: response.htmlMessage, isMessageHtml: isErrorHTML}); - formFunctions.setLoading(false); + errors.showBankAccountFormValidationError(error); + errors.showBankAccountErrorModal(error, isErrorHTML); } const nextStep = response.jsonCode === 200 && !error ? getNextStep(updatedACHData) : updatedACHData.currentStep; @@ -203,9 +203,8 @@ function mergeParamsWithLocalACHData(data) { * @param {Boolean} [params.certifyTrueInformation] * @param {Array} [params.beneficialOwners] */ -function setupWithdrawalAccount(params, formFunctions) { - // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true, errorModalMessage: '', errors: null}); - formFunctions.setLoading(true); +function setupWithdrawalAccount(params) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true, errorModalMessage: '', errors: null}); const updatedACHData = mergeParamsWithLocalACHData(params); API.BankAccount_SetupWithdrawal(updatedACHData) .then((response) => { @@ -213,8 +212,8 @@ function setupWithdrawalAccount(params, formFunctions) { const currentStep = updatedACHData.currentStep; const responseACHData = lodashGet(response, 'achData', {}); const verificationsError = lodashGet(responseACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.ERROR_MESSAGE); - if (response.jsonCode === 200 || verificationsError) { - showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData, formFunctions); + if (response.jsonCode !== 200 || verificationsError) { + showSetupWithdrawalAccountErrors(response, verificationsError, updatedACHData); return; } @@ -248,20 +247,18 @@ function setupWithdrawalAccount(params, formFunctions) { // Go to next step navigation.goToWithdrawalAccountSetupStep(getNextStep(updatedACHData), responseACHData); - // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); if (_.isEmpty(responseACHData)) { Log.info('[SetupWithdrawalAccount] No achData in response. Navigating to next step based on currently stored values.', 0, {nextStep}); navigation.goToWithdrawalAccountSetupStep(nextStep, updatedACHData); } else { navigation.goToWithdrawalAccountSetupStep(nextStep, responseACHData); } - formFunctions.setLoading(false); }) .catch((response) => { - // Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}}); console.error(response.stack); errors.showBankAccountErrorModal(Localize.translateLocal('common.genericErrorMessage')); - formFunctions.setLoading(false); }); } diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 590de2b23320..0c9449355624 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import lodashGet from 'lodash/get'; import React from 'react'; import {View, Image, ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -29,16 +28,11 @@ import WorkspaceSection from '../workspace/WorkspaceSection'; import * as ValidationUtils from '../../libs/ValidationUtils'; import * as Illustrations from '../../components/Icon/Illustrations'; -import ExpensiForm from '../../components/ExpensiForm'; -import ExpensiFormFormAlertWithSubmitButton from '../../components/ExpensiFormFormAlertWithSubmitButton'; - const propTypes = { /** Bank account currently in setup */ // eslint-disable-next-line react/no-unused-prop-types reimbursementAccount: reimbursementAccountPropTypes.isRequired, - reimbursementAccountDraft: PropTypes.object, - /** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */ receivedRedirectURI: PropTypes.string, @@ -51,7 +45,6 @@ const propTypes = { const defaultProps = { receivedRedirectURI: null, plaidLinkOAuthToken: '', - reimbursementAccountDraft: {}, }; class BankAccountStep extends React.Component { @@ -64,9 +57,19 @@ class BankAccountStep extends React.Component { this.state = { // One of CONST.BANK_ACCOUNT.SETUP_TYPE hasAcceptedTerms: ReimbursementAccountUtils.getDefaultStateForField(props, 'acceptTerms', true), - // routingNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'routingNumber'), - // accountNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'accountNumber'), + routingNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'routingNumber'), + accountNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'accountNumber'), + }; + + // Keys in this.errorTranslationKeys are associated to inputs, they are a subset of the keys found in this.state + this.errorTranslationKeys = { + routingNumber: 'bankAccount.error.routingNumber', + accountNumber: 'bankAccount.error.accountNumber', }; + + this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); + this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); + this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); } toggleTerms() { @@ -81,39 +84,54 @@ class BankAccountStep extends React.Component { /** * @returns {Boolean} */ - validate(values) { + validate() { const errors = {}; - if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(lodashGet(values, 'routingNumber', '').trim()) || !ValidationUtils.isValidRoutingNumber(lodashGet(values, 'routingNumber', ''))) { - errors.routingNumber = 'reimbursementAccount.errors.routingNumber'; - } // These are taken from BankCountry.js in Web-Secure - if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(lodashGet(values, 'accountNumber', '').trim())) { - errors.accountNumber = 'reimbursementAccount.errors.accountNumber'; + if (!CONST.BANK_ACCOUNT.REGEX.IBAN.test(this.state.accountNumber.trim())) { + errors.accountNumber = true; + } + if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(this.state.routingNumber.trim()) || !ValidationUtils.isValidRoutingNumber(this.state.routingNumber.trim())) { + errors.routingNumber = true; + } + if (!this.state.hasAcceptedTerms) { + errors.hasAcceptedTerms = true; } - // if (!values.hasAcceptedTerms) { - // errors.hasAcceptedTerms = true; - // } - // BankAccounts.setBankAccountFormValidationErrors(errors); - return errors; + BankAccounts.setBankAccountFormValidationErrors(errors); + return _.size(errors) === 0; } - addManualAccount(values, formFunctions) { - // if (!this.validate()) { - // BankAccounts.showBankAccountErrorModal(); - // return; - // } + /** + * Clear the error associated to inputKey if found and store the inputKey new value in the state. + * + * @param {String} inputKey + * @param {String} value + */ + clearErrorAndSetValue(inputKey, value) { + const newState = {[inputKey]: value}; + this.setState(newState); + BankAccounts.updateReimbursementAccountDraft(newState); + this.clearError(inputKey); + } + + addManualAccount() { + if (!this.validate()) { + BankAccounts.showBankAccountErrorModal(); + return; + } BankAccounts.setupWithdrawalAccount({ - ...values, + acceptTerms: this.state.hasAcceptedTerms, + accountNumber: this.state.accountNumber, + routingNumber: this.state.routingNumber, setupType: CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL, // Note: These are hardcoded as we're not supporting AU bank accounts for the free plan country: CONST.COUNTRY.US, currency: CONST.CURRENCY.USD, fieldsType: CONST.BANK_ACCOUNT.FIELDS_TYPE.LOCAL, - }, formFunctions); + }); } /** @@ -239,13 +257,8 @@ class BankAccountStep extends React.Component { /> )} {subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && ( - {this.props.translate('bankAccount.checkHelpLine')} @@ -256,24 +269,21 @@ class BankAccountStep extends React.Component { source={exampleCheckImage(this.props.preferredLocale)} /> this.clearErrorAndSetValue('routingNumber', value)} + value={this.state.routingNumber} + onChangeText={value => this.clearErrorAndSetValue('routingNumber', value)} disabled={shouldDisableInputs} - // errorText={this.getErrorText('routingNumber')} + errorText={this.getErrorText('routingNumber')} /> this.clearErrorAndSetValue('accountNumber', value)} + value={this.state.accountNumber} + onChangeText={value => this.clearErrorAndSetValue('accountNumber', value)} disabled={shouldDisableInputs} - shouldSaveDraft={false} - // errorText={this.getErrorText('accountNumber')} + errorText={this.getErrorText('accountNumber')} /> )} - // hasError={this.getErrors().hasAcceptedTerms} + hasError={this.getErrors().hasAcceptedTerms} /> - + )} ); @@ -308,7 +318,7 @@ export default compose( key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, reimbursementAccountDraft: { - key: `${ONYXKEYS.REIMBURSEMENT_ACCOUNT}_draft`, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, }, user: { key: ONYXKEYS.USER, From 9441dc16d9bf6232e798bf776d937a6e7bd90588 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jan 2022 12:25:04 -0700 Subject: [PATCH 20/69] finish reverting changes --- .../ExpensiTextInput/baseExpensiTextInputPropTypes.js | 1 + src/components/withLocalize.js | 1 + src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js | 1 + src/pages/ReimbursementAccount/BankAccountStep.js | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js index 4d4986b9291b..6023b6d31d9c 100644 --- a/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js +++ b/src/components/ExpensiTextInput/baseExpensiTextInputPropTypes.js @@ -39,6 +39,7 @@ const defaultProps = { name: '', errorText: '', placeholder: '', + hasError: false, containerStyles: [], inputStyle: [], autoFocus: false, diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 2763a5357b2a..6f885ef36f40 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -144,6 +144,7 @@ export default function withLocalize(WrappedComponent) { )); WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; + return WithLocalize; } diff --git a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js index 2f11ee6514b5..bbc53415a48b 100644 --- a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js +++ b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js @@ -254,6 +254,7 @@ function setupWithdrawalAccount(params) { } else { navigation.goToWithdrawalAccountSetupStep(nextStep, responseACHData); } + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); }) .catch((response) => { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}}); diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 0c9449355624..866d24658641 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -108,7 +108,7 @@ class BankAccountStep extends React.Component { * @param {String} inputKey * @param {String} value */ - clearErrorAndSetValue(inputKey, value) { + clearErrorAndSetValue(inputKey, value) { const newState = {[inputKey]: value}; this.setState(newState); BankAccounts.updateReimbursementAccountDraft(newState); From 38f32794f506807ef13f62723e242407fa110f52 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jan 2022 12:26:38 -0700 Subject: [PATCH 21/69] rm navigation --- src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js index bbc53415a48b..fbd85d89ee31 100644 --- a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js +++ b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js @@ -246,8 +246,6 @@ function setupWithdrawalAccount(params) { } // Go to next step - navigation.goToWithdrawalAccountSetupStep(getNextStep(updatedACHData), responseACHData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); if (_.isEmpty(responseACHData)) { Log.info('[SetupWithdrawalAccount] No achData in response. Navigating to next step based on currently stored values.', 0, {nextStep}); navigation.goToWithdrawalAccountSetupStep(nextStep, updatedACHData); From d66295f07839f410a8648ec28ad2bdcc84946ef0 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jan 2022 12:31:25 -0700 Subject: [PATCH 22/69] create ExpensiFormActions --- src/components/ExpensiForm.js | 2 +- src/libs/actions/ExpensiForm.js | 18 --------------- src/libs/actions/ExpensiFormActions.js | 31 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 19 deletions(-) delete mode 100644 src/libs/actions/ExpensiForm.js create mode 100644 src/libs/actions/ExpensiFormActions.js diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 13901953e7ac..7b4406dcf0b8 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -1,7 +1,7 @@ import React from 'react'; import _ from 'underscore'; import PropTypes from 'prop-types'; -import * as FormAction from '../libs/actions/ExpensiForm'; +import * as FormAction from '../libs/actions/ExpensiFormActions'; import {ScrollView, View} from 'react-native'; import styles from '../styles/styles'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/libs/actions/ExpensiForm.js b/src/libs/actions/ExpensiForm.js deleted file mode 100644 index 8a1eed80c8e8..000000000000 --- a/src/libs/actions/ExpensiForm.js +++ /dev/null @@ -1,18 +0,0 @@ -import Onyx from 'react-native-onyx'; - -/** - * @param {String} formName - * @param {Object} draft - */ -function saveFormDraft(formName, draft) { - Onyx.merge(formName, draft); -} - -function setLoading(formName, state) { - Onyx.merge(formName, {loading: state}); -} - -export { - saveFormDraft, - setLoading, -}; diff --git a/src/libs/actions/ExpensiFormActions.js b/src/libs/actions/ExpensiFormActions.js new file mode 100644 index 000000000000..fdd5253f0ef9 --- /dev/null +++ b/src/libs/actions/ExpensiFormActions.js @@ -0,0 +1,31 @@ +import Onyx from 'react-native-onyx'; + +/** + * @param {String} formName + * @param {Boolean} isSubmitting + */ +function setIsSubmitting(formName, isSubmitting) { + Onyx.merge(formName, {isSubmitting}); +} + +/** + * @param {String} formName + * @param {Boolean} serverErrorMessage + */ + function setServerErrorMessage(formName, serverErrorMessage) { + Onyx.merge(formName, {serverErrorMessage}); +} + +/** + * @param {String} formName + * @param {Object} draft + */ +function saveDraftValues(formName, draftValues) { + Onyx.merge(`${formName}DraftValues`, draftValues); +} + +export { + saveDraftValues, + setServerErrorMessage, + setIsSubmitting, +}; From 6d643be2ef7edbc5bc394b046d9f51771eca8395 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jan 2022 13:41:27 -0700 Subject: [PATCH 23/69] refactor ExpensiForm --- src/components/ExpensiForm.js | 213 ++++++++++++++----------- src/libs/actions/ExpensiFormActions.js | 6 +- 2 files changed, 120 insertions(+), 99 deletions(-) diff --git a/src/components/ExpensiForm.js b/src/components/ExpensiForm.js index 7b4406dcf0b8..d707234bd56c 100644 --- a/src/components/ExpensiForm.js +++ b/src/components/ExpensiForm.js @@ -1,26 +1,53 @@ import React from 'react'; -import _ from 'underscore'; import PropTypes from 'prop-types'; -import * as FormAction from '../libs/actions/ExpensiFormActions'; +import _ from 'underscore'; +import {withOnyx} from 'react-native-onyx'; +import compose from '../libs/compose'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import * as ExpensiFormActions from '../libs/actions/ExpensiFormActions'; import {ScrollView, View} from 'react-native'; import styles from '../styles/styles'; -import {withOnyx} from 'react-native-onyx'; -import ExpensiFormFormAlertWithSubmitButton from './ExpensiFormFormAlertWithSubmitButton'; +import FormAlertWithSubmitButton from './FormAlertWithSubmitButton'; const propTypes = { + /** A unique Onyx key identifying the form */ name: PropTypes.string.isRequired, + + /** Text to be displayed in the submit button */ + buttonText: PropTypes.string.isRequired, + + /** Callback validate the form */ + validate: PropTypes.func.isRequired, + + /** Callback to submit the form */ + onSubmit: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, + /* Onyx Props */ + + /** Contains the form state that must be accessed outside of the component */ + formState: PropTypes.shape({ + + /** Controls the loading state of the form */ + isSubmitting: PropTypes.bool, + + /** Server side error message */ + serverErrorMessage: PropTypes.string, + }), + // eslint-disable-next-line react/forbid-prop-types - defaultValues: PropTypes.object, + draftValues: PropTypes.object(), - validate: PropTypes.func.isRequired, - shouldSaveDraft: PropTypes.bool, + ...withLocalizePropTypes, }; const defaultProps = { - defaultValues: {}, - shouldSaveDraft: true, + formState: { + isSubmitting: false, + serverErrorMessage: '', + }, + draftValues: {}, }; class ExpensiForm extends React.Component { @@ -28,94 +55,78 @@ class ExpensiForm extends React.Component { super(props); this.state = { - isLoading: false, errors: {}, - alert: {}, }; this.inputRefs = {}; + this.touchedInputs = {}; - this.getFormValues = this.getFormValues.bind(this); - this.saveDraft = this.saveDraft.bind(this); + this.getValues = this.getValues.bind(this); + this.getErrorText = this.getErrorText.bind(this); + this.setTouchedInput = this.setTouchedInput.bind(this); + this.validate = this.validate.bind(this); this.onSubmit = this.onSubmit.bind(this); - this.validateForm = this.validateForm.bind(this); - this.validateInput = this.validateInput.bind(this); - this.clearInputErrors = this.clearInputErrors.bind(this); - this.setLoading = this.setLoading.bind(this); - this.setFormAlert = this.setFormAlert.bind(this); } - getFormValues() { - const formData = {}; + getValues() { + const values = {}; _.each(_.keys(this.inputRefs), (key) => { - formData[key] = this.inputRefs[key].value; + values[key] = this.inputRefs[key].value; }); - return formData; + return values; } - saveDraft(draft) { - if (!this.props.shouldSaveDraft) { - return; + getErrorText(inputName) { + if (_.isEmpty(this.state.errors[inputName])) { + return ''; } - FormAction.saveFormDraft(`${this.props.name}_draft`, {...draft}); - } - validateInput(inputName) { - const inputError = this.props.validate({[inputName]: this.inputRefs[inputName].value})[inputName]; - this.setState(prevState => ({ - errors: { - ...prevState.errors, - [inputName]: inputError, - } - })); + const translatedErrors = _.map(this.state.errors[inputName], (translationKey) => ( + this.props.translate(translationKey) + )); + return _.join(translatedErrors, ' '); } - validateForm() { - const values = this.getFormValues(); - // validate takes in form values and returns errors object in the format - // {username: 'form.errors.required', name: 'form.errors.tooShort', ...} - // how do we handle multiple errors in this case??? - const errors = this.props.validate(values); - const alert = {}; - if (!_.isEmpty(errors)) { - alert['firstErrorToFix'] = this.inputRefs[_.keys(errors)[0]]; + setTouchedInput(inputName) { + this.touchedInputs = { + ...this.touchedInputs, + [inputName]: true, } - this.setState({ - errors, - alert, - }); - return errors; } - // Should be called onFocus - clearInputErrors(inputName) { - this.setState(prevState => ({ - errors: { - ...prevState.errors, - [inputName]: undefined, - }, - })); + validate(values) { + ExpensiFormActions.setServerErrorMessage(this.props.name, ''); + const validationErrors = this.props.validate(values); + const errors = _.filter(_.keys(validationErrors), (inputName) => ( + Boolean(this.touchedInputs[inputName]) + )); + this.setState({errors}); + return errors; } - setLoading(value) { - this.setState({isLoading: value}) - } + onSubmit() { + // Return early if the form is already submitting to avoid duplicate submission + if (this.props.formState.isSubmitting) { + return; + } - setFormAlert(alert) { - this.setState({alert}); - } + // Touches all form inputs so we can validate the entire form + _.each(_.keys(this.inputRefs), (inputName) => ( + this.touchedInput[inputName] = true + )); - onSubmit() { - const values = this.getFormValues(); - const errors = this.validateForm(); - if (!_.isEmpty(errors)) { + // Validate form and return early if any errors are found + const values = this.getValues(); + if (!_.isEmpty(this.validate(values))) { return; } - this.props.onSubmit(values, {setLoading: this.setLoading, setFormAlert: this.setFormAlert}) + + // Set loading state and call submit handler + ExpensiFormActions.setIsSubmitting(this.props.name, true); + this.props.onSubmit(values); } render() { - console.log(this.props.draft) const childrenWrapperWithProps = children => ( React.Children.map(children, (child) => { // Do nothing if child is not a valid React element @@ -130,30 +141,31 @@ class ExpensiForm extends React.Component { }); } - // We check if the component has the EXPENSIFORM_COMPATIBLE_INPUT static property enabled, - // as we don't want to pass form props to non form components, e.g. View, Text, etc - if (!child.type.EXPENSIFORM_COMPATIBLE_INPUT && !child.type.EXPENSIFORM_SUBMIT_INPUT) { + // We check if the child has the isExpensiFormInput prop, + // since we we don't want to pass form props to non form components, e.g. View, Text, etc + if (!child.props.isExpensiFormInput) { return child; } - // // We clone the child passing down all submit input props - if (child.type.EXPENSIFORM_SUBMIT_INPUT) { - return React.cloneElement(child, { - onSubmit: this.onSubmit, - alert: this.state.alert, - isLoading: this.state.isLoading, - }); - } - // We clone the child passing down all form props // We should only pass refs to class components! + const inputName = child.props.name; return React.cloneElement(child, { - ref: node => this.inputRefs[child.props.name] = node, - saveDraft: this.saveDraft, - validateInput: this.validateInput, - clearInputErrors: this.clearInputErrors, - defaultValue: this.props.draft[child.props.name], - errorText: this.state.errors[child.props.name], + ref: node => this.inputRefs[inputName] = node, + defaultValue: this.props.draftValues[inputName] || child.props.defaultValue, + errorText: this.getErrorText(inputName), + onBlur: (inputName) => { + this.setTouchedInput(inputName); + this.validate(this.getValues()); + }, + onChange: (value) => { + if (child.props.shouldSaveDraft) { + ExpensiFormActions.setDraftValues(this.props.name, {[inputName]: value}) + } + if (this.touchedInputs[inputName]) { + this.validate(this.getValues()); + } + }, }); }) ); @@ -165,14 +177,17 @@ class ExpensiForm extends React.Component { contentContainerStyle={styles.flexGrow1} keyboardShouldPersistTaps="handled" > - {/* Form elements */} {childrenWrapperWithProps(this.props.children)} - 0 || Boolean(this.props.formState.serverErrorMessage)} + isLoading={this.props.formState.isSubmitting} + message={this.props.formState.serverErrorMessage} onSubmit={this.onSubmit} + onFixTheErrorsLinkPressed={() => { + this.inputRefs[_.first(_.keys(this.state.errors))].focus(); + }} /> @@ -184,8 +199,14 @@ class ExpensiForm extends React.Component { ExpensiForm.propTypes = propTypes; ExpensiForm.defaultProps = defaultProps; -export default withOnyx({ - draft: { - key: ({name}) => `${name}_draft`, - } -})(ExpensiForm);; \ No newline at end of file +export default compose( + withLocalize, + withOnyx({ + formState: { + key: props => props.name, + }, + draftValues: { + key: props => `${props.name}DraftValues`, + } + }), +)(ExpensiForm); \ No newline at end of file diff --git a/src/libs/actions/ExpensiFormActions.js b/src/libs/actions/ExpensiFormActions.js index fdd5253f0ef9..f064b2c2c14e 100644 --- a/src/libs/actions/ExpensiFormActions.js +++ b/src/libs/actions/ExpensiFormActions.js @@ -20,12 +20,12 @@ function setIsSubmitting(formName, isSubmitting) { * @param {String} formName * @param {Object} draft */ -function saveDraftValues(formName, draftValues) { +function setDraftValues(formName, draftValues) { Onyx.merge(`${formName}DraftValues`, draftValues); } export { - saveDraftValues, - setServerErrorMessage, setIsSubmitting, + setServerErrorMessage, + setDraftValues, }; From d409e29630d96b9501fc457908fe6f0f835c4c7f Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jan 2022 13:43:51 -0700 Subject: [PATCH 24/69] remove ExpensiFormFormAlertWithSubmitButton --- .../ExpensiFormFormAlertWithSubmitButton.js | 105 ------------------ 1 file changed, 105 deletions(-) delete mode 100644 src/components/ExpensiFormFormAlertWithSubmitButton.js diff --git a/src/components/ExpensiFormFormAlertWithSubmitButton.js b/src/components/ExpensiFormFormAlertWithSubmitButton.js deleted file mode 100644 index 97a51fb79018..000000000000 --- a/src/components/ExpensiFormFormAlertWithSubmitButton.js +++ /dev/null @@ -1,105 +0,0 @@ -import _ from 'underscore'; -import React from 'react'; -import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import styles from '../styles/styles'; -import Icon from './Icon'; -import * as Expensicons from './Icon/Expensicons'; -import colors from '../styles/colors'; -import Button from './Button'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; -import TextLink from './TextLink'; -import Text from './Text'; -import RenderHTML from './RenderHTML'; - -const propTypes = { - /** Submit function */ - onSubmit: PropTypes.func.isRequired, - - /** Text for the button */ - buttonText: PropTypes.string.isRequired, - - /** Styles for container element */ - containerStyles: PropTypes.arrayOf(PropTypes.object), - - /** Is the button in a loading state */ - isLoading: PropTypes.bool, - - /** Is the button in a loading state */ - alert: PropTypes.shape({ - firstErrorToFix: PropTypes.object, - message: PropTypes.string, - }), - - ...withLocalizePropTypes, -}; - -const defaultProps = { - message: '', - isDisabled: false, - containerStyles: [], - isLoading: false, -}; - -const ExpensiFormFormAlertWithSubmitButton = (props) => { - /** - * @returns {React.Component} - */ - function getAlertPrompt() { - let error = ''; - - if (!_.isEmpty(props.alert.message)) { - error = ( - ${props.alert.message}`} /> - ); - } else { - error = ( - <> - - {`${props.translate('common.please')} `} - - props.alert.firstErrorToFix.focus()} - > - {props.translate('common.fixTheErrors')} - - - {` ${props.translate('common.inTheFormBeforeContinuing')}.`} - - - ); - } - - return ( - - {error} - - ); - } - - return ( - - {!_.isEmpty(props.alert) && ( - - - {getAlertPrompt()} - - )} -