From b6836c229041f114e249e22b9241c0f4db5443a4 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 3 Dec 2025 17:17:56 +0200 Subject: [PATCH 1/4] Fix broken connection visibility for Cards --- src/pages/settings/Wallet/PaymentMethodListItem.tsx | 2 +- .../companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodListItem.tsx b/src/pages/settings/Wallet/PaymentMethodListItem.tsx index 7beeff027b3e..d7a00d65cf6e 100644 --- a/src/pages/settings/Wallet/PaymentMethodListItem.tsx +++ b/src/pages/settings/Wallet/PaymentMethodListItem.tsx @@ -118,7 +118,7 @@ function PaymentMethodListItem({item, shouldShowDefaultBadge, threeDotsMenuItems errors={item.errors} errorRowStyles={styles.ph6} canDismissError={item.canDismissError} - shouldShowErrorMessages={false} + shouldShowErrorMessages={!!item.errors} > {!!shouldShowAssignCardButton && ( From 62edb29fa0a09fe6a2efe82ae85a952eab684335 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 8 Dec 2025 10:50:17 +0200 Subject: [PATCH 2/4] do not show additional red for card broken connection --- src/libs/CardUtils.ts | 5 +++++ src/pages/settings/Wallet/PaymentMethodList.tsx | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index c23029b15ea4..04e0bf8be158 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -780,6 +780,10 @@ function getCompanyCardFeed(feedWithDomainID: string): CompanyCardFeed { return feed as CompanyCardFeed; } +function isCardWithBrokenConnection(card: Card): boolean { + return !!card.lastScrapeResult && !CONST.COMPANY_CARDS.BROKEN_CONNECTION_IGNORED_STATUSES.includes(card.lastScrapeResult); +} + export { getAssignedCardSortKey, isExpensifyCard, @@ -828,6 +832,7 @@ export { isCardPendingIssue, isCardPendingActivate, hasPendingExpensifyCardAction, + isCardWithBrokenConnection, isExpensifyCardPendingAction, getFundIdFromSettingsKey, isCardPendingReplace, diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 016592c2d7b6..357bc44a19c8 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -22,6 +22,7 @@ import { getAssignedCardSortKey, getCardFeedIcon, getPlaidInstitutionIconUrl, + isCardWithBrokenConnection, isExpensifyCard, isExpensifyCardPendingAction, lastFourNumbersFromCardName, @@ -203,6 +204,7 @@ function PaymentMethodList({ const pressHandler = onPress as CardPressHandler; const lastFourPAN = lastFourNumbersFromCardName(card.cardName); const plaidUrl = getPlaidInstitutionIconUrl(card.bank); + const isCardBroken = isCardWithBrokenConnection(card); assignedCardsGrouped.push({ key: card.cardID.toString(), plaidUrl, @@ -217,7 +219,7 @@ function PaymentMethodList({ errors: card.errors, pendingAction: card.pendingAction, brickRoadIndicator: - card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN || card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL || !!card.errors + !isCardBroken && (card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN || card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL || !!card.errors) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, icon, From 110e0ad0ae534183be6676286ad95c9480658457 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 17 Dec 2025 16:09:59 +0200 Subject: [PATCH 3/4] use correct flow to identify broken connection for current feed --- .../settings/Wallet/PaymentMethodListItem.tsx | 17 ++++---- ...WorkspaceCompanyCardsListHeaderButtons.tsx | 41 ++++++++----------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodListItem.tsx b/src/pages/settings/Wallet/PaymentMethodListItem.tsx index 437f01fa3fbf..dca15842fb54 100644 --- a/src/pages/settings/Wallet/PaymentMethodListItem.tsx +++ b/src/pages/settings/Wallet/PaymentMethodListItem.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useRef} from 'react'; +import React, {useRef} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; @@ -101,15 +101,12 @@ function PaymentMethodListItem({item, shouldShowDefaultBadge, threeDotsMenuItems } }; - const getBadgeText = useCallback( - (listItem: PaymentMethodItem) => { - if (isAccountInSetupState(listItem)) { - return translate('common.actionRequired'); - } - return shouldShowDefaultBadge ? translate('paymentMethodList.defaultPaymentMethod') : undefined; - }, - [shouldShowDefaultBadge, translate], - ); + const getBadgeText = (listItem: PaymentMethodItem) => { + if (isAccountInSetupState(listItem)) { + return translate('common.actionRequired'); + } + return shouldShowDefaultBadge ? translate('paymentMethodList.defaultPaymentMethod') : undefined; + }; return ( ()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; @@ -81,9 +77,9 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS const companyFeeds = getCompanyFeeds(cardFeeds); const currentFeedData = companyFeeds?.[selectedFeed]; const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(feed); - const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeedData); - const hasFeedError = !!cardFeeds?.[selectedFeed]?.errors; - const isSelectedFeedConnectionBroken = checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, domainOrWorkspaceAccountID), selectedFeed) || hasFeedError; + const [cardsList] = useCardsList(selectedFeed); + const {cardList, ...cards} = cardsList ?? {}; + const isSelectedFeedConnectionBroken = checkIfFeedConnectionIsBroken(cards); const isAllowedToIssueCompanyCard = useIsAllowedToIssueCompanyCard({policyID}); const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${currentFeedData?.domainID}`, {canBeMissing: true}); @@ -111,24 +107,23 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.getRoute(policyID, selectedFeed))); }; - const secondaryActions = useMemo( - () => [ - { - icon: icons.Gear, - text: translate('common.settings'), - onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.getRoute(policyID)), - value: CONST.POLICY.SECONDARY_ACTIONS.SETTINGS, - }, - ], - [policyID, icons.Gear, translate], - ); - - const supportingText = useMemo(() => { + const getSecondaryActions = () => [ + { + icon: icons.Gear, + text: translate('common.settings'), + onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.getRoute(policyID)), + value: CONST.POLICY.SECONDARY_ACTIONS.SETTINGS, + }, + ]; + const getSupportingText = () => { const firstPart = translate(isCommercialFeed ? 'workspace.companyCards.commercialFeed' : 'workspace.companyCards.directFeed'); const domainName = domain?.email ? Str.extractEmailDomain(domain.email) : undefined; const secondPart = ` (${domainName ?? policy?.name})`; return `${firstPart}${secondPart}`; - }, [domain?.email, isCommercialFeed, policy?.name, translate]); + }; + + const secondaryActions = getSecondaryActions(); + const supportingText = getSupportingText(); return ( From f476612e426b26446cc1963fbc4f87a738ca7141 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 18 Dec 2025 12:15:52 +0200 Subject: [PATCH 4/4] show correctly dot if any feed is broken --- src/components/FeedSelector.tsx | 4 +++- .../WorkspaceCompanyCardsListHeaderButtons.tsx | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/FeedSelector.tsx b/src/components/FeedSelector.tsx index 7fa78bcbe262..a4f36cf9f374 100644 --- a/src/components/FeedSelector.tsx +++ b/src/components/FeedSelector.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -37,6 +38,7 @@ type Props = { function FeedSelector({onFeedSelect, cardIcon, shouldChangeLayout, feedName, supportingText, shouldShowRBR = false, plaidUrl = null}: Props) { const styles = useThemeStyles(); const theme = useTheme(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); return ( - + ()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; @@ -77,6 +82,9 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS const companyFeeds = getCompanyFeeds(cardFeeds); const currentFeedData = companyFeeds?.[selectedFeed]; const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(feed); + const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeedData); + const hasFeedError = !!cardFeeds?.[selectedFeed]?.errors; + const hasAnyFeedConnectionBroken = checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, domainOrWorkspaceAccountID), selectedFeed) || hasFeedError; const [cardsList] = useCardsList(selectedFeed); const {cardList, ...cards} = cardsList ?? {}; const isSelectedFeedConnectionBroken = checkIfFeedConnectionIsBroken(cards); @@ -135,6 +143,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS shouldChangeLayout={shouldChangeLayout} feedName={formattedFeedName} supportingText={supportingText} + shouldShowRBR={hasAnyFeedConnectionBroken} /> {!!shouldShowAssignCardButton && (