Skip to content

Commit

Permalink
Merge pull request #27455 from kacper-mikolajczak/refactor/16247/requ…
Browse files Browse the repository at this point in the history
…estor-step-class-to-function

RequestorStep.js: class to functional component refactor
  • Loading branch information
mountiny committed Sep 27, 2023
2 parents 06b507e + 08ba6ac commit 6b7bbc3
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 157 deletions.
6 changes: 6 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,12 @@ const CONST = {
INTERNAL_DEV_EXPENSIFY_URL: 'https://www.expensify.com.dev',
STAGING_EXPENSIFY_URL: 'https://staging.expensify.com',
EXPENSIFY_URL: 'https://www.expensify.com',
BANK_ACCOUNT_PERSONAL_DOCUMENTATION_INFO_URL:
'https://community.expensify.com/discussion/6983/faq-why-do-i-need-to-provide-personal-documentation-when-setting-up-updating-my-bank-account',
PERSONAL_DATA_PROTECTION_INFO_URL: 'https://community.expensify.com/discussion/5677/deep-dive-security-how-expensify-protects-your-information',
ONFIDO_FACIAL_SCAN_POLICY_URL: 'https://onfido.com/facial-scan-policy-and-release/',
ONFIDO_PRIVACY_POLICY_URL: 'https://onfido.com/privacy/',
ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/',

// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'http://localhost:',
Expand Down
319 changes: 162 additions & 157 deletions src/pages/ReimbursementAccount/RequestorStep.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react';
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import _ from 'lodash';
import styles from '../../styles/styles';
import withLocalize from '../../components/withLocalize';
import HeaderWithBackButton from '../../components/HeaderWithBackButton';
import CONST from '../../CONST';
import TextLink from '../../components/TextLink';
Expand All @@ -16,182 +15,188 @@ import ONYXKEYS from '../../ONYXKEYS';
import RequestorOnfidoStep from './RequestorOnfidoStep';
import Form from '../../components/Form';
import ScreenWrapper from '../../components/ScreenWrapper';
import StepPropTypes from './StepPropTypes';
import useLocalize from '../../hooks/useLocalize';
import {reimbursementAccountPropTypes} from './reimbursementAccountPropTypes';
import ReimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes';

const propTypes = {
...StepPropTypes,
onBackButtonPress: PropTypes.func.isRequired,
getDefaultStateForField: PropTypes.func.isRequired,
reimbursementAccount: reimbursementAccountPropTypes.isRequired,
reimbursementAccountDraft: ReimbursementAccountDraftPropTypes.isRequired,

/** If we should show Onfido flow */
shouldShowOnfido: PropTypes.bool.isRequired,
};

class RequestorStep extends React.Component {
constructor(props) {
super(props);

this.validate = this.validate.bind(this);
this.submit = this.submit.bind(this);
}

/**
* @param {Object} values
* @returns {Object}
*/
validate(values) {
const requiredFields = ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode'];
const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields);

if (values.dob) {
if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) {
errors.dob = 'bankAccount.error.dob';
} else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) {
errors.dob = 'bankAccount.error.age';
}
}

if (values.ssnLast4 && !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) {
errors.ssnLast4 = 'bankAccount.error.ssnLast4';
}
const REQUIRED_FIELDS = ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode'];
const INPUT_KEYS = {
firstName: 'firstName',
lastName: 'lastName',
dob: 'dob',
ssnLast4: 'ssnLast4',
street: 'requestorAddressStreet',
city: 'requestorAddressCity',
state: 'requestorAddressState',
zipCode: 'requestorAddressZipCode',
};
const STEP_COUNTER = {step: 3, total: 5};

if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) {
errors.requestorAddressStreet = 'bankAccount.error.addressStreet';
}
const validate = (values) => {
const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS);

if (values.requestorAddressZipCode && !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) {
errors.requestorAddressZipCode = 'bankAccount.error.zipCode';
if (values.dob) {
if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) {
errors.dob = 'bankAccount.error.dob';
} else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) {
errors.dob = 'bankAccount.error.age';
}
}

if (!ValidationUtils.isRequiredFulfilled(values.isControllingOfficer)) {
errors.isControllingOfficer = 'requestorStep.isControllingOfficerError';
}
if (values.ssnLast4 && !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) {
errors.ssnLast4 = 'bankAccount.error.ssnLast4';
}

