Skip to content

Commit d4db9de

Browse files
authored
Merge pull request #52322 from shubham1206agra/intl-bank-account
Implemented International Bank Account flow
2 parents f35cbc0 + a63f170 commit d4db9de

Some content is hidden

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

48 files changed

+1738
-199
lines changed

src/CONST.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,7 @@ const CONST = {
939939
CONFIGURE_REIMBURSEMENT_SETTINGS_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings',
940940
COPILOT_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Assign-or-remove-a-Copilot',
941941
DELAYED_SUBMISSION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports',
942+
ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security',
942943
PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing',
943944
// Use Environment.getEnvironmentURL to get the complete URL with port number
944945
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
@@ -6440,6 +6441,50 @@ const CONST = {
64406441
},
64416442
},
64426443

6444+
CORPAY_FIELDS: {
6445+
BANK_ACCOUNT_DETAILS_FIELDS: ['accountNumber', 'localAccountNumber', 'routingCode', 'localRoutingCode', 'swiftBicCode'] as string[],
6446+
ACCOUNT_TYPE_KEY: 'BeneficiaryAccountType',
6447+
BANK_INFORMATION_FIELDS: ['bankName', 'bankAddressLine1', 'bankAddressLine2', 'bankCity', 'bankRegion', 'bankPostal', 'BeneficiaryBankBranchName'] as string[],
6448+
ACCOUNT_HOLDER_FIELDS: [
6449+
'accountHolderName',
6450+
'accountHolderAddress1',
6451+
'accountHolderAddress2',
6452+
'accountHolderCity',
6453+
'accountHolderRegion',
6454+
'accountHolderCountry',
6455+
'accountHolderPostal',
6456+
'accountHolderPhoneNumber',
6457+
'accountHolderEmail',
6458+
'ContactName',
6459+
'BeneficiaryCPF',
6460+
'BeneficiaryRUT',
6461+
'BeneficiaryCedulaID',
6462+
'BeneficiaryTaxID',
6463+
] as string[],
6464+
SPECIAL_LIST_REGION_KEYS: ['bankRegion', 'accountHolderRegion'] as string[],
6465+
SPECIAL_LIST_ADDRESS_KEYS: ['bankAddressLine1', 'accountHolderAddress1'] as string[],
6466+
STEPS_NAME: {
6467+
COUNTRY_SELECTOR: 'CountrySelector',
6468+
BANK_ACCOUNT_DETAILS: 'BankAccountDetails',
6469+
ACCOUNT_TYPE: 'AccountType',
6470+
BANK_INFORMATION: 'BankInformation',
6471+
ACCOUNT_HOLDER_INFORMATION: 'AccountHolderInformation',
6472+
CONFIRMATION: 'Confirmation',
6473+
SUCCESS: 'Success',
6474+
},
6475+
INDEXES: {
6476+
MAPPING: {
6477+
COUNTRY_SELECTOR: 0,
6478+
BANK_ACCOUNT_DETAILS: 1,
6479+
ACCOUNT_TYPE: 2,
6480+
BANK_INFORMATION: 3,
6481+
ACCOUNT_HOLDER_INFORMATION: 4,
6482+
CONFIRMATION: 5,
6483+
SUCCESS: 6,
6484+
},
6485+
},
6486+
},
6487+
64436488
HYBRID_APP: {
64446489
REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront',
64456490
},

