diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 46b60effdd9c..8fc7c1970c2d 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -1519,6 +1519,10 @@ const ROUTES = {
return `workspaces/${policyID}/workflows` as const;
},
},
+ WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: {
+ route: 'workspaces/:policyID/workflows/connect-account',
+ getRoute: (policyID: string) => `workspaces/${policyID}/workflows/connect-account` as const,
+ },
WORKSPACE_WORKFLOWS_APPROVALS_NEW: {
route: 'workspaces/:policyID/workflows/approvals/new',
getRoute: (policyID: string) => `workspaces/${policyID}/workflows/approvals/new` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 60c331502b72..df1c06c86a67 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -616,6 +616,7 @@ const SCREENS = {
WORKFLOWS_APPROVALS_APPROVER: 'Workspace_Workflows_Approvals_Approver',
WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency',
WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset',
+ WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: 'Workspace_Workflows_Connect_Existing_Bank_Account',
DESCRIPTION: 'Workspace_Overview_Description',
SHARE: 'Workspace_Overview_Share',
NAME: 'Workspace_Overview_Name',
diff --git a/src/components/AddToWalletButton/index.native.tsx b/src/components/AddToWalletButton/index.native.tsx
index 6e18ec8d579a..a6f3b51735d2 100644
--- a/src/components/AddToWalletButton/index.native.tsx
+++ b/src/components/AddToWalletButton/index.native.tsx
@@ -6,7 +6,7 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import {openWalletPage} from '@libs/actions/PaymentMethods';
+import {getPaymentMethods} from '@libs/actions/PaymentMethods';
import getPlatform from '@libs/getPlatform';
import Log from '@libs/Log';
import {checkIfWalletIsAvailable, handleAddCardToWallet, isCardInWallet} from '@libs/Wallet/index';
@@ -42,7 +42,7 @@ function AddToWalletButton({card, cardHolderName, cardDescription, buttonStyle}:
.then((status: TokenizationStatus) => {
if (status === 'success') {
Log.info('Card added to wallet');
- openWalletPage();
+ getPaymentMethods();
} else {
setIsLoading(false);
}
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 961a86e6bab2..1da1c7acc828 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -5437,6 +5437,7 @@ const translations = {
updateWorkspaceCurrency: 'Arbeitsbereichswährung aktualisieren',
workspaceCurrencyNotSupported: 'Arbeitsbereichswährung wird nicht unterstützt',
yourWorkspace: `Ihr Arbeitsbereich ist auf eine nicht unterstützte Währung eingestellt. Sehen Sie sich die Liste der unterstützten Währungen an.`,
+ chooseAnExisting: 'Wählen Sie ein vorhandenes Bankkonto zur Zahlung von Ausgaben oder fügen Sie ein neues hinzu.',
},
changeOwner: {
changeOwnerPageTitle: 'Besitzer übertragen',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 70e16c7f0607..2924e6b4ac06 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -5411,6 +5411,7 @@ const translations = {
updateWorkspaceCurrency: 'Update workspace currency',
workspaceCurrencyNotSupported: 'Workspace currency not supported',
yourWorkspace: `Your workspace is set to an unsupported currency. View the list of supported currencies.`,
+ chooseAnExisting: 'Choose an existing bank account to pay expenses or add a new one.',
},
changeOwner: {
changeOwnerPageTitle: 'Transfer owner',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index dad2ab01237d..ab9fedb35321 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -5425,6 +5425,7 @@ const translations = {
updateWorkspaceCurrency: 'Actualizar la moneda del espacio de trabajo',
workspaceCurrencyNotSupported: 'Moneda del espacio de trabajo no soportada',
yourWorkspace: `Tu espacio de trabajo está configurado en una moneda no soportada. Consulta la lista de monedas soportadas.`,
+ chooseAnExisting: 'Elige una cuenta bancaria existente para pagar gastos o añade una nueva.',
},
changeOwner: {
changeOwnerPageTitle: 'Transferir la propiedad',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 914eba731fe9..e0056e0f5ab7 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -5449,6 +5449,7 @@ const translations = {
updateWorkspaceCurrency: "Mettre à jour la devise de l'espace de travail",
workspaceCurrencyNotSupported: "Devise de l'espace de travail non prise en charge",
yourWorkspace: `Votre espace de travail est configuré avec une devise non prise en charge. Consultez la liste des devises prises en charge.`,
+ chooseAnExisting: 'Choisissez un compte bancaire existant pour payer les dépenses ou ajoutez-en un nouveau.',
},
changeOwner: {
changeOwnerPageTitle: 'Transférer le propriétaire',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 07b3cf5eeedc..149d891137ab 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -5446,6 +5446,7 @@ const translations = {
updateWorkspaceCurrency: 'Aggiorna la valuta dello spazio di lavoro',
workspaceCurrencyNotSupported: "Valuta dell'area di lavoro non supportata",
yourWorkspace: `Il tuo spazio di lavoro è impostato su una valuta non supportata. Visualizza l'elenco delle valute supportate.`,
+ chooseAnExisting: 'Scegli un conto bancario esistente per pagare le spese o aggiungine uno nuovo.',
},
changeOwner: {
changeOwnerPageTitle: 'Trasferisci proprietario',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 6971de44e693..9577f35c4e90 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -5416,6 +5416,7 @@ const translations = {
updateWorkspaceCurrency: 'ワークスペースの通貨を更新する',
workspaceCurrencyNotSupported: 'ワークスペース通貨はサポートされていません',
yourWorkspace: `ご使用のワークスペースは、サポートされていない通貨に設定されています。サポートされている通貨の一覧をご確認ください。`,
+ chooseAnExisting: '既存の銀行口座を選択して経費を支払うか、新しい口座を追加してください。',
},
changeOwner: {
changeOwnerPageTitle: 'オーナーを移行',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 336672ecaa13..5cb9033e872e 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -5444,6 +5444,7 @@ const translations = {
updateWorkspaceCurrency: 'Werkruimte valuta bijwerken',
workspaceCurrencyNotSupported: 'Werkruimtevaluta niet ondersteund',
yourWorkspace: `Uw werkruimte is ingesteld op een niet-ondersteunde valuta. Bekijk de lijst met ondersteunde valuta's.`,
+ chooseAnExisting: 'Kies een bestaande bankrekening om uitgaven te betalen of voeg een nieuwe toe.',
},
changeOwner: {
changeOwnerPageTitle: 'Eigenaar overdragen',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 0e3e537c3ab7..25eb810b6ef5 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -5432,6 +5432,7 @@ const translations = {
updateWorkspaceCurrency: 'Zaktualizuj walutę przestrzeni roboczej',
workspaceCurrencyNotSupported: 'Waluta przestrzeni roboczej nie jest obsługiwana',
yourWorkspace: `Twoje miejsce pracy jest ustawione na nieobsługiwaną walutę. Zobacz listę obsługiwanych walut.`,
+ chooseAnExisting: 'Wybierz istniejące konto bankowe do płacenia wydatków lub dodaj nowe.',
},
changeOwner: {
changeOwnerPageTitle: 'Przenieś właściciela',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index f2e632160d5c..389fd1b480ca 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -5443,6 +5443,7 @@ const translations = {
updateWorkspaceCurrency: 'Atualizar moeda do espaço de trabalho',
workspaceCurrencyNotSupported: 'Moeda do espaço de trabalho não suportada',
yourWorkspace: `Seu espaço de trabalho está configurado para uma moeda não suportada. Veja a lista de moedas suportadas.`,
+ chooseAnExisting: 'Escolha uma conta bancária existente para pagar despesas ou adicione uma nova.',
},
changeOwner: {
changeOwnerPageTitle: 'Transferir proprietário',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 7206c8021907..440f95fe135a 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -5347,6 +5347,7 @@ const translations = {
updateWorkspaceCurrency: '更新工作区货币',
workspaceCurrencyNotSupported: '工作区货币不支持',
yourWorkspace: `您的工作区设置为不支持的货币。查看支持货币列表。`,
+ chooseAnExisting: '选择现有银行账户支付费用或添加新账户。',
},
changeOwner: {
changeOwnerPageTitle: '转移所有者',
diff --git a/src/libs/API/parameters/SetWorkspaceReimbursementParams.ts b/src/libs/API/parameters/SetWorkspaceReimbursementParams.ts
index f96f6385f541..1d9178388aab 100644
--- a/src/libs/API/parameters/SetWorkspaceReimbursementParams.ts
+++ b/src/libs/API/parameters/SetWorkspaceReimbursementParams.ts
@@ -4,6 +4,7 @@ import type CONST from '@src/CONST';
type SetWorkspaceReimbursementParams = {
policyID: string;
reimbursementChoice: ValueOf;
+ bankAccountID?: number;
};
export default SetWorkspaceReimbursementParams;
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index ac29d78489b1..d9b74656e0a7 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -499,6 +499,8 @@ const SettingsModalStackNavigator = createModalStackNavigator
require('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopCustomersDisplayedAsPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_ITEMS]: () => require('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage').default,
+ [SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: () =>
+ require('../../../../pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage').default,
[SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../../pages/ReimbursementAccount/ReimbursementAccountPage').default,
[SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO]: () => require('../../../../pages/ReimbursementAccount/EnterSignerInfo').default,
[SCREENS.SETTINGS.REPORT_CARD_LOST_OR_DAMAGED]: () => require('../../../../pages/settings/Wallet/ReportCardLostPage').default,
diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts
index 32e2b8da7bdb..fb664d38905a 100755
--- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts
+++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts
@@ -33,6 +33,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = {
[SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE]: {
path: ROUTES.WORKSPACE_EDIT_REPORT_FIELDS_INITIAL_VALUE.route,
},
+ [SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: {
+ path: ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.route,
+ exact: true,
+ },
[SCREENS.REIMBURSEMENT_ACCOUNT]: {
path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route,
exact: true,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index faf002f227ee..4cc33ba9ce94 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -1969,6 +1969,9 @@ type WorkspaceSplitNavigatorParamList = {
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.INVOICES]: {
policyID: string;
};
diff --git a/src/libs/ReimbursementAccountUtils.ts b/src/libs/ReimbursementAccountUtils.ts
index 9bd7c049e6f7..b388c3389a04 100644
--- a/src/libs/ReimbursementAccountUtils.ts
+++ b/src/libs/ReimbursementAccountUtils.ts
@@ -1,6 +1,6 @@
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
-import type {ReimbursementAccountStep} from '@src/types/onyx/ReimbursementAccount';
+import type {ACHDataReimbursementAccount, ReimbursementAccountStep} from '@src/types/onyx/ReimbursementAccount';
type ReimbursementAccountStepToOpen = ValueOf | '';
@@ -35,5 +35,26 @@ function getRouteForCurrentStep(currentStep: ReimbursementAccountStep): Reimburs
}
}
-export {getRouteForCurrentStep, REIMBURSEMENT_ACCOUNT_ROUTE_NAMES};
+/**
+ * Returns true if a VBBA exists in any state other than OPEN or LOCKED
+ */
+const hasInProgressUSDVBBA = (achData?: ACHDataReimbursementAccount): boolean => {
+ return !!achData?.bankAccountID && !!achData?.state && achData?.state !== CONST.BANK_ACCOUNT.STATE.OPEN && achData?.state !== CONST.BANK_ACCOUNT.STATE.LOCKED;
+};
+
+/** Returns true if user passed first step of flow for non USD VBBA */
+const hasInProgressNonUSDVBBA = (achData?: ACHDataReimbursementAccount, nonUSDCountryDraftValue?: string): boolean => {
+ return (!!achData?.bankAccountID && !!achData?.created) || nonUSDCountryDraftValue !== '';
+};
+
+/** Returns true if VBBA flow is in progress */
+const hasInProgressVBBA = (achData?: ACHDataReimbursementAccount, isNonUSDWorkspace?: boolean, nonUSDCountryDraftValue?: string) => {
+ if (isNonUSDWorkspace) {
+ return hasInProgressNonUSDVBBA(achData, nonUSDCountryDraftValue);
+ }
+
+ return hasInProgressUSDVBBA(achData);
+};
+
+export {getRouteForCurrentStep, REIMBURSEMENT_ACCOUNT_ROUTE_NAMES, hasInProgressUSDVBBA, hasInProgressNonUSDVBBA, hasInProgressVBBA};
export type {ReimbursementAccountStepToOpen};
diff --git a/src/libs/WorkflowUtils.ts b/src/libs/WorkflowUtils.ts
index 743e87e87d81..a22d87eb717f 100644
--- a/src/libs/WorkflowUtils.ts
+++ b/src/libs/WorkflowUtils.ts
@@ -3,6 +3,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import CONST from '@src/CONST';
+import type {BankAccountList} from '@src/types/onyx';
import type {ApprovalWorkflowOnyx, Approver, Member} from '@src/types/onyx/ApprovalWorkflow';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import type {PersonalDetailsList} from '@src/types/onyx/PersonalDetails';
@@ -304,6 +305,7 @@ function convertApprovalWorkflowToPolicyEmployees({
return updatedEmployeeList;
}
+
function updateWorkflowDataOnApproverRemoval({approvalWorkflows, removedApprover, ownerDetails}: UpdateWorkflowDataOnApproverRemovalParams): UpdateWorkflowDataOnApproverRemovalResult {
const defaultWorkflow = approvalWorkflows.find((workflow) => workflow.isDefault);
const removedApproverEmail = removedApprover.login;
@@ -414,4 +416,24 @@ function updateWorkflowDataOnApproverRemoval({approvalWorkflows, removedApprover
});
}
-export {calculateApprovers, convertPolicyEmployeesToApprovalWorkflows, convertApprovalWorkflowToPolicyEmployees, INITIAL_APPROVAL_WORKFLOW, updateWorkflowDataOnApproverRemoval};
+/**
+ * Get eligible business bank accounts for the workspace reimbursement workflow
+ */
+function getEligibleExistingBusinessBankAccounts(bankAccountList: BankAccountList | undefined, policyCurrency: string | undefined) {
+ if (!bankAccountList || policyCurrency === undefined) {
+ return [];
+ }
+
+ return Object.values(bankAccountList).filter((account) => {
+ return account.bankCurrency === policyCurrency && account.accountData?.state === CONST.BANK_ACCOUNT.STATE.OPEN && account.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS;
+ });
+}
+
+export {
+ calculateApprovers,
+ convertPolicyEmployeesToApprovalWorkflows,
+ convertApprovalWorkflowToPolicyEmployees,
+ getEligibleExistingBusinessBankAccounts,
+ INITIAL_APPROVAL_WORKFLOW,
+ updateWorkflowDataOnApproverRemoval,
+};
diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts
index f3214aa90ea7..f1e817d0035c 100644
--- a/src/libs/actions/PaymentMethods.ts
+++ b/src/libs/actions/PaymentMethods.ts
@@ -1,5 +1,5 @@
import {createRef} from 'react';
-import type {MutableRefObject} from 'react';
+import type {RefObject} from 'react';
import type {GestureResponderEvent} from 'react-native';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
@@ -37,7 +37,7 @@ type KYCWallRef = {
/**
* Sets up a ref to an instance of the KYC Wall component.
*/
-const kycWallRef: MutableRefObject = createRef();
+const kycWallRef: RefObject = createRef();
/**
* When we successfully add a payment method or pass the KYC checks we will continue with our setup action if we have one set.
@@ -53,7 +53,7 @@ function continueSetup(fallbackRoute?: Route) {
kycWallRef.current.continueAction();
}
-function openWalletPage() {
+function getPaymentMethods() {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
@@ -582,7 +582,7 @@ function setInvoicingTransferBankAccount(bankAccountID: number, policyID: string
export {
deletePaymentCard,
addPaymentCard,
- openWalletPage,
+ getPaymentMethods,
makeDefaultPaymentMethod,
kycWallRef,
continueSetup,
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 205d64bf5ad8..4788c8c777be 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -182,6 +182,13 @@ type DuplicatePolicyDataOptions = {
policyCategories?: PolicyCategories;
};
+type SetWorkspaceReimbursementActionParams = {
+ policyID: string;
+ reimbursementChoice: ValueOf;
+ bankAccountID?: number;
+ reimburserEmail: string;
+};
+
const allPolicies: OnyxCollection = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY,
@@ -866,7 +873,7 @@ function clearQuickbooksOnlineAutoSyncErrorField(policyID: string | undefined) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {quickbooksOnline: {config: {errorFields: {autoSync: null}}}}});
}
-function setWorkspaceReimbursement(policyID: string, reimbursementChoice: ValueOf, reimburserEmail: string) {
+function setWorkspaceReimbursement({policyID, reimbursementChoice, bankAccountID, reimburserEmail}: SetWorkspaceReimbursementActionParams) {
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line deprecation/deprecation
const policy = getPolicy(policyID);
@@ -879,7 +886,7 @@ function setWorkspaceReimbursement(policyID: string, reimbursementChoice: ValueO
reimbursementChoice,
isLoadingWorkspaceReimbursement: true,
reimburser: reimburserEmail,
- achAccount: {reimburser: reimburserEmail},
+ achAccount: {reimburser: reimburserEmail, bankAccountID},
errorFields: {reimbursementChoice: null},
pendingFields: {reimbursementChoice: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
},
@@ -905,14 +912,14 @@ function setWorkspaceReimbursement(policyID: string, reimbursementChoice: ValueO
value: {
isLoadingWorkspaceReimbursement: false,
reimbursementChoice: policy?.reimbursementChoice ?? null,
- achAccount: {reimburser: policy?.achAccount?.reimburser ?? null},
+ achAccount: {reimburser: policy?.achAccount?.reimburser ?? null, bankAccountID: null},
errorFields: {reimbursementChoice: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')},
pendingFields: {reimbursementChoice: null},
},
},
];
- const params: SetWorkspaceReimbursementParams = {policyID, reimbursementChoice};
+ const params: SetWorkspaceReimbursementParams = {policyID, reimbursementChoice, bankAccountID};
API.write(WRITE_COMMANDS.SET_WORKSPACE_REIMBURSEMENT, params, {optimisticData, failureData, successData});
}
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx
index a03cba87f9b3..8d3f2c503787 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx
@@ -25,7 +25,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {ReimbursementAccountNavigatorParamList} from '@libs/Navigation/types';
import {goBackFromInvalidPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils';
-import {getRouteForCurrentStep} from '@libs/ReimbursementAccountUtils';
+import {getRouteForCurrentStep, hasInProgressUSDVBBA, hasInProgressVBBA} from '@libs/ReimbursementAccountUtils';
import shouldReopenOnfido from '@libs/shouldReopenOnfido';
import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy';
import withPolicy from '@pages/workspace/withPolicy';
@@ -126,26 +126,9 @@ function ReimbursementAccountPage({route, policy, isLoadingPolicy, navigation}:
};
}
- /**
- * Returns true if a VBBA exists in any state other than OPEN or LOCKED
- */
- const hasInProgressVBBA = useCallback((): boolean => {
- return !!achData?.bankAccountID && !!achData?.state && achData?.state !== CONST.BANK_ACCOUNT.STATE.OPEN && achData?.state !== CONST.BANK_ACCOUNT.STATE.LOCKED;
- }, [achData?.bankAccountID, achData?.state]);
-
- /** Returns true if user passed first step of flow for non USD VBBA */
- const hasInProgressNonUSDVBBA = useCallback((): boolean => {
- return (!!achData?.bankAccountID && !!achData?.created) || nonUSDCountryDraftValue !== '';
- }, [achData?.bankAccountID, achData?.created, nonUSDCountryDraftValue]);
-
- /** Returns true if VBBA flow is in progress */
const shouldShowContinueSetupButtonValue = useMemo(() => {
- if (isNonUSDWorkspace) {
- return hasInProgressNonUSDVBBA();
- }
-
- return hasInProgressVBBA();
- }, [isNonUSDWorkspace, hasInProgressNonUSDVBBA, hasInProgressVBBA]);
+ return hasInProgressVBBA(achData, isNonUSDWorkspace, nonUSDCountryDraftValue);
+ }, [achData, isNonUSDWorkspace, nonUSDCountryDraftValue]);
/**
When this page is first opened, `reimbursementAccount` prop might not yet be fully loaded from Onyx.
@@ -229,7 +212,7 @@ function ReimbursementAccountPage({route, policy, isLoadingPolicy, navigation}:
prevReimbursementAccount.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE &&
reimbursementAccount?.pendingAction !== prevReimbursementAccount.pendingAction
) {
- setShouldShowContinueSetupButton(hasInProgressVBBA());
+ setShouldShowContinueSetupButton(hasInProgressUSDVBBA(achData));
}
if (shouldShowContinueSetupButton) {
@@ -330,7 +313,7 @@ function ReimbursementAccountPage({route, policy, isLoadingPolicy, navigation}:
switch (currentStep) {
case CONST.BANK_ACCOUNT.STEP.COUNTRY:
- if (hasInProgressVBBA()) {
+ if (hasInProgressUSDVBBA(achData)) {
setShouldShowContinueSetupButton(true);
}
setUSDBankAccountStep(null);
@@ -375,7 +358,7 @@ function ReimbursementAccountPage({route, policy, isLoadingPolicy, navigation}:
default:
Navigation.dismissModal();
}
- }, [achData?.isOnfidoSetupComplete, achData?.state, currentStep, hasInProgressVBBA, isOffline, onfidoToken]);
+ }, [achData, currentStep, isOffline, onfidoToken]);
const isLoading =
(isLoadingApp || (reimbursementAccount?.isLoading && !reimbursementAccount?.isCreateCorpayBankAccount)) &&
diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx
index b2c609f4a791..908bdaa1a644 100644
--- a/src/pages/settings/Wallet/PaymentMethodList.tsx
+++ b/src/pages/settings/Wallet/PaymentMethodList.tsx
@@ -1,14 +1,11 @@
import {FlashList} from '@shopify/flash-list';
import lodashSortBy from 'lodash/sortBy';
-import type {ReactElement, Ref} from 'react';
+import type {ReactElement} from 'react';
import React, {useCallback, useMemo} from 'react';
import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
-import type {SvgProps} from 'react-native-svg/lib/typescript/ReactNativeSVG';
import type {ValueOf} from 'type-fest';
import type {RenderSuggestionMenuItemProps} from '@components/AutoCompleteSuggestions/types';
-import Button from '@components/Button';
-import FormAlertWrapper from '@components/FormAlertWrapper';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
@@ -16,7 +13,6 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
-import type {FormattedSelectedPaymentMethodIcon} from '@hooks/usePaymentMethodState/types';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeIllustrations from '@hooks/useThemeIllustrations';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -30,25 +26,18 @@ import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {AccountData, BankAccount, BankAccountList, Card, CardList, CompanyCardFeed} from '@src/types/onyx';
+import type {BankAccount, BankAccountList, CardList, CompanyCardFeed} from '@src/types/onyx';
import type {BankIcon} from '@src/types/onyx/Bank';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type PaymentMethod from '@src/types/onyx/PaymentMethod';
-import type {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer';
import {getEmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject';
+import type IconAsset from '@src/types/utils/IconAsset';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
+import type {CardPressHandlerParams, PaymentMethodPressHandlerParams} from './WalletPage/types';
-type PaymentMethodPressHandler = (
- event?: GestureResponderEvent | KeyboardEvent,
- accountType?: string,
- accountData?: AccountData,
- icon?: FormattedSelectedPaymentMethodIcon,
- isDefault?: boolean,
- methodID?: number,
- description?: string,
-) => void;
+type PaymentMethodPressHandler = ({event, accountType, accountData, methodID, icon, description, isDefault}: PaymentMethodPressHandlerParams) => void;
-type CardPressHandler = (event?: GestureResponderEvent | KeyboardEvent, cardData?: Card, icon?: FormattedSelectedPaymentMethodIcon, cardID?: number) => void;
+type CardPressHandler = ({event, cardID, cardData, icon}: CardPressHandlerParams) => void;
type PaymentMethodListProps = {
/** Type of active/highlighted payment method */
@@ -69,33 +58,18 @@ type PaymentMethodListProps = {
/** Should menu items be selectable with a checkbox */
shouldShowSelectedState?: boolean;
- /** React ref being forwarded to the PaymentMethodList Button */
- buttonRef?: Ref;
-
/** List container style */
style?: StyleProp;
/** List item style */
listItemStyle?: StyleProp;
- /** Type to filter the payment Method list */
- filterType?: FilterMethodPaymentType;
-
/** Whether the add bank account button should be shown on the list */
shouldShowAddBankAccount?: boolean;
- /** Whether the add Payment button be shown on the list */
- shouldShowAddPaymentMethodButton?: boolean;
-
- /** Whether the add Bank account button be shown on the list */
- shouldShowAddBankAccountButton?: boolean;
-
/** Whether the assigned cards should be shown on the list */
shouldShowAssignedCards?: boolean;
- /** Whether the empty list message should be shown when the list is empty */
- shouldShowEmptyListMessage?: boolean;
-
/** Whether the right icon should be shown in PaymentMethodItem */
shouldShowRightIcon?: boolean;
@@ -107,6 +81,18 @@ type PaymentMethodListProps = {
/** Whether the bank accounts should be displayed in private and business sections */
shouldShowBankAccountSections?: boolean;
+
+ /** Function to be called when the user presses the add bank account button */
+ onAddBankAccountPress?: () => void;
+
+ /** The icon to be displayed in the right side of the payment method item */
+ itemIconRight?: IconAsset;
+
+ /** Type of payment method to filter by */
+ filterType?: ValueOf;
+
+ /** Whether to show the default badge for the payment method */
+ shouldHideDefaultBadge?: boolean;
};
type PaymentMethodItem = PaymentMethod & {
@@ -121,7 +107,7 @@ type PaymentMethodItem = PaymentMethod & {
interactive?: boolean;
brickRoadIndicator?: ValueOf;
errors?: Errors;
- iconRight?: React.FC;
+ iconRight?: IconAsset;
isMethodActive?: boolean;
cardID?: number;
plaidUrl?: string;
@@ -155,8 +141,8 @@ function dismissError(item: PaymentMethodItem) {
}
}
-function shouldShowDefaultBadge(filteredPaymentMethods: PaymentMethod[], isDefault = false): boolean {
- if (!isDefault) {
+function shouldShowDefaultBadge(filteredPaymentMethods: PaymentMethod[], isDefault = false, shouldHideDefaultBadge = false): boolean {
+ if (!isDefault || shouldHideDefaultBadge) {
return false;
}
const defaultPaymentMethodCount = filteredPaymentMethods.filter(
@@ -179,15 +165,10 @@ function keyExtractor(item: PaymentMethod | string) {
function PaymentMethodList({
actionPaymentMethodType = '',
activePaymentMethodID = '',
- buttonRef = () => {},
- filterType = '',
listHeaderComponent,
onPress,
shouldShowSelectedState = false,
- shouldShowAddPaymentMethodButton = true,
- shouldShowAddBankAccountButton = false,
shouldShowAddBankAccount = true,
- shouldShowEmptyListMessage = true,
shouldShowAssignedCards = false,
selectedMethodID = '',
onListContentSizeChange = () => {},
@@ -196,6 +177,10 @@ function PaymentMethodList({
shouldShowRightIcon = true,
invoiceTransferBankAccountID,
shouldShowBankAccountSections = false,
+ onAddBankAccountPress = () => {},
+ itemIconRight,
+ filterType,
+ shouldHideDefaultBadge = false,
}: PaymentMethodListProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
@@ -203,7 +188,10 @@ function PaymentMethodList({
const {isOffline} = useNetwork();
const illustrations = useThemeIllustrations();
- const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.validated, canBeMissing: true});
+ const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {
+ selector: (account) => account?.validated,
+ canBeMissing: true,
+ });
const [bankAccountList = getEmptyObject(), bankAccountListResult] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
const isLoadingBankAccountList = isLoadingOnyxValue(bankAccountListResult);
@@ -211,8 +199,6 @@ function PaymentMethodList({
const isLoadingCardList = isLoadingOnyxValue(cardListResult);
// Temporarily disabled because P2P debit cards are disabled.
// const [fundList = getEmptyObject()] = useOnyx(ONYXKEYS.FUND_LIST);
- const [isLoadingPaymentMethods = true, isLoadingPaymentMethodsResult] = useOnyx(ONYXKEYS.IS_LOADING_PAYMENT_METHODS, {canBeMissing: true});
- const isLoadingPaymentMethodsOnyx = isLoadingOnyxValue(isLoadingPaymentMethodsResult);
const filteredPaymentMethods = useMemo(() => {
if (shouldShowAssignedCards) {
@@ -252,20 +238,20 @@ function PaymentMethodList({
iconStyles: [styles.cardIcon],
iconWidth: variables.cardIconWidth,
iconHeight: variables.cardIconHeight,
- iconRight: Expensicons.ThreeDots,
+ iconRight: itemIconRight ?? Expensicons.ThreeDots,
isMethodActive: activePaymentMethodID === card.cardID,
onPress: (e: GestureResponderEvent | KeyboardEvent | undefined) =>
- pressHandler(
- e,
- card,
- {
+ pressHandler({
+ event: e,
+ cardData: card,
+ icon: {
icon,
iconStyles: [styles.cardIcon],
iconWidth: variables.cardIconWidth,
iconHeight: variables.cardIconHeight,
},
- card.cardID,
- ),
+ cardID: card.cardID,
+ }),
});
return;
}
@@ -325,97 +311,83 @@ function PaymentMethodList({
const filteredCardList = {};
let combinedPaymentMethods = formatPaymentMethods(isLoadingBankAccountList ? {} : (bankAccountList ?? {}), filteredCardList, styles);
- if (filterType !== '') {
- combinedPaymentMethods = combinedPaymentMethods.filter((paymentMethod) => paymentMethod.accountType === filterType);
- }
-
if (!isOffline) {
combinedPaymentMethods = combinedPaymentMethods.filter(
(paymentMethod) => paymentMethod.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !isEmptyObject(paymentMethod.errors),
);
}
+
+ if (filterType) {
+ combinedPaymentMethods = combinedPaymentMethods.filter((paymentMethod) => (paymentMethod as BankAccount).accountData?.type === filterType);
+ }
+
combinedPaymentMethods = combinedPaymentMethods.map((paymentMethod) => {
const pressHandler = onPress as PaymentMethodPressHandler;
const isMethodActive = isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod);
return {
...paymentMethod,
onPress: (e: GestureResponderEvent) =>
- pressHandler(
- e,
- paymentMethod.accountType,
- paymentMethod.accountData,
- {
+ pressHandler({
+ event: e,
+ accountType: paymentMethod.accountType,
+ accountData: paymentMethod.accountData,
+ icon: {
icon: paymentMethod.icon,
iconHeight: paymentMethod?.iconHeight,
iconWidth: paymentMethod?.iconWidth,
iconStyles: paymentMethod?.iconStyles,
iconSize: paymentMethod?.iconSize,
},
- paymentMethod.isDefault,
- paymentMethod.methodID,
- paymentMethod.description,
- ),
+ isDefault: paymentMethod.isDefault,
+ methodID: paymentMethod.methodID,
+ description: paymentMethod.description,
+ }),
wrapperStyle: isMethodActive ? [StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)] : null,
disabled: paymentMethod.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
isMethodActive,
- iconRight: Expensicons.ThreeDots,
+ iconRight: itemIconRight ?? Expensicons.ThreeDots,
shouldShowRightIcon,
};
});
return combinedPaymentMethods;
}, [
shouldShowAssignedCards,
+ isLoadingBankAccountList,
bankAccountList,
styles,
- filterType,
isOffline,
- cardList,
- actionPaymentMethodType,
- activePaymentMethodID,
- StyleUtils,
- shouldShowRightIcon,
- onPress,
- isLoadingBankAccountList,
+ filterType,
isLoadingCardList,
+ cardList,
illustrations,
translate,
+ onPress,
+ shouldShowRightIcon,
+ itemIconRight,
+ activePaymentMethodID,
+ actionPaymentMethodType,
+ StyleUtils,
]);
- /**
- * Render placeholder when there are no payments methods
- */
- const renderListEmptyComponent = () => {translate('paymentMethodList.addFirstPaymentMethod')};
-
const onPressItem = useCallback(() => {
if (!isUserValidated) {
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute(Navigation.getActiveRoute(), ROUTES.SETTINGS_ADD_BANK_ACCOUNT.route));
return;
}
- onPress();
- }, [isUserValidated, onPress]);
+ onAddBankAccountPress();
+ }, [isUserValidated, onAddBankAccountPress]);
const renderListFooterComponent = useCallback(
- () =>
- shouldShowAddBankAccountButton ? (
-
- ) : (
-
- ),
-
- [shouldShowAddBankAccountButton, onPressItem, translate, onPress, buttonRef, styles.paymentMethod, listItemStyle],
+ () => (
+
+ ),
+
+ [onPressItem, translate, styles.paymentMethod, listItemStyle],
);
const itemsToRender = useMemo(() => {
@@ -472,6 +444,7 @@ function PaymentMethodList({
shouldShowDefaultBadge(
filteredPaymentMethods,
invoiceTransferBankAccountID ? invoiceTransferBankAccountID === item.methodID : item.methodID === userWallet?.walletLinkedAccountID,
+ shouldHideDefaultBadge,
)
? translate('paymentMethodList.defaultPaymentMethod')
: undefined
@@ -505,42 +478,21 @@ function PaymentMethodList({
listItemStyle,
shouldShowSelectedState,
selectedMethodID,
+ shouldHideDefaultBadge,
],
);
return (
- <>
-
-
- data={itemsToRender}
- renderItem={renderItem}
- keyExtractor={keyExtractor}
- ListEmptyComponent={shouldShowEmptyListMessage ? renderListEmptyComponent : null}
- ListHeaderComponent={listHeaderComponent}
- onContentSizeChange={onListContentSizeChange}
- />
- {shouldShowAddBankAccount && renderListFooterComponent()}
-
- {shouldShowAddPaymentMethodButton && (
-
- {(isFormOffline) => (
-
- )}
-
- )}
- >
+
+
+ data={itemsToRender}
+ renderItem={renderItem}
+ keyExtractor={keyExtractor}
+ ListHeaderComponent={listHeaderComponent}
+ ListFooterComponent={shouldShowAddBankAccount ? renderListFooterComponent : null}
+ onContentSizeChange={onListContentSizeChange}
+ />
+
);
}
diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
index 562cebdab32f..68a21e14046c 100644
--- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
+++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
@@ -4,7 +4,6 @@ import type {ForwardedRef, RefObject} from 'react';
import React, {useCallback, useContext, useEffect, useLayoutEffect, useRef, useState} from 'react';
import type {GestureResponderEvent} from 'react-native';
import {ActivityIndicator, View} from 'react-native';
-import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu';
import ConfirmModal from '@components/ConfirmModal';
import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
@@ -28,7 +27,7 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePaymentMethodState from '@hooks/usePaymentMethodState';
-import type {FormattedSelectedPaymentMethod, FormattedSelectedPaymentMethodIcon} from '@hooks/usePaymentMethodState/types';
+import type {FormattedSelectedPaymentMethod} from '@hooks/usePaymentMethodState/types';
import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
@@ -45,12 +44,13 @@ import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList';
import variables from '@styles/variables';
import {deletePaymentBankAccount, openPersonalBankAccountSetupView, setPersonalBankAccountContinueKYCOnSuccess} from '@userActions/BankAccounts';
import {close as closeModal} from '@userActions/Modal';
-import {clearWalletError, clearWalletTermsError, deletePaymentCard, makeDefaultPaymentMethod as makeDefaultPaymentMethodPaymentMethods, openWalletPage} from '@userActions/PaymentMethods';
+import {clearWalletError, clearWalletTermsError, deletePaymentCard, getPaymentMethods, makeDefaultPaymentMethod as makeDefaultPaymentMethodPaymentMethods} from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import {getEmptyObject} from '@src/types/utils/EmptyObject';
+import type {CardPressHandlerParams, PaymentMethodPressHandlerParams} from './types';
type WalletPageProps = {
/** Listen for window resize event on web and desktop. */
@@ -82,11 +82,9 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
const {windowWidth, windowHeight} = useWindowDimensions();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {paymentMethod, setPaymentMethod, resetSelectedPaymentMethodData} = usePaymentMethodState();
- const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false);
const [shouldShowDefaultDeleteMenu, setShouldShowDefaultDeleteMenu] = useState(false);
const [shouldShowCardMenu, setShouldShowCardMenu] = useState(false);
const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(false);
- const addPaymentMethodAnchorRef = useRef(null);
const paymentMethodButtonRef = useRef(null);
const [anchorPosition, setAnchorPosition] = useState({
anchorPositionHorizontal: 0,
@@ -144,25 +142,12 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
/**
* Display the delete/default menu, or the add payment method menu
*/
- const paymentMethodPressed = (
- nativeEvent?: GestureResponderEvent | KeyboardEvent,
- accountType?: string,
- account?: OnyxTypes.AccountData,
- icon?: FormattedSelectedPaymentMethodIcon,
- isDefault?: boolean,
- methodID?: string | number,
- description?: string,
- ) => {
- if (shouldShowAddPaymentMenu) {
- setShouldShowAddPaymentMenu(false);
- return;
- }
-
+ const paymentMethodPressed = ({event, accountData, accountType, methodID, isDefault, icon, description}: PaymentMethodPressHandlerParams) => {
if (shouldShowDefaultDeleteMenu) {
setShouldShowDefaultDeleteMenu(false);
return;
}
- paymentMethodButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement;
+ paymentMethodButtonRef.current = event?.currentTarget as HTMLDivElement;
// The delete/default menu
if (accountType) {
@@ -171,47 +156,32 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
};
if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
formattedSelectedPaymentMethod = {
- title: account?.addressName ?? '',
+ title: accountData?.addressName ?? '',
icon,
- description: description ?? getPaymentMethodDescription(accountType, account),
+ description: description ?? getPaymentMethodDescription(accountType, accountData),
type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT,
};
} else if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) {
formattedSelectedPaymentMethod = {
- title: account?.addressName ?? '',
+ title: accountData?.addressName ?? '',
icon,
- description: description ?? getPaymentMethodDescription(accountType, account),
+ description: description ?? getPaymentMethodDescription(accountType, accountData),
type: CONST.PAYMENT_METHODS.DEBIT_CARD,
};
}
setPaymentMethod({
isSelectedPaymentMethodDefault: !!isDefault,
- selectedPaymentMethod: account ?? {},
+ selectedPaymentMethod: accountData ?? {},
selectedPaymentMethodType: accountType,
formattedSelectedPaymentMethod,
methodID: methodID ?? CONST.DEFAULT_NUMBER_ID,
});
setShouldShowDefaultDeleteMenu(true);
setMenuPosition();
- return;
}
- if (isActingAsDelegate) {
- showDelegateNoAccessModal();
- return;
- }
- if (isAccountLocked) {
- showLockedAccountModal();
- return;
- }
- setShouldShowAddPaymentMenu(true);
- setMenuPosition();
};
- const assignedCardPressed = (nativeEvent?: GestureResponderEvent | KeyboardEvent, cardData?: OnyxTypes.Card, icon?: FormattedSelectedPaymentMethodIcon, cardID?: number) => {
- if (shouldShowAddPaymentMenu) {
- setShouldShowAddPaymentMenu(false);
- return;
- }
+ const assignedCardPressed = ({event, cardData, icon, cardID}: CardPressHandlerParams) => {
if (shouldShowDefaultDeleteMenu) {
setShouldShowDefaultDeleteMenu(false);
return;
@@ -221,7 +191,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
setShouldShowCardMenu(false);
return;
}
- paymentMethodButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement;
+ paymentMethodButtonRef.current = event?.currentTarget as HTMLDivElement;
setPaymentMethod({
isSelectedPaymentMethodDefault: false,
selectedPaymentMethod: {},
@@ -237,29 +207,20 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
setMenuPosition();
};
- /**
- * Hide the add payment modal
- */
- const hideAddPaymentMenu = () => {
- setShouldShowAddPaymentMenu(false);
- };
-
- /**
- * Navigate to the appropriate payment type addition screen
- */
- const addPaymentMethodTypePressed = (paymentType: string) => {
- hideAddPaymentMenu();
-
- if (paymentType === CONST.PAYMENT_METHODS.DEBIT_CARD) {
- Navigation.navigate(ROUTES.SETTINGS_ADD_DEBIT_CARD);
+ const addBankAccountPressed = () => {
+ if (shouldShowDefaultDeleteMenu) {
+ setShouldShowDefaultDeleteMenu(false);
return;
}
- if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) {
- openPersonalBankAccountSetupView({});
+ if (isActingAsDelegate) {
+ showDelegateNoAccessModal();
return;
}
-
- throw new Error('Invalid payment method type selected');
+ if (isAccountLocked) {
+ showLockedAccountModal();
+ return;
+ }
+ openPersonalBankAccountSetupView({});
};
/**
@@ -334,18 +295,14 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
if (network.isOffline) {
return;
}
- openWalletPage();
+ getPaymentMethods();
}, [network.isOffline]);
useLayoutEffect(() => {
- if (!shouldListenForResize || (!shouldShowAddPaymentMenu && !shouldShowDefaultDeleteMenu && !shouldShowCardMenu)) {
+ if (!shouldListenForResize || (!shouldShowDefaultDeleteMenu && !shouldShowCardMenu)) {
return;
}
- if (shouldShowAddPaymentMenu) {
- debounce(setMenuPosition, CONST.TIMING.RESIZE_DEBOUNCE_TIME)();
- return;
- }
setMenuPosition();
// This effect is intended to update menu position only on window dimension change.
@@ -425,195 +382,127 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
}
return (
- <>
-
- {headerWithBackButton}
-
-
-
+ {headerWithBackButton}
+
+
+
+
+ {}}
+ style={[styles.mt5, [shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8]]}
+ listItemStyle={shouldUseNarrowLayout ? styles.ph5 : styles.ph8}
+ shouldShowBankAccountSections
+ />
+
+
+ {hasAssignedCard ? (
{}}
+ shouldShowAddBankAccount={false}
+ shouldShowAssignedCards
+ onPress={assignedCardPressed}
style={[styles.mt5, [shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8]]}
listItemStyle={shouldUseNarrowLayout ? styles.ph5 : styles.ph8}
- shouldShowBankAccountSections
+ actionPaymentMethodType={shouldShowCardMenu ? paymentMethod.selectedPaymentMethodType : ''}
+ activePaymentMethodID={shouldShowCardMenu ? paymentMethod.methodID : ''}
+ onListContentSizeChange={shouldShowCardMenu ? setMenuPosition : () => {}}
/>
+ ) : null}
- {hasAssignedCard ? (
-
- ) : null}
-
- {hasWallet && (
-
- <>
- {shouldShowLoadingSpinner && (
-
- )}
- {!shouldShowLoadingSpinner && hasActivatedWallet && (
-
-
-
- )}
-
- navigateToWalletOrTransferBalancePage(source)}
- onSelectPaymentMethod={(selectedPaymentMethod: string) => {
- if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
- return;
- }
- // To allow upgrading to a gold wallet, continue with the KYC flow after adding a bank account
- setPersonalBankAccountContinueKYCOnSuccess(ROUTES.SETTINGS_WALLET);
- }}
- enablePaymentsRoute={ROUTES.SETTINGS_ENABLE_PAYMENTS}
- addDebitCardRoute={ROUTES.SETTINGS_ADD_DEBIT_CARD}
- source={hasActivatedWallet ? CONST.KYC_WALL_SOURCE.TRANSFER_BALANCE : CONST.KYC_WALL_SOURCE.ENABLE_WALLET}
- shouldIncludeDebitCard={hasActivatedWallet}
+ {hasWallet && (
+
+ <>
+ {shouldShowLoadingSpinner && (
+
+ )}
+ {!shouldShowLoadingSpinner && hasActivatedWallet && (
+
- {(
- triggerKYCFlow: (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType) => void,
- buttonRef: RefObject,
- ) => {
- if (shouldShowLoadingSpinner) {
- return null;
- }
-
- if (hasActivatedWallet) {
- return (
-
+ )}
+
+ navigateToWalletOrTransferBalancePage(source)}
+ onSelectPaymentMethod={(selectedPaymentMethod: string) => {
+ if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
+ return;
+ }
+ // To allow upgrading to a gold wallet, continue with the KYC flow after adding a bank account
+ setPersonalBankAccountContinueKYCOnSuccess(ROUTES.SETTINGS_WALLET);
+ }}
+ enablePaymentsRoute={ROUTES.SETTINGS_ENABLE_PAYMENTS}
+ addDebitCardRoute={ROUTES.SETTINGS_ADD_DEBIT_CARD}
+ source={hasActivatedWallet ? CONST.KYC_WALL_SOURCE.TRANSFER_BALANCE : CONST.KYC_WALL_SOURCE.ENABLE_WALLET}
+ shouldIncludeDebitCard={hasActivatedWallet}
+ >
+ {(triggerKYCFlow: (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType) => void, buttonRef: RefObject) => {
+ if (shouldShowLoadingSpinner) {
+ return null;
+ }
+
+ if (hasActivatedWallet) {
return (
- >
-
- )}
-
-
-
- }
- >
- {!showConfirmDeleteModal && (
-
- {isPopoverBottomMount && (
-
- )}
- {shouldShowMakeDefaultButton && (
-
- )}
-
- }
- >
+ }
+
+ if (isPendingOnfidoResult) {
+ return (
+
+
+
+ {translate('walletPage.walletActivationPending')}
+
+ );
+ }
+
+ if (hasFailedOnfido) {
+ return (
+
+
+
+ {translate('walletPage.walletActivationFailed')}
+
+ );
+ }
+
+ return (
+ }
+ onPress={() => {
+ if (isActingAsDelegate) {
+ showDelegateNoAccessModal();
+ return;
+ }
+ if (isAccountLocked) {
+ showLockedAccountModal();
+ return;
+ }
+
+ if (!isUserValidated) {
+ Navigation.navigate(
+ ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_WALLET, ROUTES.SETTINGS_ENABLE_PAYMENTS),
+ );
+ return;
+ }
+ Navigation.navigate(ROUTES.SETTINGS_ENABLE_PAYMENTS);
+ }}
+ disabled={network.isOffline}
+ wrapperStyle={[
+ styles.transferBalance,
+ shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8,
+ shouldUseNarrowLayout ? styles.ph5 : styles.ph8,
+ ]}
+ />
+ );
+ }}
+
+ >
+
+ )}
+
+
+
+ }
+ >
+ {!showConfirmDeleteModal && (
)}
+ {shouldShowMakeDefaultButton && (
+ {
+ if (isActingAsDelegate) {
+ closeModal(() => {
+ showDelegateNoAccessModal();
+ });
+ return;
+ }
+ if (isAccountLocked) {
+ closeModal(() => showLockedAccountModal());
+ return;
+ }
+ makeDefaultPaymentMethod();
+ setShouldShowDefaultDeleteMenu(false);
+ }}
+ wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]}
+ numberOfLinesTitle={0}
+ />
+ )}
{
- hideCardMenu();
- Navigation.navigate(
- ROUTES.SEARCH_ROOT.getRoute({
- query: buildCannedSearchQuery({
- type: CONST.SEARCH.DATA_TYPES.EXPENSE,
- status: CONST.SEARCH.STATUS.EXPENSE.ALL,
- cardID: String(paymentMethod.methodID),
- }),
- }),
- );
+ if (isActingAsDelegate) {
+ closeModal(() => {
+ showDelegateNoAccessModal();
+ });
+ return;
+ }
+ if (isAccountLocked) {
+ closeModal(() => showLockedAccountModal());
+ return;
+ }
+ closeModal(() => setShowConfirmDeleteModal(true));
}}
+ wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]}
/>
+ {shouldShowEnableGlobalReimbursementsButton && (
+ {
+ if (isActingAsDelegate) {
+ closeModal(() => {
+ showDelegateNoAccessModal();
+ });
+ return;
+ }
+ if (isAccountLocked) {
+ closeModal(() => showLockedAccountModal());
+ return;
+ }
+ closeModal(() => Navigation.navigate(ROUTES.SETTINGS_WALLET_ENABLE_GLOBAL_REIMBURSEMENTS.getRoute(paymentMethod.selectedPaymentMethod.bankAccountID)));
+ }}
+ wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]}
+ />
+ )}
-
- {
- hideDefaultDeleteMenu();
- deletePaymentMethod();
- }}
- onCancel={hideDefaultDeleteMenu}
- title={translate('walletPage.deleteAccount')}
- prompt={translate('walletPage.deleteConfirmation')}
- confirmText={translate('common.delete')}
- cancelText={translate('common.cancel')}
- shouldShowCancelButton
- danger
- onModalHide={resetSelectedPaymentMethodData}
- />
-
-
+ }
+ >
+
+ {isPopoverBottomMount && (
+
+ )}
+ {
+ hideCardMenu();
+ Navigation.navigate(
+ ROUTES.SEARCH_ROOT.getRoute({
+ query: buildCannedSearchQuery({
+ type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+ status: CONST.SEARCH.STATUS.EXPENSE.ALL,
+ cardID: String(paymentMethod.methodID),
+ }),
+ }),
+ );
+ }}
+ />
+
+
+ {
+ hideDefaultDeleteMenu();
+ deletePaymentMethod();
}}
- onItemSelected={(method: string) => addPaymentMethodTypePressed(method)}
- anchorRef={addPaymentMethodAnchorRef}
- shouldShowPersonalBankAccountOption
+ onCancel={hideDefaultDeleteMenu}
+ title={translate('walletPage.deleteAccount')}
+ prompt={translate('walletPage.deleteConfirmation')}
+ confirmText={translate('common.delete')}
+ cancelText={translate('common.cancel')}
+ shouldShowCancelButton
+ danger
+ onModalHide={resetSelectedPaymentMethodData}
/>
- >
+
);
}
diff --git a/src/pages/settings/Wallet/WalletPage/types.ts b/src/pages/settings/Wallet/WalletPage/types.ts
new file mode 100644
index 000000000000..2bc2809ea2d9
--- /dev/null
+++ b/src/pages/settings/Wallet/WalletPage/types.ts
@@ -0,0 +1,22 @@
+import type {GestureResponderEvent} from 'react-native';
+import type {FormattedSelectedPaymentMethodIcon} from '@hooks/usePaymentMethodState/types';
+import type {AccountData, Card} from '@src/types/onyx';
+
+type PaymentMethodPressHandlerParams = {
+ event?: GestureResponderEvent | KeyboardEvent;
+ accountType?: string;
+ accountData?: AccountData;
+ icon?: FormattedSelectedPaymentMethodIcon;
+ isDefault?: boolean;
+ methodID?: number;
+ description?: string;
+};
+
+type CardPressHandlerParams = {
+ event?: GestureResponderEvent | KeyboardEvent;
+ cardData?: Card;
+ icon?: FormattedSelectedPaymentMethodIcon;
+ cardID?: number;
+};
+
+export type {PaymentMethodPressHandlerParams, CardPressHandlerParams};
diff --git a/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx b/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx
index 48c162a962b3..8c2e7acf2c72 100644
--- a/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx
+++ b/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx
@@ -10,6 +10,7 @@ import usePermissions from '@hooks/usePermissions';
import mapCurrencyToCountry from '@libs/mapCurrencyToCountry';
import Navigation from '@libs/Navigation/Navigation';
import {goBackFromInvalidPolicy} from '@libs/PolicyUtils';
+import {getEligibleExistingBusinessBankAccounts} from '@libs/WorkflowUtils';
import {clearCorpayBankAccountFields} from '@userActions/BankAccounts';
import {clearDraftValues, setDraftValues} from '@userActions/FormActions';
import {isCurrencySupportedForGlobalReimbursement, setIsForcedToChangeCurrency, updateGeneralSettings} from '@userActions/Policy/Policy';
@@ -33,6 +34,8 @@ function WorkspaceOverviewCurrencyPage({policy}: WorkspaceOverviewCurrencyPagePr
const {isBetaEnabled} = usePermissions();
const [isForcedToChangeCurrency] = useOnyx(ONYXKEYS.IS_FORCED_TO_CHANGE_CURRENCY, {canBeMissing: true});
const [hasVBA = false] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {selector: (value) => value?.achData?.state === CONST.BANK_ACCOUNT.STATE.OPEN, canBeMissing: true});
+ const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
+
const onSelectCurrency = (item: CurrencyListItem) => {
if (!policy) {
return;
@@ -46,6 +49,11 @@ function WorkspaceOverviewCurrencyPage({policy}: WorkspaceOverviewCurrencyPagePr
setIsForcedToChangeCurrency(false);
if (isCurrencySupportedForGlobalReimbursement(item.currencyCode as CurrencyType, isBetaEnabled(CONST.BETAS.GLOBAL_REIMBURSEMENTS_ON_ND))) {
+ const hasValidExistingAccounts = getEligibleExistingBusinessBankAccounts(bankAccountList, item.currencyCode).length > 0;
+ if (hasValidExistingAccounts) {
+ Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.getRoute(policy.id));
+ return;
+ }
navigateToBankAccountRoute(policy.id, ROUTES.WORKSPACE_WORKFLOWS.getRoute(policy.id), {forceReplace: true});
return;
}
diff --git a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx
index 27ec3d36b553..5a5dd6e2a787 100644
--- a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx
+++ b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx
@@ -1,8 +1,6 @@
import type {RefObject} from 'react';
import React, {useCallback, useRef, useState} from 'react';
import {View} from 'react-native';
-import type {GestureResponderEvent} from 'react-native';
-import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu';
import ConfirmModal from '@components/ConfirmModal';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
@@ -11,20 +9,20 @@ import Section from '@components/Section';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePaymentMethodState from '@hooks/usePaymentMethodState';
-import type {FormattedSelectedPaymentMethod, FormattedSelectedPaymentMethodIcon} from '@hooks/usePaymentMethodState/types';
+import type {FormattedSelectedPaymentMethod} from '@hooks/usePaymentMethodState/types';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import getClickedTargetLocation from '@libs/getClickedTargetLocation';
import {formatPaymentMethods, getPaymentMethodDescription} from '@libs/PaymentUtils';
import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList';
+import type {PaymentMethodPressHandlerParams} from '@pages/settings/Wallet/WalletPage/types';
import variables from '@styles/variables';
import {deletePaymentBankAccount, openPersonalBankAccountSetupView} from '@userActions/BankAccounts';
import {close as closeModal} from '@userActions/Modal';
import {setInvoicingTransferBankAccount} from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {AccountData} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
type WorkspaceInvoiceVBASectionProps = {
@@ -41,9 +39,7 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps)
const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.validated, canBeMissing: true});
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
const {paymentMethod, setPaymentMethod, resetSelectedPaymentMethodData} = usePaymentMethodState();
- const addPaymentMethodAnchorRef = useRef(null);
const paymentMethodButtonRef = useRef(null);
- const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false);
const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false);
const [shouldShowDefaultDeleteMenu, setShouldShowDefaultDeleteMenu] = useState(false);
const [anchorPosition, setAnchorPosition] = useState({
@@ -81,25 +77,12 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps)
/**
* Display the delete/default menu, or the add payment method menu
*/
- const paymentMethodPressed = (
- nativeEvent?: GestureResponderEvent | KeyboardEvent,
- accountType?: string,
- account?: AccountData,
- icon?: FormattedSelectedPaymentMethodIcon,
- isDefault?: boolean,
- methodID?: string | number,
- description?: string,
- ) => {
- if (shouldShowAddPaymentMenu) {
- setShouldShowAddPaymentMenu(false);
- return;
- }
-
+ const paymentMethodPressed = ({event, accountData, accountType, methodID, icon, description}: PaymentMethodPressHandlerParams) => {
if (shouldShowDefaultDeleteMenu) {
setShouldShowDefaultDeleteMenu(false);
return;
}
- paymentMethodButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement;
+ paymentMethodButtonRef.current = event?.currentTarget as HTMLDivElement;
// The delete/default menu
if (accountType) {
@@ -108,32 +91,22 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps)
};
if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
formattedSelectedPaymentMethod = {
- title: account?.addressName ?? '',
+ title: accountData?.addressName ?? '',
icon,
- description: description ?? getPaymentMethodDescription(accountType, account),
+ description: description ?? getPaymentMethodDescription(accountType, accountData),
type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT,
};
}
setPaymentMethod({
isSelectedPaymentMethodDefault: transferBankAccountID === methodID,
- selectedPaymentMethod: account ?? {},
+ selectedPaymentMethod: accountData ?? {},
selectedPaymentMethodType: accountType,
formattedSelectedPaymentMethod,
methodID: methodID ?? CONST.DEFAULT_NUMBER_ID,
});
setShouldShowDefaultDeleteMenu(true);
setMenuPosition();
- return;
}
- setShouldShowAddPaymentMenu(true);
- setMenuPosition();
- };
-
- /**
- * Hide the add payment modal
- */
- const hideAddPaymentMenu = () => {
- setShouldShowAddPaymentMenu(false);
};
/**
@@ -161,17 +134,12 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps)
}
}, [bankAccountList, styles, paymentMethod.selectedPaymentMethodType, paymentMethod.methodID, policyID]);
- /**
- * Navigate to the appropriate payment type addition screen
- */
- const addPaymentMethodTypePressed = (paymentType: string) => {
- hideAddPaymentMenu();
- if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) {
- openPersonalBankAccountSetupView({policyID, source: 'invoice', isUserValidated});
+ const onAddBankAccountPress = () => {
+ if (shouldShowDefaultDeleteMenu) {
+ setShouldShowDefaultDeleteMenu(false);
return;
}
-
- throw new Error('Invalid payment method type selected');
+ openPersonalBankAccountSetupView({policyID, source: 'invoice', isUserValidated});
};
return (
@@ -183,15 +151,12 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps)
subtitleMuted
>
- addPaymentMethodTypePressed(method)}
- anchorRef={addPaymentMethodAnchorRef}
- shouldShowPersonalBankAccountOption
- />
);
}
diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage.tsx
new file mode 100644
index 000000000000..c04453d2bdee
--- /dev/null
+++ b/src/pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Expensicons from '@components/Icon/Expensicons';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@navigation/Navigation';
+import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types';
+import type {WorkspaceSplitNavigatorParamList} from '@navigation/types';
+import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList';
+import type {PaymentMethodPressHandlerParams} from '@pages/settings/Wallet/WalletPage/types';
+import {setWorkspaceReimbursement} from '@userActions/Policy/Policy';
+import {navigateToBankAccountRoute} from '@userActions/ReimbursementAccount';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+
+type WorkspaceWorkflowsConnectExistingBankAccountPageProps = PlatformStackScreenProps;
+
+function WorkspaceWorkflowsConnectExistingBankAccountPage({route}: WorkspaceWorkflowsConnectExistingBankAccountPageProps) {
+ const policyID = route.params?.policyID;
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: false});
+ const policyName = policy?.name ?? '';
+ const {shouldUseNarrowLayout} = useResponsiveLayout();
+
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ const handleAddBankAccountPress = () => {
+ navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID));
+ };
+
+ const handleItemPress = ({methodID}: PaymentMethodPressHandlerParams) => {
+ const newReimburserEmail = policy?.achAccount?.reimburser ?? policy?.owner ?? '';
+ setWorkspaceReimbursement({
+ policyID: route.params.policyID,
+ reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
+ bankAccountID: methodID ?? CONST.DEFAULT_NUMBER_ID,
+ reimburserEmail: newReimburserEmail,
+ });
+ Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)));
+ };
+
+ return (
+
+
+
+ {translate('workspace.bankAccount.chooseAnExisting')}
+
+
+
+ );
+}
+
+WorkspaceWorkflowsConnectExistingBankAccountPage.displayName = 'WorkspaceWorkflowsConnectExistingBankAccountPage';
+
+export default WorkspaceWorkflowsConnectExistingBankAccountPage;
diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
index c1b0b21b999c..c379c6ec3055 100644
--- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
+++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
@@ -41,12 +41,14 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig
import {getPaymentMethodDescription} from '@libs/PaymentUtils';
import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import {getCorrectedAutoReportingFrequency, isControlPolicy, isPaidGroupPolicy as isPaidGroupPolicyUtil, isPolicyAdmin as isPolicyAdminUtil} from '@libs/PolicyUtils';
-import {convertPolicyEmployeesToApprovalWorkflows, INITIAL_APPROVAL_WORKFLOW} from '@libs/WorkflowUtils';
+import {hasInProgressVBBA} from '@libs/ReimbursementAccountUtils';
+import {convertPolicyEmployeesToApprovalWorkflows, getEligibleExistingBusinessBankAccounts, INITIAL_APPROVAL_WORKFLOW} from '@libs/WorkflowUtils';
import type {WorkspaceSplitNavigatorParamList} from '@navigation/types';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicy from '@pages/workspace/withPolicy';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
+import {getPaymentMethods} from '@userActions/PaymentMethods';
import {navigateToBankAccountRoute} from '@userActions/ReimbursementAccount';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -71,10 +73,13 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;
const [cardFeeds] = useCardFeeds(policy?.id);
const [cardList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`, {canBeMissing: false});
+ const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
const workspaceCards = getAllCardsForWorkspace(workspaceAccountID, cardList, cardFeeds);
const isSmartLimitEnabled = isSmartLimitEnabledUtil(workspaceCards);
const [isUpdateWorkspaceCurrencyModalOpen, setIsUpdateWorkspaceCurrencyModalOpen] = useState(false);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false});
+ const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true});
+ const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, {canBeMissing: true});
const {approvalWorkflows, availableMembers, usedApproverEmails} = useMemo(
() =>
convertPolicyEmployeesToApprovalWorkflows({
@@ -86,6 +91,8 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
);
const {isBetaEnabled} = usePermissions();
+ const hasValidExistingAccounts = getEligibleExistingBusinessBankAccounts(bankAccountList, policy?.outputCurrency).length > 0;
+
const isAdvanceApproval = approvalWorkflows.length > 1 || (approvalWorkflows?.at(0)?.approvers ?? []).length > 1;
const updateApprovalMode = isAdvanceApproval ? CONST.POLICY.APPROVAL_MODE.ADVANCED : CONST.POLICY.APPROVAL_MODE.BASIC;
const displayNameForAuthorizedPayer = useMemo(
@@ -93,10 +100,19 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
[policy?.achAccount?.reimburser],
);
+ const isNonUSDWorkspace = policy?.outputCurrency !== CONST.CURRENCY.USD;
+ const achData = reimbursementAccount?.achData;
+ const nonUSDCountryDraftValue = reimbursementAccountDraft?.country ?? '';
+
+ const shouldShowContinueModal = useMemo(() => {
+ return hasInProgressVBBA(achData, isNonUSDWorkspace, nonUSDCountryDraftValue);
+ }, [achData, isNonUSDWorkspace, nonUSDCountryDraftValue]);
+
const onPressAutoReportingFrequency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY.getRoute(route.params.policyID)), [route.params.policyID]);
const fetchData = useCallback(() => {
openPolicyWorkflowsPage(route.params.policyID);
+ getPaymentMethods();
}, [route.params.policyID]);
const confirmCurrencyChangeAndHideModal = useCallback(() => {
@@ -111,9 +127,14 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
Navigation.navigate(ROUTES.WORKSPACE_OVERVIEW_CURRENCY.getRoute(policy.id));
} else {
updateGeneralSettings(policy.id, policy.name, CONST.CURRENCY.USD);
- navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID));
+ const hasValidExistingUSDAccounts = getEligibleExistingBusinessBankAccounts(bankAccountList, CONST.CURRENCY.USD).length > 0;
+ if (hasValidExistingUSDAccounts) {
+ Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.getRoute(route.params.policyID));
+ } else {
+ navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID));
+ }
}
- }, [isBetaEnabled, policy, route.params.policyID]);
+ }, [bankAccountList, isBetaEnabled, policy, route.params.policyID]);
const {isOffline} = useNetwork({onReconnect: fetchData});
const isPolicyAdmin = isPolicyAdminUtil(policy);
@@ -158,7 +179,11 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
}, [policy?.approvalMode, approvalWorkflows]);
const optionItems: ToggleSettingOptionRowProps[] = useMemo(() => {
- const {addressName, bankName, bankAccountID} = policy?.achAccount ?? {};
+ const {bankAccountID} = policy?.achAccount ?? {};
+ const bankAccount = bankAccountList?.[bankAccountID ?? CONST.DEFAULT_NUMBER_ID];
+ const bankName = policy?.achAccount?.bankName ?? bankAccount?.accountData?.additionalData?.bankName ?? '';
+ const addressName = policy?.achAccount?.addressName ?? bankAccount?.accountData?.addressName ?? '';
+ const accountData = bankAccount?.accountData ?? policy?.achAccount ?? {};
const shouldShowBankAccount = !!bankAccountID && policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES;
const bankIcon = getBankIcon({bankName: bankName as BankName, isCard: false, styles});
@@ -250,7 +275,11 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
}
const newReimburserEmail = policy?.achAccount?.reimburser ?? policy?.owner;
- setWorkspaceReimbursement(route.params.policyID, newReimbursementChoice, newReimburserEmail ?? '');
+ setWorkspaceReimbursement({
+ policyID: route.params.policyID,
+ reimbursementChoice: newReimbursementChoice,
+ reimburserEmail: newReimburserEmail ?? '',
+ });
},
subMenuItems:
!isOffline && policy?.isLoadingWorkspaceReimbursement === true ? (
@@ -269,7 +298,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
{
if (isAccountLocked) {
showLockedAccountModal();
@@ -279,6 +308,12 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
setIsUpdateWorkspaceCurrencyModalOpen(true);
return;
}
+
+ if (!shouldShowBankAccount && hasValidExistingAccounts && !shouldShowContinueModal) {
+ Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.getRoute(route.params.policyID));
+ return;
+ }
+
navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID));
}}
icon={shouldShowBankAccount ? bankIcon.icon : Plus}
@@ -322,6 +357,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
];
}, [
policy,
+ bankAccountList,
styles,
translate,
onPressAutoReportingFrequency,
@@ -333,8 +369,10 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
displayNameForAuthorizedPayer,
route.params.policyID,
updateApprovalMode,
- isBetaEnabled,
isAccountLocked,
+ isBetaEnabled,
+ hasValidExistingAccounts,
+ shouldShowContinueModal,
showLockedAccountModal,
filteredApprovalWorkflows,
]);
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 61229452bbeb..70b4e03cf3ca 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -829,10 +829,6 @@ const styles = (theme: ThemeColors) =>
borderBottomLeftRadius: 0,
},
- buttonCTA: {
- ...spacing.mh4,
- },
-
buttonCTAIcon: {
marginRight: 22,
marginLeft: 8,