Skip to content

Commit

Permalink
Revert "feat: Connect USD and nonUSD VBA flows"
Browse files Browse the repository at this point in the history
  • Loading branch information
neil-marcellini authored Feb 25, 2025
1 parent 7e30096 commit 0894c30
Show file tree
Hide file tree
Showing 98 changed files with 1,383 additions and 1,476 deletions.
8 changes: 0 additions & 8 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,14 +681,6 @@ const CONST = {
AGREEMENTS: 'AgreementsStep',
FINISH: 'FinishStep',
},
BANK_INFO_STEP_ACH_DATA_INPUT_IDS: {
ACCOUNT_HOLDER_NAME: 'addressName',
ACCOUNT_HOLDER_REGION: 'addressState',
ACCOUNT_HOLDER_CITY: 'addressCity',
ACCOUNT_HOLDER_ADDRESS: 'addressStreet',
ACCOUNT_HOLDER_POSTAL_CODE: 'addressZipCode',
ROUTING_CODE: 'routingNumber',
},
BUSINESS_INFO_STEP: {
PICKLIST: {
ANNUAL_VOLUME_RANGE: 'AnnualVolumeRange',
Expand Down
2 changes: 1 addition & 1 deletion src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import type TimeModalPicker from '@components/TimeModalPicker';
import type UploadFile from '@components/UploadFile';
import type ValuePicker from '@components/ValuePicker';
import type ConstantSelector from '@pages/Debug/ConstantSelector';
import type BusinessTypePicker from '@pages/ReimbursementAccount/USD/BusinessInfo/subSteps/TypeBusiness/BusinessTypePicker';
import type BusinessTypePicker from '@pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker';
import type DimensionTypeSelector from '@pages/workspace/accounting/intacct/import/DimensionTypeSelector';
import type NetSuiteCustomFieldMappingPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker';
import type NetSuiteCustomListPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker';
Expand Down
10 changes: 5 additions & 5 deletions src/components/SubStepForms/AddressStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import {getFieldRequiredErrors, isValidAddress, isValidZipCode, isValidZipCodeInternational} from '@libs/ValidationUtils';
import * as ValidationUtils from '@libs/ValidationUtils';

Check failure on line 9 in src/components/SubStepForms/AddressStep.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"
import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields';
import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks';
import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks';
import type {OnyxFormValuesMapping} from '@src/ONYXKEYS';

type AddressValues = {
Expand Down Expand Up @@ -95,16 +95,16 @@ function AddressStep<TFormID extends keyof OnyxFormValuesMapping>({

const validate = useCallback(
(values: FormOnyxValues<TFormID>): FormInputErrors<TFormID> => {
const errors = getFieldRequiredErrors(values, stepFields);
const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields);

const street = values[inputFieldsIDs.street as keyof typeof values];
if (street && !isValidAddress(street as FormValue)) {
if (street && !ValidationUtils.isValidAddress(street as FormValue)) {
// @ts-expect-error type mismatch to be fixed
errors[inputFieldsIDs.street] = translate('bankAccount.error.addressStreet');
}

const zipCode = values[inputFieldsIDs.zipCode as keyof typeof values];
if (zipCode && (shouldDisplayCountrySelector ? !isValidZipCodeInternational(zipCode as string) : !isValidZipCode(zipCode as string))) {
if (zipCode && (shouldDisplayCountrySelector ? !ValidationUtils.isValidZipCodeInternational(zipCode as string) : !ValidationUtils.isValidZipCode(zipCode as string))) {
// @ts-expect-error type mismatch to be fixed
errors[inputFieldsIDs.zipCode] = translate('bankAccount.error.zipCode');
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/SubStepForms/FullNameStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import useLocalize from '@hooks/useLocalize';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import {getFieldRequiredErrors, isRequiredFulfilled, isValidLegalName} from '@libs/ValidationUtils';
import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks';
import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks';
import CONST from '@src/CONST';
import type {OnyxFormValuesMapping} from '@src/ONYXKEYS';

Expand Down
2 changes: 1 addition & 1 deletion src/components/SubStepForms/SingleFieldStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import TextInput from '@components/TextInput';
import useLocalize from '@hooks/useLocalize';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks';
import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks';
import CONST from '@src/CONST';
import type {OnyxFormValuesMapping} from '@src/ONYXKEYS';

Expand Down
2 changes: 1 addition & 1 deletion src/libs/API/parameters/RestartBankAccountSetupParams.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type RestartBankAccountSetupParams = {
bankAccountID: number;
ownerEmail: string;
policyID: string | undefined;
policyID: string;
};

export default RestartBankAccountSetupParams;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Onyx.connect({
/**
* Reset user's reimbursement account. This will delete the bank account.
*/
function resetFreePlanBankAccount(bankAccountID: number | undefined, session: OnyxEntry<OnyxTypes.Session>, policyID: string | undefined) {
function resetFreePlanBankAccount(bankAccountID: number | undefined, session: OnyxEntry<OnyxTypes.Session>, policyID: string) {
if (!bankAccountID) {
throw new Error('Missing bankAccountID when attempting to reset free plan bank account');
}
Expand Down
15 changes: 15 additions & 0 deletions src/pages/ReimbursementAccount/ACHContractStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import CompleteVerification from './CompleteVerification/CompleteVerification';

type ACHContractStepProps = {
/** Goes to the previous step */
onBackButtonPress: () => void;
};

function ACHContractStep({onBackButtonPress}: ACHContractStepProps) {
return <CompleteVerification onBackButtonPress={onBackButtonPress} />;
}

ACHContractStep.displayName = 'ACHContractStep';

export default ACHContractStep;
1 change: 0 additions & 1 deletion src/pages/ReimbursementAccount/AddressFormFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ function AddressFormFields({
modalHeaderTitle={translate('countryStep.selectCountry')}
searchInputTitle={translate('countryStep.findCountry')}
value={values?.country}
defaultValue={defaultValues?.country}
onValueChange={handleCountryChange}
stateInputIDToReset={inputKeys.state ?? 'stateInput'}
/>
Expand Down
251 changes: 251 additions & 0 deletions src/pages/ReimbursementAccount/BankAccountStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import React, {useEffect, useMemo, useRef} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import LottieAnimations from '@components/LottieAnimations';
import MenuItem from '@components/MenuItem';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import ValidateCodeActionModal from '@components/ValidateCodeActionModal';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {getEarliestErrorField, getLatestErrorField} from '@libs/ErrorUtils';
import getPlaidDesktopMessage from '@libs/getPlaidDesktopMessage';
import {REIMBURSEMENT_ACCOUNT_ROUTE_NAMES} from '@libs/ReimbursementAccountUtils';
import {openPlaidView, setBankAccountSubStep} from '@userActions/BankAccounts';
import {openExternalLink, openExternalLinkWithToken} from '@userActions/Link';
import {updateReimbursementAccountDraft} from '@userActions/ReimbursementAccount';
import {clearContactMethodErrors, requestValidateCodeAction, validateSecondaryLogin} from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {ReimbursementAccountForm} from '@src/types/form/ReimbursementAccountForm';
import INPUT_IDS from '@src/types/form/ReimbursementAccountForm';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import BankInfo from './BankInfo/BankInfo';

type BankAccountStepProps = {
/** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */
receivedRedirectURI?: string | null;

/** During the OAuth flow we need to use the plaidLink token that we initially connected with */
plaidLinkOAuthToken?: OnyxEntry<string>;

/* The workspace name */
policyName?: string;

/* The workspace ID */
policyID?: string;

/** The bank account currently in setup */
reimbursementAccount: OnyxEntry<OnyxTypes.ReimbursementAccount>;

/** Goes to the previous step */
onBackButtonPress: () => void;

/** Should ValidateCodeActionModal be displayed or not */
isValidateCodeActionModalVisible?: boolean;

/** Toggle ValidateCodeActionModal */
toggleValidateCodeActionModal?: (isVisible: boolean) => void;
};

const bankInfoStepKeys = INPUT_IDS.BANK_INFO_STEP;

function BankAccountStep({
plaidLinkOAuthToken = '',
policyID = '',
policyName = '',
receivedRedirectURI,
reimbursementAccount,
onBackButtonPress,
isValidateCodeActionModalVisible,
toggleValidateCodeActionModal,
}: BankAccountStepProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
const contactMethod = account?.primaryLogin ?? '';
const selectedSubStep = useRef('');

const loginData = useMemo(() => loginList?.[contactMethod], [loginList, contactMethod]);
const validateLoginError = getEarliestErrorField(loginData, 'validateLogin');
const hasMagicCodeBeenSent = !!loginData?.validateCodeSent;

let subStep = reimbursementAccount?.achData?.subStep ?? '';
const shouldReinitializePlaidLink = plaidLinkOAuthToken && receivedRedirectURI && subStep !== CONST.BANK_ACCOUNT.SUBSTEP.MANUAL;
if (shouldReinitializePlaidLink) {
subStep = CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID;
}
const plaidDesktopMessage = getPlaidDesktopMessage();
const bankAccountRoute = `${ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID, REIMBURSEMENT_ACCOUNT_ROUTE_NAMES.NEW, ROUTES.WORKSPACE_INITIAL.getRoute(policyID))}`;
const personalBankAccounts = bankAccountList ? Object.keys(bankAccountList).filter((key) => bankAccountList[key].accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) : [];

useEffect(() => {
if (!account?.validated) {
return;
}

if (selectedSubStep.current === CONST.BANK_ACCOUNT.SUBSTEP.MANUAL) {
setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL);
} else if (selectedSubStep.current === CONST.BANK_ACCOUNT.SUBSTEP.PLAID) {
openPlaidView();
}
}, [account?.validated]);

const removeExistingBankAccountDetails = () => {
const bankAccountData: Partial<ReimbursementAccountForm> = {
[bankInfoStepKeys.ROUTING_NUMBER]: '',
[bankInfoStepKeys.ACCOUNT_NUMBER]: '',
[bankInfoStepKeys.PLAID_MASK]: '',
[bankInfoStepKeys.IS_SAVINGS]: undefined,
[bankInfoStepKeys.BANK_NAME]: '',
[bankInfoStepKeys.PLAID_ACCOUNT_ID]: '',
[bankInfoStepKeys.PLAID_ACCESS_TOKEN]: '',
};
updateReimbursementAccountDraft(bankAccountData);
};

if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID || subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL) {
return (
<BankInfo
onBackButtonPress={onBackButtonPress}
policyID={policyID}
/>
);
}

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={BankAccountStep.displayName}
>
<View style={[styles.flex1, styles.justifyContentBetween]}>
<HeaderWithBackButton
title={translate('workspace.common.connectBankAccount')}
subtitle={policyName}
stepCounter={subStep ? {step: 1, total: 5} : undefined}
onBackButtonPress={onBackButtonPress}
shouldShowGetAssistanceButton
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT}
/>
<ScrollView style={styles.flex1}>
<Section
title={translate('workspace.bankAccount.streamlinePayments')}
illustration={LottieAnimations.FastMoney}
subtitle={translate('bankAccount.toGetStarted')}
subtitleMuted
illustrationBackgroundColor={theme.fallbackIconColor}
isCentralPane
>
{!!plaidDesktopMessage && (
<View style={[styles.mt3, styles.flexRow, styles.justifyContentBetween]}>
<TextLink onPress={() => openExternalLinkWithToken(bankAccountRoute)}>{translate(plaidDesktopMessage)}</TextLink>
</View>
)}
{!!personalBankAccounts.length && (
<View style={[styles.flexRow, styles.mt4, styles.alignItemsCenter, styles.pb1, styles.pt1]}>
<Icon
src={Expensicons.Lightbulb}
fill={theme.icon}
additionalStyles={styles.mr2}
medium
/>
<Text
style={[styles.textLabelSupportingNormal, styles.flex1]}
suppressHighlighting
>
{translate('workspace.bankAccount.connectBankAccountNote')}
</Text>
</View>
)}
<View style={styles.mt3}>
<MenuItem
icon={Expensicons.Bank}
title={translate('bankAccount.connectOnlineWithPlaid')}
disabled={!!isPlaidDisabled}
onPress={() => {
if (isPlaidDisabled) {
return;
}
if (!account?.validated) {
selectedSubStep.current = CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID;
toggleValidateCodeActionModal?.(true);
return;
}
removeExistingBankAccountDetails();
openPlaidView();
}}
shouldShowRightIcon
wrapperStyle={[styles.sectionMenuItemTopDescription]}
/>
</View>
<View style={styles.mb3}>
<MenuItem
icon={Expensicons.Connect}
title={translate('bankAccount.connectManually')}
onPress={() => {
if (!account?.validated) {
selectedSubStep.current = CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL;
toggleValidateCodeActionModal?.(true);
return;
}
removeExistingBankAccountDetails();
setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL);
}}
shouldShowRightIcon
wrapperStyle={[styles.sectionMenuItemTopDescription]}
/>
</View>
</Section>
<View style={[styles.mv0, styles.mh5, styles.flexRow, styles.justifyContentBetween]}>
<TextLink href={CONST.OLD_DOT_PUBLIC_URLS.PRIVACY_URL}>{translate('common.privacy')}</TextLink>
<PressableWithoutFeedback
onPress={() => openExternalLink(CONST.ENCRYPTION_AND_SECURITY_HELP_URL)}
style={[styles.flexRow, styles.alignItemsCenter]}
accessibilityLabel={translate('bankAccount.yourDataIsSecure')}
>
<TextLink href={CONST.ENCRYPTION_AND_SECURITY_HELP_URL}>{translate('bankAccount.yourDataIsSecure')}</TextLink>
<View style={styles.ml1}>
<Icon
src={Expensicons.Lock}
fill={theme.link}
/>
</View>
</PressableWithoutFeedback>
</View>
</ScrollView>
<ValidateCodeActionModal
title={translate('contacts.validateAccount')}
descriptionPrimary={translate('contacts.featureRequiresValidate')}
descriptionSecondary={translate('contacts.enterMagicCode', {contactMethod})}
isVisible={!!isValidateCodeActionModalVisible}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
validatePendingAction={loginData?.pendingFields?.validateCodeSent}
sendValidateCode={() => requestValidateCodeAction()}
handleSubmitForm={(validateCode) => validateSecondaryLogin(loginList, contactMethod, validateCode)}
validateError={!isEmptyObject(validateLoginError) ? validateLoginError : getLatestErrorField(loginData, 'validateCodeSent')}
clearError={() => clearContactMethodErrors(contactMethod, !isEmptyObject(validateLoginError) ? 'validateLogin' : 'validateCodeSent')}
onClose={() => toggleValidateCodeActionModal?.(false)}
/>
</View>
</ScreenWrapper>
);
}

BankAccountStep.displayName = 'BankAccountStep';

export default BankAccountStep;
Loading

0 comments on commit 0894c30

Please sign in to comment.