diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index b150e6841ec6..eef0990f7409 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -1291,6 +1291,15 @@ const ROUTES = {
return `settings/workspaces/${policyID}/company-cards` as const;
},
},
+ WORKSPACE_COMPANY_CARDS_BANK_CONNECTION: {
+ route: 'settings/workspaces/:policyID/company-cards/:bankName/bank-connection',
+ getRoute: (policyID: string | undefined, bankName: string, backTo: string) => {
+ if (!policyID) {
+ Log.warn('Invalid policyID is used to build the WORKSPACE_COMPANY_CARDS_BANK_CONNECTION route');
+ }
+ return getUrlWithBackToParam(`settings/workspaces/${policyID}/company-cards/${bankName}/bank-connection`, backTo);
+ },
+ },
WORKSPACE_COMPANY_CARDS_ADD_NEW: {
route: 'settings/workspaces/:policyID/company-cards/add-card-feed',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/add-card-feed` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 3d85cd907f2a..76456485a3a4 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -456,6 +456,7 @@ const SCREENS = {
COMPANY_CARDS: 'Workspace_CompanyCards',
COMPANY_CARDS_ASSIGN_CARD: 'Workspace_CompanyCards_AssignCard',
COMPANY_CARDS_SELECT_FEED: 'Workspace_CompanyCards_Select_Feed',
+ COMPANY_CARDS_BANK_CONNECTION: 'Workspace_CompanyCards_BankConnection',
COMPANY_CARDS_ADD_NEW: 'Workspace_CompanyCards_New',
COMPANY_CARDS_TYPE: 'Workspace_CompanyCards_Type',
COMPANY_CARDS_INSTRUCTIONS: 'Workspace_CompanyCards_Instructions',
diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx
index d831fca562c3..c998c38e96ca 100644
--- a/src/components/DotIndicatorMessage.tsx
+++ b/src/components/DotIndicatorMessage.tsx
@@ -7,8 +7,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {isReceiptError} from '@libs/ErrorUtils';
import fileDownload from '@libs/fileDownload';
-import * as Localize from '@libs/Localize';
-import CONST from '@src/CONST';
+import {translateLocal} from '@libs/Localize';
import type {ReceiptError} from '@src/types/onyx/Transaction';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
@@ -61,38 +60,17 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica
key={index}
style={styles.offlineFeedback.text}
>
- {Localize.translateLocal('iou.error.receiptFailureMessage')}
+ {translateLocal('iou.error.receiptFailureMessage')}
{
fileDownload(message.source, message.filename);
}}
>
- {Localize.translateLocal('iou.error.saveFileMessage')}
+ {translateLocal('iou.error.saveFileMessage')}
- {Localize.translateLocal('iou.error.loseFileMessage')}
-
- );
- }
-
- if (message === CONST.COMPANY_CARDS.CONNECTION_ERROR) {
- return (
-
- {Localize.translateLocal('workspace.companyCards.brokenConnectionErrorFirstPart')}
- {
- // TODO: re-navigate the user to the bank’s website to re-authenticate https://github.com/Expensify/App/issues/50448
- }}
- >
- {Localize.translateLocal('workspace.companyCards.brokenConnectionErrorLink')}
-
-
- {Localize.translateLocal('workspace.companyCards.brokenConnectionErrorSecondPart')}
+ {translateLocal('iou.error.loseFileMessage')}
);
}
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 0b31401d7e25..1e5e5027dc4f 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -533,6 +533,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite').default,
[SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD]: () => require('../../../../pages/workspace/companyCards/assignCard/AssignCardFeedPage').default,
[SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage').default,
+ [SCREENS.WORKSPACE.COMPANY_CARDS_BANK_CONNECTION]: () => require('../../../../pages/workspace/companyCards/addNew/BankConnection').default,
[SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: () => require('../../../../pages/workspace/companyCards/addNew/AddNewCardPage').default,
[SCREENS.WORKSPACE.COMPANY_CARD_DETAILS]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage').default,
[SCREENS.WORKSPACE.COMPANY_CARD_NAME]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage').default,
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index 2c3b060e0835..7865993d08e9 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -213,6 +213,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
SCREENS.WORKSPACE.COMPANY_CARDS_NAME,
SCREENS.WORKSPACE.COMPANY_CARDS_DETAILS,
SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED,
+ SCREENS.WORKSPACE.COMPANY_CARDS_BANK_CONNECTION,
SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS,
SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME,
SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 579dfe227fb9..9b7061e09ccc 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -620,6 +620,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED]: {
path: ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.route,
},
+ [SCREENS.WORKSPACE.COMPANY_CARDS_BANK_CONNECTION]: {
+ path: ROUTES.WORKSPACE_COMPANY_CARDS_BANK_CONNECTION.route,
+ },
[SCREENS.WORKSPACE.COMPANY_CARD_DETAILS]: {
path: ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 67752a152941..1de3e11f08e4 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -822,6 +822,11 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.COMPANY_CARDS_BANK_CONNECTION]: {
+ policyID: string;
+ bankName: string;
+ backTo: Routes;
+ };
[SCREENS.WORKSPACE.COMPANY_CARD_DETAILS]: {
policyID: string;
bank: CompanyCardFeed;
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 9e5e03acd1b0..7a284ee527b9 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -9,7 +9,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm';
import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate} from '@src/types/onyx';
-import type {CardFeedData} from '@src/types/onyx/CardFeeds';
import type {ErrorFields, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon';
import type {
ConnectionLastSync,
@@ -1146,10 +1145,6 @@ function getWorkflowApprovalsUnavailable(policy: OnyxEntry) {
return policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL || !!policy?.errorFields?.approvalMode;
}
-function hasPolicyFeedsError(feeds: Record, feedToSkip?: string): boolean {
- return Object.entries(feeds).filter(([feedName, feedData]) => feedName !== feedToSkip && !!feedData.errors).length > 0;
-}
-
function getAllPoliciesLength() {
return Object.keys(allPolicies ?? {}).length;
}
@@ -1233,7 +1228,6 @@ export {
goBackFromInvalidPolicy,
hasAccountingConnections,
shouldShowSyncError,
- hasPolicyFeedsError,
shouldShowCustomUnitsError,
shouldShowEmployeeListError,
hasIntegrationAutoSync,
diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts
index d4a905d5ef6d..f6e6dbb3729d 100644
--- a/src/libs/actions/CompanyCards.ts
+++ b/src/libs/actions/CompanyCards.ts
@@ -1,4 +1,4 @@
-import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import type {
@@ -18,10 +18,11 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Card, CardFeeds} from '@src/types/onyx';
+import type {Card, CardFeeds, WorkspaceCardsList} from '@src/types/onyx';
import type {AssignCard, AssignCardData} from '@src/types/onyx/AssignCard';
import type {AddNewCardFeedData, AddNewCardFeedStep, CompanyCardFeed} from '@src/types/onyx/CardFeeds';
import type {OnyxData} from '@src/types/onyx/Request';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
type AddNewCompanyCardFlowData = {
/** Step to be set in Onyx */
@@ -403,8 +404,6 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri
function updateWorkspaceCompanyCard(workspaceAccountID: number, cardID: string, bankName: CompanyCardFeed) {
const authToken = NetworkStore.getAuthToken();
- const optimisticFeedUpdates = {[bankName]: {errors: null}};
- const failureFeedUpdates = {[bankName]: {errors: {error: CONST.COMPANY_CARDS.CONNECTION_ERROR}}};
const optimisticData: OnyxUpdate[] = [
{
@@ -437,13 +436,6 @@ function updateWorkspaceCompanyCard(workspaceAccountID: number, cardID: string,
},
},
},
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
- value: {
- settings: {companyCards: optimisticFeedUpdates},
- },
- },
];
const finallyData: OnyxUpdate[] = [
@@ -504,13 +496,6 @@ function updateWorkspaceCompanyCard(workspaceAccountID: number, cardID: string,
},
},
},
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
- value: {
- settings: {companyCards: failureFeedUpdates},
- },
- },
];
const parameters = {
@@ -740,6 +725,41 @@ function openPolicyCompanyCardsFeed(policyID: string, feed: CompanyCardFeed) {
API.read(READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_FEED, parameters);
}
+/**
+ * Takes the list of cards divided by workspaces and feeds and returns the flattened non-Expensify cards related to the provided workspace
+ *
+ * @param allCardsList the list where cards split by workspaces and feeds and stored under `card_${workspaceAccountID}_${feedName}` keys
+ * @param workspaceAccountID the workspace account id we want to get cards for
+ */
+function flatAllCardsList(allCardsList: OnyxCollection, workspaceAccountID: number): Record | undefined {
+ if (!allCardsList) {
+ return;
+ }
+
+ return Object.entries(allCardsList).reduce((acc, [key, allCards]) => {
+ if (!key.includes(workspaceAccountID.toString()) || key.includes(CONST.EXPENSIFY_CARD.BANK)) {
+ return acc;
+ }
+ const {cardList, ...feedCards} = allCards ?? {};
+ Object.assign(acc, feedCards);
+ return acc;
+ }, {});
+}
+
+/**
+ * Check if any feed card has a broken connection
+ *
+ * @param feedCards the list of the cards, related to one or several feeds
+ * @param [feedToExclude] the feed to ignore during the check, it's useful for checking broken connection error only in the feeds other than the selected one
+ */
+function checkIfFeedConnectionIsBroken(feedCards: Record | undefined, feedToExclude?: string): boolean {
+ if (!feedCards || isEmptyObject(feedCards)) {
+ return false;
+ }
+
+ return Object.values(feedCards).some((card) => card.bank !== feedToExclude && card.lastScrapeResult !== 200);
+}
+
export {
setWorkspaceCompanyCardFeedName,
deleteWorkspaceCompanyCardFeed,
@@ -757,4 +777,6 @@ export {
clearAddNewCardFlow,
setAssignCardStepAndData,
clearAssignCardStepAndData,
+ checkIfFeedConnectionIsBroken,
+ flatAllCardsList,
};
diff --git a/src/libs/actions/getCompanyCardBankConnection/index.tsx b/src/libs/actions/getCompanyCardBankConnection/index.tsx
index fb6dd9943972..a136a3783136 100644
--- a/src/libs/actions/getCompanyCardBankConnection/index.tsx
+++ b/src/libs/actions/getCompanyCardBankConnection/index.tsx
@@ -11,7 +11,7 @@ type CompanyCardBankConnection = {
isNewDot: string;
};
-export default function getCompanyCardBankConnection(policyID?: string, bankName?: string, scrapeMinDate?: string) {
+export default function getCompanyCardBankConnection(policyID?: string, bankName?: string) {
const bankConnection = Object.keys(CONST.COMPANY_CARDS.BANKS).find((key) => CONST.COMPANY_CARDS.BANKS[key as keyof typeof CONST.COMPANY_CARDS.BANKS] === bankName);
if (!bankName || !bankConnection || !policyID) {
@@ -23,7 +23,7 @@ export default function getCompanyCardBankConnection(policyID?: string, bankName
isNewDot: 'true',
domainName: PolicyUtils.getDomainNameForPolicy(policyID),
isCorporate: 'true',
- scrapeMinDate: scrapeMinDate ?? '',
+ scrapeMinDate: '',
};
const commandURL = getApiRoot({
shouldSkipWebProxy: true,
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 2a9b77551c0f..163fa60cb1fd 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -21,17 +21,29 @@ import useSingleExecution from '@hooks/useSingleExecution';
import useThemeStyles from '@hooks/useThemeStyles';
import useWaitForNavigation from '@hooks/useWaitForNavigation';
import {isConnectionInProgress} from '@libs/actions/connections';
-import * as CardUtils from '@libs/CardUtils';
-import * as CurrencyUtils from '@libs/CurrencyUtils';
+import {convertToDisplayString} from '@libs/CurrencyUtils';
import getTopmostRouteName from '@libs/Navigation/getTopmostRouteName';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
-import * as PolicyUtils from '@libs/PolicyUtils';
+import {
+ shouldShowPolicy as checkIfShouldShowPolicy,
+ getWorkspaceAccountID,
+ goBackFromInvalidPolicy,
+ hasPolicyCategoriesError,
+ isPaidGroupPolicy,
+ isPendingDeletePolicy,
+ isPolicyAdmin,
+ isPolicyFeatureEnabled,
+ shouldShowEmployeeListError,
+ shouldShowSyncError,
+ shouldShowTaxRateError,
+} from '@libs/PolicyUtils';
import {getDefaultWorkspaceAvatar, getIcons, getPolicyExpenseChat, getReportName, getReportOfflinePendingActionAndErrors} from '@libs/ReportUtils';
import type {FullScreenNavigatorParamList} from '@navigation/types';
-import * as App from '@userActions/App';
-import * as Policy from '@userActions/Policy/Policy';
-import * as ReimbursementAccount from '@userActions/ReimbursementAccount';
+import {confirmReadyToOpenApp} from '@userActions/App';
+import {checkIfFeedConnectionIsBroken, flatAllCardsList} from '@userActions/CompanyCards';
+import {clearErrors, openPolicyInitialPage, removeWorkspace, updateGeneralSettings} from '@userActions/Policy/Policy';
+import {navigateToBankAccountRoute} from '@userActions/ReimbursementAccount';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -76,20 +88,20 @@ type PolicyFeatureStates = Record;
function dismissError(policyID: string, pendingAction: PendingAction | undefined) {
if (!policyID || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) {
- PolicyUtils.goBackFromInvalidPolicy();
- Policy.removeWorkspace(policyID);
+ goBackFromInvalidPolicy();
+ removeWorkspace(policyID);
} else {
- Policy.clearErrors(policyID);
+ clearErrors(policyID);
}
}
function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: WorkspaceInitialPageProps) {
const styles = useThemeStyles();
const policy = policyDraft?.id ? policyDraft : policyProp;
- const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policy?.id);
+ const workspaceAccountID = getWorkspaceAccountID(policy?.id);
const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false);
const hasPolicyCreationError = !!(policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && !isEmptyObject(policy.errors));
- const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
+ const [allFeedsCards] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`);
const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`);
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`);
const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`);
@@ -97,7 +109,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params?.policyID}`);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const {login, accountID} = useCurrentUserPersonalDetails();
- const hasSyncError = PolicyUtils.shouldShowSyncError(policy, isConnectionInProgress(connectionSyncProgress, policy));
+ const hasSyncError = shouldShowSyncError(policy, isConnectionInProgress(connectionSyncProgress, policy));
const waitForNavigate = useWaitForNavigation();
const {singleExecution, isExecuting} = useSingleExecution();
const activeRoute = useNavigationState(getTopmostRouteName);
@@ -131,7 +143,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
if (policyDraft?.id) {
return;
}
- Policy.openPolicyInitialPage(route.params.policyID);
+ openPolicyInitialPage(route.params.policyID);
}, [policyDraft?.id, route.params.policyID]);
useNetwork({onReconnect: fetchPolicyData});
@@ -153,20 +165,19 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
/** Call update workspace currency and hide the modal */
const confirmCurrencyChangeAndHideModal = useCallback(() => {
- Policy.updateGeneralSettings(policyID, policyName, CONST.CURRENCY.USD);
+ updateGeneralSettings(policyID, policyName, CONST.CURRENCY.USD);
setIsCurrencyModalOpen(false);
- ReimbursementAccount.navigateToBankAccountRoute(policyID);
+ navigateToBankAccountRoute(policyID);
}, [policyID, policyName]);
- const hasMembersError = PolicyUtils.shouldShowEmployeeListError(policy);
- const hasPolicyCategoryError = PolicyUtils.hasPolicyCategoriesError(policyCategories);
+ const hasMembersError = shouldShowEmployeeListError(policy);
+ const hasPolicyCategoryError = hasPolicyCategoriesError(policyCategories);
const hasGeneralSettingsError =
!isEmptyObject(policy?.errorFields?.name ?? {}) ||
!isEmptyObject(policy?.errorFields?.avatarURL ?? {}) ||
!isEmptyObject(policy?.errorFields?.ouputCurrency ?? {}) ||
!isEmptyObject(policy?.errorFields?.address ?? {});
- const shouldShowProtectedItems = PolicyUtils.isPolicyAdmin(policy, login);
- const isPaidGroupPolicy = PolicyUtils.isPaidGroupPolicy(policy);
+ const shouldShowProtectedItems = isPolicyAdmin(policy, login);
const [featureStates, setFeatureStates] = useState(policyFeatureStates);
const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = [];
@@ -177,7 +188,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
setFeatureStates((currentFeatureStates) => {
const newFeatureStates = {} as PolicyFeatureStates;
(Object.keys(policy?.pendingFields ?? {}) as PolicyFeatureName[]).forEach((key) => {
- const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, key);
+ const isFeatureEnabled = isPolicyFeatureEnabled(policy, key);
newFeatureStates[key] =
prevPendingFields?.[key] !== policy?.pendingFields?.[key] || isOffline || !policy?.pendingFields?.[key] ? isFeatureEnabled : currentFeatureStates[key];
});
@@ -189,7 +200,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
}, [policy, isOffline, policyFeatureStates, prevPendingFields]);
useEffect(() => {
- App.confirmReadyToOpenApp();
+ confirmReadyToOpenApp();
}, []);
if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED]) {
@@ -212,14 +223,14 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
}
if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]) {
- const hasPolicyFeedsError = PolicyUtils.hasPolicyFeedsError(CardUtils.getCompanyFeeds(cardFeeds));
+ const hasBrokenFeedConnection = checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, workspaceAccountID));
protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.companyCards',
icon: Expensicons.CreditCard,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.COMPANY_CARDS,
- brickRoadIndicator: hasPolicyFeedsError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
+ brickRoadIndicator: hasBrokenFeedConnection ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
});
}
@@ -259,7 +270,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
icon: Expensicons.InvoiceGeneric,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_INVOICES.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.INVOICES,
- badgeText: CurrencyUtils.convertToDisplayString(policy?.invoice?.bankAccount?.stripeConnectAccountBalance ?? 0, currencyCode),
+ badgeText: convertToDisplayString(policy?.invoice?.bankAccount?.stripeConnectAccountBalance ?? 0, currencyCode),
});
}
@@ -288,7 +299,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
icon: Expensicons.Coins,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TAXES.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.TAXES,
- brickRoadIndicator: PolicyUtils.shouldShowTaxRateError(policy) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
+ brickRoadIndicator: shouldShowTaxRateError(policy) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
});
}
@@ -333,24 +344,24 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
brickRoadIndicator: hasMembersError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
routeName: SCREENS.WORKSPACE.MEMBERS,
},
- ...(isPaidGroupPolicy && shouldShowProtectedItems ? protectedCollectPolicyMenuItems : []),
+ ...(isPaidGroupPolicy(policy) && shouldShowProtectedItems ? protectedCollectPolicyMenuItems : []),
];
const prevPolicy = usePrevious(policy);
const prevProtectedMenuItems = usePrevious(protectedCollectPolicyMenuItems);
const enabledItem = protectedCollectPolicyMenuItems.find((curItem) => !prevProtectedMenuItems.some((prevItem) => curItem.routeName === prevItem.routeName));
- const shouldShowPolicy = useMemo(() => PolicyUtils.shouldShowPolicy(policy, isOffline, currentUserLogin), [policy, isOffline, currentUserLogin]);
- const prevShouldShowPolicy = useMemo(() => PolicyUtils.shouldShowPolicy(prevPolicy, isOffline, currentUserLogin), [prevPolicy, isOffline, currentUserLogin]);
+ const shouldShowPolicy = useMemo(() => checkIfShouldShowPolicy(policy, isOffline, currentUserLogin), [policy, isOffline, currentUserLogin]);
+ const prevShouldShowPolicy = useMemo(() => checkIfShouldShowPolicy(prevPolicy, isOffline, currentUserLogin), [prevPolicy, isOffline, currentUserLogin]);
// We check shouldShowPolicy and prevShouldShowPolicy to prevent the NotFound view from showing right after we delete the workspace
// eslint-disable-next-line rulesdir/no-negated-variables
const shouldShowNotFoundPage = isEmptyObject(policy) || (!shouldShowPolicy && !prevShouldShowPolicy);
useEffect(() => {
- if (isEmptyObject(prevPolicy) || PolicyUtils.isPendingDeletePolicy(prevPolicy) || !PolicyUtils.isPendingDeletePolicy(policy)) {
+ if (isEmptyObject(prevPolicy) || isPendingDeletePolicy(prevPolicy) || !isPendingDeletePolicy(policy)) {
return;
}
- PolicyUtils.goBackFromInvalidPolicy();
+ goBackFromInvalidPolicy();
}, [policy, prevPolicy]);
// We are checking if the user can access the route.
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx
index 29d0dfb0c6af..980d8e08fb77 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx
@@ -10,15 +10,15 @@ import RadioListItem from '@components/SelectionList/RadioListItem';
import type {ListItem} from '@components/SelectionList/types';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
-import * as CardUtils from '@libs/CardUtils';
+import {getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, getSelectedFeed} from '@libs/CardUtils';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
-import * as PolicyUtils from '@libs/PolicyUtils';
+import {getWorkspaceAccountID} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import variables from '@styles/variables';
-import * as Card from '@userActions/Card';
-import * as CompanyCards from '@userActions/CompanyCards';
+import {updateSelectedFeed} from '@userActions/Card';
+import {checkIfFeedConnectionIsBroken, clearAddNewCardFlow} from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -34,38 +34,42 @@ type WorkspaceCompanyCardFeedSelectorPageProps = PlatformStackScreenProps ({
- value: feed,
- text: CardUtils.getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames),
- keyForList: feed,
- isSelected: feed === selectedFeed,
- isDisabled: companyFeeds[feed]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- pendingAction: companyFeeds[feed]?.pendingAction,
- brickRoadIndicator: companyFeeds[feed]?.errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
- canShowSeveralIndicators: !!companyFeeds[feed]?.errors,
- leftElement: (
-
- ),
- }));
+ const feeds: CardFeedListItem[] = (Object.keys(companyFeeds) as CompanyCardFeed[]).map((feed) => {
+ const isFeedConnectionBroken = checkIfFeedConnectionIsBroken(allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed}`]);
+ return {
+ value: feed,
+ text: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames),
+ keyForList: feed,
+ isSelected: feed === selectedFeed,
+ isDisabled: companyFeeds[feed]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+ pendingAction: companyFeeds[feed]?.pendingAction,
+ brickRoadIndicator: isFeedConnectionBroken ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
+ canShowSeveralIndicators: isFeedConnectionBroken,
+ leftElement: (
+
+ ),
+ };
+ });
const goBack = () => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID));
const selectFeed = (feed: CardFeedListItem) => {
- Card.updateSelectedFeed(feed.value, policyID);
+ updateSelectedFeed(feed.value, policyID);
goBack();
};
@@ -95,7 +99,7 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS
title={translate('workspace.companyCards.addCards')}
icon={Expensicons.Plus}
onPress={() => {
- CompanyCards.clearAddNewCardFlow();
+ clearAddNewCardFlow();
Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.getRoute(policyID));
}}
/>
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx
index 7e4b5fe925f5..559029db3848 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx
@@ -5,17 +5,19 @@ import Button from '@components/Button';
import CaretWrapper from '@components/CaretWrapper';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
-import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {PressableWithFeedback} from '@components/Pressable';
import Text from '@components/Text';
+import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import {getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, isCustomFeed} from '@libs/CardUtils';
-import {getWorkspaceAccountID, hasPolicyFeedsError} from '@libs/PolicyUtils';
+import {getBankName, getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, isCustomFeed} from '@libs/CardUtils';
+import {getWorkspaceAccountID} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import variables from '@styles/variables';
+import {checkIfFeedConnectionIsBroken, flatAllCardsList} from '@userActions/CompanyCards';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {CompanyCardFeed} from '@src/types/onyx';
@@ -38,21 +40,21 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS
const styles = useThemeStyles();
const {translate} = useLocalize();
const theme = useTheme();
+ const StyleUtils = useStyleUtils();
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
const workspaceAccountID = getWorkspaceAccountID(policyID);
const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
+ const [allFeedsCards] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`);
const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout;
const formattedFeedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames);
const isCommercialFeed = isCustomFeed(selectedFeed);
const companyFeeds = getCompanyFeeds(cardFeeds);
const currentFeedData = companyFeeds?.[selectedFeed];
+ const bankName = getBankName(selectedFeed);
+ const isSelectedFeedConnectionBroken = checkIfFeedConnectionIsBroken(allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${selectedFeed}`]);
return (
-
+
Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.getRoute(policyID))}
@@ -70,7 +72,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS
{formattedFeedName}
- {hasPolicyFeedsError(companyFeeds, selectedFeed) && (
+ {checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, workspaceAccountID), selectedFeed) && (
-
+ {isSelectedFeedConnectionBroken && !!bankName && (
+
+
+
+ {translate('workspace.companyCards.brokenConnectionErrorFirstPart')}
+ Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_BANK_CONNECTION.getRoute(policyID, bankName, Navigation.getActiveRoute()))}
+ >
+ {translate('workspace.companyCards.brokenConnectionErrorLink')}
+
+ {translate('workspace.companyCards.brokenConnectionErrorSecondPart')}
+
+
+ )}
+
);
}
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx
index 7dc7a4d370e5..470031f2d3c8 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx
@@ -16,7 +16,7 @@ import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import {getWorkspaceAccountID, isDeletedPolicyEmployee} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
-import {openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAssignCardStepAndData} from '@userActions/CompanyCards';
+import {checkIfFeedConnectionIsBroken, openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAssignCardStepAndData} from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -55,18 +55,19 @@ function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) {
const isPending = !!selectedFeedData?.pending;
const isFeedAdded = !isPending && !isNoFeed;
const isFeedExpired = isSelectedFeedExpired(selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined);
+ const isFeedConnectionBroken = checkIfFeedConnectionIsBroken(cards);
const fetchCompanyCards = useCallback(() => {
openPolicyCompanyCardsPage(policyID, workspaceAccountID);
}, [policyID, workspaceAccountID]);
const {isOffline} = useNetwork({onReconnect: fetchCompanyCards});
- const isLoading = !isOffline && (!cardFeeds || (cardFeeds.isLoading && !cardsList));
+ const isLoading = !isOffline && (!cardFeeds || (!!cardFeeds.isLoading && !cardsList));
useFocusEffect(fetchCompanyCards);
useEffect(() => {
- if (!!isLoading || !selectedFeed || isPending) {
+ if (isLoading || !selectedFeed || isPending) {
return;
}
@@ -149,7 +150,7 @@ function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) {
cardsList={cardsList}
policyID={policyID}
handleAssignCard={handleAssignCard}
- isDisabledAssignCardButton={!selectedFeedData || !!selectedFeedData?.errors}
+ isDisabledAssignCardButton={!selectedFeedData || isFeedConnectionBroken}
/>
)}
diff --git a/src/pages/workspace/companyCards/addNew/BankConnection/index.native.tsx b/src/pages/workspace/companyCards/addNew/BankConnection/index.native.tsx
index 76bf9b2bd967..903abeb0d22f 100644
--- a/src/pages/workspace/companyCards/addNew/BankConnection/index.native.tsx
+++ b/src/pages/workspace/companyCards/addNew/BankConnection/index.native.tsx
@@ -3,7 +3,6 @@ import {ActivityIndicator} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {WebViewNavigation} from 'react-native-webview';
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';
@@ -17,17 +16,21 @@ import {checkIfNewFeedConnected} from '@libs/CardUtils';
import getUAForWebView from '@libs/getUAForWebView';
import Navigation from '@libs/Navigation/Navigation';
import {getWorkspaceAccountID} from '@libs/PolicyUtils';
+import type {PlatformStackRouteProp} from '@navigation/PlatformStackNavigation/types';
+import type {SettingsNavigatorParamList} from '@navigation/types';
import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards';
import getCompanyCardBankConnection from '@userActions/getCompanyCardBankConnection';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
type BankConnectionStepProps = {
policyID?: string;
+ route?: PlatformStackRouteProp;
};
-function BankConnection({policyID}: BankConnectionStepProps) {
+function BankConnection({policyID: policyIDFromProps, route}: BankConnectionStepProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
@@ -35,7 +38,9 @@ function BankConnection({policyID}: BankConnectionStepProps) {
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 {bankName: bankNameFromRoute, backTo, policyID: policyIDFromRoute} = route?.params ?? {};
+ const policyID = policyIDFromProps ?? policyIDFromRoute;
+ const bankName = bankNameFromRoute ?? addNewCard?.data?.selectedBank;
const url = getCompanyCardBankConnection(policyID, bankName);
const workspaceAccountID = getWorkspaceAccountID(policyID);
const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
@@ -46,6 +51,10 @@ function BankConnection({policyID}: BankConnectionStepProps) {
const renderLoading = () => ;
const handleBackButtonPress = () => {
+ if (backTo) {
+ Navigation.goBack(backTo);
+ return;
+ }
if (bankName === CONST.COMPANY_CARDS.BANKS.BREX) {
setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_BANK});
return;
@@ -85,7 +94,7 @@ function BankConnection({policyID}: BankConnectionStepProps) {
shouldEnableMaxHeight
>
diff --git a/src/pages/workspace/companyCards/addNew/BankConnection/index.tsx b/src/pages/workspace/companyCards/addNew/BankConnection/index.tsx
index e563f9020a39..fb7731589e84 100644
--- a/src/pages/workspace/companyCards/addNew/BankConnection/index.tsx
+++ b/src/pages/workspace/companyCards/addNew/BankConnection/index.tsx
@@ -1,6 +1,5 @@
import React, {useCallback, useEffect, useMemo, useState} 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';
@@ -13,25 +12,31 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {checkIfNewFeedConnected} from '@libs/CardUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getWorkspaceAccountID} from '@libs/PolicyUtils';
+import type {PlatformStackRouteProp} from '@navigation/PlatformStackNavigation/types';
+import type {SettingsNavigatorParamList} from '@navigation/types';
import {updateSelectedFeed} from '@userActions/Card';
import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards';
import getCompanyCardBankConnection from '@userActions/getCompanyCardBankConnection';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import openBankConnection from './openBankConnection';
let customWindow: Window | null = null;
type BankConnectionStepProps = {
policyID?: string;
+ route?: PlatformStackRouteProp;
};
-function BankConnection({policyID}: BankConnectionStepProps) {
+function BankConnection({policyID: policyIDFromProps, route}: BankConnectionStepProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
- const bankName: ValueOf | undefined = addNewCard?.data?.selectedBank;
+ const {bankName: bankNameFromRoute, backTo, policyID: policyIDFromRoute} = route?.params ?? {};
+ const policyID = policyIDFromProps ?? policyIDFromRoute;
+ const bankName = bankNameFromRoute ?? addNewCard?.data?.selectedBank;
const workspaceAccountID = getWorkspaceAccountID(policyID);
const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
const prevFeedsData = usePrevious(cardFeeds?.settings?.oAuthAccountDetails);
@@ -49,6 +54,10 @@ function BankConnection({policyID}: BankConnectionStepProps) {
const handleBackButtonPress = () => {
customWindow?.close();
+ if (backTo) {
+ Navigation.goBack(backTo);
+ return;
+ }
if (bankName === CONST.COMPANY_CARDS.BANKS.BREX) {
setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_BANK});
return;
@@ -88,7 +97,7 @@ function BankConnection({policyID}: BankConnectionStepProps) {
return (
;
/** Direct card feed data */
@@ -58,9 +55,6 @@ type DirectCardFeedData = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Whether any actions are pending */
pending?: boolean;
-
- /** Broken connection errors */
- errors?: OnyxCommon.Errors;
}>;
/** Card feed data */