Skip to content
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

Business Info Page #30924

Merged
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4f13317
add: BusinessInfo and NameBusiness substep
Swor71 Nov 3, 2023
d493d3a
add: TaxIdBusiness substep
Swor71 Nov 3, 2023
82e383c
add: WebsiteBusiness substep
Swor71 Nov 3, 2023
942d549
add: PhoneNumberBusiness substep
Swor71 Nov 3, 2023
cfd4ace
add: extracted getDefaultStateForField util and used it across the flow
Swor71 Nov 3, 2023
36e10e6
add: SSN with getDefaultStateForField util used
Swor71 Nov 3, 2023
ce24ca8
add: AddressBusiness substep
Swor71 Nov 3, 2023
316040a
add: TypeBusiness substep
Swor71 Nov 3, 2023
92b0ae2
add: IncorporationDateBusiness substep
Swor71 Nov 6, 2023
de9b598
add: IncorporationStateBusiness substep
Swor71 Nov 6, 2023
098e60c
add: getSubstepValues util
Swor71 Nov 6, 2023
427ab33
add: ConfirmationBusiness substep
Swor71 Nov 6, 2023
8152cd4
Merge remote-tracking branch 'mmuzyk/michal/personal-info-page' into …
Swor71 Nov 6, 2023
4a29bc9
change: removed the usages of updateOnyxVBBAData
Swor71 Nov 6, 2023
e229faa
fix: IncorporationDate date picker usage
Swor71 Nov 6, 2023
0a33668
add: getInitialSubstepForBusinessInfo util and adjusted BusinessInfo …
Swor71 Nov 6, 2023
72cc6f1
add: validation on all substeps
Swor71 Nov 6, 2023
dff94a5
change: cleaned up CompanyStep component
Swor71 Nov 6, 2023
8d579c5
add: Spanish translations, moved website link to const
Swor71 Nov 7, 2023
72546e5
change: removed isRequired for reimbursementAccount
Swor71 Nov 7, 2023
8fa9a25
fix: initial substep after CR
Swor71 Nov 7, 2023
3b668bc
change: removed extra View wrapper when using Form
Swor71 Nov 7, 2023
749e6a0
fix: getInitialSubstepForBusinessInfo util
Swor71 Nov 8, 2023
d4a6cc4
Merge branch 'vit-tieredBankAccountFlow' into marcin/business-info-page
Swor71 Nov 8, 2023
eafe68b
fix: lint errors after merge of vit-tieredBankAccountFlow
Swor71 Nov 8, 2023
2b2f4a2
fix: removed additional spaces in jsdoc comment for getSubstepValues
Swor71 Nov 13, 2023
8197628
Merge branch 'vit-tieredBankAccountFlow' into marcin/business-info-page
Swor71 Nov 13, 2023
87f077c
fix: lint issues after merging latest feature branch (main sync)
Swor71 Nov 13, 2023
1140b43
Revert "fix: lint issues after merging latest feature branch (main sy…
Swor71 Nov 13, 2023
5fd108e
fix: lint issues
Swor71 Nov 13, 2023
d6bd017
fix: changed keyboardType to inputMode, full state name on Confirmati…
Swor71 Nov 14, 2023
218c487
change: a11y label / role
Swor71 Nov 14, 2023
4fe37bb
fix: add max date of today for IncorporationDate
Swor71 Nov 14, 2023
75fae66
fix: added wrapper styles to IncorporateState to fix padding issue
Swor71 Nov 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,22 @@ const CONST = {
ZIP_CODE: 'requestorAddressZipCode',
},
},
BUSINESS_INFO_STEP: {
INPUT_KEY: {
COMPANY_NAME: 'companyName',
COMPANY_TAX_ID: 'companyTaxID',
COMPANY_WEBSITE: 'website',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not companyWebsite?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that has been used before, can we fix it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mountiny hm where did you find companyWebsite as the key? I've moved what was there in the CompanyStep component to the new components and their respective keys:

https://github.com/Expensify/App/pull/30924/files#diff-40ada854d0b6039d4db7da6cede12f7be0cf7d113cc16c8cbc638af4bd0121d4L81

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed we can do that later

COMPANY_PHONE: 'companyPhone',
STREET: 'addressStreet',
CITY: 'addressCity',
STATE: 'addressState',
ZIP_CODE: 'addressZipCode',
INCORPORATION_TYPE: 'incorporationType',
INCORPORATION_DATE: 'incorporationDate',
INCORPORATION_STATE: 'incorporationState',
HAS_NO_CONNECTION_TO_CANNABIS: 'hasNoConnectionToCannabis',
},
},
PLAID: {
ALLOWED_THROTTLED_COUNT: 2,
ERROR: {
Expand Down Expand Up @@ -475,6 +491,7 @@ const CONST = {
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/',
LIST_OF_RESTRICTED_BUSINESSES: 'https://community.expensify.com/discussion/6191/list-of-restricted-businesses',

// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'http://localhost:',
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useSubStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import {useState, useRef, useCallback} from 'react';
import PropTypes from 'prop-types';

const propTypes = {
/** an array of substep components */
/** An array of substep components */
bodyContent: PropTypes.arrayOf(PropTypes.element).isRequired,

/** an index of the component from bodyContent array to start from */
/** An index of the component from bodyContent array to start from */
onFinished: PropTypes.func.isRequired,

/** a callback to be fired when pressing Confirm on the last substep screen */
/** A callback to be fired when pressing Confirm on the last substep screen */
startFrom: PropTypes.number,
};

Expand Down
33 changes: 32 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1367,9 +1367,40 @@ export default {
last4SSN: 'Last 4 Social Security Number',
enterYourAddress: 'Enter your address.',
address: 'Address',
letsDoubleCheck: "Let's double check everything looks right",
letsDoubleCheck: "Let's double check that everything looks right",
byAddingThisBankAccount: 'By adding this bank account, you confirm that you have read, understand and accept',
},
businessInfoStep: {
Swor71 marked this conversation as resolved.
Show resolved Hide resolved
businessInfo: 'Business info',
enterTheNameOfYourBusiness: 'Enter the name of your business.',
businessName: 'Legal business name',
enterYourCompanysTaxIdNumber: 'Enter your company’s Tax ID number.',
taxIDNumber: 'Tax ID number',
taxIDNumberPlaceholder: '9 digits',
enterYourCompanysWebsite: 'Enter your company’s website.',
companyWebsite: 'Company website',
enterYourCompanysPhoneNumber: 'Enter your company’s phone number.',
enterYourCompanysAddress: 'Enter your company’s address.',
selectYourCompanysType: 'Select your company’s type.',
companyType: 'Company type',
incorporationType: {
LLC: 'LLC',
CORPORATION: 'Corp',
PARTNERSHIP: 'Partnership',
COOPERATIVE: 'Cooperative',
SOLE_PROPRIETORSHIP: 'Sole proprietorship',
OTHER: 'Other',
},
selectYourCompanysIncorporationDate: 'Select your company’s incorporation date.',
incorporationDate: 'Incorporation date',
incorporationDatePlaceholder: 'Start date (yyyy-mm-dd)',
incorporationState: 'Incorporation state',
pleaseSelectTheStateYourCompanyWasIncorporatedIn: 'Please select the state your company was incorporated in.',
letsDoubleCheck: "Let's double check that everything looks right",
companyAddress: 'Company address',
listOfRestrictedBusinesses: 'list of restricted businesses',
confirmCompanyIsNot: 'I confirm that this company is not on the',
},
validationStep: {
headerTitle: 'Validate Bank Account',
buttonText: 'Finish setup',
Expand Down
31 changes: 31 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,37 @@ export default {
letsDoubleCheck: 'Revisemos que todo esté bien',
byAddingThisBankAccount: 'Agregando esta cuenta bancaria, confirmas que as leído, entendido y aceptado',
},
businessInfoStep: {
businessInfo: 'Información de Negocio',
enterTheNameOfYourBusiness: 'Introduzca el nombre de su negocio.',
businessName: 'Nombre del Negocio',
enterYourCompanysTaxIdNumber: 'Introduzca el número de identificación fiscal.',
taxIDNumber: 'Número de identificación fiscal',
taxIDNumberPlaceholder: '9 dígitos',
enterYourCompanysWebsite: 'Introduzca el sitio web de su compañia.',
companyWebsite: 'Sitio web de la compañia',
enterYourCompanysPhoneNumber: 'Introduzca el número de teléfono de su compañia.',
enterYourCompanysAddress: 'Introduzca el la dirección de su compañia.',
selectYourCompanysType: 'Seleccione el tipo de compañia.',
companyType: 'Tipo de compañia',
incorporationType: {
LLC: 'SRL',
CORPORATION: 'Corporación',
PARTNERSHIP: 'Asociación',
COOPERATIVE: 'Cooperativa',
SOLE_PROPRIETORSHIP: 'Empresa unipersonal',
OTHER: 'Otros',
},
selectYourCompanysIncorporationDate: 'Seleccione la fecha de constitución de su empresa.',
incorporationDate: 'Fecha de constitución',
incorporationDatePlaceholder: 'Fecha de inicio (yyyy-mm-dd)',
incorporationState: 'Estado de constitución',
pleaseSelectTheStateYourCompanyWasIncorporatedIn: 'Seleccione el estado en el que se constituyó su empresa.',
letsDoubleCheck: 'Verifiquemos que todo esté correcto',
companyAddress: 'Dirección de la empresa',
listOfRestrictedBusinesses: 'lista de negocios restringidos',
confirmCompanyIsNot: 'Confirmo que esta empresa no está en la',
},
Comment on lines +1391 to +1420
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm the verification of these in slack?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as with the other parts of this flow, given time constraints, it's been said that we will have a final check towards the end of the work on it, accepted as non blocking by Vit here

validationStep: {
headerTitle: 'Validar cuenta bancaria',
buttonText: 'Finalizar configuración',
Expand Down
152 changes: 152 additions & 0 deletions src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
import _ from 'underscore';
import {parsePhoneNumber} from 'awesome-phonenumber';
import PropTypes from 'prop-types';
import useSubStep from '../../../hooks/useSubStep';
import ONYXKEYS from '../../../ONYXKEYS';
import {reimbursementAccountPropTypes} from '../reimbursementAccountPropTypes';
import HeaderWithBackButton from '../../../components/HeaderWithBackButton';
import InteractiveStepSubHeader from '../../../components/InteractiveStepSubHeader';
import useLocalize from '../../../hooks/useLocalize';
import styles from '../../../styles/styles';
import CONST from '../../../CONST';
import * as BankAccounts from '../../../libs/actions/BankAccounts';
import Navigation from '../../../libs/Navigation/Navigation';
import ROUTES from '../../../ROUTES';
import ScreenWrapper from '../../../components/ScreenWrapper';
import getDefaultStateForField from '../utils/getDefaultStateForField';
import NameBusiness from './substeps/NameBusiness';
import TaxIdBusiness from './substeps/TaxIdBusiness';
import WebsiteBusiness from './substeps/WebsiteBusiness';
import PhoneNumberBusiness from './substeps/PhoneNumberBusiness';
import AddressBusiness from './substeps/AddressBusiness';
import TypeBusiness from './substeps/TypeBusiness';
import IncorporationDateBusiness from './substeps/IncorporationDateBusiness';
import IncorporationStateBusiness from './substeps/IncorporationStateBusiness';
import ConfirmationBusiness from './substeps/ConfirmationBusiness';
import getSubstepValues from '../utils/getSubstepValues';
import reimbursementAccountDraftPropTypes from '../ReimbursementAccountDraftPropTypes';
import * as ReimbursementAccountProps from '../reimbursementAccountPropTypes';
import getInitialSubstepForBusinessInfo from '../utils/getInitialSubstepForBusinessInfo';

const propTypes = {
/** Reimbursement account from ONYX */
reimbursementAccount: reimbursementAccountPropTypes,

/** The draft values of the bank account being setup */
reimbursementAccountDraft: reimbursementAccountDraftPropTypes,

/* The workspace policyID */
policyID: PropTypes.string,
};

const defaultProps = {
reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps,
reimbursementAccountDraft: {},
policyID: '',
};

const STEPS_HEADER_HEIGHT = 40;
// TODO Will most likely come from different place
Swor71 marked this conversation as resolved.
Show resolved Hide resolved
const STEP_NAMES = ['1', '2', '3', '4', '5'];

const bodyContent = [
NameBusiness,
TaxIdBusiness,
WebsiteBusiness,
PhoneNumberBusiness,
AddressBusiness,
TypeBusiness,
IncorporationDateBusiness,
IncorporationStateBusiness,
ConfirmationBusiness,
];

const businessInfoStepKeys = CONST.BANK_ACCOUNT.BUSINESS_INFO_STEP.INPUT_KEY;

function BusinessInfo({reimbursementAccount, reimbursementAccountDraft, policyID}) {
const {translate} = useLocalize();

/**
* @param {Array} fieldNames
*
* @returns {*}
*/
const getBankAccountFields = useCallback(
(fieldNames) => ({
..._.pick(lodashGet(reimbursementAccount, 'achData'), ...fieldNames),
..._.pick(reimbursementAccountDraft, ...fieldNames),
}),
[reimbursementAccount, reimbursementAccountDraft],
);

const submit = useCallback(() => {
const values = getSubstepValues(businessInfoStepKeys, reimbursementAccountDraft, reimbursementAccount);

const payload = {
bankAccountID: getDefaultStateForField({reimbursementAccount, fieldName: 'bankAccountID', defaultValue: 0}),
...values,
...getBankAccountFields(['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']),
companyTaxID: values.companyTaxID.replace(CONST.REGEX.NON_NUMERIC, ''),
companyPhone: parsePhoneNumber(values.companyPhone, {regionCode: CONST.COUNTRY.US}).number.significant,
Comment on lines +89 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{BZ Checklist} this might have caused #50103,

RC:

  • When navigating back to the company tax page, clicking the "Next" button again triggers UpdateCompanyInformationForBankAccount with the website data as "https://", which is an invalid value. As a result, the backend returns an Onyx error:

This was fixed in #50715

};

BankAccounts.updateCompanyInformationForBankAccount(payload, policyID);
}, [getBankAccountFields, reimbursementAccount, reimbursementAccountDraft, policyID]);

const startFrom = useMemo(() => getInitialSubstepForBusinessInfo(lodashGet(reimbursementAccount, ['achData'], {})), [reimbursementAccount]);
Swor71 marked this conversation as resolved.
Show resolved Hide resolved

const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent, startFrom, onFinished: submit});

const handleBackButtonPress = () => {
if (screenIndex === 0) {
Navigation.goBack(ROUTES.HOME);
} else {
prevScreen();
}
};

return (
<ScreenWrapper
testID={BusinessInfo.displayName}
includeSafeAreaPaddingBottom={false}
shouldEnablePickerAvoiding={false}
shouldEnableMaxHeight
>
<HeaderWithBackButton
title={translate('businessInfoStep.businessInfo')}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT}
onBackButtonPress={handleBackButtonPress}
/>
<View style={[styles.ph5, styles.mv3, {height: STEPS_HEADER_HEIGHT}]}>
<InteractiveStepSubHeader
onStepSelected={() => {}}
// TODO Will be replaced with proper values
startStep={2}
stepNames={STEP_NAMES}
/>
</View>
<SubStep
isEditing={isEditing}
onNext={nextScreen}
onMove={moveTo}
/>
</ScreenWrapper>
);
}

BusinessInfo.propTypes = propTypes;
BusinessInfo.defaultProps = defaultProps;
BusinessInfo.displayName = 'BusinessInfo';

export default withOnyx({
reimbursementAccount: {
key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
},
reimbursementAccountDraft: {
key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT,
},
})(BusinessInfo);
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import {View} from 'react-native';
import useLocalize from '../../../../hooks/useLocalize';
import styles from '../../../../styles/styles';
import Text from '../../../../components/Text';
import CONST from '../../../../CONST';
import Form from '../../../../components/Form';
import ONYXKEYS from '../../../../ONYXKEYS';
import subStepPropTypes from '../../subStepPropTypes';
import * as ValidationUtils from '../../../../libs/ValidationUtils';
import {reimbursementAccountPropTypes} from '../../reimbursementAccountPropTypes';
import getDefaultStateForField from '../../utils/getDefaultStateForField';
import AddressForm from '../../AddressForm';

const propTypes = {
/** Reimbursement account from ONYX */
reimbursementAccount: reimbursementAccountPropTypes.isRequired,
Swor71 marked this conversation as resolved.
Show resolved Hide resolved

...subStepPropTypes,
};

const companyBusinessInfoKey = CONST.BANK_ACCOUNT.BUSINESS_INFO_STEP.INPUT_KEY;

const INPUT_KEYS = {
street: companyBusinessInfoKey.STREET,
city: companyBusinessInfoKey.CITY,
state: companyBusinessInfoKey.STATE,
zipCode: companyBusinessInfoKey.ZIP_CODE,
};

const REQUIRED_FIELDS = [companyBusinessInfoKey.STREET, companyBusinessInfoKey.CITY, companyBusinessInfoKey.STATE, companyBusinessInfoKey.ZIP_CODE];

const validate = (values) => {
const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS);

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

if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) {
errors.addressZipCode = 'bankAccount.error.zipCode';
}

return errors;
};

