diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 437a1c28665e..5b487ce69c4b 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -5173,6 +5173,8 @@ const CONST = { }, }, PERSONAL_CARDS: { + // Account-not-found is ignored for company feed health, but a personal cardholder can reconnect this card. + ACCOUNT_NOT_FOUND_SCRAPE_STATUS: 434, STEP: { SELECT_BANK: 'SelectBank', BANK_CONNECTION: 'BankConnection', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 66a9fbffe219..97c7b29df058 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -1736,7 +1736,11 @@ function isCardPendingReplace(card?: Card) { * @param card personal card to check */ function isPersonalCardBrokenConnection(card?: Card) { - return card?.lastScrapeResult && !CONST.COMPANY_CARDS.BROKEN_CONNECTION_IGNORED_STATUSES.includes(card?.lastScrapeResult); + if (card?.pendingFields?.lastScrape) { + return false; + } + + return !!card?.lastScrapeResult && (isCardConnectionBroken(card) || card.lastScrapeResult === CONST.PERSONAL_CARDS.ACCOUNT_NOT_FOUND_SCRAPE_STATUS); } function isExpensifyCardPendingAction(card?: Card, privatePersonalDetails?: PrivatePersonalDetails): boolean { diff --git a/src/libs/actions/OnyxDerived/configs/cardFeedErrors.ts b/src/libs/actions/OnyxDerived/configs/cardFeedErrors.ts index 0ece5f7b0ddc..6353ac9e401b 100644 --- a/src/libs/actions/OnyxDerived/configs/cardFeedErrors.ts +++ b/src/libs/actions/OnyxDerived/configs/cardFeedErrors.ts @@ -7,6 +7,7 @@ import { isCardConnectionBroken, isLastScrapePastDismissThreshold, isPersonalCard, + isPersonalCardBrokenConnection, } from '@libs/CardUtils'; import createOnyxDerivedValueConfig from '@userActions/OnyxDerived/createOnyxDerivedValueConfig'; @@ -85,7 +86,7 @@ export default createOnyxDerivedValueConfig({ : {}), } as Record; - const isFeedConnectionBroken = isCardConnectionBroken(card) && !isPastDismissThreshold; + const isFeedConnectionBroken = isPersonalCardBrokenConnection(card) && !isPastDismissThreshold; // Track personal cards with broken feed connection if (isFeedConnectionBroken) { personalCardsWithBrokenConnection[card.cardID] = card; diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 67aa77dee111..23a9919eda9f 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -31,7 +31,9 @@ import { isExpensifyCard, isExpensifyCardPendingAction, isExpiredCard, + isLastScrapePastDismissThreshold, isPersonalCard, + isPersonalCardBrokenConnection, isTravelCard, lastFourNumbersFromCardName, maskCardNumber, @@ -301,12 +303,14 @@ function PaymentMethodList({ } } - if (isUserPersonalCard && (!isEmptyObject(card.errors) || isCardConnectionBroken(card))) { + if (isUserPersonalCard && (!isEmptyObject(card.errors) || isPersonalCardBrokenConnection(card))) { brickRoadIndicator = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } const companyCardFeedForCard = getCompanyCardFeedWithDomainIDForCard(card); - const isCardBroken = isCardConnectionBroken(card) && !isBrokenConnectionPastDismissThreshold(card); + const isCardBroken = isUserPersonalCard + ? isPersonalCardBrokenConnection(card) && !isLastScrapePastDismissThreshold(card) + : isCardConnectionBroken(card) && !isBrokenConnectionPastDismissThreshold(card); const isCardInactiveState = isCardInactive(card); const cardConnectionStatusDisplay = getCardConnectionStatusDisplay({ shouldShowConnectionStatus, diff --git a/src/pages/settings/Wallet/PersonalCardDetailsPage.tsx b/src/pages/settings/Wallet/PersonalCardDetailsPage.tsx index 5eaaaf501624..83bd7ddaa311 100644 --- a/src/pages/settings/Wallet/PersonalCardDetailsPage.tsx +++ b/src/pages/settings/Wallet/PersonalCardDetailsPage.tsx @@ -21,7 +21,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {getActiveServer} from '@libs/ApiUtils'; import navigateToCardTransactions from '@libs/CardNavigationUtils'; -import {getCardFeedIcon, getPlaidInstitutionIconUrl, isCardConnectionBroken, isPersonalCard} from '@libs/CardUtils'; +import {getCardFeedIcon, getPlaidInstitutionIconUrl, isPersonalCard, isPersonalCardBrokenConnection} from '@libs/CardUtils'; import {getLatestErrorField} from '@libs/ErrorUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -74,7 +74,7 @@ function PersonalCardDetailsPage({route}: PersonalCardDetailsPageProps) { const card = cardList?.[cardID]; const cardBank = card?.bank ?? ''; - const isCardBroken = card ? isCardConnectionBroken(card) : false; + const isCardBroken = isPersonalCardBrokenConnection(card); const isUserPersonalCard = !!(card && isPersonalCard(card)); // Personal cards always belong to the current user, so fall back to the current user's personal details diff --git a/src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/useFixPersonalCardConnection.ts b/src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/useFixPersonalCardConnection.ts index d86828a7261c..3348b628bf28 100644 --- a/src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/useFixPersonalCardConnection.ts +++ b/src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/useFixPersonalCardConnection.ts @@ -2,7 +2,7 @@ import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import {updatePersonalCardConnection} from '@libs/actions/PersonalCards'; -import {getBankName, getPlaidInstitutionId, isCardConnectionBroken} from '@libs/CardUtils'; +import {getBankName, getPlaidInstitutionId, isPersonalCardBrokenConnection} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalCardBankConnection} from '@userActions/getCompanyCardBankConnection'; @@ -24,7 +24,7 @@ function useFixPersonalCardConnection(cardID: string) { const isPlaid = !!(card?.bank && getPlaidInstitutionId(card.bank as CompanyCardFeed)); const url = isPlaid ? null : getPersonalCardBankConnection(bankDisplayName); const country = card?.nameValuePairs?.country ?? CONST.COUNTRY.US; - const isCardBroken = card ? isCardConnectionBroken(card) : false; + const isCardBroken = isPersonalCardBrokenConnection(card); useEffect(() => { if (isLoadingOnyxValue(cardListMetadata)) { diff --git a/tests/unit/CardFeedErrorsDerivedValueTest.ts b/tests/unit/CardFeedErrorsDerivedValueTest.ts index c0f382b33588..ee3c32662a4a 100644 --- a/tests/unit/CardFeedErrorsDerivedValueTest.ts +++ b/tests/unit/CardFeedErrorsDerivedValueTest.ts @@ -384,6 +384,8 @@ describe('CardFeedErrors Derived Value', () => { const result = cardFeedErrorsConfig.compute([globalCardList, {}, {}, undefined], DERIVED_VALUE_CONTEXT); expect(result.personalCard.shouldShowRBR).toBe(true); + expect(result.personalCard.isFeedConnectionBroken).toBe(true); + expect(result.personalCardsWithBrokenConnection[CARD_IDS.card1]).toEqual(card); }); }); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 3f1a88deb292..25f677f60f7d 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -75,6 +75,7 @@ import { isExpiredCard, isMatchingCard, isPersonalCard, + isPersonalCardBrokenConnection, isTravelCardTransaction, isUkEuExpensifyCard, lastFourNumbersFromCardName, @@ -4437,6 +4438,24 @@ describe('CardUtils', () => { }); }); + describe('isPersonalCardBrokenConnection', () => { + it('returns true for account-not-found, which is actionable for personal cards but ignored for company feed health', () => { + const card: Card = {...createRandomCard(1), lastScrapeResult: CONST.PERSONAL_CARDS.ACCOUNT_NOT_FOUND_SCRAPE_STATUS}; + + expect(isPersonalCardBrokenConnection(card)).toBe(true); + }); + + it('returns false while a personal-card sync is pending', () => { + const card: Card = { + ...createRandomCard(1), + lastScrapeResult: CONST.PERSONAL_CARDS.ACCOUNT_NOT_FOUND_SCRAPE_STATUS, + pendingFields: {lastScrape: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }; + + expect(isPersonalCardBrokenConnection(card)).toBe(false); + }); + }); + describe('isLastScrapePastDismissThreshold', () => { afterEach(() => { jest.restoreAllMocks();