diff --git a/assets/images/companyCards/pending-bank.svg b/assets/images/companyCards/pending-bank.svg
new file mode 100644
index 000000000000..dc265466d53f
--- /dev/null
+++ b/assets/images/companyCards/pending-bank.svg
@@ -0,0 +1,263 @@
+
+
diff --git a/src/CONST.ts b/src/CONST.ts
index c51864208eca..2de265de53ce 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -2623,6 +2623,14 @@ const CONST = {
WELLS_FARGO: 'Wells Fargo',
OTHER: 'Other',
},
+ BANK_CONNECTIONS: {
+ WELLS_FARGO: 'wellsfargo',
+ CHASE: 'chase',
+ BREX: 'brex',
+ CAPITAL_ONE: 'capitalone',
+ CITI_BANK: 'citibank',
+ AMEX: 'americanexpressfdx',
+ },
AMEX_CUSTOM_FEED: {
CORPORATE: 'American Express Corporate Cards',
BUSINESS: 'American Express Business Cards',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 30c7196f19b4..cf15013fed9b 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -21,6 +21,7 @@ const PUBLIC_SCREENS_ROUTES = {
ROOT: '',
TRANSITION_BETWEEN_APPS: 'transition',
CONNECTION_COMPLETE: 'connection-complete',
+ BANK_CONNECTION_COMPLETE: 'bank-connection-complete',
VALIDATE_LOGIN: 'v/:accountID/:validateCode',
UNLINK_LOGIN: 'u/:accountID/:validateCode',
APPLE_SIGN_IN: 'sign-in-with-apple',
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index bae8f6af1ab2..18ae1792686f 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -12,6 +12,7 @@ import WellsFargoCompanyCardDetail from '@assets/images/companyCards/card-wellsf
import OtherCompanyCardDetail from '@assets/images/companyCards/card=-generic.svg';
import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg';
import MasterCardCompanyCards from '@assets/images/companyCards/mastercard.svg';
+import PendingBank from '@assets/images/companyCards/pending-bank.svg';
import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg';
import VisaCompanyCards from '@assets/images/companyCards/visa.svg';
import EmptyCardState from '@assets/images/emptystate__expensifycard.svg';
@@ -207,6 +208,7 @@ export {
Approval,
WalletAlt,
Workflows,
+ PendingBank,
ThreeLeggedLaptopWoman,
House,
Alert,
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 5798f7fe48e9..ae9d199d423e 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -39,6 +39,7 @@ import type {
ChangeTypeParams,
CharacterLengthLimitParams,
CharacterLimitParams,
+ CompanyCardBankName,
CompanyCardFeedNameParams,
ConfirmThatParams,
ConnectionNameParams,
@@ -3314,6 +3315,9 @@ const translations = {
emptyAddedFeedDescription: 'Get started by assigning your first card to a member.',
pendingFeedTitle: `We're reviewing your request...`,
pendingFeedDescription: `We're currently reviewing your feed details. Once that's done we'll reach out to you via`,
+ pendingBankTitle: 'Check your browser window',
+ pendingBankDescription: ({bankName}: CompanyCardBankName) => `Please connect to ${bankName} via your browser window that just opened. If one didn’t open, `,
+ pendingBankLink: 'please click here.',
giveItNameInstruction: 'Give the card a name that sets it apart from the others.',
updating: 'Updating...',
noAccountsFound: 'No accounts found',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 84c03d5d9bf3..9e182d99a94a 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -37,6 +37,7 @@ import type {
ChangeTypeParams,
CharacterLengthLimitParams,
CharacterLimitParams,
+ CompanyCardBankName,
CompanyCardFeedNameParams,
ConfirmThatParams,
ConnectionNameParams,
@@ -3359,6 +3360,9 @@ const translations = {
emptyAddedFeedDescription: 'Comienza asignando tu primera tarjeta a un miembro.',
pendingFeedTitle: `Estamos revisando tu solicitud...`,
pendingFeedDescription: `Actualmente estamos revisando los detalles de tu feed. Una vez hecho esto, nos pondremos en contacto contigo a través de`,
+ pendingBankTitle: 'Comprueba la ventana de tu navegador',
+ pendingBankDescription: ({bankName}: CompanyCardBankName) => `Conéctese a ${bankName} a través de la ventana del navegador que acaba de abrir. Si no se abrió, `,
+ pendingBankLink: 'por favor haga clic aquí.',
giveItNameInstruction: 'Nombra la tarjeta para distingirla de las demás.',
updating: 'Actualizando...',
noAccountsFound: 'No se han encontrado cuentas',
diff --git a/src/languages/params.ts b/src/languages/params.ts
index 02dafa76a46d..9341b914d1d0 100644
--- a/src/languages/params.ts
+++ b/src/languages/params.ts
@@ -538,6 +538,10 @@ type ImportedTypesParams = {
importedTypes: string[];
};
+type CompanyCardBankName = {
+ bankName: string;
+};
+
export type {
AuthenticationErrorParams,
ImportMembersSuccessfullDescriptionParams,
@@ -729,6 +733,7 @@ export type {
DateParams,
FiltersAmountBetweenParams,
StatementPageTitleParams,
+ CompanyCardBankName,
DisconnectPromptParams,
DisconnectTitleParams,
CharacterLengthLimitParams,
diff --git a/src/libs/actions/getCompanyCardBankConnection/index.tsx b/src/libs/actions/getCompanyCardBankConnection/index.tsx
new file mode 100644
index 000000000000..935c5d297cb0
--- /dev/null
+++ b/src/libs/actions/getCompanyCardBankConnection/index.tsx
@@ -0,0 +1,34 @@
+import {getApiRoot} from '@libs/ApiUtils';
+import * as NetworkStore from '@libs/Network/NetworkStore';
+import CONST from '@src/CONST';
+
+type CompanyCardBankConnection = {
+ authToken: string;
+ domainName: string;
+ scrapeMinDate: string;
+ isCorporate: string;
+};
+
+// TODO remove this when BE will support bank UI callbacks
+const bankUrl = 'https://secure.chase.com/web/auth/#/logon/logon/chaseOnline?redirect_url=';
+
+export default function getCompanyCardBankConnection(bankName?: string, domainName?: string, scrapeMinDate?: string) {
+ const bankConnection = Object.keys(CONST.COMPANY_CARDS.BANKS).find((key) => CONST.COMPANY_CARDS.BANKS[key as keyof typeof CONST.COMPANY_CARDS.BANKS] === bankName);
+
+ // TODO remove this when BE will support bank UI callbacks
+ if (!domainName) {
+ return bankUrl;
+ }
+
+ if (!bankName || !bankConnection) {
+ return null;
+ }
+ const authToken = NetworkStore.getAuthToken();
+ const params: CompanyCardBankConnection = {authToken: authToken ?? '', domainName: domainName ?? '', isCorporate: 'true', scrapeMinDate: scrapeMinDate ?? ''};
+ const commandURL = getApiRoot({
+ shouldSkipWebProxy: true,
+ command: '',
+ });
+ const bank = CONST.COMPANY_CARDS.BANK_CONNECTIONS[bankConnection as keyof typeof CONST.COMPANY_CARDS.BANK_CONNECTIONS];
+ return `${commandURL}partners/banks/${bank}/oauth_callback.php?${new URLSearchParams(params).toString()}`;
+}
diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
index 480bd5d538fe..105c7107548d 100644
--- a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
+++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
@@ -6,6 +6,7 @@ import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPol
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import AmexCustomFeed from './AmexCustomFeed';
+import BankConnection from './BankConnection';
import CardInstructionsStep from './CardInstructionsStep';
import CardNameStep from './CardNameStep';
import CardTypeStep from './CardTypeStep';
@@ -28,6 +29,8 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) {
return ;
case CONST.COMPANY_CARDS.STEP.CARD_TYPE:
return ;
+ case CONST.COMPANY_CARDS.STEP.BANK_CONNECTION:
+ return ;
case CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS:
return ;
case CONST.COMPANY_CARDS.STEP.CARD_NAME:
diff --git a/src/pages/workspace/companyCards/addNew/BankConnection/index.native.tsx b/src/pages/workspace/companyCards/addNew/BankConnection/index.native.tsx
new file mode 100644
index 000000000000..8de2b9baba9a
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/BankConnection/index.native.tsx
@@ -0,0 +1,79 @@
+import React, {useEffect, useRef, useState} from 'react';
+import {useOnyx} from 'react-native-onyx';
+import {WebView} from 'react-native-webview';
+import type {ValueOf} from 'type-fest';
+import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
+import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Modal from '@components/Modal';
+import useLocalize from '@hooks/useLocalize';
+import getUAForWebView from '@libs/getUAForWebView';
+import * as CompanyCards from '@userActions/CompanyCards';
+import getCompanyCardBankConnection from '@userActions/getCompanyCardBankConnection';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+
+function BankConnection() {
+ const {translate} = useLocalize();
+ const webViewRef = useRef(null);
+ const [isWebViewOpen, setWebViewOpen] = useState(false);
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const authToken = session?.authToken ?? null;
+ const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
+ const bankName: ValueOf | undefined = addNewCard?.data?.selectedBank;
+ const url = getCompanyCardBankConnection(bankName);
+
+ const renderLoading = () => ;
+
+ const handleBackButtonPress = () => {
+ setWebViewOpen(false);
+ if (bankName === CONST.COMPANY_CARDS.BANKS.BREX) {
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_BANK});
+ return;
+ }
+ if (bankName === CONST.COMPANY_CARDS.BANKS.AMEX) {
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.AMEX_CUSTOM_FEED});
+ return;
+ }
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE});
+ };
+
+ useEffect(() => {
+ setWebViewOpen(true);
+ }, []);
+
+ return (
+
+
+
+ {url && (
+
+ )}
+
+
+ );
+}
+
+BankConnection.displayName = 'BankConnection';
+
+export default BankConnection;
diff --git a/src/pages/workspace/companyCards/addNew/BankConnection/index.tsx b/src/pages/workspace/companyCards/addNew/BankConnection/index.tsx
new file mode 100644
index 000000000000..2b4d86f2e43b
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/BankConnection/index.tsx
@@ -0,0 +1,91 @@
+import React, {useCallback, useEffect} from 'react';
+import {useOnyx} from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
+import BlockingView from '@components/BlockingViews/BlockingView';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import Text from '@components/Text';
+import TextLink from '@components/TextLink';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import getCurrentUrl from '@navigation/currentUrl';
+import * as CompanyCards from '@userActions/CompanyCards';
+import getCompanyCardBankConnection from '@userActions/getCompanyCardBankConnection';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import openBankConnection from './openBankConnection';
+
+let customWindow: Window | null = null;
+
+function BankConnection() {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
+ const bankName: ValueOf | undefined = addNewCard?.data?.selectedBank;
+ const currentUrl = getCurrentUrl();
+ const isBankConnectionCompleteRoute = currentUrl.includes(ROUTES.BANK_CONNECTION_COMPLETE);
+ const url = getCompanyCardBankConnection(bankName);
+
+ const onOpenBankConnectionFlow = useCallback(() => {
+ if (!url) {
+ return;
+ }
+ customWindow = openBankConnection(url);
+ }, [url]);
+
+ const handleBackButtonPress = () => {
+ customWindow?.close();
+ if (bankName === CONST.COMPANY_CARDS.BANKS.BREX) {
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_BANK});
+ return;
+ }
+ if (bankName === CONST.COMPANY_CARDS.BANKS.AMEX) {
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.AMEX_CUSTOM_FEED});
+ return;
+ }
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE});
+ };
+
+ const CustomSubtitle = (
+
+ {bankName && translate(`workspace.moreFeatures.companyCards.pendingBankDescription`, {bankName})}
+ {translate('workspace.moreFeatures.companyCards.pendingBankLink')}
+
+ );
+
+ useEffect(() => {
+ if (!url) {
+ return;
+ }
+ if (isBankConnectionCompleteRoute) {
+ customWindow?.close();
+ return;
+ }
+ customWindow = openBankConnection(url);
+ }, [isBankConnectionCompleteRoute, url]);
+
+ return (
+
+
+
+
+ );
+}
+
+BankConnection.displayName = 'BankConnection';
+
+export default BankConnection;
diff --git a/src/pages/workspace/companyCards/addNew/BankConnection/openBankConnection/index.tsx b/src/pages/workspace/companyCards/addNew/BankConnection/openBankConnection/index.tsx
new file mode 100644
index 000000000000..91a81bdbd6c6
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/BankConnection/openBankConnection/index.tsx
@@ -0,0 +1,5 @@
+const handleOpenBankConnectionFlow = (url: string) => {
+ return window.open(url, '_blank');
+};
+
+export default handleOpenBankConnectionFlow;
diff --git a/src/pages/workspace/companyCards/addNew/BankConnection/openBankConnection/index.website.tsx b/src/pages/workspace/companyCards/addNew/BankConnection/openBankConnection/index.website.tsx
new file mode 100644
index 000000000000..220404cee0e7
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/BankConnection/openBankConnection/index.website.tsx
@@ -0,0 +1,14 @@
+const WINDOW_WIDTH = 700;
+const WINDOW_HEIGHT = 600;
+
+const handleOpenBankConnectionFlow = (url: string) => {
+ const screenWidth = window.screen.width;
+ const screenHeight = window.screen.height;
+ const left = (screenWidth - WINDOW_WIDTH) / 2;
+ const top = (screenHeight - WINDOW_HEIGHT) / 2;
+ const popupFeatures = `width=${WINDOW_WIDTH},height=${WINDOW_HEIGHT},left=${left},top=${top},scrollbars=yes,resizable=yes`;
+
+ return window.open(url, 'popupWindow', popupFeatures);
+};
+
+export default handleOpenBankConnectionFlow;
diff --git a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx
index fd4fe021185c..959e7c10f3aa 100644
--- a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx
+++ b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx
@@ -28,7 +28,7 @@ function SelectFeedType() {
return;
}
CompanyCards.setAddNewCompanyCardStepAndData({
- step: typeSelected === CONST.COMPANY_CARDS.FEED_TYPE.DIRECT ? CONST.COMPANY_CARDS.STEP.SELECT_BANK : CONST.COMPANY_CARDS.STEP.CARD_TYPE,
+ step: typeSelected === CONST.COMPANY_CARDS.FEED_TYPE.DIRECT ? CONST.COMPANY_CARDS.STEP.BANK_CONNECTION : CONST.COMPANY_CARDS.STEP.CARD_TYPE,
data: {selectedFeedType: typeSelected},
});
};
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 71e8e0259bab..3ea14bb14515 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -5157,6 +5157,11 @@ const styles = (theme: ThemeColors) =>
height: 188,
},
+ pendingBankCardIllustration: {
+ width: 217,
+ height: 150,
+ },
+
cardIcon: {
overflow: 'hidden',
borderRadius: variables.cardBorderRadius,