Skip to content

Commit f835dd3

Browse files
authored
Merge pull request #54798 from shubham1206agra/intl-bank-account
Implemented International Bank Account flow
2 parents de8bdd4 + 63fbf3b commit f835dd3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1855
-306
lines changed

src/CONST.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ const CONST = {
670670
PER_DIEM: 'newDotPerDiem',
671671
NEWDOT_MERGE_ACCOUNTS: 'newDotMergeAccounts',
672672
NEWDOT_MANAGER_MCTEST: 'newDotManagerMcTest',
673+
NEWDOT_INTERNATIONAL_DEPOSIT_BANK_ACCOUNT: 'newDotInternationalDepositBankAccount',
673674
NSQS: 'nsqs',
674675
},
675676
BUTTON_STATES: {
@@ -930,6 +931,7 @@ const CONST = {
930931
CONFIGURE_REIMBURSEMENT_SETTINGS_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings',
931932
COPILOT_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Assign-or-remove-a-Copilot',
932933
DELAYED_SUBMISSION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports',
934+
ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security',
933935
PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing',
934936
TEST_RECEIPT_URL: `${CLOUDFRONT_URL}/images/fake-receipt__tacotodds.png`,
935937
// Use Environment.getEnvironmentURL to get the complete URL with port number
@@ -6452,6 +6454,53 @@ const CONST = {
64526454
},
64536455
},
64546456

6457+
CORPAY_FIELDS: {
6458+
EXCLUDED_COUNTRIES: ['IR', 'CU', 'SY', 'UA', 'KP', 'RU'] as string[],
6459+
EXCLUDED_CURRENCIES: ['IRR', 'CUP', 'SYP', 'UAH', 'KPW', 'RUB'] as string[],
6460+
BANK_ACCOUNT_DETAILS_FIELDS: ['accountNumber', 'localAccountNumber', 'routingCode', 'localRoutingCode', 'swiftBicCode'] as string[],
6461+
ACCOUNT_TYPE_KEY: 'BeneficiaryAccountType',
6462+
ACCOUNT_HOLDER_COUNTRY_KEY: 'accountHolderCountry',
6463+
BANK_INFORMATION_FIELDS: ['bankName', 'bankAddressLine1', 'bankAddressLine2', 'bankCity', 'bankRegion', 'bankPostal', 'BeneficiaryBankBranchName'] as string[],
6464+
ACCOUNT_HOLDER_FIELDS: [
6465+
'accountHolderName',
6466+
'accountHolderAddress1',
6467+
'accountHolderAddress2',
6468+
'accountHolderCity',
6469+
'accountHolderRegion',
6470+
'accountHolderCountry',
6471+
'accountHolderPostal',
6472+
'accountHolderPhoneNumber',
6473+
'accountHolderEmail',
6474+
'ContactName',
6475+
'BeneficiaryCPF',
6476+
'BeneficiaryRUT',
6477+
'BeneficiaryCedulaID',
6478+
'BeneficiaryTaxID',
6479+
] as string[],
6480+
SPECIAL_LIST_REGION_KEYS: ['bankRegion', 'accountHolderRegion'] as string[],
6481+
SPECIAL_LIST_ADDRESS_KEYS: ['bankAddressLine1', 'accountHolderAddress1'] as string[],
6482+
STEPS_NAME: {
6483+
COUNTRY_SELECTOR: 'CountrySelector',
6484+
BANK_ACCOUNT_DETAILS: 'BankAccountDetails',
6485+
ACCOUNT_TYPE: 'AccountType',
6486+
BANK_INFORMATION: 'BankInformation',
6487+
ACCOUNT_HOLDER_INFORMATION: 'AccountHolderInformation',
6488+
CONFIRMATION: 'Confirmation',
6489+
SUCCESS: 'Success',
6490+
},
6491+
INDEXES: {
6492+
MAPPING: {
6493+
COUNTRY_SELECTOR: 0,
6494+
BANK_ACCOUNT_DETAILS: 1,
6495+
ACCOUNT_TYPE: 2,
6496+
BANK_INFORMATION: 3,
6497+
ACCOUNT_HOLDER_INFORMATION: 4,
6498+
CONFIRMATION: 5,
6499+
SUCCESS: 6,
6500+
},
6501+
},
6502+
},
6503+
64556504
HYBRID_APP: {
64566505
REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront',
64576506
},

src/ONYXKEYS.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,8 @@ const ONYXKEYS = {
609609
HOME_ADDRESS_FORM_DRAFT: 'homeAddressFormDraft',
610610
PERSONAL_DETAILS_FORM: 'personalDetailsForm',
611611
PERSONAL_DETAILS_FORM_DRAFT: 'personalDetailsFormDraft',
612+
INTERNATIONAL_BANK_ACCOUNT_FORM: 'internationalBankAccountForm',
613+
INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT: 'internationalBankAccountFormDraft',
612614
NEW_ROOM_FORM: 'newRoomForm',
613615
NEW_ROOM_FORM_DRAFT: 'newRoomFormDraft',
614616
ROOM_SETTINGS_FORM: 'roomSettingsForm',
@@ -835,6 +837,7 @@ type OnyxFormValuesMapping = {
835837
[ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AGE_FORM]: FormTypes.RulesMaxExpenseAgeForm;
836838
[ONYXKEYS.FORMS.SEARCH_SAVED_SEARCH_RENAME_FORM]: FormTypes.SearchSavedSearchRenameForm;
837839
[ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm | FormTypes.DebugTransactionForm | FormTypes.DebugTransactionViolationForm;
840+
[ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM]: FormTypes.InternationalBankAccountForm;
838841
[ONYXKEYS.FORMS.WORKSPACE_PER_DIEM_FORM]: FormTypes.WorkspacePerDiemForm;
839842
};
840843

src/ROUTES.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ const ROUTES = {
202202
},
203203
SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card',
204204
SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account',
205+
SETTINGS_ADD_US_BANK_ACCOUNT: 'settings/wallet/add-us-bank-account',
205206
SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments',
206207
SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: {
207208
route: 'settings/wallet/card/:domain/digital-details/update-address',

src/SCREENS.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const SCREENS = {
7070
ADD_DEBIT_CARD: 'Settings_Add_Debit_Card',
7171
ADD_PAYMENT_CARD_CHANGE_CURRENCY: 'Settings_Add_Payment_Card_Change_Currency',
7272
ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account',
73+
ADD_US_BANK_ACCOUNT: 'Settings_Add_US_Bank_Account',
7374
CLOSE: 'Settings_Close',
7475
TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth',
7576
REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged',

src/components/CurrencyPicker.tsx

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,105 @@
1-
import React, {forwardRef, useState} from 'react';
2-
import type {ForwardedRef} from 'react';
3-
import {View} from 'react-native';
1+
import type {ReactNode} from 'react';
2+
import React, {Fragment, useState} from 'react';
43
import useLocalize from '@hooks/useLocalize';
5-
import useStyleUtils from '@hooks/useStyleUtils';
64
import useThemeStyles from '@hooks/useThemeStyles';
7-
import variables from '@styles/variables';
5+
import {getCurrencySymbol} from '@libs/CurrencyUtils';
6+
import Navigation from '@libs/Navigation/Navigation';
87
import CONST from '@src/CONST';
9-
import CurrencySelectionListWithOnyx from './CurrencySelectionList';
8+
import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView';
9+
import CurrencySelectionList from './CurrencySelectionList';
10+
import type {CurrencyListItem} from './CurrencySelectionList/types';
1011
import HeaderWithBackButton from './HeaderWithBackButton';
1112
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
1213
import Modal from './Modal';
1314
import ScreenWrapper from './ScreenWrapper';
14-
import type {ValuePickerItem, ValuePickerProps} from './ValuePicker/types';
1515

1616
type CurrencyPickerProps = {
17-
selectedCurrency?: string;
17+
/** Label for the input */
18+
label: string;
19+
20+
/** Current value of the selected item */
21+
value?: string;
22+
23+
/** Custom content to display in the header */
24+
headerContent?: ReactNode;
25+
26+
/** Callback when the list item is selected */
27+
onInputChange?: (value: string, key?: string) => void;
28+
29+
/** Form Error description */
30+
errorText?: string;
31+
32+
/** List of currencies to exclude from the list */
33+
excludeCurrencies?: string[];
34+
35+
/** Is the MenuItem interactive */
36+
interactive?: boolean;
37+
38+
/** Should show the full page offline view (whenever the user is offline) */
39+
shouldShowFullPageOfflineView?: boolean;
1840
};
19-
function CurrencyPicker({selectedCurrency, label = '', errorText = '', value, onInputChange, furtherDetails}: ValuePickerProps & CurrencyPickerProps, forwardedRef: ForwardedRef<View>) {
20-
const StyleUtils = useStyleUtils();
21-
const styles = useThemeStyles();
22-
const [isPickerVisible, setIsPickerVisible] = useState(false);
23-
const {translate} = useLocalize();
2441

25-
const showPickerModal = () => {
26-
setIsPickerVisible(true);
27-
};
42+
function CurrencyPicker({label, value, errorText, headerContent, excludeCurrencies, interactive, shouldShowFullPageOfflineView = false, onInputChange = () => {}}: CurrencyPickerProps) {
43+
const {translate} = useLocalize();
44+
const [isPickerVisible, setIsPickerVisible] = useState(false);
45+
const styles = useThemeStyles();
2846

2947
const hidePickerModal = () => {
3048
setIsPickerVisible(false);
3149
};
3250

33-
const updateInput = (item: ValuePickerItem) => {
34-
if (item.value !== selectedCurrency) {
35-
onInputChange?.(item.value);
36-
}
51+
const updateInput = (item: CurrencyListItem) => {
52+
onInputChange?.(item.currencyCode);
3753
hidePickerModal();
3854
};
3955

40-
const descStyle = !selectedCurrency || selectedCurrency.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null;
56+
const BlockingComponent = shouldShowFullPageOfflineView ? FullPageOfflineBlockingView : Fragment;
4157

4258
return (
43-
<View>
59+
<>
4460
<MenuItemWithTopDescription
45-
ref={forwardedRef}
4661
shouldShowRightIcon
47-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
48-
title={value || ''}
49-
descriptionTextStyle={descStyle}
62+
title={value ? `${value} - ${getCurrencySymbol(value)}` : undefined}
5063
description={label}
51-
onPress={showPickerModal}
52-
furtherDetails={furtherDetails}
64+
onPress={() => setIsPickerVisible(true)}
5365
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
5466
errorText={errorText}
67+
interactive={interactive}
5568
/>
56-
5769
<Modal
5870
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
5971
isVisible={isPickerVisible}
6072
onClose={hidePickerModal}
6173
onModalHide={hidePickerModal}
6274
hideModalContentWhileAnimating
6375
useNativeDriver
64-
onBackdropPress={hidePickerModal}
76+
onBackdropPress={Navigation.dismissModal}
6577
>
6678
<ScreenWrapper
67-
style={styles.pb0}
79+
style={[styles.pb0]}
6880
includePaddingTop={false}
69-
includeSafeAreaPaddingBottom={false}
70-
testID={label}
81+
includeSafeAreaPaddingBottom
82+
testID={CurrencyPicker.displayName}
7183
>
7284
<HeaderWithBackButton
7385
title={label}
86+
shouldShowBackButton
7487
onBackButtonPress={hidePickerModal}
7588
/>
76-
<CurrencySelectionListWithOnyx
77-
onSelect={(item) => updateInput({value: item.currencyCode})}
78-
searchInputLabel={translate('common.currency')}
79-
initiallySelectedCurrencyCode={selectedCurrency}
80-
/>
89+
<BlockingComponent>
90+
{!!headerContent && headerContent}
91+
<CurrencySelectionList
92+
initiallySelectedCurrencyCode={value}
93+
onSelect={updateInput}
94+
searchInputLabel={translate('common.search')}
95+
excludedCurrencies={excludeCurrencies}
96+
/>
97+
</BlockingComponent>
8198
</ScreenWrapper>
8299
</Modal>
83-
</View>
100+
</>
84101
);
85102
}
86103

87104
CurrencyPicker.displayName = 'CurrencyPicker';
88-
89-
export default forwardRef(CurrencyPicker);
105+
export default CurrencyPicker;

src/components/CurrencySelectionList/index.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import SelectionList from '@components/SelectionList';
55
import RadioListItem from '@components/SelectionList/RadioListItem';
66
import SelectableListItem from '@components/SelectionList/SelectableListItem';
77
import useLocalize from '@hooks/useLocalize';
8-
import * as CurrencyUtils from '@libs/CurrencyUtils';
8+
import {getCurrencySymbol} from '@libs/CurrencyUtils';
99
import ONYXKEYS from '@src/ONYXKEYS';
1010
import {isEmptyObject} from '@src/types/utils/EmptyObject';
1111
import type {CurrencyListItem, CurrencySelectionListProps} from './types';
@@ -17,6 +17,7 @@ function CurrencySelectionList({
1717
selectedCurrencies = [],
1818
canSelectMultiple = false,
1919
recentlyUsedCurrencies,
20+
excludedCurrencies = [],
2021
}: CurrencySelectionListProps) {
2122
const [currencyList] = useOnyx(ONYXKEYS.CURRENCY_LIST);
2223
const [searchValue, setSearchValue] = useState('');
@@ -25,10 +26,10 @@ function CurrencySelectionList({
2526
const {sections, headerMessage} = useMemo(() => {
2627
const currencyOptions: CurrencyListItem[] = Object.entries(currencyList ?? {}).reduce((acc, [currencyCode, currencyInfo]) => {
2728
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode || selectedCurrencies.includes(currencyCode);
28-
if (isSelectedCurrency || !currencyInfo?.retired) {
29+
if (!excludedCurrencies.includes(currencyCode) && (isSelectedCurrency || !currencyInfo?.retired)) {
2930
acc.push({
3031
currencyName: currencyInfo?.name ?? '',
31-
text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`,
32+
text: `${currencyCode} - ${getCurrencySymbol(currencyCode)}`,
3233
currencyCode,
3334
keyForList: currencyCode,
3435
isSelected: isSelectedCurrency,
@@ -43,7 +44,7 @@ function CurrencySelectionList({
4344
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode;
4445
return {
4546
currencyName: currencyInfo?.name ?? '',
46-
text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`,
47+
text: `${currencyCode} - ${getCurrencySymbol(currencyCode)}`,
4748
currencyCode,
4849
keyForList: currencyCode,
4950
isSelected: isSelectedCurrency,
@@ -86,7 +87,7 @@ function CurrencySelectionList({
8687
}
8788

8889
return {sections: result, headerMessage: isEmpty ? translate('common.noResultsFound') : ''};
89-
}, [currencyList, searchValue, translate, initiallySelectedCurrencyCode, selectedCurrencies, getUnselectedOptions, recentlyUsedCurrencies]);
90+
}, [currencyList, recentlyUsedCurrencies, searchValue, getUnselectedOptions, translate, initiallySelectedCurrencyCode, selectedCurrencies, excludedCurrencies]);
9091

9192
return (
9293
<SelectionList

src/components/CurrencySelectionList/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ type CurrencySelectionListProps = {
2323

2424
/** Whether this is a multi-select list */
2525
canSelectMultiple?: boolean;
26+
27+
/** List of excluded currency codes */
28+
excludedCurrencies?: string[];
2629
};
2730

2831
export type {CurrencyListItem, CurrencySelectionListProps};

src/components/Form/FormProvider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ type FormProviderProps<TFormID extends OnyxFormKey = OnyxFormKey> = FormProps<TF
7272

7373
/** Whether HTML is allowed in form inputs */
7474
allowHTML?: boolean;
75+
76+
/** Whether the form is loading */
77+
isLoading?: boolean;
7578
};
7679

7780
function FormProvider(
@@ -85,6 +88,7 @@ function FormProvider(
8588
onSubmit,
8689
shouldTrimValues = true,
8790
allowHTML = false,
91+
isLoading = false,
8892
...rest
8993
}: FormProviderProps,
9094
forwardedRef: ForwardedRef<FormRef>,
@@ -193,7 +197,7 @@ function FormProvider(
193197
const submit = useDebounceNonReactive(
194198
useCallback(() => {
195199
// Return early if the form is already submitting to avoid duplicate submission
196-
if (formState?.isLoading) {
200+
if (!!formState?.isLoading || isLoading) {
197201
return;
198202
}
199203

@@ -214,7 +218,7 @@ function FormProvider(
214218
}
215219

216220
KeyboardUtils.dismiss().then(() => onSubmit(trimmedStringValues));
217-
}, [enabledWhenOffline, formState?.isLoading, inputValues, network?.isOffline, onSubmit, onValidate, shouldTrimValues]),
221+
}, [enabledWhenOffline, formState?.isLoading, inputValues, isLoading, network?.isOffline, onSubmit, onValidate, shouldTrimValues]),
218222
1000,
219223
{leading: true, trailing: false},
220224
);
@@ -415,6 +419,7 @@ function FormProvider(
415419
onSubmit={submit}
416420
inputRefs={inputRefs}
417421
errors={errors}
422+
isLoading={isLoading}
418423
enabledWhenOffline={enabledWhenOffline}
419424
>
420425
{typeof children === 'function' ? children({inputValues}) : children}

0 commit comments

Comments
 (0)