From 262ed5bffcfef2ef6798753445ef0b8d95838573 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 19 Jan 2026 20:50:20 +0000 Subject: [PATCH 01/17] feat: add frontend error for card feed backend load issue --- src/hooks/useCardFeeds.tsx | 11 +++--- src/hooks/useCompanyCards.ts | 7 ++-- src/languages/en.ts | 3 ++ src/languages/es.ts | 3 ++ src/libs/CardFeedUtils.ts | 19 ++++++++++- src/libs/actions/CompanyCards.ts | 34 ++++++++++++++++++- .../WorkspaceCompanyCardsTable/index.tsx | 16 +++++++-- src/types/onyx/CardFeeds.ts | 19 +++++++++++ src/types/onyx/index.ts | 4 ++- 9 files changed, 104 insertions(+), 12 deletions(-) diff --git a/src/hooks/useCardFeeds.tsx b/src/hooks/useCardFeeds.tsx index 52e52bb79279a..20520b1849dfb 100644 --- a/src/hooks/useCardFeeds.tsx +++ b/src/hooks/useCardFeeds.tsx @@ -1,9 +1,10 @@ import {useMemo} from 'react'; import type {OnyxCollection, ResultMetadata} from 'react-native-onyx'; +import {getWorkspaceCardFeedsStatus} from '@libs/CardFeedUtils'; import {getCompanyCardFeedWithDomainID} from '@libs/CardUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CardFeeds, CompanyCardFeed, CompanyCardFeedWithDomainID} from '@src/types/onyx'; -import type {CustomCardFeedData, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; +import type {CardFeeds, CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CardFeedsStatus, CompanyCardFeed, CustomCardFeedData, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; import useOnyx from './useOnyx'; import useWorkspaceAccountID from './useWorkspaceAccountID'; @@ -32,7 +33,7 @@ type CombinedCardFeeds = Record; * 2. The result metadata from the Onyx collection fetch. * 3. Card feeds specific to the given policyID (or `undefined` if unavailable). */ -const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefined, ResultMetadata>, CardFeeds | undefined] => { +const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefined, ResultMetadata>, CardFeeds | undefined, CardFeedsStatus] => { const workspaceAccountID = useWorkspaceAccountID(policyID); const [allFeeds, allFeedsResult] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true}); const defaultFeed = allFeeds?.[`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`]; @@ -75,7 +76,9 @@ const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefi }, result); }, [allFeeds, policyID, workspaceAccountID]); - return [workspaceFeeds, allFeedsResult, defaultFeed]; + const workspaceCardFeedsStatus = getWorkspaceCardFeedsStatus(allFeeds); + + return [workspaceFeeds, allFeedsResult, defaultFeed, workspaceCardFeedsStatus]; }; export default useCardFeeds; diff --git a/src/hooks/useCompanyCards.ts b/src/hooks/useCompanyCards.ts index 8e4846f6d4e34..36e142c2860a8 100644 --- a/src/hooks/useCompanyCards.ts +++ b/src/hooks/useCompanyCards.ts @@ -5,7 +5,7 @@ import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CardFeeds, CardList} from '@src/types/onyx'; import type {AssignableCardsList, WorkspaceCardsList} from '@src/types/onyx/Card'; -import type {CompanyCardFeed, CompanyCardFeedWithDomainID, CompanyFeeds} from '@src/types/onyx/CardFeeds'; +import type {CardFeedsStatusByDomainID, CompanyCardFeed, CompanyCardFeedWithDomainID, CompanyFeeds} from '@src/types/onyx/CardFeeds'; import useCardFeeds from './useCardFeeds'; import type {CombinedCardFeed, CombinedCardFeeds} from './useCardFeeds'; import useCardsList from './useCardsList'; @@ -24,6 +24,7 @@ type UsCompanyCardsResult = Partial<{ feedName: CompanyCardFeedWithDomainID; cardList: AssignableCardsList; assignedCards: CardList; + workspaceCardFeedsStatus: CardFeedsStatusByDomainID; cardNames: string[]; allCardFeeds: CombinedCardFeeds; companyCardFeeds: CompanyFeeds; @@ -38,7 +39,7 @@ type UsCompanyCardsResult = Partial<{ 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 [allCardFeeds, allCardFeedsMetadata, workspaceCardFeedsStatus] = useCardFeeds(policyID); const feedName = feedNameProp ?? getSelectedFeed(lastSelectedFeed, allCardFeeds); const bankName = feedName ? getCompanyCardFeed(feedName) : undefined; @@ -69,7 +70,7 @@ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProp return {onyxMetadata}; } - return {allCardFeeds, feedName, companyCardFeeds, cardList, assignedCards, cardNames, selectedFeed, bankName, cardFeedType, onyxMetadata}; + return {allCardFeeds, feedName, companyCardFeeds, cardList, assignedCards,workspaceCardFeedsStatus, cardNames, selectedFeed, bankName, cardFeedType, onyxMetadata}; } export default useCompanyCards; diff --git a/src/languages/en.ts b/src/languages/en.ts index b908471215a9e..8fd16e496194b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4692,6 +4692,9 @@ const translations = { companyCards: { addCards: 'Add cards', selectCards: 'Select cards', + error: { + feedsCouldNotBeLoaded: 'An error occurred while loading the feeds. Please try again or contact your administrator.', + }, addNewCard: { other: 'Other', cardProviders: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 51c48fb1c8c14..d7ea04ff17ee1 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4437,6 +4437,9 @@ ${amount} para ${merchant} - ${date}`, companyCards: { addCards: 'Añadir tarjetas', selectCards: 'Seleccionar tarjetas', + error: { + feedsCouldNotBeLoaded: 'Ocurrió un error al cargar los feeds. Por favor, inténtelo de nuevo o contacte a su administrador.', + }, addNewCard: { other: 'Otros', cardProviders: { diff --git a/src/libs/CardFeedUtils.ts b/src/libs/CardFeedUtils.ts index 6aa9a7d1fad75..c23cfa6d67fd7 100644 --- a/src/libs/CardFeedUtils.ts +++ b/src/libs/CardFeedUtils.ts @@ -4,7 +4,7 @@ import type {AdditionalCardProps} from '@components/SelectionListWithSections/Se import type IllustrationsType from '@styles/theme/illustrations/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Card, CardFeeds, CardList, CompanyCardFeed, PersonalDetailsList, WorkspaceCardsList} from '@src/types/onyx'; +import type {Card, CardFeeds, CardFeedsStatus, CardFeedsStatusByDomainID, CardList, CompanyCardFeed, PersonalDetailsList, WorkspaceCardsList} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import { getBankName, @@ -508,6 +508,21 @@ function getCardFeedsForDisplayPerPolicy(allCardFeeds: OnyxCollection return cardFeedsForDisplayPerPolicy; } +function getCardFeedStatus(feed: CardFeeds | undefined): CardFeedsStatus { + return { + errors: feed?.errors, + isLoading: feed?.isLoading, + }; +} + +function getWorkspaceCardFeedsStatus(allFeeds: OnyxCollection | undefined): CardFeedsStatusByDomainID { + return Object.entries(allFeeds ?? {}).reduce((acc, [onyxKey, feeds]) => { + const domainID = Number(onyxKey.split('_').at(-1)); + acc[domainID] = getCardFeedStatus(feeds); + return acc; + }, {} as CardFeedsStatusByDomainID); +} + export type {CardFilterItem, CardFeedNamesWithType, CardFeedForDisplay}; export { buildCardsData, @@ -522,4 +537,6 @@ export { getDomainFeedData, getCardFeedsForDisplay, getCardFeedsForDisplayPerPolicy, + getCardFeedStatus, + getWorkspaceCardFeedsStatus, }; diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 441b98e71dc92..2310cfdd561da 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -851,7 +851,39 @@ function openPolicyCompanyCardsFeed(domainAccountID: number, policyID: string, f feed, }; - API.read(READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_FEED, parameters); + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, + value: { + isLoading: true, + }, + }, + ]; + + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, + value: { + isLoading: false, + errors: null, + }, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, + value: { + isLoading: false, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.companyCards.error.feedsCouldNotBeLoaded'), + }, + }, + ]; + + API.read(READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_FEED, parameters, {optimisticData, successData, failureData}); } function openAssignFeedCardPage(policyID: string, feed: CompanyCardFeed, domainOrWorkspaceAccountID: number) { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 7a6a488ec59a5..fc4fa92254425 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -2,6 +2,7 @@ import type {ListRenderItemInfo} from '@shopify/flash-list'; import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import CardFeedIcon from '@components/CardFeedIcon'; +import ErrorMessageRow from '@components/ErrorMessageRow'; import ScrollView from '@components/ScrollView'; import TableRowSkeleton from '@components/Skeletons/TableRowSkeleton'; import Table from '@components/Table'; @@ -21,6 +22,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Card, Policy} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import WorkspaceCompanyCardsTableHeaderButtons from './WorkspaceCompanyCardsTableHeaderButtons'; import WorkspaceCompanyCardTableItem from './WorkspaceCompanyCardsTableItem'; @@ -49,6 +51,7 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl feedName, cardList, assignedCards, + workspaceCardFeedsStatus, cardNames, cardFeedType, selectedFeed, @@ -66,6 +69,8 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const hasNoAssignedCard = Object.keys(assignedCards ?? {}).length === 0; const isInitiallyLoadingFeeds = isLoadingOnyxValue(allCardFeedsMetadata); + const workspaceCardFeedsErrors = workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.errors; + const isFeedsError = !isEmptyObject(workspaceCardFeedsErrors); const isNoFeed = !selectedFeed && !isInitiallyLoadingFeeds; const isFeedPending = !!selectedFeed?.pending; const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || policy?.id === undefined || isLoadingOnyxValue(lastSelectedFeedMetadata); @@ -73,8 +78,8 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const isLoadingCards = cardFeedType === 'directFeed' ? selectedFeed?.accountList === undefined : isLoadingOnyxValue(cardListMetadata) || cardList === undefined; const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); - const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed; - const showTableControls = showCards && !!selectedFeed && !isLoadingCards; + const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed && !isFeedsError; + const showTableControls = showCards && !!selectedFeed && !isLoadingCards && !isFeedsError; const isGB = countryByIp === CONST.COUNTRY.GB; const shouldShowGBDisclaimer = isGB && (isNoFeed || hasNoAssignedCard); @@ -304,6 +309,13 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl )} + {isFeedsError && ( + + )} + {showCards && ( <> {!shouldUseNarrowTableLayout && !isLoadingFeed && } diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index 204b6540b56d7..d1b1a2605c91c 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -154,6 +154,20 @@ type DomainSettings = { }; }; +/** Card feeds status */ +type CardFeedsStatus = { + /** Whether we are loading the data via the API */ + isLoading?: boolean; + + /** Collection of errors coming from BE */ + errors?: OnyxCommon.Errors; +}; + +/** + * Collection of card feeds status by domain ID + */ +type CardFeedsStatusByDomainID = Record; + /** Card feeds model, including domain settings */ type CardFeeds = { /** Feed settings */ @@ -174,6 +188,9 @@ type CardFeeds = { useTechnicalContactBillingCard?: boolean; }; + /** Errors related to all card feeds within a workspace or domain */ + errors?: OnyxCommon.Errors; + /** Whether we are loading the data via the API */ isLoading?: boolean; } & DomainSettings; @@ -251,6 +268,8 @@ export type { DirectCardFeedData, CardFeedProvider, CardFeedData, + CardFeedsStatus, + CardFeedsStatusByDomainID, CompanyFeeds, CompanyCardFeedWithDomainID, CustomCardFeedData, diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 81e89f0df9e32..c9687d6b8c6b6 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -19,7 +19,7 @@ import type Card from './Card'; import type {CardList, FailedCompanyCardAssignment, FailedCompanyCardAssignments, IssueNewCard, ProvisioningCardData, WorkspaceCardsList} from './Card'; import type CardContinuousReconciliation from './CardContinuousReconciliation'; import type CardFeeds from './CardFeeds'; -import type {AddNewCompanyCardFeed, CompanyCardFeed, CompanyCardFeedWithDomainID, DomainSettings, FundID} from './CardFeeds'; +import type {AddNewCompanyCardFeed, CardFeedsStatus, CardFeedsStatusByDomainID, CompanyCardFeed, CompanyCardFeedWithDomainID, DomainSettings, FundID} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; import type {CorpayFields, CorpayFormField} from './CorpayFields'; @@ -284,6 +284,8 @@ export type { CancellationDetails, ApprovalWorkflowOnyx, CardFeeds, + CardFeedsStatus, + CardFeedsStatusByDomainID, DomainSettings, SaveSearch, RecentSearchItem, From 35ebda1f05d558e3255d2284342f5eb104c154b8 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 19 Jan 2026 22:12:16 +0000 Subject: [PATCH 02/17] fix: improve feed and all feed errors --- src/hooks/useCardFeeds.tsx | 10 ++++-- src/hooks/useCompanyCards.ts | 4 +-- src/languages/en.ts | 3 +- src/languages/es.ts | 3 +- src/libs/actions/CompanyCards.ts | 33 +++++++++++++++---- .../WorkspaceCompanyCardsTable/index.tsx | 31 ++++++++++++----- src/types/onyx/CardFeeds.ts | 16 +++++---- 7 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/hooks/useCardFeeds.tsx b/src/hooks/useCardFeeds.tsx index 20520b1849dfb..d137a66fb5514 100644 --- a/src/hooks/useCardFeeds.tsx +++ b/src/hooks/useCardFeeds.tsx @@ -4,7 +4,7 @@ import {getWorkspaceCardFeedsStatus} from '@libs/CardFeedUtils'; import {getCompanyCardFeedWithDomainID} from '@libs/CardUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CardFeeds, CompanyCardFeedWithDomainID} from '@src/types/onyx'; -import type {CardFeedsStatus, CompanyCardFeed, CustomCardFeedData, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; +import type {CardFeedsStatus, CardFeedsStatusByDomainID, CompanyCardFeed, CustomCardFeedData, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; import useOnyx from './useOnyx'; import useWorkspaceAccountID from './useWorkspaceAccountID'; @@ -15,6 +15,8 @@ type CombinedCardFeed = CustomCardFeedData & /** Feed name */ feed: CompanyCardFeed; + + status?: CardFeedsStatus; }; type CombinedCardFeeds = Record; @@ -33,7 +35,7 @@ type CombinedCardFeeds = Record; * 2. The result metadata from the Onyx collection fetch. * 3. Card feeds specific to the given policyID (or `undefined` if unavailable). */ -const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefined, ResultMetadata>, CardFeeds | undefined, CardFeedsStatus] => { +const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefined, ResultMetadata>, CardFeeds | undefined, CardFeedsStatusByDomainID] => { const workspaceAccountID = useWorkspaceAccountID(policyID); const [allFeeds, allFeedsResult] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true}); const defaultFeed = allFeeds?.[`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`]; @@ -52,8 +54,11 @@ const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefi for (const [key, feedSettings] of Object.entries(feed.settings.companyCards)) { const feedName = key as CompanyCardFeed; + const feedOAuthAccountDetails = feed.settings.oAuthAccountDetails?.[feedName]; const feedCompanyCardNickname = feed.settings.companyCardNicknames?.[feedName]; + const status = feed.settings.cardFeedsStatus?.[feedName]; + const domainID = onyxKey.split('_').at(-1); const shouldAddFeed = domainID && (feedSettings.preferredPolicy ? feedSettings.preferredPolicy === policyID : domainID === workspaceAccountID.toString()); @@ -69,6 +74,7 @@ const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefi customFeedName: feedCompanyCardNickname, domainID: Number(domainID), feed: feedName, + status, }; } diff --git a/src/hooks/useCompanyCards.ts b/src/hooks/useCompanyCards.ts index 36e142c2860a8..5fd83a254a6c9 100644 --- a/src/hooks/useCompanyCards.ts +++ b/src/hooks/useCompanyCards.ts @@ -39,7 +39,7 @@ type UsCompanyCardsResult = Partial<{ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProps): UsCompanyCardsResult { const [lastSelectedFeed, lastSelectedFeedMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); - const [allCardFeeds, allCardFeedsMetadata, workspaceCardFeedsStatus] = useCardFeeds(policyID); + const [allCardFeeds, allCardFeedsMetadata, , workspaceCardFeedsStatus] = useCardFeeds(policyID); const feedName = feedNameProp ?? getSelectedFeed(lastSelectedFeed, allCardFeeds); const bankName = feedName ? getCompanyCardFeed(feedName) : undefined; @@ -70,7 +70,7 @@ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProp return {onyxMetadata}; } - return {allCardFeeds, feedName, companyCardFeeds, cardList, assignedCards,workspaceCardFeedsStatus, cardNames, selectedFeed, bankName, cardFeedType, onyxMetadata}; + return {allCardFeeds, feedName, companyCardFeeds, cardList, assignedCards, workspaceCardFeedsStatus, cardNames, selectedFeed, bankName, cardFeedType, onyxMetadata}; } export default useCompanyCards; diff --git a/src/languages/en.ts b/src/languages/en.ts index 8fd16e496194b..052b765e00559 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4693,7 +4693,8 @@ const translations = { addCards: 'Add cards', selectCards: 'Select cards', error: { - feedsCouldNotBeLoaded: 'An error occurred while loading the feeds. Please try again or contact your administrator.', + workspaceFeedsCouldNotBeLoaded: 'An error occurred while loading workspace card feeds. Please try again or contact your administrator.', + feedCouldNotBeLoaded: 'An error occurred while loading this feed. Please try again or contact your administrator.', }, addNewCard: { other: 'Other', diff --git a/src/languages/es.ts b/src/languages/es.ts index d7ea04ff17ee1..f09b4bc5025f1 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4438,7 +4438,8 @@ ${amount} para ${merchant} - ${date}`, addCards: 'Añadir tarjetas', selectCards: 'Seleccionar tarjetas', error: { - feedsCouldNotBeLoaded: 'Ocurrió un error al cargar los feeds. Por favor, inténtelo de nuevo o contacte a su administrador.', + workspaceFeedsCouldNotBeLoaded: 'Ocurrió un error al cargar las fuentes de tarjetas del espacio de trabajo. Por favor, inténtelo de nuevo o contacte a su administrador.', + feedCouldNotBeLoaded: 'Ocurrió un error al cargar esta fuente de tarjetas. Por favor, inténtelo de nuevo o contacte a su administrador.', }, addNewCard: { other: 'Otros', diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 2310cfdd561da..104c8221ed798 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -31,6 +31,7 @@ import type { CardFeedDetails, CompanyCardFeed, CompanyCardFeedWithDomainID, + CompanyCardFeedWithNumber, StatementPeriodEnd, StatementPeriodEndDay, } from '@src/types/onyx/CardFeeds'; @@ -823,6 +824,7 @@ function openPolicyCompanyCardsPage(policyID: string, domainOrWorkspaceAccountID key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainOrWorkspaceAccountID}`, value: { isLoading: false, + errors: null }, }, ]; @@ -833,6 +835,7 @@ function openPolicyCompanyCardsPage(policyID: string, domainOrWorkspaceAccountID key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainOrWorkspaceAccountID}`, value: { isLoading: false, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.companyCards.error.workspaceFeedsCouldNotBeLoaded'), }, }, ]; @@ -844,7 +847,7 @@ function openPolicyCompanyCardsPage(policyID: string, domainOrWorkspaceAccountID API.read(READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_PAGE, params, {optimisticData, successData, failureData}); } -function openPolicyCompanyCardsFeed(domainAccountID: number, policyID: string, feed: CompanyCardFeed) { +function openPolicyCompanyCardsFeed(domainAccountID: number, policyID: string, feed: CompanyCardFeedWithNumber) { const parameters: OpenPolicyCompanyCardsFeedParams = { domainAccountID, policyID, @@ -856,7 +859,13 @@ function openPolicyCompanyCardsFeed(domainAccountID: number, policyID: string, f onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, value: { - isLoading: true, + settings: { + cardFeedsStatus: { + [feed]: { + isLoading: true, + }, + }, + }, }, }, ]; @@ -866,8 +875,14 @@ function openPolicyCompanyCardsFeed(domainAccountID: number, policyID: string, f onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, value: { - isLoading: false, - errors: null, + settings: { + cardFeedsStatus: { + [feed]: { + isLoading: false, + errors: null + }, + }, + }, }, }, ]; @@ -877,8 +892,14 @@ function openPolicyCompanyCardsFeed(domainAccountID: number, policyID: string, f onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, value: { - isLoading: false, - errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.companyCards.error.feedsCouldNotBeLoaded'), + settings: { + cardFeedsStatus: { + [feed]: { + isLoading: false, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.companyCards.error.feedCouldNotBeLoaded'), + }, + }, + }, }, }, ]; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index fc4fa92254425..a647954be8bb1 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -69,17 +69,30 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const hasNoAssignedCard = Object.keys(assignedCards ?? {}).length === 0; const isInitiallyLoadingFeeds = isLoadingOnyxValue(allCardFeedsMetadata); + const areWorkspaceCardFeedsLoading = workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.isLoading; const workspaceCardFeedsErrors = workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.errors; - const isFeedsError = !isEmptyObject(workspaceCardFeedsErrors); + const hasWorkspaceFeedsError = !isEmptyObject(workspaceCardFeedsErrors); + + console.log({workspaceCardFeedsErrors, workspaceCardFeedsStatus, areWorkspaceCardFeedsLoading}); + + const selectedFeedStatus = selectedFeed?.status; + const selectedFeedErrors = selectedFeedStatus?.errors; + const hasSelectedFeedError = !isEmptyObject(selectedFeedErrors); + + const hasFeedErrors = hasWorkspaceFeedsError || hasSelectedFeedError; + const feedErrors = {...workspaceCardFeedsErrors, ...selectedFeedErrors}; + const isNoFeed = !selectedFeed && !isInitiallyLoadingFeeds; const isFeedPending = !!selectedFeed?.pending; - const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || policy?.id === undefined || isLoadingOnyxValue(lastSelectedFeedMetadata); + const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || policy?.id === undefined || isLoadingOnyxValue(lastSelectedFeedMetadata) || !!selectedFeedStatus?.isLoading; const isLoadingCards = cardFeedType === 'directFeed' ? selectedFeed?.accountList === undefined : isLoadingOnyxValue(cardListMetadata) || cardList === undefined; - const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); + const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata) || areWorkspaceCardFeedsLoading); + + const isShowingLoadingState = isLoadingPage || isLoadingFeed; - const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed && !isFeedsError; - const showTableControls = showCards && !!selectedFeed && !isLoadingCards && !isFeedsError; + const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed && !hasFeedErrors; + const showTableControls = showCards && !!selectedFeed && !isLoadingCards && !hasFeedErrors; const isGB = countryByIp === CONST.COUNTRY.GB; const shouldShowGBDisclaimer = isGB && (isNoFeed || hasNoAssignedCard); @@ -263,6 +276,8 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl tableRef.current?.updateSorting(activeSortingInWideLayout); }, [activeSortingInWideLayout, shouldUseNarrowTableLayout]); + console.log({hasWorkspaceFeedsError, hasSelectedFeedError, hasFeedError: hasFeedErrors, showCards, feedErrors}); + return ( : } > - {(showCards || isLoadingPage || isFeedPending) && ( + {(showCards || isLoadingPage || isFeedPending || (hasSelectedFeedError && !hasWorkspaceFeedsError)) && ( )} - {isFeedsError && ( + {hasFeedErrors && !isShowingLoadingState && ( )} diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index d1b1a2605c91c..e16bf2d44e810 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -168,6 +168,11 @@ type CardFeedsStatus = { */ type CardFeedsStatusByDomainID = Record; +/** + * Collection of card feeds status by domain ID + */ +type WorkspaceCardFeedsStatus = Record; + /** Card feeds model, including domain settings */ type CardFeeds = { /** Feed settings */ @@ -181,19 +186,16 @@ type CardFeeds = { /** Account details */ oAuthAccountDetails?: Partial>; + /** Collection of card feeds status by domain ID */ + cardFeedsStatus?: WorkspaceCardFeedsStatus; + /** Email address of the technical contact for the domain */ technicalContactEmail?: string; /** Whether to use the technical contact's billing card */ useTechnicalContactBillingCard?: boolean; }; - - /** Errors related to all card feeds within a workspace or domain */ - errors?: OnyxCommon.Errors; - - /** Whether we are loading the data via the API */ - isLoading?: boolean; -} & DomainSettings; +} & CardFeedsStatus & DomainSettings; /** Data required to be sent to add a new card */ type AddNewCardFeedData = { From 27259c85ec0ee9464f04bbbb5f9bb9fb88226427 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 19 Jan 2026 22:15:57 +0000 Subject: [PATCH 03/17] fix: don't show empty state if error --- .../workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index a647954be8bb1..e87bfab30106e 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -303,7 +303,7 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl )} - {(isLoadingPage || isFeedPending || isNoFeed) && ( + {(isLoadingPage || isFeedPending || isNoFeed) && !hasWorkspaceFeedsError && ( {isLoadingPage && } From 771ae47b890cb8e52a11cedfc4dba6b2131650b3 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 19 Jan 2026 22:19:59 +0000 Subject: [PATCH 04/17] fix: prettier --- src/libs/actions/CompanyCards.ts | 4 ++-- src/types/onyx/CardFeeds.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 104c8221ed798..37c4df933769c 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -824,7 +824,7 @@ function openPolicyCompanyCardsPage(policyID: string, domainOrWorkspaceAccountID key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainOrWorkspaceAccountID}`, value: { isLoading: false, - errors: null + errors: null, }, }, ]; @@ -879,7 +879,7 @@ function openPolicyCompanyCardsFeed(domainAccountID: number, policyID: string, f cardFeedsStatus: { [feed]: { isLoading: false, - errors: null + errors: null, }, }, }, diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index e16bf2d44e810..931f5e7a1452e 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -195,7 +195,8 @@ type CardFeeds = { /** Whether to use the technical contact's billing card */ useTechnicalContactBillingCard?: boolean; }; -} & CardFeedsStatus & DomainSettings; +} & CardFeedsStatus & + DomainSettings; /** Data required to be sent to add a new card */ type AddNewCardFeedData = { From dfca6c6838e82ae9380e8c0f5320847730683279 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 19 Jan 2026 22:22:33 +0000 Subject: [PATCH 05/17] fix: ESLint --- .../companyCards/WorkspaceCompanyCardsTable/index.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index e87bfab30106e..2ac9cc78536d4 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -69,12 +69,10 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const hasNoAssignedCard = Object.keys(assignedCards ?? {}).length === 0; const isInitiallyLoadingFeeds = isLoadingOnyxValue(allCardFeedsMetadata); - const areWorkspaceCardFeedsLoading = workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.isLoading; + const areWorkspaceCardFeedsLoading = !!workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.isLoading; const workspaceCardFeedsErrors = workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.errors; const hasWorkspaceFeedsError = !isEmptyObject(workspaceCardFeedsErrors); - console.log({workspaceCardFeedsErrors, workspaceCardFeedsStatus, areWorkspaceCardFeedsLoading}); - const selectedFeedStatus = selectedFeed?.status; const selectedFeedErrors = selectedFeedStatus?.errors; const hasSelectedFeedError = !isEmptyObject(selectedFeedErrors); @@ -263,6 +261,7 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl isNarrowLayoutRef.current = true; const activeSorting = tableRef.current?.getActiveSorting(); + // eslint-disable-next-line react-hooks/set-state-in-effect setActiveSortingInWideLayout(activeSorting); tableRef.current?.updateSorting({columnKey: 'member', order: 'asc'}); return; @@ -276,8 +275,6 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl tableRef.current?.updateSorting(activeSortingInWideLayout); }, [activeSortingInWideLayout, shouldUseNarrowTableLayout]); - console.log({hasWorkspaceFeedsError, hasSelectedFeedError, hasFeedError: hasFeedErrors, showCards, feedErrors}); - return (
Date: Mon, 19 Jan 2026 22:24:07 +0000 Subject: [PATCH 06/17] fix: expose new type --- src/types/onyx/CardFeeds.ts | 1 + src/types/onyx/index.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index 931f5e7a1452e..73528f48146ef 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -273,6 +273,7 @@ export type { CardFeedData, CardFeedsStatus, CardFeedsStatusByDomainID, + WorkspaceCardFeedsStatus, CompanyFeeds, CompanyCardFeedWithDomainID, CustomCardFeedData, diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index c9687d6b8c6b6..fc7d2b4fc1dee 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -19,7 +19,7 @@ import type Card from './Card'; import type {CardList, FailedCompanyCardAssignment, FailedCompanyCardAssignments, IssueNewCard, ProvisioningCardData, WorkspaceCardsList} from './Card'; import type CardContinuousReconciliation from './CardContinuousReconciliation'; import type CardFeeds from './CardFeeds'; -import type {AddNewCompanyCardFeed, CardFeedsStatus, CardFeedsStatusByDomainID, CompanyCardFeed, CompanyCardFeedWithDomainID, DomainSettings, FundID} from './CardFeeds'; +import type {AddNewCompanyCardFeed, CardFeedsStatus, CardFeedsStatusByDomainID, CompanyCardFeed, CompanyCardFeedWithDomainID, DomainSettings, FundID, WorkspaceCardFeedsStatus} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; import type {CorpayFields, CorpayFormField} from './CorpayFields'; @@ -286,6 +286,7 @@ export type { CardFeeds, CardFeedsStatus, CardFeedsStatusByDomainID, + WorkspaceCardFeedsStatus, DomainSettings, SaveSearch, RecentSearchItem, From 4c3b0e4b49ff18da2c5484102feb21683fcd334b Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 19 Jan 2026 22:47:47 +0000 Subject: [PATCH 07/17] fix: prettier --- src/types/onyx/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index fc7d2b4fc1dee..c218189678c3f 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -19,7 +19,16 @@ import type Card from './Card'; import type {CardList, FailedCompanyCardAssignment, FailedCompanyCardAssignments, IssueNewCard, ProvisioningCardData, WorkspaceCardsList} from './Card'; import type CardContinuousReconciliation from './CardContinuousReconciliation'; import type CardFeeds from './CardFeeds'; -import type {AddNewCompanyCardFeed, CardFeedsStatus, CardFeedsStatusByDomainID, CompanyCardFeed, CompanyCardFeedWithDomainID, DomainSettings, FundID, WorkspaceCardFeedsStatus} from './CardFeeds'; +import type { + AddNewCompanyCardFeed, + CardFeedsStatus, + CardFeedsStatusByDomainID, + CompanyCardFeed, + CompanyCardFeedWithDomainID, + DomainSettings, + FundID, + WorkspaceCardFeedsStatus, +} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; import type {CorpayFields, CorpayFormField} from './CorpayFields'; From 7f01e8f485205708a8ee40438c15e1b4ffd32423 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 20 Jan 2026 12:08:31 +0000 Subject: [PATCH 08/17] feat: implement better error UI --- src/components/BlockingViews/BlockingView.tsx | 2 +- .../BlockingViews/FullPageErrorView.tsx | 23 +++++++++-- src/components/Search/index.tsx | 1 + src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../WorkspaceCompanyCardsPage.tsx | 19 +++++++-- .../WorkspaceCompanyCardsTable/index.tsx | 39 ++++++++++++++----- src/styles/index.ts | 7 +++- 8 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/components/BlockingViews/BlockingView.tsx b/src/components/BlockingViews/BlockingView.tsx index c822a4605c796..88f4207da81e8 100644 --- a/src/components/BlockingViews/BlockingView.tsx +++ b/src/components/BlockingViews/BlockingView.tsx @@ -152,7 +152,7 @@ function BlockingView({ /> )} - {title} + {title} {CustomSubtitle} {!CustomSubtitle && ( diff --git a/src/components/BlockingViews/FullPageErrorView.tsx b/src/components/BlockingViews/FullPageErrorView.tsx index 5cd9d28fb25a3..abd1ba8924798 100644 --- a/src/components/BlockingViews/FullPageErrorView.tsx +++ b/src/components/BlockingViews/FullPageErrorView.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import type {StyleProp, TextStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -26,12 +26,28 @@ type FullPageErrorViewProps = { /** Whether we should force the full page view */ shouldForceFullScreen?: boolean; + /** The style of the title message */ + titleStyle?: StyleProp; + /** The style of the subtitle message */ subtitleStyle?: StyleProp; + + /** The style of the container */ + containerStyle?: StyleProp; }; // eslint-disable-next-line rulesdir/no-negated-variables -function FullPageErrorView({testID, children = null, shouldShow = false, title = '', subtitle = '', shouldForceFullScreen = false, subtitleStyle}: FullPageErrorViewProps) { +function FullPageErrorView({ + testID, + children = null, + shouldShow = false, + title = '', + subtitle = '', + shouldForceFullScreen = false, + subtitleStyle, + titleStyle, + containerStyle, +}: FullPageErrorViewProps) { const styles = useThemeStyles(); const illustrations = useMemoizedLazyIllustrations(['BrokenMagnifyingGlass']); @@ -39,7 +55,7 @@ function FullPageErrorView({testID, children = null, shouldShow = false, title = return ( diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index f7348a64ddbe9..fdc25c4312d41 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -1055,6 +1055,7 @@ function Search({ openPolicyCompanyCardsPage(policyID, domainOrWorkspaceAccountID), }); - useEffect(() => { + const loadPolicyCompanyCardsPage = useCallback(() => { openPolicyCompanyCardsPage(policyID, domainOrWorkspaceAccountID); }, [policyID, domainOrWorkspaceAccountID]); - const isLoading = !isOffline && (!allCardFeeds || (isFeedAdded && isLoadingOnyxValue(cardsListMetadata))); useEffect(() => { + loadPolicyCompanyCardsPage(); + }, [loadPolicyCompanyCardsPage]); + + const isLoading = !isOffline && (!allCardFeeds || (isFeedAdded && isLoadingOnyxValue(cardsListMetadata))); + + const loadPolicyCompanyCardsFeed = useCallback(() => { if (isLoading || !bankName || isFeedPending) { return; } @@ -64,7 +69,11 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const clientMemberEmails = Object.keys(getMemberAccountIDsForWorkspace(policy?.employeeList)); openWorkspaceMembersPage(policyID, clientMemberEmails); openPolicyCompanyCardsFeed(domainOrWorkspaceAccountID, policyID, bankName); - }, [bankName, isLoading, policyID, isFeedPending, domainOrWorkspaceAccountID, policy?.employeeList]); + }, [bankName, domainOrWorkspaceAccountID, isFeedPending, isLoading, policy?.employeeList, policyID]); + + useEffect(() => { + loadPolicyCompanyCardsPage(); + }, [loadPolicyCompanyCardsPage]); const {assignCard, isAssigningCardDisabled} = useAssignCard({feedName, policyID, setShouldShowOfflineModal}); @@ -85,6 +94,8 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { policy={policy} onAssignCard={assignCard} isAssigningCardDisabled={isAssigningCardDisabled} + onReloadPage={loadPolicyCompanyCardsPage} + onReloadFeed={loadPolicyCompanyCardsFeed} /> diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 2ac9cc78536d4..c4d54c36918b1 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -1,8 +1,9 @@ import type {ListRenderItemInfo} from '@shopify/flash-list'; import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; +import FullPageErrorView from '@components/BlockingViews/FullPageErrorView'; +import Button from '@components/Button'; import CardFeedIcon from '@components/CardFeedIcon'; -import ErrorMessageRow from '@components/ErrorMessageRow'; import ScrollView from '@components/ScrollView'; import TableRowSkeleton from '@components/Skeletons/TableRowSkeleton'; import Table from '@components/Table'; @@ -34,14 +35,20 @@ type WorkspaceCompanyCardsTableProps = { /** Current policy */ policy: Policy | undefined; + /** Whether to disable assign card button */ + isAssigningCardDisabled: boolean; + /** On assign card callback */ onAssignCard: (cardID: string) => void; - /** Whether to disable assign card button */ - isAssigningCardDisabled: boolean; + /** On reload page callback */ + onReloadPage: () => void; + + /** On reload feed callback */ + onReloadFeed: () => void; }; -function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabled}: WorkspaceCompanyCardsTableProps) { +function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabled, onReloadPage, onReloadFeed}: WorkspaceCompanyCardsTableProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const {translate, localeCompare} = useLocalize(); @@ -71,14 +78,16 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const areWorkspaceCardFeedsLoading = !!workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.isLoading; const workspaceCardFeedsErrors = workspaceCardFeedsStatus?.[domainOrWorkspaceAccountID]?.errors; + const firstWorkspaceFeedError = workspaceCardFeedsErrors ? Object.entries(workspaceCardFeedsErrors).at(0) : undefined; const hasWorkspaceFeedsError = !isEmptyObject(workspaceCardFeedsErrors); const selectedFeedStatus = selectedFeed?.status; const selectedFeedErrors = selectedFeedStatus?.errors; + const firstSelectedFeedError = selectedFeedErrors ? Object.entries(selectedFeedErrors).at(0) : undefined; const hasSelectedFeedError = !isEmptyObject(selectedFeedErrors); const hasFeedErrors = hasWorkspaceFeedsError || hasSelectedFeedError; - const feedErrors = {...workspaceCardFeedsErrors, ...selectedFeedErrors}; + const feedErrorToShow = firstWorkspaceFeedError ?? firstSelectedFeedError; const isNoFeed = !selectedFeed && !isInitiallyLoadingFeeds; const isFeedPending = !!selectedFeed?.pending; @@ -322,10 +331,22 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl )} {hasFeedErrors && !isShowingLoadingState && ( - + + + +
- - + +