return errors;
if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) {
errors.requestorAddressStreet = 'bankAccount.error.addressStreet';
}

submit(values) {
const payload = {
bankAccountID: lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0,
...values,
};
if (values.requestorAddressZipCode && !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) {
errors.requestorAddressZipCode = 'bankAccount.error.zipCode';
}

BankAccounts.updatePersonalInformationForBankAccount(payload);
if (!ValidationUtils.isRequiredFulfilled(values.isControllingOfficer)) {
errors.isControllingOfficer = 'requestorStep.isControllingOfficerError';
}

render() {
if (this.props.shouldShowOnfido) {
return (
<RequestorOnfidoStep
reimbursementAccount={this.props.reimbursementAccount}
reimbursementAccountDraft={this.props.reimbursementAccountDraft}
onBackButtonPress={this.props.onBackButtonPress}
getDefaultStateForField={this.props.getDefaultStateForField}
/>
);
}
return errors;
};

function RequestorStep({reimbursementAccount, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) {
const {translate} = useLocalize();

const defaultValues = useMemo(
() => ({
firstName: getDefaultStateForField(INPUT_KEYS.firstName),
lastName: getDefaultStateForField(INPUT_KEYS.lastName),
street: getDefaultStateForField(INPUT_KEYS.street),
city: getDefaultStateForField(INPUT_KEYS.city),
state: getDefaultStateForField(INPUT_KEYS.state),
zipCode: getDefaultStateForField(INPUT_KEYS.zipCode),
dob: getDefaultStateForField(INPUT_KEYS.dob),
ssnLast4: getDefaultStateForField(INPUT_KEYS.ssnLast4),
}),
[getDefaultStateForField],
);

const submit = useCallback(
(values) => {
const payload = {
bankAccountID: _.get(reimbursementAccount, 'achData.bankAccountID', 0),
...values,
};

BankAccounts.updatePersonalInformationForBankAccount(payload);
},
[reimbursementAccount],
);

const renderLabelComponent = () => (
<View style={[styles.flex1, styles.pr1]}>
<Text>{translate('requestorStep.isControllingOfficer')}</Text>
</View>
);

if (shouldShowOnfido) {
return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={RequestorStep.displayName}
>
<HeaderWithBackButton
title={this.props.translate('requestorStep.headerTitle')}
stepCounter={{step: 3, total: 5}}
shouldShowGetAssistanceButton
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT}
onBackButtonPress={this.props.onBackButtonPress}
/>
<Form
formID={ONYXKEYS.REIMBURSEMENT_ACCOUNT}
submitButtonText={this.props.translate('common.saveAndContinue')}
validate={this.validate}
scrollContextEnabled
onSubmit={this.submit}
style={[styles.mh5, styles.flexGrow1]}
>
<Text>{this.props.translate('requestorStep.subtitle')}</Text>
<View style={[styles.mb5, styles.mt1, styles.dFlex, styles.flexRow]}>
<TextLink
style={[styles.textMicro]}
// eslint-disable-next-line max-len
href="https://community.expensify.com/discussion/6983/faq-why-do-i-need-to-provide-personal-documentation-when-setting-up-updating-my-bank-account"
>
{`${this.props.translate('requestorStep.learnMore')}`}
</TextLink>
<Text style={[styles.textMicroSupporting]}>{' | '}</Text>
<TextLink
style={[styles.textMicro, styles.textLink]}
// eslint-disable-next-line max-len
href="https://community.expensify.com/discussion/5677/deep-dive-security-how-expensify-protects-your-information"
>
{`${this.props.translate('requestorStep.isMyDataSafe')}`}
</TextLink>
</View>
<IdentityForm
translate={this.props.translate}
defaultValues={{
firstName: this.props.getDefaultStateForField('firstName'),
lastName: this.props.getDefaultStateForField('lastName'),
street: this.props.getDefaultStateForField('requestorAddressStreet'),
city: this.props.getDefaultStateForField('requestorAddressCity'),
state: this.props.getDefaultStateForField('requestorAddressState'),
zipCode: this.props.getDefaultStateForField('requestorAddressZipCode'),
dob: this.props.getDefaultStateForField('dob'),
ssnLast4: this.props.getDefaultStateForField('ssnLast4'),
}}
inputKeys={{
firstName: 'firstName',
lastName: 'lastName',
dob: 'dob',
ssnLast4: 'ssnLast4',
street: 'requestorAddressStreet',
city: 'requestorAddressCity',
state: 'requestorAddressState',
zipCode: 'requestorAddressZipCode',
}}
shouldSaveDraft
/>
<CheckboxWithLabel
accessibilityLabel={this.props.translate('requestorStep.isControllingOfficer')}
inputID="isControllingOfficer"
defaultValue={this.props.getDefaultStateForField('isControllingOfficer', false)}
LabelComponent={() => (
<View style={[styles.flex1, styles.pr1]}>
<Text>{this.props.translate('requestorStep.isControllingOfficer')}</Text>
</View>
)}
style={[styles.mt4]}
shouldSaveDraft
/>
<Text style={[styles.mt3, styles.textMicroSupporting]}>
{this.props.translate('requestorStep.onFidoConditions')}
<TextLink
href="https://onfido.com/facial-scan-policy-and-release/"
style={[styles.textMicro]}
>
{this.props.translate('onfidoStep.facialScan')}
</TextLink>
{', '}
<TextLink
href="https://onfido.com/privacy/"
style={[styles.textMicro]}
>
{this.props.translate('common.privacy')}
</TextLink>
{` ${this.props.translate('common.and')} `}
<TextLink
href="https://onfido.com/terms-of-service/"
style={[styles.textMicro]}
>
{this.props.translate('common.termsOfService')}
</TextLink>
</Text>
</Form>
</ScreenWrapper>
<RequestorOnfidoStep
reimbursementAccount={reimbursementAccount}
reimbursementAccountDraft={reimbursementAccountDraft}
onBackButtonPress={onBackButtonPress}
getDefaultStateForField={getDefaultStateForField}
/>
);
}

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={RequestorStep.displayName}
>
<HeaderWithBackButton
title={translate('requestorStep.headerTitle')}
stepCounter={STEP_COUNTER}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT}
onBackButtonPress={onBackButtonPress}
shouldShowGetAssistanceButton
/>
<Form
formID={ONYXKEYS.REIMBURSEMENT_ACCOUNT}
submitButtonText={translate('common.saveAndContinue')}
validate={validate}
onSubmit={submit}
style={[styles.mh5, styles.flexGrow1]}
scrollContextEnabled
>
<Text>{translate('requestorStep.subtitle')}</Text>
<View style={[styles.mb5, styles.mt1, styles.dFlex, styles.flexRow]}>
<TextLink
style={[styles.textMicro]}
href={CONST.BANK_ACCOUNT_PERSONAL_DOCUMENTATION_INFO_URL}
>
{translate('requestorStep.learnMore')}
</TextLink>
<Text style={[styles.textMicroSupporting]}>{' | '}</Text>
<TextLink
style={[styles.textMicro, styles.textLink]}
href={CONST.PERSONAL_DATA_PROTECTION_INFO_URL}
>
{translate('requestorStep.isMyDataSafe')}
</TextLink>
</View>
<IdentityForm
translate={translate}
defaultValues={defaultValues}
inputKeys={INPUT_KEYS}
shouldSaveDraft
/>
<CheckboxWithLabel
accessibilityLabel={translate('requestorStep.isControllingOfficer')}
inputID="isControllingOfficer"
defaultValue={getDefaultStateForField('isControllingOfficer', false)}
LabelComponent={renderLabelComponent}
style={[styles.mt4]}
shouldSaveDraft
/>
<Text style={[styles.mt3, styles.textMicroSupporting]}>
{translate('requestorStep.onFidoConditions')}
<TextLink
href={CONST.ONFIDO_FACIAL_SCAN_POLICY_URL}
style={[styles.textMicro]}
>
{translate('onfidoStep.facialScan')}
</TextLink>
{', '}
<TextLink
href={CONST.ONFIDO_PRIVACY_POLICY_URL}
style={[styles.textMicro]}
>
{translate('common.privacy')}
</TextLink>
{` ${translate('common.and')} `}
<TextLink
href={CONST.ONFIDO_TERMS_OF_SERVICE_URL}
style={[styles.textMicro]}
>
{translate('common.termsOfService')}
</TextLink>
</Text>
</Form>
</ScreenWrapper>
);
}

RequestorStep.propTypes = propTypes;
RequestorStep.displayName = 'RequestorStep';

export default withLocalize(RequestorStep);
export default React.forwardRef(RequestorStep);

0 comments on commit 6b7bbc3

Please sign in to comment.