-
Notifications
You must be signed in to change notification settings - Fork 3.2k
[Form Refactor] ACHContractStep #13501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
05d3bb2
4b089ce
797e578
780212a
bee453f
6abfb7e
cb0fff7
292e3f3
57c61d3
b816f5b
382b75a
a379d39
3ad4f46
fbe7e15
a36e8a8
6a32616
49098db
3f5c1cc
ea35dc3
b67bd24
efed1c3
8d167d8
ccb589b
d5085c6
45c97c1
853a4f9
d4a95a0
d27dc93
aee6c38
a63e322
5937ed9
0df7ec4
27a6f27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ import ONYXKEYS from '../../ONYXKEYS'; | |
import compose from '../../libs/compose'; | ||
import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; | ||
import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; | ||
import ReimbursementAccountForm from './ReimbursementAccountForm'; | ||
import Form from '../../components/Form'; | ||
|
||
const propTypes = { | ||
/** Name of the company */ | ||
|
@@ -36,55 +36,91 @@ const propTypes = { | |
class ACHContractStep extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.validate = this.validate.bind(this); | ||
|
||
this.addBeneficialOwner = this.addBeneficialOwner.bind(this); | ||
this.submit = this.submit.bind(this); | ||
|
||
this.state = { | ||
ownsMoreThan25Percent: ReimbursementAccountUtils.getDefaultStateForField(props, 'ownsMoreThan25Percent', false), | ||
hasOtherBeneficialOwners: ReimbursementAccountUtils.getDefaultStateForField(props, 'hasOtherBeneficialOwners', false), | ||
acceptTermsAndConditions: ReimbursementAccountUtils.getDefaultStateForField(props, 'acceptTermsAndConditions', false), | ||
certifyTrueInformation: ReimbursementAccountUtils.getDefaultStateForField(props, 'certifyTrueInformation', false), | ||
beneficialOwners: ReimbursementAccountUtils.getDefaultStateForField(props, 'beneficialOwners', []), | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
} | ||
|
||
// These fields need to be filled out in order to submit the form (doesn't include IdentityForm fields) | ||
this.requiredFields = [ | ||
'acceptTermsAndConditions', | ||
'certifyTrueInformation', | ||
]; | ||
|
||
// Map a field to the key of the error's translation | ||
this.errorTranslationKeys = { | ||
acceptTermsAndConditions: 'common.error.acceptedTerms', | ||
certifyTrueInformation: 'beneficialOwnersStep.error.certify', | ||
}; | ||
|
||
this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); | ||
luacmartins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); | ||
this.clearErrors = inputKeys => ReimbursementAccountUtils.clearErrors(this.props, inputKeys); | ||
this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); | ||
/** | ||
* Get default value from reimbursementAccount or achData | ||
* @param {String} fieldName | ||
* @param {*} defaultValue | ||
* @returns {String} | ||
*/ | ||
getDefaultStateForField(fieldName, defaultValue) { | ||
return lodashGet(this.props, ['reimbursementAccount', 'achData', fieldName], defaultValue); | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* @returns {Boolean} | ||
* @param {Object} values | ||
luacmartins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @returns {Object} | ||
*/ | ||
validate() { | ||
let beneficialOwnersErrors = []; | ||
if (this.state.hasOtherBeneficialOwners) { | ||
beneficialOwnersErrors = _.map(this.state.beneficialOwners, ValidationUtils.validateIdentity); | ||
validate(values) { | ||
const errors = {}; | ||
|
||
// let beneficialOwnersErrors = []; | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (values.hasOtherBeneficialOwners) { | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// beneficialOwnersErrors = _.map(this.state.beneficialOwners, ValidationUtils.validateIdentity); | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_.each(values.beneficialOwners, (beneficialOwner, index) => { | ||
if (!ValidationUtils.isRequiredFulfilled(beneficialOwner.firstName)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.firstName'); | ||
} | ||
|
||
if (!ValidationUtils.isRequiredFulfilled(beneficialOwner.lastName)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.lastName'); | ||
} | ||
|
||
if (!ValidationUtils.isRequiredFulfilled(beneficialOwner.dob)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.dob'); | ||
} | ||
|
||
if (values.dob && !ValidationUtils.meetsAgeRequirements(values.dob)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.age'); | ||
} | ||
|
||
if (!ValidationUtils.isRequiredFulfilled(values.ssnLast4) || !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.ssnLast4'); | ||
} | ||
|
||
if (!ValidationUtils.isRequiredFulfilled(beneficialOwner.beneficialOwnerAddressStreet)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.address'); | ||
} | ||
|
||
if (values.beneficialOwnerAddressStreet && !ValidationUtils.isValidAddress(beneficialOwner.beneficialOwnerAddressStreet)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.addressStreet'); | ||
} | ||
|
||
if (!ValidationUtils.isRequiredFulfilled(beneficialOwner.beneficialOwnerAddressCity)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.addressCity'); | ||
} | ||
|
||
if (!ValidationUtils.isRequiredFulfilled(beneficialOwner.beneficialOwnerAddressState)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.addressState'); | ||
} | ||
|
||
if (!ValidationUtils.isRequiredFulfilled(beneficialOwner.beneficialOwnerAddressZipCode) || !ValidationUtils.isValidZipCode(values.beneficialOwnerAddressZipCode)) { | ||
errors[`beneficialOwner${index}`] = this.props.translate('bankAccount.error.zipCode'); | ||
} | ||
}); | ||
} | ||
|
||
const errors = {}; | ||
_.each(this.requiredFields, (inputKey) => { | ||
if (ValidationUtils.isRequiredFulfilled(this.state[inputKey])) { | ||
return; | ||
} | ||
if (!ValidationUtils.isRequiredFulfilled(values.acceptTermsAndConditions)) { | ||
errors.acceptTermsAndConditions = this.props.translate('common.error.acceptedTerms'); | ||
} | ||
|
||
errors[inputKey] = true; | ||
}); | ||
BankAccounts.setBankAccountFormValidationErrors({...errors, beneficialOwnersErrors}); | ||
return _.every(beneficialOwnersErrors, _.isEmpty) && _.isEmpty(errors); | ||
if (!ValidationUtils.isRequiredFulfilled(values.certifyTrueInformation)) { | ||
errors.certifyTrueInformation = this.props.translate('beneficialOwnersStep.error.certify'); | ||
} | ||
|
||
return errors; | ||
} | ||
|
||
removeBeneficialOwner(beneficialOwner) { | ||
|
@@ -164,7 +200,6 @@ class ACHContractStep extends React.Component { | |
BankAccounts.updateReimbursementAccountDraft(newState); | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return newState; | ||
}); | ||
this.clearError(fieldName); | ||
} | ||
|
||
render() { | ||
|
@@ -176,52 +211,43 @@ class ACHContractStep extends React.Component { | |
onCloseButtonPress={Navigation.dismissModal} | ||
onBackButtonPress={() => { | ||
BankAccounts.clearOnfidoToken(); | ||
BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); | ||
BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.beneficialOwner); | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}} | ||
shouldShowGetAssistanceButton | ||
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT} | ||
shouldShowBackButton | ||
/> | ||
<ReimbursementAccountForm | ||
reimbursementAccount={this.props.reimbursementAccount} | ||
onSubmit={this.submit} | ||
<Form | ||
formID={ONYXKEYS.FORMS.ACH_CONTRACT_FORM} | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
validate={this.validate} | ||
onSubmit={() => {}} | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
submitButtonText={this.props.translate('common.save')} | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
style={[styles.mh5, styles.flexGrow1]} | ||
> | ||
<Text style={[styles.mb5]}> | ||
<Text>{this.props.translate('beneficialOwnersStep.checkAllThatApply')}</Text> | ||
</Text> | ||
<CheckboxWithLabel | ||
inputID="ownsMoreThan25Percent" | ||
style={[styles.mb2]} | ||
isChecked={this.state.ownsMoreThan25Percent} | ||
onInputChange={() => this.toggleCheckbox('ownsMoreThan25Percent')} | ||
LabelComponent={() => ( | ||
<Text> | ||
{this.props.translate('beneficialOwnersStep.iOwnMoreThan25Percent')} | ||
<Text style={[styles.textStrong]}>{this.props.companyName}</Text> | ||
</Text> | ||
)} | ||
shouldSaveDraft | ||
/> | ||
<CheckboxWithLabel | ||
inputID="hasOtherBeneficialOwners" | ||
style={[styles.mb2]} | ||
isChecked={this.state.hasOtherBeneficialOwners} | ||
onInputChange={() => { | ||
this.setState((prevState) => { | ||
const hasOtherBeneficialOwners = !prevState.hasOtherBeneficialOwners; | ||
const newState = { | ||
hasOtherBeneficialOwners, | ||
beneficialOwners: hasOtherBeneficialOwners && _.isEmpty(prevState.beneficialOwners) | ||
? [{}] | ||
: prevState.beneficialOwners, | ||
}; | ||
BankAccounts.updateReimbursementAccountDraft(newState); | ||
return newState; | ||
}); | ||
}} | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
LabelComponent={() => ( | ||
<Text> | ||
{this.props.translate('beneficialOwnersStep.someoneOwnsMoreThan25Percent')} | ||
<Text style={[styles.textStrong]}>{this.props.companyName}</Text> | ||
</Text> | ||
)} | ||
shouldSaveDraft | ||
/> | ||
{this.state.hasOtherBeneficialOwners && ( | ||
<View style={[styles.mb2]}> | ||
|
@@ -233,18 +259,27 @@ class ACHContractStep extends React.Component { | |
<IdentityForm | ||
translate={this.props.translate} | ||
style={[styles.mb2]} | ||
onFieldChange={values => this.clearErrorAndSetBeneficialOwnerValues(index, values)} | ||
values={{ | ||
firstName: owner.firstName || '', | ||
lastName: owner.lastName || '', | ||
street: owner.street || '', | ||
city: owner.city || '', | ||
state: owner.state || '', | ||
zipCode: owner.zipCode || '', | ||
dob: owner.dob || '', | ||
ssnLast4: owner.ssnLast4 || '', | ||
defaultValues={{ | ||
firstName: this.getDefaultStateForField('firstName'), | ||
lastName: this.getDefaultStateForField('lastName'), | ||
street: this.getDefaultStateForField('beneficialOwnerAddressStreet'), | ||
city: this.getDefaultStateForField('beneficialOwnerAddressCity'), | ||
state: this.getDefaultStateForField('beneficialOwnerAddressState'), | ||
zipCode: this.getDefaultStateForField('beneficialOwnerAddressZipCode'), | ||
dob: this.getDefaultStateForField('dob'), | ||
ssnLast4: this.getDefaultStateForField('ssnLast4'), | ||
}} | ||
inputKeys={{ | ||
firstName: 'firstName', | ||
lastName: 'lastName', | ||
dob: 'dob', | ||
ssnLast4: 'ssnLast4', | ||
street: 'beneficialOwnerAddressStreet', | ||
city: 'beneficialOwnerAddressCity', | ||
state: 'beneficialOwnerAddressState', | ||
zipCode: 'beneficialOwnerAddressZipCode', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These keys should be dynamic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll look into a solution for these since we might need to make changes to the Form component itself There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @grgia I took a look at dynamically adding/removing
Let me know what you think of this approach! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method looks like it's working well! I wrote out my plan for testing, so I still need to QA edge cases There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good! I'll take another look today! Thanks for working on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK I am still a little confused, but haven't yet given this a proper investigation to produce any alternatives. Something does not sit right with me about the random id thing. I feel like I would have remembered this from the Design Doc (maybe it didn't come up). It has the feeling of something we might want to bring up in Slack, decide together the best way to do it, and then update everyone on the process (maybe modify the very awesome There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this came up in the doc (at least I don't recall it either). IMO the random id solution solves this issue quite well, but I'm open to discussing this further in Slack if others prefer that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd love to see this get to the finish line this week, but I'm happy to hold this for a discussion and move forward with whatever is decided (final testing for the current method or implementing a new method). I agree that the current method works well, and also that it would be worth updating everyone on the process and why it's done this way in FORMS.md There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so, sounds like we agree that it's a good thing to discuss and that you are offering to lead a discussion on it? Or no? 😄 It might feel like a small battle - but I am concerned with this solution because it feels like something I would never remember how to do or know the reason why a random ID is used. I am barely understanding the explanation about why it is needed. I'd say that's a sign we haven't solved it in the best way possible (maybe I'm wrong). But if it has to be difficult to understand then at least we can document it better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Started here! |
||
}} | ||
errors={lodashGet(this.getErrors(), `beneficialOwnersErrors[${index}]`, {})} | ||
shouldSaveDraft | ||
/> | ||
{this.state.beneficialOwners.length > 1 && ( | ||
<TextLink onPress={() => this.removeBeneficialOwner(owner)}> | ||
|
@@ -265,9 +300,8 @@ class ACHContractStep extends React.Component { | |
{this.props.translate('beneficialOwnersStep.agreement')} | ||
</Text> | ||
<CheckboxWithLabel | ||
inputID="acceptTermsAndConditions" | ||
style={[styles.mt4]} | ||
isChecked={this.state.acceptTermsAndConditions} | ||
onInputChange={() => this.toggleCheckbox('acceptTermsAndConditions')} | ||
LabelComponent={() => ( | ||
<View style={[styles.flexRow]}> | ||
<Text>{this.props.translate('common.iAcceptThe')}</Text> | ||
|
@@ -276,19 +310,17 @@ class ACHContractStep extends React.Component { | |
</TextLink> | ||
</View> | ||
)} | ||
errorText={this.getErrorText('acceptTermsAndConditions')} | ||
hasError={this.getErrors().acceptTermsAndConditions} | ||
shouldSaveDraft | ||
/> | ||
<CheckboxWithLabel | ||
inputID="certifyTrueInformation" | ||
style={[styles.mt4]} | ||
isChecked={this.state.certifyTrueInformation} | ||
onInputChange={() => this.toggleCheckbox('certifyTrueInformation')} | ||
LabelComponent={() => ( | ||
<Text>{this.props.translate('beneficialOwnersStep.certifyTrueAndAccurate')}</Text> | ||
)} | ||
errorText={this.getErrorText('certifyTrueInformation')} | ||
shouldSaveDraft | ||
/> | ||
</ReimbursementAccountForm> | ||
</Form> | ||
</> | ||
); | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.