function AddressBusiness({reimbursementAccount, onNext, isEditing}) {
const {translate} = useLocalize();

const defaultValues = {
street: getDefaultStateForField({reimbursementAccount, fieldName: companyBusinessInfoKey.STREET, defaultValue: ''}),
city: getDefaultStateForField({reimbursementAccount, fieldName: companyBusinessInfoKey.CITY, defaultValue: ''}),
state: getDefaultStateForField({reimbursementAccount, fieldName: companyBusinessInfoKey.STATE, defaultValue: ''}),
zipCode: getDefaultStateForField({reimbursementAccount, fieldName: companyBusinessInfoKey.ZIP_CODE, defaultValue: ''}),
};

return (
<Form
formID={ONYXKEYS.REIMBURSEMENT_ACCOUNT}
submitButtonText={isEditing ? translate('common.confirm') : translate('common.next')}
validate={validate}
onSubmit={onNext}
submitButtonStyles={[styles.mb0, styles.pb5]}
style={[styles.mh5, styles.flexGrow1]}
>
<View>
<Text style={[styles.textHeadline]}>{translate('businessInfoStep.enterYourCompanysAddress')}</Text>
<Text>{translate('common.noPO')}</Text>
<AddressForm
inputKeys={INPUT_KEYS}
shouldSaveDraft
translate={translate}
defaultValues={defaultValues}
streetTranslationKey="common.companyAddress"
/>
</View>
</Form>
);
}

AddressBusiness.propTypes = propTypes;
AddressBusiness.displayName = 'AddressBusiness';

export default withOnyx({
reimbursementAccount: {
key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
},
})(AddressBusiness);
Loading
Loading