From f449fb4a0ad5337eecb9d9e053902cbad7632806 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 19 Dec 2025 22:56:41 -0500 Subject: [PATCH 1/8] fix: show RBRs left of checkmark --- .../SelectionList/ListItem/BaseListItem.tsx | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index 5961ef7181321..05c0d87256a8f 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -76,6 +76,10 @@ function BaseListItem({ return rightHandSideComponent; }; + const shouldShowCheckmark = !canSelectMultiple && !!item.isSelected && !rightHandSideComponent && shouldUseDefaultRightHandSideCheckmark; + + const shouldShowRBRIndicator = (!item.isSelected || !!item.canShowSeveralIndicators) && !!item.brickRoadIndicator && shouldDisplayRBR; + return ( onDismissError(item)} @@ -136,7 +140,23 @@ function BaseListItem({ > {typeof children === 'function' ? children(hovered) : children} - {!canSelectMultiple && !!item.isSelected && !rightHandSideComponent && shouldUseDefaultRightHandSideCheckmark && ( + {shouldShowRBRIndicator && ( + + + + )} + + {shouldShowRBRIndicator && !shouldShowCheckmark && ( + + + + )} + + {shouldShowCheckmark && ( ({ )} - {(!item.isSelected || !!item.canShowSeveralIndicators) && !!item.brickRoadIndicator && shouldDisplayRBR && ( - - - - )} {rightHandSideComponentRender()} From 00445ae9be53de7e2883acdf6122957e19bf2c34 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 19 Dec 2025 23:39:49 -0500 Subject: [PATCH 2/8] fix: show rbr left of checkmark --- .../SelectionList/ListItem/BaseListItem.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index 05c0d87256a8f..b711705cc9687 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -80,6 +80,8 @@ function BaseListItem({ const shouldShowRBRIndicator = (!item.isSelected || !!item.canShowSeveralIndicators) && !!item.brickRoadIndicator && shouldDisplayRBR; + const shouldShowHiddenCheckmark = shouldShowRBRIndicator && !shouldShowCheckmark; + return ( onDismissError(item)} @@ -150,15 +152,9 @@ function BaseListItem({ )} - {shouldShowRBRIndicator && !shouldShowCheckmark && ( - - - - )} - - {shouldShowCheckmark && ( + {(shouldShowCheckmark || shouldShowHiddenCheckmark) && ( From ea36d59c35ade9e5ea38f7f3f94c79e1b36d7de7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 19 Dec 2025 23:39:59 -0500 Subject: [PATCH 3/8] fix: RBR positioning in FeedSelector --- src/components/FeedSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FeedSelector.tsx b/src/components/FeedSelector.tsx index 3af19065119de..8fee5ed822b49 100644 --- a/src/components/FeedSelector.tsx +++ b/src/components/FeedSelector.tsx @@ -49,7 +49,7 @@ function FeedSelector({onFeedSelect, CardFeedIcon, feedName, supportingText, sho - + Date: Fri, 19 Dec 2025 23:40:23 -0500 Subject: [PATCH 4/8] feat: add optional `feedName` prop to `useCompanyCards`` --- src/hooks/useCompanyCards.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/hooks/useCompanyCards.ts b/src/hooks/useCompanyCards.ts index 89f6542692c46..d09fac5ef5b29 100644 --- a/src/hooks/useCompanyCards.ts +++ b/src/hooks/useCompanyCards.ts @@ -13,6 +13,11 @@ import useOnyx from './useOnyx'; type CardFeedType = ValueOf; +type UseCompanyCardsProps = { + policyID: string | undefined; + feedName?: CompanyCardFeedWithDomainID; +}; + type UsCompanyCardsResult = Partial<{ cardFeedType: CardFeedType; bankName: CompanyCardFeed; @@ -31,11 +36,11 @@ type UsCompanyCardsResult = Partial<{ }; }; -function useCompanyCards(policyID?: string): UsCompanyCardsResult { +function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProps): UsCompanyCardsResult { const [lastSelectedFeed, lastSelectedFeedMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); const [allCardFeeds, allCardFeedsMetadata] = useCardFeeds(policyID); - const feedName = getSelectedFeed(lastSelectedFeed, allCardFeeds); + const feedName = feedNameProp ?? getSelectedFeed(lastSelectedFeed, allCardFeeds); const bankName = feedName ? getCompanyCardFeed(feedName) : undefined; const [cardsList, cardListMetadata] = useCardsList(feedName); From 5e7388baf58ee601c551d36942c79bf6649e14de Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 19 Dec 2025 23:40:47 -0500 Subject: [PATCH 5/8] fix: failed card assignments not unique --- src/libs/actions/CompanyCards.ts | 20 +++++++++++++++---- .../WorkspaceCompanyCardsTableItem.tsx | 8 ++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index d7b2b49841c87..898230b9ab191 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -309,7 +309,13 @@ function deleteWorkspaceCompanyCardFeed(policyID: string, domainOrWorkspaceAccou API.write(WRITE_COMMANDS.DELETE_COMPANY_CARD_FEED, parameters, {optimisticData, successData, failureData}); } -function assignWorkspaceCompanyCard(policy: OnyxEntry, domainOrWorkspaceAccountID: number, translate: LocaleContextProps['translate'], data?: Partial) { +function assignWorkspaceCompanyCard( + policy: OnyxEntry, + domainOrWorkspaceAccountID: number, + translate: LocaleContextProps['translate'], + feed: CompanyCardFeedWithDomainID, + data?: Partial, +) { if (!data || !policy?.id) { return; } @@ -318,6 +324,8 @@ function assignWorkspaceCompanyCard(policy: OnyxEntry, domainOrWorkspace const optimisticCardAssignedReportAction = ReportUtils.buildOptimisticCardAssignedReportAction(assigneeDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID); const failedCardAssignment: FailedCompanyCardAssignment = { + domainOrWorkspaceAccountID, + feed, cardholder, cardName, cardNumber: encryptedCardNumber, @@ -373,7 +381,7 @@ function assignWorkspaceCompanyCard(policy: OnyxEntry, domainOrWorkspace }, { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}`, + key: `${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}_${feed}`, value: { [encryptedCardNumber]: failedCardAssignment, }, @@ -464,8 +472,12 @@ function unassignWorkspaceCompanyCard(domainOrWorkspaceAccountID: number, bankNa API.write(WRITE_COMMANDS.UNASSIGN_COMPANY_CARD, parameters, onyxData); } -function resetFailedWorkspaceCompanyCardAssignment(domainOrWorkspaceAccountID: number, cardNumber: string) { - Onyx.merge(`${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}`, { +function resetFailedWorkspaceCompanyCardAssignment(domainOrWorkspaceAccountID: number, feed: CompanyCardFeedWithDomainID | undefined, cardNumber: string) { + if (!feed) { + return; + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}_${feed}`, { [cardNumber]: null, }); } diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableItem.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableItem.tsx index b6adc6227633f..5ae5f761cd4d1 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableItem.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableItem.tsx @@ -17,7 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import {getDefaultAvatarURL} from '@libs/UserAvatarUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import type {Card, CompanyCardFeed, FailedCompanyCardAssignment, PersonalDetails} from '@src/types/onyx'; +import type {Card, CompanyCardFeed, CompanyCardFeedWithDomainID, FailedCompanyCardAssignment, PersonalDetails} from '@src/types/onyx'; type WorkspaceCompanyCardTableItemData = { /** Card number */ @@ -49,6 +49,9 @@ type WorkspaceCompanyCardTableItemProps = { /** Policy ID */ policyID: string; + /** Feed name */ + feed: CompanyCardFeedWithDomainID | undefined; + /** Domain or workspace account ID */ domainOrWorkspaceAccountID: number; @@ -74,6 +77,7 @@ type WorkspaceCompanyCardTableItemProps = { function WorkspaceCompanyCardTableItem({ item, policyID, + feed, domainOrWorkspaceAccountID, CardFeedIcon, isPlaidCardFeed, @@ -115,7 +119,7 @@ function WorkspaceCompanyCardTableItem({ return; } - resetFailedWorkspaceCompanyCardAssignment(domainOrWorkspaceAccountID, cardName); + resetFailedWorkspaceCompanyCardAssignment(domainOrWorkspaceAccountID, feed, cardName); }; const assignCard = () => onAssignCard(cardName); From f54593053ac8531b78cf624e9f5118a12761bcb1 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 19 Dec 2025 23:40:52 -0500 Subject: [PATCH 6/8] Update ConfirmationStep.tsx --- .../workspace/companyCards/assignCard/ConfirmationStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx index 7439dc0282a36..a2f21efb0b4c9 100644 --- a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx @@ -93,7 +93,7 @@ function ConfirmationStep({route}: ConfirmationStepProps) { Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_BROKEN_CARD_FEED_CONNECTION.getRoute(policyID, feed)); return; } - assignWorkspaceCompanyCard(policy, domainOrWorkspaceAccountID, translate, {...cardToAssign, cardholder, bankName}); + assignWorkspaceCompanyCard(policy, domainOrWorkspaceAccountID, translate, feed, {...cardToAssign, cardholder, bankName}); }; const editStep = (step: string) => { From c5533bf48b7032389f3ac824f0fe0cdaf6ad73fe Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 19 Dec 2025 23:41:09 -0500 Subject: [PATCH 7/8] fix: implement RBR --- src/pages/workspace/WorkspaceInitialPage.tsx | 31 +++++++++- .../WorkspaceCompanyCardFeedSelectorPage.tsx | 43 +++++--------- .../WorkspaceCompanyCardsPage.tsx | 2 +- ...orkspaceCompanyCardsTableHeaderButtons.tsx | 29 ++------- .../WorkspaceCompanyCardsTable/index.tsx | 5 +- .../companyCards/hooks/useCardFeedErrors.ts | 59 +++++++++++++++++++ src/types/onyx/Card.ts | 7 +++ 7 files changed, 119 insertions(+), 57 deletions(-) create mode 100644 src/pages/workspace/companyCards/hooks/useCardFeedErrors.ts diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 8939aa5af66cc..fd45de0c07683 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -14,6 +14,8 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useCardFeeds from '@hooks/useCardFeeds'; +import type {CompanyCardFeedWithDomainID} from '@hooks/useCardFeeds'; +import useCompanyCards from '@hooks/useCompanyCards'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useGetReceiptPartnersIntegrationData from '@hooks/useGetReceiptPartnersIntegrationData'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; @@ -58,6 +60,7 @@ import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type {PolicyFeatureName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; +import useCompanyCardFeedErrors from './companyCards/hooks/useCardFeedErrors'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -210,6 +213,11 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac const [highlightedFeature, setHighlightedFeature] = useState(undefined); + const {companyCardFeeds} = useCompanyCards({policyID}); + const companyCardFeedNames = Object.keys(companyCardFeeds ?? {}) as CompanyCardFeedWithDomainID[]; + const {getCardFeedErrors} = useCompanyCardFeedErrors({policyID}); + const hasCompanyCardFeedError = companyCardFeedNames.some((feed) => getCardFeedErrors(feed).shouldShowRBR); + const workspaceMenuItems: WorkspaceMenuItem[] = useMemo(() => { const protectedMenuItems: WorkspaceMenuItem[] = []; @@ -337,7 +345,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac icon: expensifyIcons.CreditCard, action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)))), screenName: SCREENS.WORKSPACE.COMPANY_CARDS, - brickRoadIndicator: hasBrokenFeedConnection ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + brickRoadIndicator: hasCompanyCardFeedError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, highlighted: highlightedFeature === CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED, }); } @@ -391,7 +399,23 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac return menuItems; }, [ - expensifyIcons, + expensifyIcons.Document, + expensifyIcons.Gear, + expensifyIcons.Building, + expensifyIcons.Users, + expensifyIcons.Sync, + expensifyIcons.Receipt, + expensifyIcons.Folder, + expensifyIcons.Tag, + expensifyIcons.Coins, + expensifyIcons.Workflows, + expensifyIcons.Feed, + expensifyIcons.Car, + expensifyIcons.LuggageWithLines, + expensifyIcons.ExpensifyCard, + expensifyIcons.CreditCard, + expensifyIcons.CalendarSolid, + expensifyIcons.InvoiceGeneric, singleExecution, waitForNavigate, featureStates, @@ -402,11 +426,12 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac policyID, hasSyncError, highlightedFeature, - hasPolicyCategoryError, shouldShowEnterCredentialsError, + hasPolicyCategoryError, allFeedsCards, workspaceAccountID, cardsDomainIDs, + hasCompanyCardFeedError, ]); // We only update feature states if they aren't pending. diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx index abce9b520dd08..242a49502ee59 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx @@ -8,9 +8,9 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/ListItem/RadioListItem'; import type {ListItem} from '@components/SelectionList/types'; -import useCardFeeds from '@hooks/useCardFeeds'; import type {CombinedCardFeed, CompanyCardFeedWithDomainID} from '@hooks/useCardFeeds'; import {useCompanyCardFeedIcons} from '@hooks/useCompanyCardIcons'; +import useCompanyCards from '@hooks/useCompanyCards'; import useIsBlockedToAddFeed from '@hooks/useIsBlockedToAddFeed'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -18,16 +18,7 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; -import { - checkIfFeedConnectionIsBroken, - filterInactiveCards, - getCardFeedIcon, - getCompanyFeeds, - getCustomOrFormattedFeedName, - getDomainOrWorkspaceAccountID, - getPlaidInstitutionIconUrl, - getSelectedFeed, -} from '@libs/CardUtils'; +import {getCardFeedIcon, getCustomOrFormattedFeedName, getPlaidInstitutionIconUrl} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import Navigation from '@navigation/Navigation'; @@ -40,6 +31,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {CompanyCardFeed} from '@src/types/onyx'; +import useCompanyCardFeedErrors from './hooks/useCardFeedErrors'; type CardFeedListItem = ListItem & { /** Combined feed key */ @@ -54,41 +46,36 @@ type WorkspaceCompanyCardFeedSelectorPageProps = PlatformStackScreenProps).map(([key, feedSettings]) => { - const filteredFeedCards = filterInactiveCards( - allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${getDomainOrWorkspaceAccountID(workspaceAccountID, feedSettings)}_${feedSettings.feed}`], - ); - const isFeedConnectionBroken = checkIfFeedConnectionIsBroken(filteredFeedCards); + const {companyCardFeeds, feedName: selectedFeedName} = useCompanyCards({policyID}); + const {getCardFeedErrors} = useCompanyCardFeedErrors({policyID, feedName: selectedFeedName}); + + const feeds: CardFeedListItem[] = (Object.entries(companyCardFeeds ?? {}) as Array<[CompanyCardFeedWithDomainID, CombinedCardFeed]>).map(([feedName, feedSettings]) => { const plaidUrl = getPlaidInstitutionIconUrl(feedSettings.feed); const domain = allDomains?.[`${ONYXKEYS.COLLECTION.DOMAIN}${feedSettings.domainID}`]; const domainName = domain?.email ? Str.extractEmailDomain(domain.email) : undefined; + const {shouldShowRBR} = getCardFeedErrors(feedName); + return { - value: key, + value: feedName, feed: feedSettings.feed, alternateText: domainName ?? policy?.name, text: getCustomOrFormattedFeedName(feedSettings.feed, feedSettings.customFeedName), - keyForList: key, - isSelected: key === selectedFeed, + keyForList: feedName, + isSelected: feedName === selectedFeedName, isDisabled: feedSettings.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, pendingAction: feedSettings.pendingAction, - brickRoadIndicator: isFeedConnectionBroken ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, - canShowSeveralIndicators: isFeedConnectionBroken, + brickRoadIndicator: shouldShowRBR ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + canShowSeveralIndicators: shouldShowRBR, leftElement: plaidUrl ? ( { if (!feedName) { return; @@ -130,7 +113,7 @@ function WorkspaceCompanyCardsTableHeaderButtons({policyID, feedName, isLoading, CardFeedIcon={CardFeedIcon} feedName={formattedFeedName} supportingText={supportingText} - shouldShowRBR={checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, domainOrWorkspaceAccountID), feedName)} + shouldShowRBR={shouldShowRBR} /> )} @@ -161,7 +144,7 @@ function WorkspaceCompanyCardsTableHeaderButtons({policyID, feedName, isLoading, - {isSelectedFeedConnectionBroken && !!bankName && ( + {(isFeedConnectionBroken || hasFeedError) && ( CompanyCardFeedErrors; +}; + +function useCompanyCardFeedErrors({policyID, feedName}: UseCompanyCardFeedErrorsProps): UseCompanyCardFeedErrorsResult { + const workspaceAccountID = useWorkspaceAccountID(policyID); + + const {companyCardFeeds} = useCompanyCards({policyID, feedName}); + const [allFeedsCards] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`, {canBeMissing: false}); + const [failedCompanyCardAssignmentsPerFeed] = useOnyx(ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS, {canBeMissing: true}); + + const getCardFeedErrors = (feedNameFn: CompanyCardFeedWithDomainID | undefined): CompanyCardFeedErrors => { + const bankName = getCompanyCardFeed(feedNameFn); + const selectedFeed = bankName && companyCardFeeds?.[bankName]; + const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, selectedFeed); + + const hasFailedCardAssignments = !isEmptyObject( + failedCompanyCardAssignmentsPerFeed?.[`${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}_${feedNameFn ?? ''}`], + ); + const hasFeedError = feedNameFn ? !!selectedFeed?.errors : false; + const isFeedConnectionBroken = checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, domainOrWorkspaceAccountID), feedNameFn); + + const shouldShowRBR = hasFailedCardAssignments || hasFeedError || isFeedConnectionBroken; + + return { + shouldShowRBR, + hasFailedCardAssignments, + hasFeedError, + isFeedConnectionBroken, + }; + }; + + return { + ...getCardFeedErrors(feedName), + getCardFeedErrors, + }; +} + +export default useCompanyCardFeedErrors; diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index 73f65a038a030..05e10fe6518f0 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -1,5 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import type {CompanyCardFeedWithDomainID} from './CardFeeds'; import type * as OnyxCommon from './OnyxCommon'; import type PersonalDetails from './PersonalDetails'; @@ -293,6 +294,12 @@ type WorkspaceCardsList = CardList & { * Pending action for a company card assignment */ type FailedCompanyCardAssignment = { + /** The domain or workspace account ID */ + domainOrWorkspaceAccountID: number; + + /** The name of the feed */ + feed: CompanyCardFeedWithDomainID; + /** Cardholder personal details */ cardholder?: PersonalDetails; From b690575928ebd28c69c0240c655376602d82dec8 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 19 Dec 2025 23:48:36 -0500 Subject: [PATCH 8/8] fix: always show RBR next to FeedSelector for any feed errors --- src/pages/workspace/WorkspaceInitialPage.tsx | 22 ++----------------- ...orkspaceCompanyCardsTableHeaderButtons.tsx | 6 +++-- .../hooks/useHasWorkspaceCompanyCardErrors.ts | 17 ++++++++++++++ 3 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 src/pages/workspace/companyCards/hooks/useHasWorkspaceCompanyCardErrors.ts diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index fd45de0c07683..87de40a152876 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -13,9 +13,6 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; -import useCardFeeds from '@hooks/useCardFeeds'; -import type {CompanyCardFeedWithDomainID} from '@hooks/useCardFeeds'; -import useCompanyCards from '@hooks/useCompanyCards'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useGetReceiptPartnersIntegrationData from '@hooks/useGetReceiptPartnersIntegrationData'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; @@ -32,7 +29,6 @@ import {confirmReadyToOpenApp} from '@libs/actions/App'; import {isConnectionInProgress} from '@libs/actions/connections'; import {shouldShowQBOReimbursableExportDestinationAccountError} from '@libs/actions/connections/QuickbooksOnline'; import {clearErrors, openPolicyInitialPage, removeWorkspace} from '@libs/actions/Policy/Policy'; -import {checkIfFeedConnectionIsBroken, flatAllCardsList, getCompanyFeeds} from '@libs/CardUtils'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -60,7 +56,7 @@ import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type {PolicyFeatureName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; -import useCompanyCardFeedErrors from './companyCards/hooks/useCardFeedErrors'; +import useHasWorkspaceCompanyCardErrors from './companyCards/hooks/useHasWorkspaceCompanyCardErrors'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -116,17 +112,11 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac ] as const); const policy = policyDraft?.id ? policyDraft : policyProp; - const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const hasPolicyCreationError = policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && !isEmptyObject(policy.errors); const isFocused = useIsFocused(); - const [cardFeeds] = useCardFeeds(policy?.id); - const [allFeedsCards] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`, {canBeMissing: true}); const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`, {canBeMissing: true}); const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector, canBeMissing: false}); const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params?.policyID}`, {canBeMissing: true}); - const cardsDomainIDs = Object.values(getCompanyFeeds(cardFeeds)) - .map((data) => data.domainID) - .filter((domainID): domainID is number => !!domainID); const {login, accountID} = useCurrentUserPersonalDetails(); const hasSyncError = shouldShowSyncError(policy, isConnectionInProgress(connectionSyncProgress, policy)); const {shouldShowEnterCredentialsError} = useGetReceiptPartnersIntegrationData(policy?.id); @@ -213,10 +203,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac const [highlightedFeature, setHighlightedFeature] = useState(undefined); - const {companyCardFeeds} = useCompanyCards({policyID}); - const companyCardFeedNames = Object.keys(companyCardFeeds ?? {}) as CompanyCardFeedWithDomainID[]; - const {getCardFeedErrors} = useCompanyCardFeedErrors({policyID}); - const hasCompanyCardFeedError = companyCardFeedNames.some((feed) => getCardFeedErrors(feed).shouldShowRBR); + const hasCompanyCardFeedError = useHasWorkspaceCompanyCardErrors({policyID}); const workspaceMenuItems: WorkspaceMenuItem[] = useMemo(() => { const protectedMenuItems: WorkspaceMenuItem[] = []; @@ -338,8 +325,6 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac } if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]) { - const hasBrokenFeedConnection = checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, workspaceAccountID, cardsDomainIDs)); - protectedMenuItems.push({ translationKey: 'workspace.common.companyCards', icon: expensifyIcons.CreditCard, @@ -428,9 +413,6 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac highlightedFeature, shouldShowEnterCredentialsError, hasPolicyCategoryError, - allFeedsCards, - workspaceAccountID, - cardsDomainIDs, hasCompanyCardFeedError, ]); diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx index eef4e797186a7..7730add244a53 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx @@ -20,6 +20,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, isCustomFeed} from '@libs/CardUtils'; import Navigation from '@navigation/Navigation'; import useCompanyCardFeedErrors from '@pages/workspace/companyCards/hooks/useCardFeedErrors'; +import useHasWorkspaceCompanyCardErrors from '@pages/workspace/companyCards/hooks/useHasWorkspaceCompanyCardErrors'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -62,7 +63,8 @@ function WorkspaceCompanyCardsTableHeaderButtons({policyID, feedName, isLoading, const currentFeedData = feedName ? companyFeeds?.[feedName] : undefined; const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${currentFeedData?.domainID}`, {canBeMissing: true}); - const {shouldShowRBR, hasFeedError, isFeedConnectionBroken} = useCompanyCardFeedErrors({policyID, feedName}); + const {hasFeedError, isFeedConnectionBroken} = useCompanyCardFeedErrors({policyID, feedName}); + const hasCompanyCardFeedError = useHasWorkspaceCompanyCardErrors({policyID}); const openBankConnection = () => { if (!feedName) { @@ -113,7 +115,7 @@ function WorkspaceCompanyCardsTableHeaderButtons({policyID, feedName, isLoading, CardFeedIcon={CardFeedIcon} feedName={formattedFeedName} supportingText={supportingText} - shouldShowRBR={shouldShowRBR} + shouldShowRBR={hasCompanyCardFeedError} /> )} diff --git a/src/pages/workspace/companyCards/hooks/useHasWorkspaceCompanyCardErrors.ts b/src/pages/workspace/companyCards/hooks/useHasWorkspaceCompanyCardErrors.ts new file mode 100644 index 0000000000000..e1f5546ea9ed2 --- /dev/null +++ b/src/pages/workspace/companyCards/hooks/useHasWorkspaceCompanyCardErrors.ts @@ -0,0 +1,17 @@ +import type {CompanyCardFeedWithDomainID} from '@hooks/useCardFeeds'; +import useCompanyCards from '@hooks/useCompanyCards'; +import useCompanyCardFeedErrors from './useCardFeedErrors'; + +type UseHasWorkspaceCompanyCardErrorsProps = { + policyID: string | undefined; +}; + +function useHasWorkspaceCompanyCardErrors({policyID}: UseHasWorkspaceCompanyCardErrorsProps): boolean { + const {companyCardFeeds} = useCompanyCards({policyID}); + const companyCardFeedNames = Object.keys(companyCardFeeds ?? {}) as CompanyCardFeedWithDomainID[]; + const {getCardFeedErrors} = useCompanyCardFeedErrors({policyID}); + const hasCompanyCardFeedError = companyCardFeedNames.some((feed) => getCardFeedErrors(feed).shouldShowRBR); + return hasCompanyCardFeedError; +} + +export default useHasWorkspaceCompanyCardErrors;