diff --git a/package.json b/package.json
index 0f4b18023910..f01461d4f4b6 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure",
"typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc",
"typecheck-tsgo": "tsgo --noEmit --incremental --tsBuildInfoFile tsconfig.tsgo.tsbuildinfo",
- "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=313 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto",
+ "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=292 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto",
"lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh",
"lint-watch": "npx eslint-watch --watch --changed",
"shellcheck": "./scripts/shellCheck.sh",
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx
index 79d9fba7542a..3c0ac6dc7e09 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx
@@ -1,6 +1,6 @@
import {format, parseISO} from 'date-fns';
import {Str} from 'expensify-common';
-import React, {useMemo, useRef} from 'react';
+import React, {useState} from 'react';
import {View} from 'react-native';
import ActivityIndicator from '@components/ActivityIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -32,7 +32,6 @@ import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
import {getConnectedIntegration} from '@libs/PolicyUtils';
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
-import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import Navigation from '@navigation/Navigation';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
@@ -53,51 +52,62 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
const {policyID, cardID, backTo} = route.params;
const feedName = decodeURIComponent(route.params.feed) as CompanyCardFeedWithDomainID;
const bank = getCompanyCardFeed(feedName);
- const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`);
- const [customCardNames] = useOnyx(ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES);
- const [shouldUseStagingServer = isUsingStagingApi()] = useOnyx(ONYXKEYS.SHOULD_USE_STAGING_SERVER);
- const policy = usePolicy(policyID);
- const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;
- const isUnassigningRef = useRef(false);
+
const {translate, getLocalDateFromDatetime} = useLocalize();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const illustrations = useThemeIllustrations();
const companyCardFeedIcons = useCompanyCardFeedIcons();
const expensifyIcons = useMemoizedLazyExpensifyIcons(['FallbackAvatar', 'Hourglass', 'MoneySearch', 'RemoveMembers', 'Sync', 'Trashcan']);
-
const {isOffline} = useNetwork();
- const accountingIntegrations = CONST.POLICY.CONNECTIONS.ACCOUNTING_CONNECTION_NAMES;
- const syncingAccountingIntegration = accountingIntegrations.find((integration) => integration === connectionSyncProgress?.connectionName);
- const connectedIntegration = getConnectedIntegration(policy, accountingIntegrations) ?? syncingAccountingIntegration;
const {showConfirmModal} = useConfirmModal();
+ const policy = usePolicy(policyID);
+ const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`);
+ const [customCardNames] = useOnyx(ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES);
+ const [shouldUseStagingServer = isUsingStagingApi()] = useOnyx(ONYXKEYS.SHOULD_USE_STAGING_SERVER);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [allBankCards, allBankCardsMetadata] = useCardsList(feedName);
const [cardList, cardListMetadata] = useOnyx(ONYXKEYS.CARD_LIST);
+ const [cardFeeds] = useCardFeeds(policyID);
- // Prefer feed-scoped card from WORKSPACE_CARDS_LIST to maintain proper access control
- // Only use CARD_LIST as fallback if card is being unassigned (has pendingAction: DELETE)
+ const [isUnassigning, setIsUnassigning] = useState(false);
+
+ const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;
+ const syncingAccountingIntegration = CONST.POLICY.CONNECTIONS.ACCOUNTING_CONNECTION_NAMES.find((integration) => integration === connectionSyncProgress?.connectionName);
+ const connectedIntegration = getConnectedIntegration(policy, CONST.POLICY.CONNECTIONS.ACCOUNTING_CONNECTION_NAMES) ?? syncingAccountingIntegration;
+
+ // Prefer feed-scoped card from WORKSPACE_CARDS_LIST to maintain proper access control.
+ // Only use CARD_LIST as fallback if card is being unassigned (has pendingAction: DELETE).
// This prevents showing cards from other feeds/workspaces via deep links while still
- // preventing NotHerePage flash during the unassignment flow
+ // preventing NotHerePage flash during the unassignment flow.
const feedScopedCard = allBankCards?.[cardID];
const globalCard = cardList?.[cardID];
const isCardBeingUnassigned = globalCard?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
const card = feedScopedCard ?? (isCardBeingUnassigned ? globalCard : undefined);
- const cardBank = card?.bank;
const cardholder = personalDetails?.[card?.accountID ?? CONST.DEFAULT_NUMBER_ID];
const displayName = getDisplayNameOrDefault(cardholder);
const exportMenuItem = getExportMenuItem(connectedIntegration, policyID, translate, policy, card);
- const [cardFeeds] = useCardFeeds(policyID);
const companyFeeds = getCompanyFeeds(cardFeeds);
const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, companyFeeds[feedName]);
const plaidUrl = getPlaidInstitutionIconUrl(feedName);
+ // Show "Break connection" only when Mock Bank requests target non-production APIs.
+ const isMockBank = bank?.includes(CONST.COMPANY_CARDS.BANK_CONNECTIONS.MOCK_BANK);
+ const isUsingNonProductionAPI = shouldUseStagingServer || CONFIG.IS_USING_LOCAL_WEB;
+ const shouldShowBreakConnection = isMockBank && isUsingNonProductionAPI;
+
+ const lastScrape = card?.lastScrape
+ ? format(getLocalDateFromDatetime(card.lastScrape), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING)
+ : translate('workspace.moreFeatures.companyCards.neverUpdated');
+
+ const errorRowStyles = [styles.ph5, styles.mb3];
+
const unassignCard = () => {
if (card) {
- isUnassigningRef.current = true;
+ setIsUnassigning(true);
unassignWorkspaceCompanyCard(domainOrWorkspaceAccountID, bank, card);
}
Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack());
@@ -111,25 +121,8 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, cardID, bank, card?.lastScrapeResult, true);
};
- // Show "Break connection" only when Mock Bank requests target non-production APIs.
- const isMockBank = bank?.includes(CONST.COMPANY_CARDS.BANK_CONNECTIONS.MOCK_BANK);
- const isUsingNonProductionAPI = shouldUseStagingServer || CONFIG.IS_USING_LOCAL_WEB;
- const shouldShowBreakConnection = isMockBank && isUsingNonProductionAPI;
-
- const lastScrape = useMemo(() => {
- if (!card?.lastScrape) {
- return translate('workspace.moreFeatures.companyCards.neverUpdated');
- }
- return format(getLocalDateFromDatetime(card?.lastScrape), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING);
- }, [getLocalDateFromDatetime, card?.lastScrape, translate]);
-
- const lastUpdatedActivityReasonAttributes: SkeletonSpanReasonAttributes = {
- context: 'WorkspaceCompanyCardDetailsPage',
- isLoadingLastUpdated: card?.isLoadingLastUpdated,
- };
-
- // Don't show NotFoundPage if card is being unassigned or data is still loading
- if ((!card && !isUnassigningRef.current && !isLoadingOnyxValue(allBankCardsMetadata) && !isLoadingOnyxValue(cardListMetadata)) || (isCardBeingUnassigned && !isUnassigningRef.current)) {
+ // Don't show NotFoundPage if the card is being unassigned or data is still loading.
+ if ((!card && !isUnassigning && !isLoadingOnyxValue(allBankCardsMetadata) && !isLoadingOnyxValue(cardListMetadata)) || (isCardBeingUnassigned && !isUnassigning)) {
return ;
}
@@ -156,7 +149,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
) : (
clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'cardTitle')}
>
@@ -209,7 +202,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
{exportMenuItem?.shouldShowMenuItem ? (
{
if (!exportMenuItem.exportType) {
@@ -232,7 +225,10 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
rightComponent={
}
description={translate('workspace.moreFeatures.companyCards.lastUpdated')}
@@ -241,7 +237,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
/>
clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'scrapeMinDate', true)}
>
@@ -269,7 +265,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
/>
clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'lastScrape', true)}
>