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

Implemented International Bank Account flow #54798

Merged
merged 37 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4bf95b9
Revert "[CP Staging] Revert "Implemented International Bank Account f…
shubham1206agra Jan 2, 2025
085aeda
Merge branch 'Expensify:main' into intl-bank-account
shubham1206agra Jan 4, 2025
6f0a577
Fix navigation from plaid account
shubham1206agra Jan 4, 2025
e4225f4
Fix navigation from plaid account success setup
shubham1206agra Jan 4, 2025
a060234
Fix navigation from plaid header back button
shubham1206agra Jan 4, 2025
6b982b3
Fix button on account type step
shubham1206agra Jan 4, 2025
0a8574f
Fix scrolling of country page
shubham1206agra Jan 4, 2025
da88626
Fix scrolling of country page
shubham1206agra Jan 4, 2025
c07ec58
Fix excluded currency and countries
shubham1206agra Jan 4, 2025
3cddf30
Fix validation
shubham1206agra Jan 4, 2025
ca22f9b
Fix: Currency does not auto update after changing country
shubham1206agra Jan 4, 2025
13a8197
Fix: Make default payment method option uses a message icon instead o…
shubham1206agra Jan 4, 2025
976956e
Fix the top of the confirmation page header
shubham1206agra Jan 4, 2025
dbd5145
Minor fix
shubham1206agra Jan 4, 2025
d88fe50
Fix draft saving mechanism
shubham1206agra Jan 4, 2025
1dfe4ca
Fix testValidation function
shubham1206agra Jan 4, 2025
300a666
Fix draft saving on account type step
shubham1206agra Jan 4, 2025
47eb2b4
Fix offline view of confirmation step
shubham1206agra Jan 4, 2025
d9e53a9
Fix order of menu items
shubham1206agra Jan 4, 2025
4e324cb
Minor fix
shubham1206agra Jan 4, 2025
434cd21
Merge branch 'Expensify:main' into intl-bank-account
shubham1206agra Jan 10, 2025
edbc2a1
Fix crash
shubham1206agra Jan 10, 2025
0d12036
Make currency picker non-interactive when offline
shubham1206agra Jan 10, 2025
dff31f4
Merge branch 'Expensify:main' into intl-bank-account
shubham1206agra Jan 12, 2025
cef7c93
Fixed offline view on country selection page
shubham1206agra Jan 12, 2025
2ec7312
Fixed account type button
shubham1206agra Jan 12, 2025
b305672
Merge main
shubham1206agra Jan 14, 2025
b386ff5
Fix lint
shubham1206agra Jan 14, 2025
cdd16f2
Fix lint
shubham1206agra Jan 14, 2025
64b13a6
Merge main
shubham1206agra Jan 16, 2025
11c0e77
Added the full page offline view on currency page
shubham1206agra Jan 16, 2025
d8761d5
Merge branch 'Expensify:main' into intl-bank-account
shubham1206agra Jan 17, 2025
7ed8ae5
Add a beta for the feature
shubham1206agra Jan 17, 2025
01d156c
Merge main
shubham1206agra Jan 21, 2025
e74e380
Fix lint
shubham1206agra Jan 21, 2025
36793b4
Merge branch 'main' into intl-bank-account
shubham1206agra Jan 21, 2025
63fbf3b
Merge branch 'main' into intl-bank-account
shubham1206agra Jan 22, 2025
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
49 changes: 49 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ const CONST = {
PER_DIEM: 'newDotPerDiem',
NEWDOT_MERGE_ACCOUNTS: 'newDotMergeAccounts',
NEWDOT_MANAGER_MCTEST: 'newDotManagerMcTest',
NEWDOT_INTERNATIONAL_DEPOSIT_BANK_ACCOUNT: 'newDotInternationalDepositBankAccount',
NSQS: 'nsqs',
},
BUTTON_STATES: {
Expand Down Expand Up @@ -930,6 +931,7 @@ const CONST = {
CONFIGURE_REIMBURSEMENT_SETTINGS_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings',
COPILOT_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Assign-or-remove-a-Copilot',
DELAYED_SUBMISSION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports',
ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security',
PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing',
TEST_RECEIPT_URL: `${CLOUDFRONT_URL}/images/fake-receipt__tacotodds.png`,
// Use Environment.getEnvironmentURL to get the complete URL with port number
Expand Down Expand Up @@ -6452,6 +6454,53 @@ const CONST = {
},
},

CORPAY_FIELDS: {
EXCLUDED_COUNTRIES: ['IR', 'CU', 'SY', 'UA', 'KP', 'RU'] as string[],
EXCLUDED_CURRENCIES: ['IRR', 'CUP', 'SYP', 'UAH', 'KPW', 'RUB'] as string[],
BANK_ACCOUNT_DETAILS_FIELDS: ['accountNumber', 'localAccountNumber', 'routingCode', 'localRoutingCode', 'swiftBicCode'] as string[],
ACCOUNT_TYPE_KEY: 'BeneficiaryAccountType',
ACCOUNT_HOLDER_COUNTRY_KEY: 'accountHolderCountry',
BANK_INFORMATION_FIELDS: ['bankName', 'bankAddressLine1', 'bankAddressLine2', 'bankCity', 'bankRegion', 'bankPostal', 'BeneficiaryBankBranchName'] as string[],
ACCOUNT_HOLDER_FIELDS: [
'accountHolderName',
'accountHolderAddress1',
'accountHolderAddress2',
'accountHolderCity',
'accountHolderRegion',
'accountHolderCountry',
'accountHolderPostal',
'accountHolderPhoneNumber',
'accountHolderEmail',
'ContactName',
'BeneficiaryCPF',
'BeneficiaryRUT',
'BeneficiaryCedulaID',
'BeneficiaryTaxID',
] as string[],
SPECIAL_LIST_REGION_KEYS: ['bankRegion', 'accountHolderRegion'] as string[],
SPECIAL_LIST_ADDRESS_KEYS: ['bankAddressLine1', 'accountHolderAddress1'] as string[],
STEPS_NAME: {
COUNTRY_SELECTOR: 'CountrySelector',
BANK_ACCOUNT_DETAILS: 'BankAccountDetails',
ACCOUNT_TYPE: 'AccountType',
BANK_INFORMATION: 'BankInformation',
ACCOUNT_HOLDER_INFORMATION: 'AccountHolderInformation',
CONFIRMATION: 'Confirmation',
SUCCESS: 'Success',
},
INDEXES: {
MAPPING: {
COUNTRY_SELECTOR: 0,
BANK_ACCOUNT_DETAILS: 1,
ACCOUNT_TYPE: 2,
BANK_INFORMATION: 3,
ACCOUNT_HOLDER_INFORMATION: 4,
CONFIRMATION: 5,
SUCCESS: 6,
},
},
},

HYBRID_APP: {
REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront',
},
Expand Down
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@ const ONYXKEYS = {
HOME_ADDRESS_FORM_DRAFT: 'homeAddressFormDraft',
PERSONAL_DETAILS_FORM: 'personalDetailsForm',
PERSONAL_DETAILS_FORM_DRAFT: 'personalDetailsFormDraft',
INTERNATIONAL_BANK_ACCOUNT_FORM: 'internationalBankAccountForm',
INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT: 'internationalBankAccountFormDraft',
NEW_ROOM_FORM: 'newRoomForm',
NEW_ROOM_FORM_DRAFT: 'newRoomFormDraft',
ROOM_SETTINGS_FORM: 'roomSettingsForm',
Expand Down Expand Up @@ -835,6 +837,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AGE_FORM]: FormTypes.RulesMaxExpenseAgeForm;
[ONYXKEYS.FORMS.SEARCH_SAVED_SEARCH_RENAME_FORM]: FormTypes.SearchSavedSearchRenameForm;
[ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm | FormTypes.DebugTransactionForm | FormTypes.DebugTransactionViolationForm;
[ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM]: FormTypes.InternationalBankAccountForm;
[ONYXKEYS.FORMS.WORKSPACE_PER_DIEM_FORM]: FormTypes.WorkspacePerDiemForm;
};

Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ const ROUTES = {
},
SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card',
SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account',
SETTINGS_ADD_US_BANK_ACCOUNT: 'settings/wallet/add-us-bank-account',
SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments',
SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: {
route: 'settings/wallet/card/:domain/digital-details/update-address',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const SCREENS = {
ADD_DEBIT_CARD: 'Settings_Add_Debit_Card',
ADD_PAYMENT_CARD_CHANGE_CURRENCY: 'Settings_Add_Payment_Card_Change_Currency',
ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account',
ADD_US_BANK_ACCOUNT: 'Settings_Add_US_Bank_Account',
CLOSE: 'Settings_Close',
TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth',
REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged',
Expand Down
98 changes: 57 additions & 41 deletions src/components/CurrencyPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,105 @@
import React, {forwardRef, useState} from 'react';
import type {ForwardedRef} from 'react';
import {View} from 'react-native';
import type {ReactNode} from 'react';
import React, {Fragment, useState} from 'react';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import {getCurrencySymbol} from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
import CurrencySelectionListWithOnyx from './CurrencySelectionList';
import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView';
import CurrencySelectionList from './CurrencySelectionList';
import type {CurrencyListItem} from './CurrencySelectionList/types';
import HeaderWithBackButton from './HeaderWithBackButton';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
import Modal from './Modal';
import ScreenWrapper from './ScreenWrapper';
import type {ValuePickerItem, ValuePickerProps} from './ValuePicker/types';

type CurrencyPickerProps = {
selectedCurrency?: string;
/** Label for the input */
label: string;

/** Current value of the selected item */
value?: string;

/** Custom content to display in the header */
headerContent?: ReactNode;

/** Callback when the list item is selected */
onInputChange?: (value: string, key?: string) => void;

/** Form Error description */
errorText?: string;

/** List of currencies to exclude from the list */
excludeCurrencies?: string[];

/** Is the MenuItem interactive */
interactive?: boolean;

/** Should show the full page offline view (whenever the user is offline) */
shouldShowFullPageOfflineView?: boolean;
};
function CurrencyPicker({selectedCurrency, label = '', errorText = '', value, onInputChange, furtherDetails}: ValuePickerProps & CurrencyPickerProps, forwardedRef: ForwardedRef<View>) {
const StyleUtils = useStyleUtils();
const styles = useThemeStyles();
const [isPickerVisible, setIsPickerVisible] = useState(false);
const {translate} = useLocalize();

const showPickerModal = () => {
setIsPickerVisible(true);
};
function CurrencyPicker({label, value, errorText, headerContent, excludeCurrencies, interactive, shouldShowFullPageOfflineView = false, onInputChange = () => {}}: CurrencyPickerProps) {
const {translate} = useLocalize();
const [isPickerVisible, setIsPickerVisible] = useState(false);
const styles = useThemeStyles();

const hidePickerModal = () => {
setIsPickerVisible(false);
};

const updateInput = (item: ValuePickerItem) => {
if (item.value !== selectedCurrency) {
onInputChange?.(item.value);
}
const updateInput = (item: CurrencyListItem) => {
onInputChange?.(item.currencyCode);
hidePickerModal();
};

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

return (
<View>
<>
<MenuItemWithTopDescription
ref={forwardedRef}
shouldShowRightIcon
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
title={value || ''}
descriptionTextStyle={descStyle}
title={value ? `${value} - ${getCurrencySymbol(value)}` : undefined}
description={label}
onPress={showPickerModal}
furtherDetails={furtherDetails}
onPress={() => setIsPickerVisible(true)}
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={errorText}
interactive={interactive}
/>

<Modal
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
isVisible={isPickerVisible}
onClose={hidePickerModal}
onModalHide={hidePickerModal}
hideModalContentWhileAnimating
useNativeDriver
onBackdropPress={hidePickerModal}
onBackdropPress={Navigation.dismissModal}
>
<ScreenWrapper
style={styles.pb0}
style={[styles.pb0]}
includePaddingTop={false}
includeSafeAreaPaddingBottom={false}
testID={label}
includeSafeAreaPaddingBottom
testID={CurrencyPicker.displayName}
>
<HeaderWithBackButton
title={label}
shouldShowBackButton
onBackButtonPress={hidePickerModal}
/>
<CurrencySelectionListWithOnyx
onSelect={(item) => updateInput({value: item.currencyCode})}
searchInputLabel={translate('common.currency')}
initiallySelectedCurrencyCode={selectedCurrency}
/>
<BlockingComponent>
{!!headerContent && headerContent}
<CurrencySelectionList
initiallySelectedCurrencyCode={value}
onSelect={updateInput}
searchInputLabel={translate('common.search')}
excludedCurrencies={excludeCurrencies}
/>
</BlockingComponent>
</ScreenWrapper>
</Modal>
</View>
</>
);
}

CurrencyPicker.displayName = 'CurrencyPicker';

export default forwardRef(CurrencyPicker);
export default CurrencyPicker;
11 changes: 6 additions & 5 deletions src/components/CurrencySelectionList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import SelectableListItem from '@components/SelectionList/SelectableListItem';
import useLocalize from '@hooks/useLocalize';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import {getCurrencySymbol} from '@libs/CurrencyUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {CurrencyListItem, CurrencySelectionListProps} from './types';
Expand All @@ -17,6 +17,7 @@ function CurrencySelectionList({
selectedCurrencies = [],
canSelectMultiple = false,
recentlyUsedCurrencies,
excludedCurrencies = [],
}: CurrencySelectionListProps) {
const [currencyList] = useOnyx(ONYXKEYS.CURRENCY_LIST);
const [searchValue, setSearchValue] = useState('');
Expand All @@ -25,10 +26,10 @@ function CurrencySelectionList({
const {sections, headerMessage} = useMemo(() => {
const currencyOptions: CurrencyListItem[] = Object.entries(currencyList ?? {}).reduce((acc, [currencyCode, currencyInfo]) => {
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode || selectedCurrencies.includes(currencyCode);
if (isSelectedCurrency || !currencyInfo?.retired) {
if (!excludedCurrencies.includes(currencyCode) && (isSelectedCurrency || !currencyInfo?.retired)) {
acc.push({
currencyName: currencyInfo?.name ?? '',
text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`,
text: `${currencyCode} - ${getCurrencySymbol(currencyCode)}`,
currencyCode,
keyForList: currencyCode,
isSelected: isSelectedCurrency,
Expand All @@ -43,7 +44,7 @@ function CurrencySelectionList({
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode;
return {
currencyName: currencyInfo?.name ?? '',
text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`,
text: `${currencyCode} - ${getCurrencySymbol(currencyCode)}`,
currencyCode,
keyForList: currencyCode,
isSelected: isSelectedCurrency,
Expand Down Expand Up @@ -86,7 +87,7 @@ function CurrencySelectionList({
}

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

return (
<SelectionList
Expand Down
3 changes: 3 additions & 0 deletions src/components/CurrencySelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type CurrencySelectionListProps = {

/** Whether this is a multi-select list */
canSelectMultiple?: boolean;

/** List of excluded currency codes */
excludedCurrencies?: string[];
};

export type {CurrencyListItem, CurrencySelectionListProps};
9 changes: 7 additions & 2 deletions src/components/Form/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type FormProviderProps<TFormID extends OnyxFormKey = OnyxFormKey> = FormProps<TF

/** Whether HTML is allowed in form inputs */
allowHTML?: boolean;

/** Whether the form is loading */
isLoading?: boolean;
};

function FormProvider(
Expand All @@ -85,6 +88,7 @@ function FormProvider(
onSubmit,
shouldTrimValues = true,
allowHTML = false,
isLoading = false,
...rest
}: FormProviderProps,
forwardedRef: ForwardedRef<FormRef>,
Expand Down Expand Up @@ -193,7 +197,7 @@ function FormProvider(
const submit = useDebounceNonReactive(
useCallback(() => {
// Return early if the form is already submitting to avoid duplicate submission
if (formState?.isLoading) {
if (!!formState?.isLoading || isLoading) {
return;
}

Expand All @@ -214,7 +218,7 @@ function FormProvider(
}

KeyboardUtils.dismiss().then(() => onSubmit(trimmedStringValues));
}, [enabledWhenOffline, formState?.isLoading, inputValues, network?.isOffline, onSubmit, onValidate, shouldTrimValues]),
}, [enabledWhenOffline, formState?.isLoading, inputValues, isLoading, network?.isOffline, onSubmit, onValidate, shouldTrimValues]),
1000,
{leading: true, trailing: false},
);
Expand Down Expand Up @@ -415,6 +419,7 @@ function FormProvider(
onSubmit={submit}
inputRefs={inputRefs}
errors={errors}
isLoading={isLoading}
enabledWhenOffline={enabledWhenOffline}
>
{typeof children === 'function' ? children({inputValues}) : children}
Expand Down
Loading
Loading