From 65f0c5d2f4ccce254b7565442dad8ef6c0552e36 Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Fri, 24 Jul 2026 04:44:12 +0100
Subject: [PATCH 01/17] add export menu item to Expensify Card details
---
...namicWorkspaceExpensifyCardDetailsPage.tsx | 36 ++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx b/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx
index 79cbd3fe8a35..6590f1b15faf 100644
--- a/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx
+++ b/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx
@@ -29,14 +29,16 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
+import {clearCompanyCardErrorField} from '@libs/actions/CompanyCards';
import {openPolicyExpensifyCardsPage} from '@libs/actions/Policy/Policy';
import navigateToCardTransactions from '@libs/CardNavigationUtils';
import {getAllCardsForWorkspace, getCardFeedTextColor, getCardHintText, getTranslationKeyForLimitType, isCardFrozen, maskCard} from '@libs/CardUtils';
+import {getLatestErrorField} from '@libs/ErrorUtils';
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {temporaryGetDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
-import {arePolicyRulesEnabled, canMemberWrite} from '@libs/PolicyUtils';
+import {arePolicyRulesEnabled, canMemberWrite, getConnectedIntegration} from '@libs/PolicyUtils';
import {getSpendRuleByCardID, getSpendRuleSummaryText} from '@libs/SpendRulesUtils';
import Navigation from '@navigation/Navigation';
@@ -62,6 +64,8 @@ import {cardByIdSelector} from '@selectors/Card';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
+import {getExportMenuItem} from '../companyCards/utils';
+
type DynamicWorkspaceExpensifyCardDetailsPageProps = PlatformStackScreenProps<
SettingsNavigatorParamList,
typeof SCREENS.WORKSPACE.DYNAMIC_EXPENSIFY_CARD_DETAILS | typeof SCREENS.EXPENSIFY_CARD.DYNAMIC_EXPENSIFY_CARD_DETAILS
@@ -117,6 +121,12 @@ function DynamicWorkspaceExpensifyCardDetailsPage({route}: DynamicWorkspaceExpen
const [allFeedsCards, allFeedsCardsResult] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
const workspaceCards = getAllCardsForWorkspace(defaultFundID, allFeedsCards, cardFeeds, expensifyCardSettings, /* includeDeactivated */ true);
+ const workspaceAccountID = policy?.policyAccountID ?? CONST.DEFAULT_NUMBER_ID;
+
+ const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`);
+ const syncingAccountingIntegration = CONST.POLICY.CONNECTIONS.ACCOUNTING_CONNECTION_NAMES.find((integration) => integration === connectionSyncProgress?.connectionName);
+ const connectedIntegration = getConnectedIntegration(policy, CONST.POLICY.CONNECTIONS.ACCOUNTING_CONNECTION_NAMES) ?? syncingAccountingIntegration;
+
const workspaceCard = workspaceCards?.[cardID];
const card = workspaceCard ?? cardFromCardList;
const currency = useCurrencyForExpensifyCard({
@@ -131,6 +141,7 @@ function DynamicWorkspaceExpensifyCardDetailsPage({route}: DynamicWorkspaceExpen
passedPersonalDetails: cardholder,
translate,
});
+ const exportMenuItem = getExportMenuItem(connectedIntegration, policyID, translate, styles, policy, card);
const translationForLimitType = getTranslationKeyForLimitType(card?.nameValuePairs?.limitType);
const remainingLimitHintTranslationKey = getLimitHintTranslationKey(card?.nameValuePairs?.limitType);
const remainingLimitHint = remainingLimitHintTranslationKey ? translate(remainingLimitHintTranslationKey, formattedAvailableSpendAmount) : undefined;
@@ -415,6 +426,29 @@ function DynamicWorkspaceExpensifyCardDetailsPage({route}: DynamicWorkspaceExpen
interactive={canWriteExpensifyCard}
/>
+ {exportMenuItem?.shouldShowMenuItem ? (
+ {
+ if (!exportMenuItem.exportType) {
+ return;
+ }
+ clearCompanyCardErrorField(Number(card?.fundID ?? workspaceAccountID), cardID, CONST.EXPENSIFY_CARD.BANK, exportMenuItem.exportType);
+ }}
+ >
+ Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.getRoute()))}
+ interactive={canWriteExpensifyCard}
+ sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.COMPANY_CARDS.CARD_EXPORT}
+ />
+
+ ) : null}
{canEditSpendRules && (
From 8d842238de699db5821564299ab89a78ab64185e Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Fri, 24 Jul 2026 04:52:55 +0100
Subject: [PATCH 02/17] update WORKSPACE_COMPANY_CARD_EXPORT route to accept
feed and cardID params and add DYNAMIC_EXPENSIFY_CARD_DETAILS entry
---
src/ROUTES.ts | 8 ++++----
.../rillet/export/RilletCardAccountCardList.tsx | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index c55bfb86f95d..e45d220687a5 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -808,12 +808,12 @@ const DYNAMIC_ROUTES = {
getRoute: (feed: CompanyCardFeedWithDomainID, cardID: string) => `company-card-details/${encodeURIComponent(feed)}/${encodeURIComponent(cardID)}` as const,
},
WORKSPACE_COMPANY_CARD_EXPORT: {
- path: 'edit/export/:cardID?',
- entryScreens: [SCREENS.WORKSPACE.DYNAMIC_COMPANY_CARD_DETAILS, SCREENS.WORKSPACE.ACCOUNTING.RILLET_CARD_ACCOUNT_CARD_LIST],
+ path: 'edit/export/:feed?/:cardID?',
+ entryScreens: [SCREENS.WORKSPACE.DYNAMIC_COMPANY_CARD_DETAILS, SCREENS.WORKSPACE.DYNAMIC_EXPENSIFY_CARD_DETAILS, SCREENS.WORKSPACE.ACCOUNTING.RILLET_CARD_ACCOUNT_CARD_LIST],
- // The cardID param is optional because some entry screens e.g. `SCREENS.WORKSPACE.DYNAMIC_COMPANY_CARD_DETAILS`
+ // The feed and cardID params are optional because some entry screens e.g. `SCREENS.WORKSPACE.DYNAMIC_COMPANY_CARD_DETAILS`
// already has the cardID in their path and we don't want to double pass the cardID.
- getRoute: (cardID?: string) => `edit/export${cardID ? `/${encodeURIComponent(cardID)}` : ''}` as const,
+ getRoute: (feed?: CardFeedWithDomainID, cardID?: string) => `edit/export${feed ? `/${encodeURIComponent(feed)}` : ''}${cardID ? `/${encodeURIComponent(cardID)}` : ''}` as const,
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE: {
path: 'assign-card/:feed/:cardID/assignee',
diff --git a/src/pages/workspace/accounting/rillet/export/RilletCardAccountCardList.tsx b/src/pages/workspace/accounting/rillet/export/RilletCardAccountCardList.tsx
index 3355d52e2065..60775dc3b1e0 100644
--- a/src/pages/workspace/accounting/rillet/export/RilletCardAccountCardList.tsx
+++ b/src/pages/workspace/accounting/rillet/export/RilletCardAccountCardList.tsx
@@ -93,7 +93,7 @@ function RilletCardAccountCardList({
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.getRoute(String(cardID))))}
+ onPress={() => Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.getRoute(feedWithDomainID, String(cardID))))}
shouldShowRightIcon
brickRoadIndicator={
areCardsCustomExportInErrorFields(
From 33fe9565ca2bc0b7ccec03334c2f03fc0398d8ec Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Fri, 24 Jul 2026 04:57:22 +0100
Subject: [PATCH 03/17] make feed and cardID params required
---
src/ROUTES.ts | 7 ++-----
.../DynamicWorkspaceCompanyCardDetailsPage.tsx | 6 +++---
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index e45d220687a5..7c4aa221ea06 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -808,12 +808,9 @@ const DYNAMIC_ROUTES = {
getRoute: (feed: CompanyCardFeedWithDomainID, cardID: string) => `company-card-details/${encodeURIComponent(feed)}/${encodeURIComponent(cardID)}` as const,
},
WORKSPACE_COMPANY_CARD_EXPORT: {
- path: 'edit/export/:feed?/:cardID?',
+ path: 'edit/export/:feed/:cardID',
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_COMPANY_CARD_DETAILS, SCREENS.WORKSPACE.DYNAMIC_EXPENSIFY_CARD_DETAILS, SCREENS.WORKSPACE.ACCOUNTING.RILLET_CARD_ACCOUNT_CARD_LIST],
-
- // The feed and cardID params are optional because some entry screens e.g. `SCREENS.WORKSPACE.DYNAMIC_COMPANY_CARD_DETAILS`
- // already has the cardID in their path and we don't want to double pass the cardID.
- getRoute: (feed?: CardFeedWithDomainID, cardID?: string) => `edit/export${feed ? `/${encodeURIComponent(feed)}` : ''}${cardID ? `/${encodeURIComponent(cardID)}` : ''}` as const,
+ getRoute: (feed: CardFeedWithDomainID, cardID: string) => `edit/export/${encodeURIComponent(feed)}/${encodeURIComponent(cardID)}` as const,
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE: {
path: 'assign-card/:feed/:cardID/assignee',
diff --git a/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx
index a0920da17692..6249528d5ded 100644
--- a/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx
+++ b/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx
@@ -71,9 +71,9 @@ import {getExportMenuItem} from './utils';
type DynamicWorkspaceCompanyCardDetailsPageProps = PlatformStackScreenProps;
function DynamicWorkspaceCompanyCardDetailsPage({route}: DynamicWorkspaceCompanyCardDetailsPageProps) {
- const {policyID, cardID} = route.params;
+ const {policyID, feed, cardID} = route.params;
const backPath = useDynamicBackPath(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.path);
- const feedName = decodeURIComponent(route.params.feed) as CompanyCardFeedWithDomainID;
+ const feedName = decodeURIComponent(feed) as CompanyCardFeedWithDomainID;
const bank = getCompanyCardFeed(feedName);
const {translate, getLocalDateFromDatetime} = useLocalize();
@@ -258,7 +258,7 @@ function DynamicWorkspaceCompanyCardDetailsPage({route}: DynamicWorkspaceCompany
title={exportMenuItem.title}
numberOfLinesTitle={2}
shouldShowRightIcon={canWriteCompanyCards}
- onPress={() => Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.getRoute()))}
+ onPress={() => Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.getRoute(feed, cardID)))}
interactive={canWriteCompanyCards}
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.COMPANY_CARDS.CARD_EXPORT}
/>
From 6713d5d17728fb54d060ba7200aca18443322711 Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Fri, 24 Jul 2026 05:09:34 +0100
Subject: [PATCH 04/17] pass feed and cardID params
---
.../DynamicWorkspaceExpensifyCardDetailsPage.tsx | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx b/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx
index 6590f1b15faf..f0b443bdec53 100644
--- a/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx
+++ b/src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx
@@ -32,7 +32,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {clearCompanyCardErrorField} from '@libs/actions/CompanyCards';
import {openPolicyExpensifyCardsPage} from '@libs/actions/Policy/Policy';
import navigateToCardTransactions from '@libs/CardNavigationUtils';
-import {getAllCardsForWorkspace, getCardFeedTextColor, getCardHintText, getTranslationKeyForLimitType, isCardFrozen, maskCard} from '@libs/CardUtils';
+import {getAllCardsForWorkspace, getCardFeedTextColor, getCardHintText, getCardFeedWithDomainID, getTranslationKeyForLimitType, isCardFrozen, maskCard} from '@libs/CardUtils';
import {getLatestErrorField} from '@libs/ErrorUtils';
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -443,7 +443,16 @@ function DynamicWorkspaceExpensifyCardDetailsPage({route}: DynamicWorkspaceExpen
title={exportMenuItem.title}
numberOfLinesTitle={2}
shouldShowRightIcon={canWriteExpensifyCard}
- onPress={() => Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.getRoute()))}
+ onPress={() =>
+ Navigation.navigate(
+ createDynamicRoute(
+ DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.getRoute(
+ getCardFeedWithDomainID(card.bank, card?.fundID ?? workspaceAccountID),
+ String(card.cardID),
+ ),
+ ),
+ )
+ }
interactive={canWriteExpensifyCard}
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.COMPANY_CARDS.CARD_EXPORT}
/>
From c481f56a924d0e1342287d4ede51142e645a777c Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Fri, 24 Jul 2026 05:17:56 +0100
Subject: [PATCH 05/17] fix ts
---
src/libs/actions/CompanyCards.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts
index 8ba9884979eb..51bc536a4024 100644
--- a/src/libs/actions/CompanyCards.ts
+++ b/src/libs/actions/CompanyCards.ts
@@ -36,6 +36,7 @@ import type {
AddNewCardFeedStep,
CardFeedData,
CardFeedDetails,
+ CardFeedWithNumber,
CompanyCardFeed,
CompanyCardFeedWithDomainID,
CompanyCardFeedWithNumber,
@@ -965,7 +966,7 @@ function setCompanyCardExportAccount(policyID: string, domainOrWorkspaceAccountI
});
}
-function clearCompanyCardErrorField(domainOrWorkspaceAccountID: number, cardID: string, bankName: CompanyCardFeedWithNumber, fieldName: string, isRootLevel?: boolean) {
+function clearCompanyCardErrorField(domainOrWorkspaceAccountID: number, cardID: string, bankName: CardFeedWithNumber, fieldName: string, isRootLevel?: boolean) {
if (isRootLevel) {
Onyx.merge(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${bankName}`, {
[cardID]: {
From 0eeac671bfade05cf46a6099fc68e04c6bb1446b Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Fri, 24 Jul 2026 05:18:31 +0100
Subject: [PATCH 06/17] clean up
---
...DynamicWorkspaceCompanyCardDetailsPage.tsx | 32 +++++++++----------
...namicWorkspaceExpensifyCardDetailsPage.tsx | 1 -
2 files changed, 16 insertions(+), 17 deletions(-)
diff --git a/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx
index 6249528d5ded..0fb3d5e792c9 100644
--- a/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx
+++ b/src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx
@@ -241,6 +241,22 @@ function DynamicWorkspaceCompanyCardDetailsPage({route}: DynamicWorkspaceCompany
interactive={false}
titleStyle={styles.walletCardNumber}
/>
+ clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'scrapeMinDate', true)}
+ >
+ Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_EDIT_TRANSACTION_START_DATE.getRoute(policyID, cardID, feedName))}
+ interactive={canWriteCompanyCards}
+ sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.COMPANY_CARDS.TRANSACTION_START_DATE}
+ />
+
{exportMenuItem?.shouldShowMenuItem ? (
) : null}
- clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'scrapeMinDate', true)}
- >
- Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_EDIT_TRANSACTION_START_DATE.getRoute(policyID, cardID, feedName))}
- interactive={canWriteCompanyCards}
- sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.COMPANY_CARDS.TRANSACTION_START_DATE}
- />
-
{canWriteCompanyCards && shouldShowBreakConnection && (
) : null}
-
{canEditSpendRules && (