src/ONYXKEYS.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ const ONYXKEYS = {
600600
HOME_ADDRESS_FORM_DRAFT: 'homeAddressFormDraft',
601601
PERSONAL_DETAILS_FORM: 'personalDetailsForm',
602602
PERSONAL_DETAILS_FORM_DRAFT: 'personalDetailsFormDraft',
603+
INTERNATIONAL_BANK_ACCOUNT_FORM: 'internationalBankAccountForm',
604+
INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT: 'internationalBankAccountFormDraft',
603605
NEW_ROOM_FORM: 'newRoomForm',
604606
NEW_ROOM_FORM_DRAFT: 'newRoomFormDraft',
605607
ROOM_SETTINGS_FORM: 'roomSettingsForm',
@@ -819,6 +821,7 @@ type OnyxFormValuesMapping = {
819821
[ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AGE_FORM]: FormTypes.RulesMaxExpenseAgeForm;
820822
[ONYXKEYS.FORMS.SEARCH_SAVED_SEARCH_RENAME_FORM]: FormTypes.SearchSavedSearchRenameForm;
821823
[ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm | FormTypes.DebugTransactionForm | FormTypes.DebugTransactionViolationForm;
824+
[ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM]: FormTypes.InternationalBankAccountForm;
822825
[ONYXKEYS.FORMS.WORKSPACE_PER_DIEM_FORM]: FormTypes.WorkspacePerDiemForm;
823826
};
824827

src/ROUTES.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ const ROUTES = {
200200
},
201201
SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card',
202202
SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account',
203+
SETTINGS_ADD_US_BANK_ACCOUNT: 'settings/wallet/add-us-bank-account',
203204
SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments',
204205
SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: {
205206
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
@@ -69,6 +69,7 @@ const SCREENS = {
6969
ADD_DEBIT_CARD: 'Settings_Add_Debit_Card',
7070
ADD_PAYMENT_CARD_CHANGE_CURRENCY: 'Settings_Add_Payment_Card_Change_Currency',
7171
ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account',
72+
ADD_US_BANK_ACCOUNT: 'Settings_Add_US_Bank_Account',
7273
CLOSE: 'Settings_Close',
7374
TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth',
7475
REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged',

src/components/CurrencyPicker.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import type {ReactNode} from 'react';
2+
import React, {useState} from 'react';
3+
import useLocalize from '@hooks/useLocalize';
4+
import useThemeStyles from '@hooks/useThemeStyles';
5+
import * as CurrencyUtils from '@libs/CurrencyUtils';
6+
import Navigation from '@libs/Navigation/Navigation';
7+
import CONST from '@src/CONST';
8+
import CurrencySelectionList from './CurrencySelectionList';
9+
import type {CurrencyListItem} from './CurrencySelectionList/types';
10+
import HeaderWithBackButton from './HeaderWithBackButton';
11+
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
12+
import Modal from './Modal';
13+
import ScreenWrapper from './ScreenWrapper';
14+
15+
type CurrencyPickerProps = {
16+
/** Current value of the selected item */
17+
value?: string;
18+
19+
/** Custom content to display in the header */
20+
headerContent?: ReactNode;
21+
22+
/** Callback when the list item is selected */
23+
onInputChange?: (value: string, key?: string) => void;
24+
25+
/** Form Error description */
26+
errorText?: string;
27+
};
28+
29+
function CurrencyPicker({value, errorText, headerContent, onInputChange = () => {}}: CurrencyPickerProps) {
30+
const {translate} = useLocalize();
31+
const [isPickerVisible, setIsPickerVisible] = useState(false);
32+
const styles = useThemeStyles();
33+
34+
const hidePickerModal = () => {
35+
setIsPickerVisible(false);
36+
};
37+
38+
const updateInput = (item: CurrencyListItem) => {
39+
onInputChange?.(item.currencyCode);
40+
hidePickerModal();
41+
};
42+
43+
return (
44+
<>
45+
<MenuItemWithTopDescription
46+
shouldShowRightIcon
47+
title={value ? `${value} - ${CurrencyUtils.getCurrencySymbol(value)}` : undefined}
48+
description={translate('common.currency')}
49+
onPress={() => setIsPickerVisible(true)}
50+
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
51+
errorText={errorText}
52+
/>
53+
<Modal
54+
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
55+
isVisible={isPickerVisible}
56+
onClose={hidePickerModal}
57+
onModalHide={hidePickerModal}
58+
hideModalContentWhileAnimating
59+
useNativeDriver
60+
onBackdropPress={Navigation.dismissModal}
61+
>
62+
<ScreenWrapper
63+
style={[styles.pb0]}
64+
includePaddingTop={false}
65+
includeSafeAreaPaddingBottom
66+
testID={CurrencyPicker.displayName}
67+
>
68+
<HeaderWithBackButton
69+
title={translate('common.currency')}
70+
shouldShowBackButton
71+
onBackButtonPress={hidePickerModal}
72+
/>
73+
{!!headerContent && headerContent}
74+
<CurrencySelectionList
75+
initiallySelectedCurrencyCode={value}
76+
onSelect={updateInput}
77+
searchInputLabel={translate('common.search')}
78+
/>
79+
</ScreenWrapper>
80+
</Modal>
81+
</>
82+
);
83+
}
84+
85+
CurrencyPicker.displayName = 'CurrencyPicker';
86+
export default CurrencyPicker;

src/components/Form/FormProvider.tsx

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

7070
/** Whether HTML is allowed in form inputs */
7171
allowHTML?: boolean;
72+
73+
/** Whether the form is loading */
74+
isLoading?: boolean;
7275
};
7376

7477
function FormProvider(
@@ -82,6 +85,7 @@ function FormProvider(
8285
onSubmit,
8386
shouldTrimValues = true,
8487
allowHTML = false,
88+
isLoading = false,
8589
...rest
8690
}: FormProviderProps,
8791
forwardedRef: ForwardedRef<FormRef>,
@@ -189,7 +193,7 @@ function FormProvider(
189193
const submit = useDebounceNonReactive(
190194
useCallback(() => {
191195
// Return early if the form is already submitting to avoid duplicate submission
192-
if (formState?.isLoading) {
196+
if (!!formState?.isLoading || isLoading) {
193197
return;
194198
}
195199

@@ -210,7 +214,7 @@ function FormProvider(
210214
}
211215

212216
KeyboardUtils.dismiss().then(() => onSubmit(trimmedStringValues));
213-
}, [enabledWhenOffline, formState?.isLoading, inputValues, network?.isOffline, onSubmit, onValidate, shouldTrimValues]),
217+
}, [enabledWhenOffline, formState?.isLoading, inputValues, isLoading, network?.isOffline, onSubmit, onValidate, shouldTrimValues]),
214218
1000,
215219
{leading: true, trailing: false},
216220
);
@@ -406,6 +410,7 @@ function FormProvider(
406410
onSubmit={submit}
407411
inputRefs={inputRefs}
408412
errors={errors}
413+
isLoading={isLoading}
409414
enabledWhenOffline={enabledWhenOffline}
410415
>
411416
{typeof children === 'function' ? children({inputValues}) : children}

src/components/Form/FormWrapper.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type FormWrapperProps = ChildrenProps &
3636

3737
/** Callback to submit the form */
3838
onSubmit: () => void;
39+
40+
/** Whether the form is loading */
41+
isLoading?: boolean;
3942
};
4043

4144
function FormWrapper({
@@ -57,6 +60,7 @@ function FormWrapper({
5760
shouldHideFixErrorsAlert = false,
5861
disablePressOnEnter = false,
5962
isSubmitDisabled = false,
63+
isLoading = false,
6064
}: FormWrapperProps) {
6165
const styles = useThemeStyles();
6266
const {paddingBottom: safeAreaInsetPaddingBottom} = useStyledSafeAreaInsets();
@@ -112,7 +116,7 @@ function FormWrapper({
112116
buttonText={submitButtonText}
113117
isDisabled={isSubmitDisabled}
114118
isAlertVisible={((!isEmptyObject(errors) || !isEmptyObject(formState?.errorFields)) && !shouldHideFixErrorsAlert) || !!errorMessage}
115-
isLoading={!!formState?.isLoading}
119+
isLoading={!!formState?.isLoading || isLoading}
116120
message={isEmptyObject(formState?.errorFields) ? errorMessage : undefined}
117121
onSubmit={onSubmit}
118122
footerContent={footerContent}
@@ -143,6 +147,7 @@ function FormWrapper({
143147
formState?.isLoading,
144148
shouldHideFixErrorsAlert,
145149
errorMessage,
150+
isLoading,
146151
onSubmit,
147152
footerContent,
148153
onFixTheErrorsLinkPressed,

src/components/SelectionList/BaseSelectionList.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ function BaseSelectionList<TItem extends ListItem>(
6767
showScrollIndicator = true,
6868
showLoadingPlaceholder = false,
6969
showConfirmButton = false,
70+
isConfirmButtonDisabled = false,
7071
shouldUseDefaultTheme = false,
7172
shouldPreventDefaultFocusOnSelectRow = false,
7273
containerStyle,
@@ -765,7 +766,7 @@ function BaseSelectionList<TItem extends ListItem>(
765766
{
766767
captureOnInputs: true,
767768
shouldBubble: !flattenedSections.allOptions.at(focusedIndex) || focusedIndex === -1,
768-
isActive: !disableKeyboardShortcuts && isFocused,
769+
isActive: !disableKeyboardShortcuts && isFocused && !isConfirmButtonDisabled,
769770
},
770771
);
771772

@@ -848,6 +849,7 @@ function BaseSelectionList<TItem extends ListItem>(
848849
onPress={onConfirm}
849850
pressOnEnter
850851
enterKeyEventListenerPriority={1}
852+
isDisabled={isConfirmButtonDisabled}
851853
/>
852854
</FixedFooter>
853855
)}

src/components/SelectionList/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,9 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {
485485
/** Whether to show the default confirm button */
486486
showConfirmButton?: boolean;
487487

488+
/** Whether to show the default confirm button disabled */
489+
isConfirmButtonDisabled?: boolean;
490+
488491
/** Whether to use the default theme for the confirm button */
489492
shouldUseDefaultTheme?: boolean;
490493

src/components/TextPicker/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ import CONST from '@src/CONST';
77
import TextSelectorModal from './TextSelectorModal';
88
import type {TextPickerProps} from './types';
99

10-
function TextPicker({value, description, placeholder = '', errorText = '', onInputChange, furtherDetails, rightLabel, ...rest}: TextPickerProps, forwardedRef: ForwardedRef<View>) {
10+
function TextPicker(
11+
{value, description, placeholder = '', errorText = '', onInputChange, furtherDetails, rightLabel, disabled = false, interactive = true, ...rest}: TextPickerProps,
12+
forwardedRef: ForwardedRef<View>,
13+
) {
1114
const styles = useThemeStyles();
1215
const [isPickerVisible, setIsPickerVisible] = useState(false);
1316

1417
const showPickerModal = () => {
18+
if (disabled) {
19+
return;
20+
}
1521
setIsPickerVisible(true);
1622
};
1723

@@ -30,7 +36,7 @@ function TextPicker({value, description, placeholder = '', errorText = '', onInp
3036
<View>
3137
<MenuItemWithTopDescription
3238
ref={forwardedRef}
33-
shouldShowRightIcon
39+
shouldShowRightIcon={!disabled}
3440
title={value ?? placeholder ?? ''}
3541
description={description}
3642
onPress={showPickerModal}
@@ -39,13 +45,15 @@ function TextPicker({value, description, placeholder = '', errorText = '', onInp
3945
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
4046
errorText={errorText}
4147
style={[styles.moneyRequestMenuItem]}
48+
interactive={interactive}
4249
/>
4350
<TextSelectorModal
4451
value={value}
4552
isVisible={isPickerVisible}
4653
description={description}
4754
onClose={hidePickerModal}
4855
onValueSelected={updateInput}
56+
disabled={disabled}
4957
// eslint-disable-next-line react/jsx-props-no-spreading
5058
{...rest}
5159
/>

src/components/TextPicker/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type TextPickerProps = {
4242

4343
/** Whether to show the tooltip text */
4444
shouldShowTooltips?: boolean;
45-
} & Pick<MenuItemBaseProps, 'rightLabel' | 'subtitle' | 'description'> &
45+
} & Pick<MenuItemBaseProps, 'rightLabel' | 'subtitle' | 'description' | 'interactive'> &
4646
TextProps;
4747

4848
export type {TextSelectorModalProps, TextPickerProps};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type {FormOnyxKeys} from '@components/Form/types';
2+
import type {OnyxFormKey} from '@src/ONYXKEYS';
3+
import ONYXKEYS from '@src/ONYXKEYS';
4+
import useStepFormSubmit from './useStepFormSubmit';
5+
import type {SubStepProps} from './useSubStep/types';
6+
7+
type UseInternationalBankAccountFormSubmitParams = Pick<SubStepProps, 'onNext'> & {
8+
formId?: OnyxFormKey;
9+
fieldIds: Array<FormOnyxKeys<typeof ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM>>;
10+
shouldSaveDraft: boolean;
11+
};
12+
13+
/**
14+
* Hook for handling submit method in Missing Personal Details substeps.
15+
* When user is in editing mode, we should save values only when user confirms the change
16+
* @param onNext - callback
17+
* @param fieldIds - field IDs for particular step
18+
* @param shouldSaveDraft - if we should save draft values
19+
*/
20+
export default function useInternationalBankAccountFormSubmit({onNext, fieldIds, shouldSaveDraft}: UseInternationalBankAccountFormSubmitParams) {
21+
return useStepFormSubmit<typeof ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM>({
22+
formId: ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM,
23+
onNext,
24+
fieldIds,
25+
shouldSaveDraft,
26+
});
27+
}

0 commit comments

Comments
 (0)