diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 5a0fe86933af..ab936fdfae2e 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3345,6 +3345,7 @@ const CONST = { AMEX_FILE_DOWNLOAD: 'americanexpressfd.us', CSV: 'ccupload', }, + FEED_KEY_SEPARATOR: '#', STEP_NAMES: ['1', '2', '3', '4'], STEP: { BANK_CONNECTION: 'BankConnection', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index fb7c1526f162..22af17fee262 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1090,7 +1090,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean; - [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeed; + [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeedWithDomainID; [ONYXKEYS.COLLECTION.LAST_SELECTED_EXPENSIFY_CARD_FEED]: OnyxTypes.FundID; [ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST]: OnyxTypes.CardOnWaitlist; [ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard; diff --git a/src/hooks/useCardFeeds.tsx b/src/hooks/useCardFeeds.tsx index a926c0a9db18..46282e29dae8 100644 --- a/src/hooks/useCardFeeds.tsx +++ b/src/hooks/useCardFeeds.tsx @@ -1,17 +1,29 @@ import {useMemo} from 'react'; import type {OnyxCollection, ResultMetadata} from 'react-native-onyx'; +import {getCompanyCardFeedWithDomainID} from '@libs/CardUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CardFeeds, CompanyCardFeed} from '@src/types/onyx'; +import type {CardFeeds, CompanyCardFeed, CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CustomCardFeedData, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; import useOnyx from './useOnyx'; import useWorkspaceAccountID from './useWorkspaceAccountID'; +type CombinedCardFeed = CustomCardFeedData & + Partial & { + /** Custom feed name, originally coming from settings.companyCardNicknames */ + customFeedName?: string; + + /** Feed name */ + feed: CompanyCardFeed; + }; + +type CombinedCardFeeds = Record; + /** * This is a custom hook that combines workspace and domain card feeds for a given policy. * * This hook: * - Gets all available feeds (ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER) from Onyx. * - Extracts and compiles card feeds data including only feeds where the `preferredPolicy` matches the `policyID`. - * - Merges a workspace feed with relevant domain feeds. * * @param policyID - The workspace policyID to filter and construct card feeds for. * @returns - @@ -20,7 +32,7 @@ import useWorkspaceAccountID from './useWorkspaceAccountID'; * 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): [CardFeeds | undefined, ResultMetadata>, CardFeeds | undefined] => { +const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefined, ResultMetadata>, CardFeeds | undefined] => { 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}`]; @@ -30,18 +42,9 @@ const useCardFeeds = (policyID: string | undefined): [CardFeeds | undefined, Res return undefined; } - const {companyCards = {}, companyCardNicknames = {}, oAuthAccountDetails = {}} = defaultFeed?.settings ?? {}; + const result: CombinedCardFeeds = {}; - const result: CardFeeds & {settings: Required} = { - settings: { - companyCards: {...companyCards}, - companyCardNicknames: {...companyCardNicknames}, - oAuthAccountDetails: {...oAuthAccountDetails}, - }, - isLoading: defaultFeed?.isLoading, - }; - - return Object.entries(allFeeds).reduce}>((acc, [onyxKey, feed]) => { + return Object.entries(allFeeds).reduce((acc, [onyxKey, feed]) => { if (!feed?.settings?.companyCards) { return acc; } @@ -49,29 +52,30 @@ const useCardFeeds = (policyID: string | undefined): [CardFeeds | undefined, Res Object.entries(feed.settings.companyCards).forEach(([key, feedSettings]) => { const feedName = key as CompanyCardFeed; const feedOAuthAccountDetails = feed.settings.oAuthAccountDetails?.[feedName]; - const feedCompanyCardNicknames = feed.settings.companyCardNicknames?.[feedName]; + const feedCompanyCardNickname = feed.settings.companyCardNicknames?.[feedName]; + const domainID = onyxKey.split('_').at(-1); - if (feedSettings.preferredPolicy !== policyID || acc.settings.companyCards[feedName]) { + if (feedSettings.preferredPolicy !== policyID || !domainID) { return; } - const domainID = onyxKey.split('_').at(-1); - - acc.settings.companyCards[feedName] = {...feedSettings, domainID: domainID ? Number(domainID) : undefined}; + const combinedFeedKey = getCompanyCardFeedWithDomainID(feedName, domainID); - if (feedOAuthAccountDetails) { - acc.settings.oAuthAccountDetails[feedName] = feedOAuthAccountDetails; - } - if (feedCompanyCardNicknames) { - acc.settings.companyCardNicknames[feedName] = feedCompanyCardNicknames; - } + acc[combinedFeedKey] = { + ...feedSettings, + ...feedOAuthAccountDetails, + customFeedName: feedCompanyCardNickname, + domainID: Number(domainID), + feed: feedName, + }; }); return acc; }, result); - }, [allFeeds, defaultFeed?.isLoading, defaultFeed?.settings, policyID]); + }, [allFeeds, policyID]); return [workspaceFeeds, allFeedsResult, defaultFeed]; }; export default useCardFeeds; +export type {CombinedCardFeeds, CompanyCardFeedWithDomainID, CombinedCardFeed}; diff --git a/src/hooks/useCardsList.tsx b/src/hooks/useCardsList.tsx index 769144a1a02b..934d1cb63b6d 100644 --- a/src/hooks/useCardsList.tsx +++ b/src/hooks/useCardsList.tsx @@ -1,18 +1,14 @@ import type {ResultMetadata} from 'react-native-onyx'; -import {filterInactiveCards, getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; +import {filterInactiveCards} from '@libs/CardUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CardList, CompanyCardFeed} from '@src/types/onyx'; -import useCardFeeds from './useCardFeeds'; +import type {CardList, CompanyCardFeedWithDomainID} from '@src/types/onyx'; import useOnyx from './useOnyx'; -import useWorkspaceAccountID from './useWorkspaceAccountID'; -/* Custom hook that retrieves a list of company cards for the given policy and selected feed. */ -const useCardsList = (policyID: string | undefined, selectedFeed: CompanyCardFeed | undefined): [CardList | undefined, ResultMetadata] => { - const workspaceAccountID = useWorkspaceAccountID(policyID); - const [cardFeeds] = useCardFeeds(policyID); - const companyCards = getCompanyFeeds(cardFeeds); - const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, selectedFeed ? companyCards[selectedFeed] : undefined); - const [cardsList, cardsListMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${selectedFeed}`, { +/* Custom hook that retrieves a list of company cards for the given selected feed. */ +const useCardsList = (selectedFeed: CompanyCardFeedWithDomainID | undefined): [CardList | undefined, ResultMetadata] => { + const [feed, domainOrWorkspaceAccountID] = selectedFeed?.split(CONST.COMPANY_CARD.FEED_KEY_SEPARATOR) ?? []; + const [cardsList, cardsListMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${feed}`, { selector: filterInactiveCards, canBeMissing: true, }); diff --git a/src/hooks/useIsAllowedToIssueCompanyCard.ts b/src/hooks/useIsAllowedToIssueCompanyCard.ts index 148dc0433065..050506c12151 100644 --- a/src/hooks/useIsAllowedToIssueCompanyCard.ts +++ b/src/hooks/useIsAllowedToIssueCompanyCard.ts @@ -17,7 +17,11 @@ function useIsAllowedToIssueCompanyCard({policyID}: {policyID?: string}) { const selectedFeedData = selectedFeed && companyCards[selectedFeed]; const [adminAccess] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${selectedFeedData?.domainID}`, {canBeMissing: true}); - return selectedFeedData?.domainID ? !!adminAccess : isPolicyAdmin; + if (selectedFeedData?.domainID === policy?.workspaceAccountID) { + return isPolicyAdmin; + } + + return !!adminAccess; } export default useIsAllowedToIssueCompanyCard; diff --git a/src/hooks/useUpdateFeedBrokenConnection.ts b/src/hooks/useUpdateFeedBrokenConnection.ts index 1c4daab40368..77fdc8ffe815 100644 --- a/src/hooks/useUpdateFeedBrokenConnection.ts +++ b/src/hooks/useUpdateFeedBrokenConnection.ts @@ -1,14 +1,14 @@ import {useCallback} from 'react'; -import {checkIfFeedConnectionIsBroken, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFeedConnectionBrokenCard} from '@libs/CardUtils'; +import {checkIfFeedConnectionIsBroken, getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFeedConnectionBrokenCard} from '@libs/CardUtils'; import {updateWorkspaceCompanyCard} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; import useCardFeeds from './useCardFeeds'; import useCardsList from './useCardsList'; import usePolicy from './usePolicy'; -export default function useUpdateFeedBrokenConnection({policyID, feed}: {policyID?: string; feed?: CompanyCardFeed}) { - const [cardsList] = useCardsList(policyID, feed); +export default function useUpdateFeedBrokenConnection({policyID, feed}: {policyID?: string; feed?: CompanyCardFeedWithDomainID}) { + const [cardsList] = useCardsList(feed); const policy = usePolicy(policyID); const [cardFeeds] = useCardFeeds(policyID); const companyFeeds = getCompanyFeeds(cardFeeds); @@ -23,7 +23,7 @@ export default function useUpdateFeedBrokenConnection({policyID, feed}: {policyI if (!brokenCardId || !feed) { return; } - updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, brokenCardId, feed, brokenCard?.lastScrapeResult); + updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, brokenCardId, getCompanyCardFeed(feed), brokenCard?.lastScrapeResult); }, [brokenCard?.lastScrapeResult, brokenCardId, domainOrWorkspaceAccountID, feed]); return {updateBrokenConnection, isFeedConnectionBroken}; diff --git a/src/libs/CardFeedUtils.ts b/src/libs/CardFeedUtils.ts index a36199da7444..913395fd3af8 100644 --- a/src/libs/CardFeedUtils.ts +++ b/src/libs/CardFeedUtils.ts @@ -9,8 +9,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import { getBankName, getCardFeedIcon, - getCompanyFeeds, getCustomOrFormattedFeedName, + getOriginalCompanyFeeds, getPlaidInstitutionIconUrl, getPlaidInstitutionId, isCard, @@ -424,7 +424,7 @@ function getCardFeedsForDisplay(allCardFeeds: OnyxCollection, allCard return; } - Object.keys(getCompanyFeeds(cardFeeds, true, true)).forEach((key) => { + Object.keys(getOriginalCompanyFeeds(cardFeeds)).forEach((key) => { const feed = key as CompanyCardFeed; const id = `${fundID}_${feed}`; @@ -436,7 +436,7 @@ function getCardFeedsForDisplay(allCardFeeds: OnyxCollection, allCard id, feed, fundID, - name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames, false) ?? feed, + name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames?.[feed], false) ?? feed, }; }); }); @@ -478,7 +478,7 @@ function getCardFeedsForDisplayPerPolicy(allCardFeeds: OnyxCollection return; } - Object.entries(getCompanyFeeds(cardFeeds, true, true)).forEach(([key, feedData]) => { + Object.entries(getOriginalCompanyFeeds(cardFeeds)).forEach(([key, feedData]) => { const preferredPolicy = 'preferredPolicy' in feedData ? (feedData.preferredPolicy ?? '') : ''; const feed = key as CompanyCardFeed; const id = `${fundID}_${feed}`; @@ -487,7 +487,7 @@ function getCardFeedsForDisplayPerPolicy(allCardFeeds: OnyxCollection id, feed, fundID, - name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames, false) ?? feed, + name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames?.[feed], false) ?? feed, }); }); }); diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index f70e3308e321..9a5ea851e745 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -4,6 +4,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import type {CombinedCardFeed, CombinedCardFeeds} from '@hooks/useCardFeeds'; import type IllustrationsType from '@styles/theme/illustrations/types'; import * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; @@ -11,7 +12,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {BankAccountList, Card, CardFeeds, CardList, CompanyCardFeed, CurrencyList, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; import type {FilteredCardList} from '@src/types/onyx/Card'; -import type {CardFeedData, CompanyCardFeedWithNumber, CompanyCardNicknames, CompanyFeeds, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; +import type {CardFeedData, CompanyCardFeedWithDomainID, CompanyCardFeedWithNumber, CompanyFeeds} from '@src/types/onyx/CardFeeds'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -344,16 +345,27 @@ function isCustomFeed(feed: CompanyCardFeedWithNumber): boolean { return [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD, CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX].some((value) => feed.startsWith(value)); } -function getCompanyFeeds(cardFeeds: OnyxEntry, shouldFilterOutRemovedFeeds = false, shouldFilterOutPendingFeeds = false): CompanyFeeds { +function getOriginalCompanyFeeds(cardFeeds: OnyxEntry): CompanyFeeds { return Object.fromEntries( Object.entries(cardFeeds?.settings?.companyCards ?? {}).filter(([key, value]) => { + if (value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || value.pending) { + return false; + } + return key !== CONST.EXPENSIFY_CARD.BANK; + }), + ); +} + +function getCompanyFeeds(cardFeeds: OnyxEntry, shouldFilterOutRemovedFeeds = false, shouldFilterOutPendingFeeds = false): CombinedCardFeeds { + return Object.fromEntries( + Object.entries(cardFeeds ?? {}).filter(([, value]) => { if (shouldFilterOutRemovedFeeds && value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return false; } if (shouldFilterOutPendingFeeds && value.pending) { return false; } - return key !== CONST.EXPENSIFY_CARD.BANK; + return !value.feed.includes(CONST.EXPENSIFY_CARD.BANK); }), ); } @@ -406,13 +418,11 @@ const getBankCardDetailsImage = (bank: ValueOf return iconMap[bank]; }; -function getCustomOrFormattedFeedName(feed?: CompanyCardFeed, companyCardNicknames?: CompanyCardNicknames, shouldAddCardsSuffix = true): string | undefined { +function getCustomOrFormattedFeedName(feed?: CompanyCardFeed, customFeedName?: string, shouldAddCardsSuffix = true): string | undefined { if (!feed) { return; } - const customFeedName = companyCardNicknames?.[feed]; - if (customFeedName && typeof customFeedName !== 'string') { return ''; } @@ -514,21 +524,24 @@ function getCorrectStepForPlaidSelectedBank(selectedBank: ValueOf, cardFeeds: OnyxEntry): CompanyCardFeed | undefined { - const defaultFeed = Object.keys(getCompanyFeeds(cardFeeds, true)).at(0) as CompanyCardFeed | undefined; - return lastSelectedFeed ?? defaultFeed; +function getSelectedFeed(lastSelectedFeed: OnyxEntry, cardFeeds: OnyxEntry): CompanyCardFeedWithDomainID | undefined { + const defaultFeed = Object.keys(getCompanyFeeds(cardFeeds, true)).at(0) as CompanyCardFeedWithDomainID | undefined; + if (!lastSelectedFeed?.includes(CONST.COMPANY_CARD.FEED_KEY_SEPARATOR)) { + return defaultFeed; + } + return lastSelectedFeed; } -function isSelectedFeedExpired(directFeed: DirectCardFeedData | undefined): boolean { - if (!directFeed || !directFeed.expiration) { - return false; - } +function getCompanyCardFeedWithDomainID(feedName: CompanyCardFeed, domainID: number | string): CompanyCardFeedWithDomainID { + return `${feedName}${CONST.COMPANY_CARD.FEED_KEY_SEPARATOR}${domainID}`; +} - return isBefore(fromUnixTime(directFeed.expiration), new Date()); +function isSelectedFeedExpired(cardFeed: CombinedCardFeed | undefined): boolean { + return cardFeed?.expiration ? isBefore(fromUnixTime(cardFeed.expiration), new Date()) : false; } /** Returns list of cards which can be assigned */ -function getFilteredCardList(list: WorkspaceCardsList | undefined, directFeed: DirectCardFeedData | undefined, workspaceCardFeeds: OnyxCollection) { +function getFilteredCardList(list: WorkspaceCardsList | undefined, accountList: string[] | undefined, workspaceCardFeeds: OnyxCollection) { const {cardList: customFeedCardsToAssign, ...cards} = list ?? {}; const assignedCards = new Set(Object.values(cards).map((card) => card.cardName)); @@ -547,8 +560,8 @@ function getFilteredCardList(list: WorkspaceCardsList | undefined, directFeed: D }); }); - if (directFeed) { - const unassignedDirectFeedCards = directFeed.accountList.filter((cardNumber) => !assignedCards.has(cardNumber) && !allWorkspaceAssignedCards.has(cardNumber)); + if (accountList) { + const unassignedDirectFeedCards = accountList.filter((cardNumber) => !assignedCards.has(cardNumber) && !allWorkspaceAssignedCards.has(cardNumber)); return Object.fromEntries(unassignedDirectFeedCards.map((cardNumber) => [cardNumber, cardNumber])); } @@ -572,7 +585,7 @@ function checkIfNewFeedConnected(prevFeedsData: CompanyFeeds, currentFeedsData: return { isNewFeedConnected: currentFeeds.length > prevFeeds.length || (plaidBank && currentFeeds.includes(`${CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID}.${plaidBank}`)), - newFeed: currentFeeds.find((feed) => !prevFeeds.includes(feed)) as CompanyCardFeed | undefined, + newFeed: currentFeeds.find((feed) => !prevFeeds.includes(feed)) as CompanyCardFeedWithDomainID | undefined, }; } @@ -584,11 +597,11 @@ function filterInactiveCards(cards: CardList | undefined): CardList { function getAllCardsForWorkspace( workspaceAccountID: number, allCardList: OnyxCollection, - cardFeeds?: CardFeeds, + cardFeeds?: CombinedCardFeeds, expensifyCardSettings?: OnyxCollection, ): CardList { const cards = {}; - const companyCardsDomainFeeds = Object.entries(cardFeeds?.settings?.companyCards ?? {}).map(([feedName, feedData]) => ({domainID: feedData.domainID, feedName})); + const companyCardsDomainFeeds = Object.entries(cardFeeds ?? {}).map(([feedName, feedData]) => ({domainID: feedData.domainID, feedName})); const expensifyCardsDomainIDs = Object.keys(expensifyCardSettings ?? {}) .map((key) => key.split('_').at(-1)) .filter((id): id is string => !!id); @@ -611,9 +624,11 @@ function isSmartLimitEnabled(cards: CardList) { const CUSTOM_FEEDS = [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD, CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX]; -function getFeedType(feedKey: CompanyCardFeed, cardFeeds: OnyxEntry): CompanyCardFeedWithNumber { +function getFeedType(feedKey: CompanyCardFeed, cardFeeds: OnyxEntry): CompanyCardFeedWithNumber { if (CUSTOM_FEEDS.some((feed) => feed === feedKey)) { - const filteredFeeds = Object.keys(cardFeeds?.settings?.companyCards ?? {}).filter((str) => str.includes(feedKey)); + const filteredFeeds = Object.keys(cardFeeds ?? {}) + .filter((str) => str.includes(feedKey)) + .map((str) => getCompanyCardFeed(str as CompanyCardFeedWithDomainID)); const feedNumbers = filteredFeeds.map((str) => parseInt(str.replace(feedKey, ''), 10)).filter(Boolean); feedNumbers.sort((a, b) => a - b); @@ -720,6 +735,12 @@ function getFeedConnectionBrokenCard(feedCards: Record | undefined return Object.values(feedCards).find((card) => !isEmptyObject(card) && card.bank !== feedToExclude && card.lastScrapeResult !== 200); } +/** Extract feed from feed with domainID */ +function getCompanyCardFeed(feedWithDomainID: CompanyCardFeedWithDomainID): CompanyCardFeed { + const [feed] = feedWithDomainID.split(CONST.COMPANY_CARD.FEED_KEY_SEPARATOR); + return feed as CompanyCardFeed; +} + export { getAssignedCardSortKey, isExpensifyCard, @@ -773,5 +794,8 @@ export { getPlaidInstitutionId, getFeedConnectionBrokenCard, getCorrectStepForPlaidSelectedBank, + getOriginalCompanyFeeds, + getCompanyCardFeed, + getCompanyCardFeedWithDomainID, getEligibleBankAccountsForUkEuCard, }; diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 51d1b1d33441..bde8d1f86abc 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -22,7 +22,7 @@ import * as NetworkStore from '@libs/Network/NetworkStore'; import * as PolicyUtils from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Card, CompanyCardFeed} from '@src/types/onyx'; +import type {Card, CompanyCardFeedWithDomainID} from '@src/types/onyx'; import type {CardLimitType, ExpensifyCardDetails, IssueNewCardData, IssueNewCardStep} from '@src/types/onyx/Card'; import type {ConnectionName} from '@src/types/onyx/Policy'; @@ -945,7 +945,7 @@ function toggleContinuousReconciliation(workspaceAccountID: number, shouldUseCon }); } -function updateSelectedFeed(feed: CompanyCardFeed, policyID: string | undefined) { +function updateSelectedFeed(feed: CompanyCardFeedWithDomainID, policyID: string | undefined) { if (!policyID) { return; } diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 1e671f59571e..3d5c9d1c2c25 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -1,5 +1,6 @@ import type {NullishDeep, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import type {CombinedCardFeeds} from '@hooks/useCardFeeds'; import * as API from '@libs/API'; import type { AssignCompanyCardParams, @@ -20,9 +21,18 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Card, CardFeeds} from '@src/types/onyx'; +import type {Card} from '@src/types/onyx'; import type {AssignCard, AssignCardData} from '@src/types/onyx/AssignCard'; -import type {AddNewCardFeedData, AddNewCardFeedStep, CardFeedData, CardFeedDetails, CompanyCardFeed, StatementPeriodEnd, StatementPeriodEndDay} from '@src/types/onyx/CardFeeds'; +import type { + AddNewCardFeedData, + AddNewCardFeedStep, + CardFeedData, + CardFeedDetails, + CompanyCardFeed, + CompanyCardFeedWithDomainID, + StatementPeriodEnd, + StatementPeriodEndDay, +} from '@src/types/onyx/CardFeeds'; import type {OnyxData} from '@src/types/onyx/Request'; type AddNewCompanyCardFlowData = { @@ -63,10 +73,10 @@ function addNewCompanyCardsFeed( policyID: string | undefined, cardFeed: CompanyCardFeed, feedDetails: CardFeedDetails, - cardFeeds: OnyxEntry, + cardFeeds: OnyxEntry, statementPeriodEnd: StatementPeriodEnd | undefined, statementPeriodEndDay: StatementPeriodEndDay | undefined, - lastSelectedFeed?: CompanyCardFeed, + lastSelectedFeed?: CompanyCardFeedWithDomainID, ) { const authToken = NetworkStore.getAuthToken(); const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); @@ -153,7 +163,7 @@ function addNewCompanyCardsFeed( API.write(WRITE_COMMANDS.REQUEST_FEED_SETUP, parameters, {optimisticData, failureData, successData, finallyData}); } -function setWorkspaceCompanyCardFeedName(policyID: string, domainOrWorkspaceAccountID: number, bankName: string, userDefinedName: string) { +function setWorkspaceCompanyCardFeedName(policyID: string, domainOrWorkspaceAccountID: number, bankName: CompanyCardFeed, userDefinedName: string) { const authToken = NetworkStore.getAuthToken(); const onyxData: OnyxData = { optimisticData: [ @@ -210,7 +220,7 @@ function setWorkspaceCompanyCardTransactionLiability(domainOrWorkspaceAccountID: API.write(WRITE_COMMANDS.SET_COMPANY_CARD_TRANSACTION_LIABILITY, parameters, onyxData); } -function deleteWorkspaceCompanyCardFeed(policyID: string, domainOrWorkspaceAccountID: number, bankName: CompanyCardFeed, cardIDs: string[], feedToOpen?: CompanyCardFeed) { +function deleteWorkspaceCompanyCardFeed(policyID: string, domainOrWorkspaceAccountID: number, bankName: CompanyCardFeed, cardIDs: string[], feedToOpen?: CompanyCardFeedWithDomainID) { const authToken = NetworkStore.getAuthToken(); const isCustomFeed = CardUtils.isCustomFeed(bankName); const optimisticFeedUpdates = {[bankName]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}}; @@ -375,7 +385,7 @@ function assignWorkspaceCompanyCard(policyID: string, data?: Partial; @@ -52,7 +52,7 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti const selectedBank = addNewCard?.data?.selectedBank; const {bankName: bankNameFromRoute, backTo, policyID: policyIDFromRoute} = route?.params ?? {}; const policyID = policyIDFromProps ?? policyIDFromRoute; - const bankName = feed ? getBankName(feed) : (bankNameFromRoute ?? addNewCard?.data?.plaidConnectedFeed ?? selectedBank); + const bankName = feed ? getBankName(getCompanyCardFeed(feed)) : (bankNameFromRoute ?? addNewCard?.data?.plaidConnectedFeed ?? selectedBank); const {isBetaEnabled} = usePermissions(); const plaidToken = addNewCard?.data?.publicToken ?? assignCard?.data?.plaidAccessToken; const isPlaid = isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && !!plaidToken; @@ -60,17 +60,17 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti const url = getCompanyCardBankConnection(policyID, bankName); const [cardFeeds] = useCardFeeds(policyID); const [isConnectionCompleted, setConnectionCompleted] = useState(false); - const prevFeedsData = usePrevious(cardFeeds?.settings?.oAuthAccountDetails); - const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.settings?.oAuthAccountDetails?.[feed]) : false; + const prevFeedsData = usePrevious(cardFeeds); + const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.[feed]) : false; const {isNewFeedConnected, newFeed} = useMemo( - () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds?.settings?.oAuthAccountDetails ?? {}, addNewCard?.data?.plaidConnectedFeed), - [addNewCard?.data?.plaidConnectedFeed, cardFeeds?.settings?.oAuthAccountDetails, prevFeedsData], + () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds ?? {}, addNewCard?.data?.plaidConnectedFeed), + [addNewCard?.data?.plaidConnectedFeed, cardFeeds, prevFeedsData], ); const headerTitleAddCards = !backTo ? translate('workspace.companyCards.addCards') : undefined; const headerTitle = feed ? translate('workspace.companyCards.assignCard') : headerTitleAddCards; const onImportPlaidAccounts = useImportPlaidAccounts(policyID); const {updateBrokenConnection, isFeedConnectionBroken} = useUpdateFeedBrokenConnection({policyID, feed}); - const isNewFeedHasError = !!(newFeed && cardFeeds?.settings?.oAuthAccountDetails?.[newFeed]?.errors); + const isNewFeedHasError = !!(newFeed && cardFeeds?.[newFeed]?.errors); const renderLoading = () => ; diff --git a/src/pages/workspace/companyCards/BankConnection/index.tsx b/src/pages/workspace/companyCards/BankConnection/index.tsx index 37f4daace2e5..d956ec2edc02 100644 --- a/src/pages/workspace/companyCards/BankConnection/index.tsx +++ b/src/pages/workspace/companyCards/BankConnection/index.tsx @@ -17,7 +17,7 @@ import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import useUpdateFeedBrokenConnection from '@hooks/useUpdateFeedBrokenConnection'; import {setAssignCardStepAndData} from '@libs/actions/CompanyCards'; -import {checkIfNewFeedConnected, getBankName, isSelectedFeedExpired} from '@libs/CardUtils'; +import {checkIfNewFeedConnected, getBankName, getCompanyCardFeed, isSelectedFeedExpired} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -29,7 +29,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; import openBankConnection from './openBankConnection'; let customWindow: Window | null = null; @@ -39,7 +39,7 @@ type BankConnectionProps = { policyID?: string; /** Selected feed for assign card flow */ - feed?: CompanyCardFeed; + feed?: CompanyCardFeedWithDomainID; /** Route params for add new card flow */ route?: PlatformStackRouteProp; @@ -53,13 +53,13 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti const {bankName: bankNameFromRoute, backTo, policyID: policyIDFromRoute} = route?.params ?? {}; const policyID = policyIDFromProps ?? policyIDFromRoute; const [cardFeeds] = useCardFeeds(policyID); - const prevFeedsData = usePrevious(cardFeeds?.settings?.oAuthAccountDetails); + const prevFeedsData = usePrevious(cardFeeds); const [shouldBlockWindowOpen, setShouldBlockWindowOpen] = useState(false); const selectedBank = addNewCard?.data?.selectedBank; - const bankName = feed ? getBankName(feed) : (bankNameFromRoute ?? addNewCard?.data?.plaidConnectedFeed ?? selectedBank); + const bankName = feed ? getBankName(getCompanyCardFeed(feed)) : (bankNameFromRoute ?? addNewCard?.data?.plaidConnectedFeed ?? selectedBank); const {isNewFeedConnected, newFeed} = useMemo( - () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds?.settings?.oAuthAccountDetails ?? {}, addNewCard?.data?.plaidConnectedFeed), - [addNewCard?.data?.plaidConnectedFeed, cardFeeds?.settings?.oAuthAccountDetails, prevFeedsData], + () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds ?? {}, addNewCard?.data?.plaidConnectedFeed), + [addNewCard?.data?.plaidConnectedFeed, cardFeeds, prevFeedsData], ); const {isOffline} = useNetwork(); const plaidToken = addNewCard?.data?.publicToken ?? assignCard?.data?.plaidAccessToken; @@ -68,10 +68,10 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti const isPlaid = isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && !!plaidToken; const url = getCompanyCardBankConnection(policyID, bankName); - const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.settings?.oAuthAccountDetails?.[feed]) : false; + const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.[feed]) : false; const headerTitleAddCards = !backTo ? translate('workspace.companyCards.addCards') : undefined; const headerTitle = feed ? translate('workspace.companyCards.assignCard') : headerTitleAddCards; - const isNewFeedHasError = !!(newFeed && cardFeeds?.settings?.oAuthAccountDetails?.[newFeed]?.errors); + const isNewFeedHasError = !!(newFeed && cardFeeds?.[newFeed]?.errors); const onImportPlaidAccounts = useImportPlaidAccounts(policyID); const onOpenBankConnectionFlow = useCallback(() => { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx index 40598d869b91..b0691d963b23 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx @@ -14,7 +14,7 @@ import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; import {setCompanyCardExportAccount} from '@libs/actions/CompanyCards'; -import {getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; +import {getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getConnectedIntegration, getCurrentConnectionName} from '@libs/PolicyUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; @@ -24,7 +24,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; import {getExportMenuItem} from './utils'; type WorkspaceCompanyCardAccountSelectCardProps = PlatformStackScreenProps; @@ -34,12 +34,12 @@ function WorkspaceCompanyCardAccountSelectCardPage({route}: WorkspaceCompanyCard const styles = useThemeStyles(); const {environmentURL} = useEnvironment(); const {policyID, cardID, backTo} = route.params; - const bank = decodeURIComponent(route.params.bank); + const bank = decodeURIComponent(route.params.bank) as CompanyCardFeedWithDomainID; const policy = usePolicy(policyID); const workspaceAccountID = useWorkspaceAccountID(policyID); const [searchText, setSearchText] = useState(''); - const [allBankCards] = useCardsList(policyID, bank as CompanyCardFeed); + const [allBankCards] = useCardsList(bank); const card = allBankCards?.[cardID]; const connectedIntegration = getConnectedIntegration(policy) ?? CONST.POLICY.CONNECTIONS.NAME.QBO; // We need to have an unchanged active route for getExportMenuItem so the export page link is not updated incorrectly when user is in other pages @@ -54,7 +54,7 @@ function WorkspaceCompanyCardAccountSelectCardPage({route}: WorkspaceCompanyCard const [cardFeeds] = useCardFeeds(policyID); const companyFeeds = getCompanyFeeds(cardFeeds); - const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, companyFeeds[bank as CompanyCardFeed]); + const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, companyFeeds[bank]); const searchedListOptions = useMemo(() => { return tokenizedSearch(exportMenuItem?.data ?? [], searchText, (option) => [option.value]); @@ -81,7 +81,7 @@ function WorkspaceCompanyCardAccountSelectCardPage({route}: WorkspaceCompanyCard } const isDefaultCardSelected = value === defaultCard; const exportValue = isDefaultCardSelected ? CONST.COMPANY_CARDS.DEFAULT_EXPORT_TYPE : value; - setCompanyCardExportAccount(policyID, domainOrWorkspaceAccountID, cardID, exportMenuItem.exportType, exportValue, bank); + setCompanyCardExportAccount(policyID, domainOrWorkspaceAccountID, cardID, exportMenuItem.exportType, exportValue, getCompanyCardFeed(bank)); Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, bank)); }, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx index 43961fd46334..41b5799e4fd0 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx @@ -23,7 +23,7 @@ import usePolicy from '@hooks/usePolicy'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getCardFeedIcon, getCompanyFeeds, getDefaultCardName, getDomainOrWorkspaceAccountID, getPlaidInstitutionIconUrl, maskCardNumber} from '@libs/CardUtils'; +import {getCardFeedIcon, getCompanyCardFeed, getCompanyFeeds, getDefaultCardName, getDomainOrWorkspaceAccountID, getPlaidInstitutionIconUrl, maskCardNumber} from '@libs/CardUtils'; import {getLatestErrorField} from '@libs/ErrorUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -39,7 +39,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeed, CompanyCardFeedWithDomainID} from '@src/types/onyx'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import {getExportMenuItem} from './utils'; @@ -47,7 +47,8 @@ type WorkspaceCompanyCardDetailsPageProps = PlatformStackScreenProps { setIsUnassignModalVisible(false); if (card) { - unassignWorkspaceCompanyCard(domainOrWorkspaceAccountID, bank, card); + unassignWorkspaceCompanyCard(domainOrWorkspaceAccountID, feed, card); } Navigation.goBack(); }; const updateCard = () => { - updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, cardID, bank as CompanyCardFeed, card?.lastScrapeResult); + updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, cardID, feed, card?.lastScrapeResult); }; const lastScrape = useMemo(() => { @@ -154,7 +155,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag @@ -162,7 +163,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag pendingAction={card?.nameValuePairs?.pendingFields?.cardTitle} errorRowStyles={[styles.ph5, styles.mb3]} errors={getLatestErrorField(card?.nameValuePairs ?? {}, 'cardTitle')} - onClose={() => clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'cardTitle')} + onClose={() => clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, feed, 'cardTitle')} > clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'lastScrape', true)} + onClose={() => clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, feed, 'lastScrape', true)} > ; @@ -33,7 +33,7 @@ type WorkspaceCompanyCardEditCardNamePageProps = PlatformStackScreenProps) => { - updateCompanyCardName(domainOrWorkspaceAccountID, cardID, values[INPUT_IDS.NAME], bank, defaultValue); + updateCompanyCardName(domainOrWorkspaceAccountID, cardID, values[INPUT_IDS.NAME], getCompanyCardFeed(bank), defaultValue); Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, bank), {compareParams: false}); }; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx index 6edc5d99120b..69de0bdc40d1 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx @@ -1,3 +1,4 @@ +import {Str} from 'expensify-common'; import React from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; @@ -9,6 +10,7 @@ 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 useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; @@ -39,8 +41,11 @@ import type SCREENS from '@src/SCREENS'; import type {CompanyCardFeed} from '@src/types/onyx'; type CardFeedListItem = ListItem & { + /** Combined feed key */ + value: CompanyCardFeedWithDomainID; + /** Card feed value */ - value: CompanyCardFeed; + feed: CompanyCardFeed; }; type WorkspaceCompanyCardFeedSelectorPageProps = PlatformStackScreenProps; @@ -51,6 +56,7 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const {translate} = useLocalize(); + const [allDomains] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN, {canBeMissing: false}); const styles = useThemeStyles(); const illustrations = useThemeIllustrations(); const [cardFeeds] = useCardFeeds(policyID); @@ -60,21 +66,24 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS const companyFeeds = getCompanyFeeds(cardFeeds); const isCollect = isCollectPolicy(policy); - const feeds: CardFeedListItem[] = Object.entries(companyFeeds).map(([key, feedSettings]) => { - const feed = key as CompanyCardFeed; + const feeds: CardFeedListItem[] = (Object.entries(companyFeeds) as Array<[CompanyCardFeedWithDomainID, CombinedCardFeed]>).map(([key, feedSettings]) => { const filteredFeedCards = filterInactiveCards( - allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${getDomainOrWorkspaceAccountID(workspaceAccountID, feedSettings)}_${feed}`], + allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${getDomainOrWorkspaceAccountID(workspaceAccountID, feedSettings)}_${feedSettings.feed}`], ); const isFeedConnectionBroken = checkIfFeedConnectionIsBroken(filteredFeedCards); - const plaidUrl = getPlaidInstitutionIconUrl(feed); + const plaidUrl = getPlaidInstitutionIconUrl(feedSettings.feed); + const domain = allDomains?.[`${ONYXKEYS.COLLECTION.DOMAIN}${feedSettings.domainID}`]; + const domainName = domain?.email ? Str.extractEmailDomain(domain.email) : undefined; return { - value: feed, - text: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames), - keyForList: feed, - isSelected: feed === selectedFeed, - isDisabled: companyFeeds[feed]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - pendingAction: companyFeeds[feed]?.pendingAction, + value: key, + feed: feedSettings.feed, + alternateText: domainName ?? policy?.name, + text: getCustomOrFormattedFeedName(feedSettings.feed, feedSettings.customFeedName), + keyForList: key, + isSelected: key === selectedFeed, + isDisabled: feedSettings.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingAction: feedSettings.pendingAction, brickRoadIndicator: isFeedConnectionBroken ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, canShowSeveralIndicators: isFeedConnectionBroken, leftElement: plaidUrl ? ( @@ -84,7 +93,7 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS /> ) : ( { const isChangedValue = (newStatementPeriodEndDay ?? newStatementPeriodEnd) !== statementPeriodEndDay; - if (selectedFeed && isChangedValue) { - setFeedStatementPeriodEndDay(policyID, selectedFeed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); + if (feed && isChangedValue) { + setFeedStatementPeriodEndDay(policyID, feed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); } Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.getRoute(policyID)); }, - [policyID, selectedFeed, statementPeriodEndDay, domainOrWorkspaceAccountID], + [policyID, feed, statementPeriodEndDay, domainOrWorkspaceAccountID], ); const goBack = useCallback(() => { @@ -64,12 +65,12 @@ function WorkspaceCompanyCardStatementCloseDatePage({ }, [policyID]); const clearError = useCallback(() => { - if (!selectedFeed) { + if (!feed) { return; } - clearErrorField(selectedFeed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); - }, [selectedFeed, domainOrWorkspaceAccountID]); + clearErrorField(feed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); + }, [feed, domainOrWorkspaceAccountID]); if (isLoadingOnyxValue(cardFeedsResult) || isLoadingOnyxValue(lastSelectedFeedResult)) { return ; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsErrorConfirmation.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsErrorConfirmation.tsx index ac2885e169f1..bc3d851a72d4 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsErrorConfirmation.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsErrorConfirmation.tsx @@ -8,17 +8,17 @@ import useCardsList from '@hooks/useCardsList'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; +import {getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; import Navigation from '@navigation/Navigation'; import {deleteWorkspaceCompanyCardFeed, setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import {enableExpensifyCard} from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; type WorkspaceCompanyCardsErrorConfirmationProps = { policyID?: string; - newFeed?: CompanyCardFeed; + newFeed?: CompanyCardFeedWithDomainID; }; function WorkspaceCompanyCardsErrorConfirmation({policyID, newFeed}: WorkspaceCompanyCardsErrorConfirmationProps) { @@ -26,7 +26,7 @@ function WorkspaceCompanyCardsErrorConfirmation({policyID, newFeed}: WorkspaceCo const styles = useThemeStyles(); const policy = usePolicy(policyID); const isExpensifyCardFeatureEnabled = !!policy?.areExpensifyCardsEnabled; - const [cardsList] = useCardsList(policyID, newFeed); + const [cardsList] = useCardsList(newFeed); const [cardFeeds] = useCardFeeds(policyID); const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const companyFeeds = getCompanyFeeds(cardFeeds); @@ -39,10 +39,10 @@ function WorkspaceCompanyCardsErrorConfirmation({policyID, newFeed}: WorkspaceCo } const {cardList, ...cards} = cardsList ?? {}; const cardIDs = Object.keys(cards); - const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeed[]).find( + const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeedWithDomainID[]).find( (feed) => feed !== newFeed && companyFeeds[feed]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); - deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, newFeed, cardIDs, feedToOpen); + deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, getCompanyCardFeed(newFeed), cardIDs, feedToOpen); }; const onButtonPress = () => { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx index 978adbf95316..5f1c5cda7520 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx @@ -11,13 +11,13 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useSearchResults from '@hooks/useSearchResults'; import useThemeStyles from '@hooks/useThemeStyles'; -import {filterCardsByPersonalDetails, getCardsByCardholderName, getDefaultCardName, sortCardsByCardholderName} from '@libs/CardUtils'; +import {filterCardsByPersonalDetails, getCardsByCardholderName, getCompanyCardFeedWithDomainID, getDefaultCardName, sortCardsByCardholderName} from '@libs/CardUtils'; import {getMemberAccountIDsForWorkspace} from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Card, WorkspaceCardsList} from '@src/types/onyx'; +import type {Card, CompanyCardFeed, WorkspaceCardsList} from '@src/types/onyx'; import WorkspaceCompanyCardsFeedAddedEmptyPage from './WorkspaceCompanyCardsFeedAddedEmptyPage'; import WorkspaceCompanyCardsListRow from './WorkspaceCompanyCardsListRow'; @@ -72,10 +72,10 @@ function WorkspaceCompanyCardsList({cardsList, policyID, handleAssignCard, isDis hoverStyle={styles.hoveredComponentBG} disabled={isCardDeleted} onPress={() => { - if (!cardID || !item?.accountID) { + if (!cardID || !item?.accountID || !item.fundID) { return; } - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, item.bank)); + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, getCompanyCardFeedWithDomainID(item.bank as CompanyCardFeed, item.fundID))); }} > {({hovered}) => ( diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx index 1157d3d81a0e..474f7ce79ff8 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx @@ -1,3 +1,4 @@ +import {Str} from 'expensify-common'; import React, {useMemo} from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; @@ -7,6 +8,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; +import type {CompanyCardFeedWithDomainID} from '@hooks/useCardFeeds'; import useCardFeeds from '@hooks/useCardFeeds'; import useIsAllowedToIssueCompanyCard from '@hooks/useIsAllowedToIssueCompanyCard'; import useLocalize from '@hooks/useLocalize'; @@ -23,6 +25,7 @@ import { flatAllCardsList, getBankName, getCardFeedIcon, + getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, @@ -36,7 +39,7 @@ import {setAddNewCompanyCardStepAndData, setAssignCardStepAndData} from '@userAc import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {CompanyCardFeed, CurrencyList} from '@src/types/onyx'; +import type {CurrencyList} from '@src/types/onyx'; import type {AssignCardData} from '@src/types/onyx/AssignCard'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; @@ -45,7 +48,7 @@ type WorkspaceCompanyCardsListHeaderButtonsProps = { policyID: string; /** Currently selected feed */ - selectedFeed: CompanyCardFeed; + selectedFeed: CompanyCardFeedWithDomainID; /** Whether to show assign card button */ shouldShowAssignCardButton?: boolean; @@ -67,22 +70,24 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; - const formattedFeedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); + const feed = getCompanyCardFeed(selectedFeed); + const formattedFeedName = getCustomOrFormattedFeedName(feed, cardFeeds?.[selectedFeed]?.customFeedName); const isCommercialFeed = isCustomFeed(selectedFeed); const plaidUrl = getPlaidInstitutionIconUrl(selectedFeed); const companyFeeds = getCompanyFeeds(cardFeeds); const currentFeedData = companyFeeds?.[selectedFeed]; - const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(selectedFeed); + const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(feed); const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeedData); const filteredFeedCards = filterInactiveCards(allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${selectedFeed}`]); - const hasFeedError = !!cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed]?.errors; + const hasFeedError = !!cardFeeds?.[selectedFeed]?.errors; const isSelectedFeedConnectionBroken = checkIfFeedConnectionIsBroken(filteredFeedCards) || hasFeedError; const isAllowedToIssueCompanyCard = useIsAllowedToIssueCompanyCard({policyID}); + const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${currentFeedData?.domainID}`, {canBeMissing: true}); const openBankConnection = () => { const institutionId = !!getPlaidInstitutionId(selectedFeed); const data: Partial = { - bankName: selectedFeed, + bankName: feed, }; if (institutionId) { const country = getPlaidCountry(policy?.outputCurrency, currencyList, countryByIp); @@ -115,16 +120,23 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS [policyID, translate], ); + const supportingText = useMemo(() => { + 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]); + return ( Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.getRoute(policyID))} - cardIcon={getCardFeedIcon(selectedFeed, illustrations)} + cardIcon={getCardFeedIcon(feed, illustrations)} shouldChangeLayout={shouldChangeLayout} feedName={formattedFeedName} - supportingText={translate(isCommercialFeed ? 'workspace.companyCards.commercialFeed' : 'workspace.companyCards.directFeed')} + supportingText={supportingText} shouldShowRBR={checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, domainOrWorkspaceAccountID), selectedFeed)} /> diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index 34367b7e228c..86dc3915da99 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -14,6 +14,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import { checkIfFeedConnectionIsBroken, + getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFilteredCardList, @@ -56,9 +57,10 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); const [workspaceCardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`, {canBeMissing: true}); - const [cardFeeds] = useCardFeeds(policyID); + const [cardFeeds, , defaultFeed] = useCardFeeds(policyID); const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds); - const [cardsList] = useCardsList(policyID, selectedFeed); + const feed = selectedFeed ? getCompanyCardFeed(selectedFeed) : undefined; + const [cardsList] = useCardsList(selectedFeed); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const {isBetaEnabled} = usePermissions(); @@ -68,7 +70,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); - const filteredCardList = getFilteredCardList(cardsList, selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined, workspaceCardFeeds); + const filteredCardList = getFilteredCardList(cardsList, selectedFeed ? cardFeeds?.[selectedFeed]?.accountList : undefined, workspaceCardFeeds); const companyCards = getCompanyFeeds(cardFeeds); const selectedFeedData = selectedFeed && companyCards[selectedFeed]; @@ -84,7 +86,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { }, [policyID, domainOrWorkspaceAccountID]); const {isOffline} = useNetwork({onReconnect: fetchCompanyCards}); - const isLoading = !isOffline && (!cardFeeds || (!!cardFeeds.isLoading && isEmptyObject(cardsList))); + const isLoading = !isOffline && (!cardFeeds || (!!defaultFeed?.isLoading && isEmptyObject(cardsList))); const isGB = countryByIp === CONST.COUNTRY.GB; const shouldShowGBDisclaimer = isGB && isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && (isNoFeed || hasNoAssignedCard); const isAllowedToIssueCompanyCard = useIsAllowedToIssueCompanyCard({policyID}); @@ -94,12 +96,12 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { }, [fetchCompanyCards]); useEffect(() => { - if (isLoading || !selectedFeed || isPending) { + if (isLoading || !feed || isPending) { return; } - openPolicyCompanyCardsFeed(domainOrWorkspaceAccountID, policyID, selectedFeed); - }, [selectedFeed, isLoading, policyID, isPending, domainOrWorkspaceAccountID]); + openPolicyCompanyCardsFeed(domainOrWorkspaceAccountID, policyID, feed); + }, [feed, isLoading, policyID, isPending, domainOrWorkspaceAccountID]); const handleAssignCard = () => { if (isActingAsDelegate) { @@ -120,19 +122,17 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { } const data: Partial = { - bankName: selectedFeed, + bankName: feed, }; let currentStep: AssignCardStep = CONST.COMPANY_CARD.STEP.ASSIGNEE; const employeeList = Object.values(policy?.employeeList ?? {}).filter((employee) => !isDeletedPolicyEmployee(employee, isOffline)); - const selectedFeedCompanyCardsData = selectedFeed ? cardFeeds?.settings?.companyCards?.[selectedFeed] : undefined; - const selectedFeedOAuthData = selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined; - const isFeedExpired = isSelectedFeedExpired(selectedFeedOAuthData); - const plaidAccessToken = selectedFeedCompanyCardsData?.plaidAccessToken; + const isFeedExpired = isSelectedFeedExpired(selectedFeedData); + const plaidAccessToken = selectedFeedData?.plaidAccessToken; // Refetch plaid card list if (!isFeedExpired && plaidAccessToken) { - const country = selectedFeedCompanyCardsData?.country ?? ''; + const country = selectedFeedData?.country ?? ''; importPlaidAccounts('', selectedFeed, '', country, getDomainNameForPolicy(policyID), '', undefined, undefined, plaidAccessToken); } diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx index 14c67362dabb..1e2cd87bd7de 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx @@ -14,7 +14,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; +import {getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -44,8 +44,9 @@ function WorkspaceCompanyCardsSettingsFeedNamePage({ const [lastSelectedFeed, lastSelectedFeedResult] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); const [cardFeeds, cardFeedsResult] = useCardFeeds(policyID); const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds); + const feed = selectedFeed ? getCompanyCardFeed(selectedFeed) : undefined; const companyFeeds = getCompanyFeeds(cardFeeds); - const feedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); + const feedName = selectedFeed ? getCustomOrFormattedFeedName(feed, cardFeeds?.[selectedFeed]?.customFeedName) : undefined; const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, selectedFeed ? companyFeeds[selectedFeed] : undefined); const validate = useCallback( @@ -68,8 +69,8 @@ function WorkspaceCompanyCardsSettingsFeedNamePage({ ); const submit = ({name}: WorkspaceCompanyCardFeedName) => { - if (selectedFeed) { - setWorkspaceCompanyCardFeedName(policyID, domainOrWorkspaceAccountID, selectedFeed, name); + if (feed) { + setWorkspaceCompanyCardFeedName(policyID, domainOrWorkspaceAccountID, feed, name); } Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.getRoute(policyID)); }; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx index 30d73c609019..4a638ecb1ff5 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx @@ -16,7 +16,7 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {deleteWorkspaceCompanyCardFeed, setWorkspaceCompanyCardTransactionLiability} from '@libs/actions/CompanyCards'; -import {getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; +import {getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -26,7 +26,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; type WorkspaceCompanyCardsSettingsPageProps = PlatformStackScreenProps; @@ -45,9 +45,10 @@ function WorkspaceCompanyCardsSettingsPage({ const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); const selectedFeed = useMemo(() => getSelectedFeed(lastSelectedFeed, cardFeeds), [cardFeeds, lastSelectedFeed]); + const feed = selectedFeed ? getCompanyCardFeed(selectedFeed) : undefined; - const [cardsList] = useCardsList(policyID, selectedFeed); - const feedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); + const [cardsList] = useCardsList(selectedFeed); + const feedName = selectedFeed ? getCustomOrFormattedFeedName(feed, cardFeeds?.[selectedFeed]?.customFeedName) : undefined; const companyFeeds = getCompanyFeeds(cardFeeds); const selectedFeedData = selectedFeed ? companyFeeds[selectedFeed] : undefined; const liabilityType = selectedFeedData?.liabilityType; @@ -77,27 +78,27 @@ function WorkspaceCompanyCardsSettingsPage({ const deleteCompanyCardFeed = () => { setDeleteCompanyCardConfirmModalVisible(false); Navigation.goBack(); - if (selectedFeed) { + if (feed) { const {cardList, ...cards} = cardsList ?? {}; const cardIDs = Object.keys(cards); - const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeed[]).find( - (feed) => feed !== selectedFeed && companyFeeds[feed]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeedWithDomainID[]).find( + (feedWithDomainID) => feedWithDomainID !== selectedFeed && companyFeeds[feedWithDomainID]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, selectedFeed, cardIDs, feedToOpen); + deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, feed, cardIDs, feedToOpen); }); } }; const onToggleLiability = (isOn: boolean) => { - if (!selectedFeed) { + if (!feed) { return; } setWorkspaceCompanyCardTransactionLiability( domainOrWorkspaceAccountID, policyID, - selectedFeed, + feed, isOn ? CONST.COMPANY_CARDS.DELETE_TRANSACTIONS.ALLOW : CONST.COMPANY_CARDS.DELETE_TRANSACTIONS.RESTRICT, ); }; diff --git a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx index ef882baee50e..92f7bce11d76 100644 --- a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx @@ -11,9 +11,10 @@ import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; import {updateSelectedFeed} from '@libs/actions/Card'; import {setAddNewCompanyCardStepAndData} from '@libs/actions/CompanyCards'; -import {getBankName} from '@libs/CardUtils'; +import {getBankName, getCompanyCardFeedWithDomainID} from '@libs/CardUtils'; import Parser from '@libs/Parser'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; @@ -50,12 +51,13 @@ function CardInstructionsStep({policyID}: CardInstructionsStepProps) { const isOtherBankSelected = bank === CONST.COMPANY_CARDS.BANKS.OTHER; const translationKey = getCardInstructionHeader(feedProvider); const {isBetaEnabled} = usePermissions(); + const workspaceAccountID = useWorkspaceAccountID(policyID); const buttonTranslation = isStripeFeedProvider ? translate('common.submit') : translate('common.next'); const submit = () => { if (isStripeFeedProvider && policyID) { - updateSelectedFeed(feedProvider, policyID); + updateSelectedFeed(getCompanyCardFeedWithDomainID(feedProvider, workspaceAccountID), policyID); Navigation.goBack(); return; } diff --git a/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx b/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx index ae70fbe3136f..79a36cc0203c 100644 --- a/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx +++ b/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx @@ -5,7 +5,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; import {clearErrorField, setFeedStatementPeriodEndDay} from '@libs/actions/CompanyCards'; -import {getCompanyFeeds, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; +import {getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import WorkspaceCompanyCardStatementCloseDateSelectionList from '@pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -22,6 +22,7 @@ function DirectStatementCloseDateStep({policyID}: DirectStatementCloseDateStepPr const [lastSelectedFeed, lastSelectedFeedResult] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); const [cardFeeds, cardFeedsResult] = useCardFeeds(policyID); const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds); + const feed = selectedFeed ? getCompanyCardFeed(selectedFeed) : undefined; const workspaceAccountID = useWorkspaceAccountID(policyID); const companyFeeds = getCompanyFeeds(cardFeeds); const selectedFeedData = selectedFeed ? companyFeeds[selectedFeed] : undefined; @@ -51,22 +52,22 @@ function DirectStatementCloseDateStep({policyID}: DirectStatementCloseDateStepPr return; } const isChangedValue = (newStatementPeriodEndDay ?? newStatementPeriodEnd) !== statementPeriodEndDay; - if (selectedFeed && isChangedValue) { - setFeedStatementPeriodEndDay(policyID, selectedFeed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); + if (feed && isChangedValue) { + setFeedStatementPeriodEndDay(policyID, feed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); } goBack(); }, - [policyID, statementPeriodEndDay, selectedFeed, goBack, domainOrWorkspaceAccountID], + [policyID, statementPeriodEndDay, goBack, feed, domainOrWorkspaceAccountID], ); const clearError = useCallback(() => { - if (!selectedFeed) { + if (!feed) { return; } - clearErrorField(selectedFeed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); - }, [selectedFeed, domainOrWorkspaceAccountID]); + clearErrorField(feed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); + }, [feed, domainOrWorkspaceAccountID]); if (isLoadingOnyxValue(cardFeedsResult) || isLoadingOnyxValue(lastSelectedFeedResult)) { return ; diff --git a/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx b/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx index a025a80bfa90..2846ddf95777 100644 --- a/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx +++ b/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx @@ -22,10 +22,10 @@ import {setPlaidEvent} from '@userActions/BankAccounts'; import {importPlaidAccounts, openPlaidCompanyCardLogin} from '@userActions/Plaid'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeed; policyID?: string; onExit?: () => void}) { +function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeedWithDomainID; policyID?: string; onExit?: () => void}) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); diff --git a/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx b/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx index 2d0f4dabe373..b8b240dcfb85 100644 --- a/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx +++ b/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx @@ -14,7 +14,7 @@ import {clearAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; import AssigneeStep from './AssigneeStep'; import CardNameStep from './CardNameStep'; import CardSelectionStep from './CardSelectionStep'; @@ -27,7 +27,7 @@ function AssignCardFeedPage({route, policy}: AssignCardFeedPageProps) { const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: true}); const currentStep = assignCard?.currentStep; - const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeed; + const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeedWithDomainID; const backTo = route.params?.backTo; const policyID = policy?.id; const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isActingAsDelegateSelector, canBeMissing: true}); diff --git a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx index 529899fdb651..c165629db143 100644 --- a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx @@ -34,7 +34,7 @@ type AssigneeStepProps = { policy: OnyxEntry; /** Selected feed */ - feed: OnyxTypes.CompanyCardFeed; + feed: OnyxTypes.CompanyCardFeedWithDomainID; }; function AssigneeStep({policy, feed}: AssigneeStepProps) { @@ -44,9 +44,9 @@ function AssigneeStep({policy, feed}: AssigneeStepProps) { const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: true}); const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: false}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); - const [list] = useCardsList(policy?.id, feed); + const [list] = useCardsList(feed); const [cardFeeds] = useCardFeeds(policy?.id); - const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[feed], workspaceCardFeeds); + const filteredCardList = getFilteredCardList(list, cardFeeds?.[feed]?.accountList, workspaceCardFeeds); const isEditing = assignCard?.isEditing; diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index bdba42f1da2f..89d8fbc4dd28 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -18,18 +18,18 @@ import useOnyx from '@hooks/useOnyx'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import {setAssignCardStepAndData} from '@libs/actions/CompanyCards'; -import {getCardFeedIcon, getFilteredCardList, getPlaidInstitutionIconUrl, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; +import {getCardFeedIcon, getCompanyCardFeed, getFilteredCardList, getPlaidInstitutionIconUrl, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; type CardSelectionStepProps = { /** Selected feed */ - feed: CompanyCardFeed; + feed: CompanyCardFeedWithDomainID; /** Current policy id */ policyID: string | undefined; @@ -41,14 +41,14 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { const illustrations = useThemeIllustrations(); const [searchText, setSearchText] = useState(''); const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: false}); - const [list] = useCardsList(policyID, feed); + const [list] = useCardsList(feed); const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: false}); const [cardFeeds] = useCardFeeds(policyID); const plaidUrl = getPlaidInstitutionIconUrl(feed); const isEditing = assignCard?.isEditing; const assigneeDisplayName = getPersonalDetailByEmail(assignCard?.data?.email ?? '')?.displayName ?? ''; - const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[feed], workspaceCardFeeds); + const filteredCardList = getFilteredCardList(list, cardFeeds?.[feed]?.accountList, workspaceCardFeeds); const [cardSelected, setCardSelected] = useState(assignCard?.data?.encryptedCardNumber ?? ''); const [shouldShowError, setShouldShowError] = useState(false); @@ -66,7 +66,7 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { /> ) : ( ()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); - const bankName = (assignCard?.data?.bankName as CompanyCardFeed | undefined) ?? feed; + const bankName = assignCard?.data?.bankName ?? getCompanyCardFeed(feed); const [cardFeeds] = useCardFeeds(policyID); const data = assignCard?.data; @@ -74,7 +74,7 @@ function ConfirmationStep({policyID, feed, backTo}: ConfirmationStepProps) { return; } - const isFeedExpired = isSelectedFeedExpired(bankName ? cardFeeds?.settings?.oAuthAccountDetails?.[bankName] : undefined); + const isFeedExpired = isSelectedFeedExpired(cardFeeds?.[feed]); const institutionId = !!getPlaidInstitutionId(bankName); if (isFeedExpired) { diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 8f2551386431..c7a18cf6f41f 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -27,7 +27,16 @@ import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import {setPolicyPreventSelfApproval} from '@libs/actions/Policy/Policy'; import {removeApprovalWorkflow as removeApprovalWorkflowAction, updateApprovalWorkflow} from '@libs/actions/Workflow'; -import {getAllCardsForWorkspace, getCardFeedIcon, getCompanyFeeds, getPlaidInstitutionIconUrl, isExpensifyCardFullySetUp, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; +import { + getAllCardsForWorkspace, + getCardFeedIcon, + getCompanyCardFeedWithDomainID, + getCompanyFeeds, + getPlaidInstitutionIconUrl, + isExpensifyCardFullySetUp, + lastFourNumbersFromCardName, + maskCardNumber, +} from '@libs/CardUtils'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import navigateAfterInteraction from '@libs/Navigation/navigateAfterInteraction'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -276,7 +285,17 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.getRoute(policyID, card.cardID.toString(), Navigation.getActiveRoute())); return; } - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, card.cardID.toString(), card.bank, Navigation.getActiveRoute())); + if (!card.fundID) { + return; + } + Navigation.navigate( + ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute( + policyID, + card.cardID.toString(), + getCompanyCardFeedWithDomainID(card.bank as CompanyCardFeed, card.fundID), + Navigation.getActiveRoute(), + ), + ); }, [policyID], ); diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index f022b1a09705..b4cdc4796946 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -7,6 +7,7 @@ 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 type {CombinedCardFeed} from '@hooks/useCardFeeds'; import useCardFeeds from '@hooks/useCardFeeds'; import useCardsList from '@hooks/useCardsList'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; @@ -16,6 +17,7 @@ import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import { getCardFeedIcon, + getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, @@ -40,12 +42,15 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeed, CompanyCardFeedWithDomainID} from '@src/types/onyx'; import type {AssignCardData, AssignCardStep} from '@src/types/onyx/AssignCard'; type CardFeedListItem = ListItem & { + /** Combined feed key */ + value: CompanyCardFeedWithDomainID; + /** Card feed value */ - value: string; + feed: CompanyCardFeed; }; type WorkspaceMemberNewCardPageProps = WithPolicyAndFullscreenLoadingProps & PlatformStackScreenProps; @@ -61,7 +66,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew const styles = useThemeStyles(); const lazyIllustrations = useMemoizedLazyIllustrations(['ExpensifyCardImage'] as const); const illustrations = useThemeIllustrations(); - const [cardFeeds] = useCardFeeds(policyID); + const [cardFeeds, , defaultFeed] = useCardFeeds(policyID); const [selectedFeed, setSelectedFeed] = useState(''); const [shouldShowError, setShouldShowError] = useState(false); const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, {canBeMissing: true}); @@ -71,11 +76,12 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew const memberLogin = personalDetails?.[accountID]?.login ?? ''; const memberName = personalDetails?.[accountID]?.firstName ? personalDetails?.[accountID]?.firstName : personalDetails?.[accountID]?.login; const companyFeeds = getCompanyFeeds(cardFeeds, false, true); - const isFeedExpired = isSelectedFeedExpired((selectedFeed as CompanyCardFeed) ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed as CompanyCardFeed] : undefined); - const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, companyFeeds[selectedFeed as CompanyCardFeed]); + const currentFeed = selectedFeed ? cardFeeds?.[selectedFeed as CompanyCardFeedWithDomainID] : undefined; + const isFeedExpired = isSelectedFeedExpired(currentFeed); + const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeed); - const [list] = useCardsList(policyID, selectedFeed as CompanyCardFeed); - const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed as CompanyCardFeed], workspaceCardFeeds); + const [list] = useCardsList(selectedFeed as CompanyCardFeedWithDomainID); + const filteredCardList = getFilteredCardList(list, currentFeed?.accountList, workspaceCardFeeds); const shouldShowExpensifyCard = isExpensifyCardFullySetUp(policy, cardSettings); @@ -98,7 +104,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew } else { const data: Partial = { email: memberLogin, - bankName: selectedFeed, + bankName: getCompanyCardFeed(selectedFeed as CompanyCardFeedWithDomainID), cardName: `${memberName}'s card`, }; let currentStep: AssignCardStep = CONST.COMPANY_CARD.STEP.CARD; @@ -122,25 +128,26 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew } }; - const handleSelectFeed = (feed: CardFeedListItem) => { - setSelectedFeed(feed.value); - const workspaceCards = workspaceCardFeeds?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed.value as CompanyCardFeed}`] ?? {}; + const handleSelectFeed = ({value, feed}: CardFeedListItem) => { + setSelectedFeed(value); + const workspaceCards = workspaceCardFeeds?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed}`] ?? {}; const hasAllCardsData = !!workspaceCards.cardList; - if (isCustomFeed(feed.value as CompanyCardFeed) && !hasAllCardsData) { - openAssignFeedCardPage(policyID, feed.value as CompanyCardFeed, domainOrWorkspaceAccountID); + if (isCustomFeed(feed) && !hasAllCardsData) { + openAssignFeedCardPage(policyID, feed, domainOrWorkspaceAccountID); } setShouldShowError(false); }; - const companyCardFeeds: CardFeedListItem[] = (Object.keys(companyFeeds) as CompanyCardFeed[]).map((key) => { - const plaidUrl = getPlaidInstitutionIconUrl(key); + const companyCardFeeds: CardFeedListItem[] = (Object.entries(companyFeeds) as Array<[CompanyCardFeedWithDomainID, CombinedCardFeed]>).map(([key, value]) => { + const plaidUrl = getPlaidInstitutionIconUrl(value.feed); return { value: key, - text: getCustomOrFormattedFeedName(key, cardFeeds?.settings?.companyCardNicknames), + feed: value.feed, + text: getCustomOrFormattedFeedName(value.feed, value.customFeedName), keyForList: key, - isDisabled: companyFeeds[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - pendingAction: companyFeeds[key]?.pendingAction, + isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingAction: value.pendingAction, isSelected: selectedFeed === key, leftElement: plaidUrl ? ( @@ -150,7 +157,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew /> ) : ( diff --git a/src/types/onyx/AssignCard.ts b/src/types/onyx/AssignCard.ts index dd0ba64f8683..18415925ceef 100644 --- a/src/types/onyx/AssignCard.ts +++ b/src/types/onyx/AssignCard.ts @@ -2,6 +2,7 @@ import type {LinkAccount} from 'react-native-plaid-link-sdk'; import type {PlaidAccount} from 'react-plaid-link'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import type {CompanyCardFeed} from './CardFeeds'; /** Assign card flow steps */ type AssignCardStep = ValueOf; @@ -18,7 +19,7 @@ type AssignCardData = { cardNumber: string; /** The name of the feed */ - bankName: string; + bankName: CompanyCardFeed; /** The name of the card */ cardName: string; diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index 33a179bf1f9a..31ee7d1ac767 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -7,8 +7,11 @@ import type * as OnyxCommon from './OnyxCommon'; /** Card feed */ type CompanyCardFeed = ValueOf; +/** Company card feed with domain ID */ +type CompanyCardFeedWithDomainID = `${CompanyCardFeed}${typeof CONST.COMPANY_CARD.FEED_KEY_SEPARATOR}${string}`; + /** Custom card feed with a number */ -type CompanyCardFeedWithNumber = CompanyCardFeed | `${CompanyCardFeed}${number}`; +type CompanyCardFeedWithNumber = CompanyCardFeed | `${CompanyCardFeed}${number}` | CompanyCardFeedWithDomainID; /** Statement period end */ type StatementPeriodEnd = Exclude, typeof CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH>; @@ -225,6 +228,8 @@ export type { CardFeedProvider, CardFeedData, CompanyFeeds, + CompanyCardFeedWithDomainID, + CustomCardFeedData, CompanyCardNicknames, CompanyCardFeedWithNumber, FundID, diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 6cab4c8f6799..4fd8c653057d 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -16,7 +16,7 @@ import type CancellationDetails from './CancellationDetails'; import type Card from './Card'; import type {CardList, IssueNewCard, ProvisioningCardData, WorkspaceCardsList} from './Card'; import type CardFeeds from './CardFeeds'; -import type {AddNewCompanyCardFeed, CompanyCardFeed, FundID} from './CardFeeds'; +import type {AddNewCompanyCardFeed, CompanyCardFeed, CompanyCardFeedWithDomainID, FundID} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; import type {CorpayFields, CorpayFormField} from './CorpayFields'; @@ -165,6 +165,7 @@ export type { IssueNewCard, AddNewCompanyCardFeed, CompanyCardFeed, + CompanyCardFeedWithDomainID, LastExportMethod, Locale, LockAccountDetails, diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 170015cd9d32..0461e7945543 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -3,6 +3,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import type IllustrationsType from '@styles/theme/illustrations/types'; import type * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; +import type {CombinedCardFeeds} from '@src/hooks/useCardFeeds'; import IntlStore from '@src/languages/IntlStore'; import { checkIfFeedConnectionIsBroken, @@ -17,11 +18,14 @@ import { getCardFeedIcon, getCardsByCardholderName, getCompanyCardDescription, + getCompanyCardFeed, + getCompanyCardFeedWithDomainID, getCompanyFeeds, getCustomOrFormattedFeedName, getFeedType, getFilteredCardList, getMonthFromExpirationDateString, + getOriginalCompanyFeeds, getSelectedFeed, getYearFromExpirationDateString, hasIssuedExpensifyCard, @@ -32,7 +36,7 @@ import { maskCardNumber, sortCardsByCardholderName, } from '@src/libs/CardUtils'; -import type {Card, CardFeeds, CardList, CompanyCardFeed, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; +import type {Card, CardFeeds, CardList, CompanyCardFeed, CompanyCardFeedWithDomainID, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; import type {CompanyCardFeedWithNumber} from '@src/types/onyx/CardFeeds'; import {localeCompare} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -59,7 +63,7 @@ const directFeedBanks = [ const companyCardsCustomFeedSettings = { [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: { - pending: true, + pending: false, }, [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: { liabilityType: 'personal', @@ -84,14 +88,7 @@ const companyCardsCustomVisaFeedSettingsWithNumbers = { pending: false, }, }; -const companyCardsCustomFeedSettingsWithoutExpensifyBank = { - [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: { - pending: true, - }, - [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: { - liabilityType: 'personal', - }, -}; + const companyCardsDirectFeedSettings = { [CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE]: { liabilityType: 'personal', @@ -102,7 +99,7 @@ const companyCardsDirectFeedSettings = { }; const companyCardsSettingsWithoutExpensifyBank = { [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: { - pending: true, + pending: false, }, [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: { liabilityType: 'personal', @@ -110,13 +107,16 @@ const companyCardsSettingsWithoutExpensifyBank = { ...companyCardsDirectFeedSettings, }; -const companyCardsSettingsWithOnePendingFeed = { +const companyCardsSettingsWithPendingRemovedFeeds = { [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: { pending: true, }, [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: { pending: false, }, + [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, }; const oAuthAccountDetails = { @@ -226,6 +226,49 @@ const customFeedCardsList = { const customFeedName = 'Custom feed name'; const unknownFeed = 'ofx.chase.com' as CompanyCardFeed; +const combinedCardFeeds: CombinedCardFeeds = { + [`${CONST.COMPANY_CARD.FEED_BANK_NAME.VISA}#11111111`]: { + liabilityType: 'personal', + pending: false, + domainID: 11111111, + customFeedName: 'Custom feed name', + feed: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + }, + [`${CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD}#11111111`]: { + pending: true, + domainID: 11111111, + feed: CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD, + }, + [`${CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE}#22222222`]: { + liabilityType: 'personal', + domainID: 22222222, + accountList: ['CREDIT CARD...6607', 'CREDIT CARD...5501'], + credentials: 'xxxxx', + expiration: 1730998958, + pending: false, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + feed: CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE, + }, + [`${CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE}#11111111`]: { + liabilityType: 'personal', + domainID: 11111111, + accountList: ['CREDIT CARD...1233', 'CREDIT CARD...5678', 'CREDIT CARD...4444', 'CREDIT CARD...3333', 'CREDIT CARD...7788'], + credentials: 'xxxxx', + expiration: 1730998959, + pending: false, + feed: CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE, + }, +}; + +const combinedCardFeedsWithExpensifyCard: CombinedCardFeeds = { + ...combinedCardFeeds, + [`${CONST.EXPENSIFY_CARD.BANK}#11111111`]: { + domainID: 11111111, + pending: false, + feed: CONST.EXPENSIFY_CARD.BANK, + }, +}; + const policyWithCardsEnabled = { areExpensifyCardsEnabled: true, } as unknown as Policy; @@ -253,36 +296,10 @@ const cardFeedsCollection: OnyxCollection = { oAuthAccountDetails, }, }, - // Policy with direct feeds only + // Policy with pending and removed feeds FAKE_ID_2: { settings: { - companyCards: companyCardsDirectFeedSettings, - oAuthAccountDetails, - }, - }, - // Policy with custom feeds only - FAKE_ID_3: { - settings: { - companyCards: companyCardsCustomFeedSettings, - }, - }, - // Policy with custom feeds only, feed names with numbers - FAKE_ID_4: { - settings: { - companyCards: companyCardsCustomFeedSettingsWithNumbers, - }, - }, - // Policy with several Visa feeds - FAKE_ID_5: { - settings: { - companyCards: companyCardsCustomVisaFeedSettingsWithNumbers, - }, - }, - - // Policy with one pending feed - FAKE_ID_6: { - settings: { - companyCards: companyCardsSettingsWithOnePendingFeed, + companyCards: companyCardsSettingsWithPendingRemovedFeeds, }, }, // Policy with unknown feed @@ -429,56 +446,65 @@ describe('CardUtils', () => { }); }); - describe('getCompanyFeeds', () => { + describe('getOriginalCompanyFeeds', () => { it('Should return both custom and direct feeds with filtered out "Expensify Card" bank', () => { - const companyFeeds = getCompanyFeeds(cardFeedsCollection.FAKE_ID_1); + const companyFeeds = getOriginalCompanyFeeds(cardFeedsCollection.FAKE_ID_1); expect(companyFeeds).toStrictEqual(companyCardsSettingsWithoutExpensifyBank); }); - it('Should return direct feeds only since custom feeds are not exist', () => { - const companyFeeds = getCompanyFeeds(cardFeedsCollection.FAKE_ID_2); - expect(companyFeeds).toStrictEqual(companyCardsDirectFeedSettings); + it('Should return only feeds that are not pending/removed', () => { + const companyFeeds = getOriginalCompanyFeeds(cardFeedsCollection.FAKE_ID_2); + expect(Object.keys(companyFeeds).length).toStrictEqual(1); }); - it('Should return custom feeds only with filtered out "Expensify Card" bank since direct feeds are not exist', () => { - const companyFeeds = getCompanyFeeds(cardFeedsCollection.FAKE_ID_3); - expect(companyFeeds).toStrictEqual(companyCardsCustomFeedSettingsWithoutExpensifyBank); + it('Should return empty object if undefined is passed', () => { + const companyFeeds = getOriginalCompanyFeeds(undefined); + expect(companyFeeds).toStrictEqual({}); + }); + }); + + describe('getCompanyFeeds', () => { + it('Should filter out Expensify Card bank by default', () => { + const companyFeeds = getCompanyFeeds(combinedCardFeedsWithExpensifyCard); + const feedKeys = Object.keys(companyFeeds); + expect(feedKeys).not.toContain(`${CONST.EXPENSIFY_CARD.BANK}#11111111`); + }); + + it('Should filter out pending feeds when shouldFilterOutPendingFeeds is true', () => { + const companyFeeds = getCompanyFeeds(combinedCardFeeds, false, true); + const feedKeys = Object.keys(companyFeeds); + expect(feedKeys).not.toContain(`${CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD}#11111111`); + }); + + it('Should filter out removed feeds when shouldFilterOutRemovedFeeds is true', () => { + const companyFeeds = getCompanyFeeds(combinedCardFeeds, true, false); + const feedKeys = Object.keys(companyFeeds); + expect(feedKeys).not.toContain(`${CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE}#22222222`); }); it('Should return empty object if undefined is passed', () => { const companyFeeds = getCompanyFeeds(undefined); expect(companyFeeds).toStrictEqual({}); }); - - it('Should return only feeds that are not pending', () => { - const companyFeeds = getCompanyFeeds(cardFeedsCollection.FAKE_ID_6, false, true); - expect(Object.keys(companyFeeds).length).toStrictEqual(1); - }); }); describe('getSelectedFeed', () => { it('Should return last selected custom feed', () => { - const lastSelectedCustomFeed = CONST.COMPANY_CARD.FEED_BANK_NAME.VISA; - const selectedFeed = getSelectedFeed(lastSelectedCustomFeed, cardFeedsCollection.FAKE_ID_1); + const lastSelectedCustomFeed: CompanyCardFeedWithDomainID = `${CONST.COMPANY_CARD.FEED_BANK_NAME.VISA}#12345`; + const selectedFeed = getSelectedFeed(lastSelectedCustomFeed, combinedCardFeeds); expect(selectedFeed).toBe(lastSelectedCustomFeed); }); it('Should return last selected direct feed', () => { - const lastSelectedDirectFeed = CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE; - const selectedFeed = getSelectedFeed(lastSelectedDirectFeed, cardFeedsCollection.FAKE_ID_1); + const lastSelectedDirectFeed: CompanyCardFeedWithDomainID = `${CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE}#12345`; + const selectedFeed = getSelectedFeed(lastSelectedDirectFeed, combinedCardFeeds); expect(selectedFeed).toBe(lastSelectedDirectFeed); }); - it('Should return the first available custom feed if lastSelectedFeed is undefined', () => { + it('Should return the first available feed if lastSelectedFeed is undefined', () => { const lastSelectedFeed = undefined; - const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeedsCollection.FAKE_ID_3); - expect(selectedFeed).toBe(CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD); - }); - - it('Should return the first available direct feed if lastSelectedFeed is undefined', () => { - const lastSelectedFeed = undefined; - const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeedsCollection.FAKE_ID_2); - expect(selectedFeed).toBe(CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE); + const selectedFeed = getSelectedFeed(lastSelectedFeed, combinedCardFeeds); + expect(selectedFeed).toBe(`${CONST.COMPANY_CARD.FEED_BANK_NAME.VISA}#11111111`); }); it('Should return undefined if lastSelectedFeed is undefined and there is no card feeds', () => { @@ -496,28 +522,26 @@ describe('CardUtils', () => { }); it('Should return custom name if exists', () => { const feed = CONST.COMPANY_CARD.FEED_BANK_NAME.VISA; - const companyCardNicknames = cardFeedsCollection.FAKE_ID_1?.settings?.companyCardNicknames; - const feedName = getCustomOrFormattedFeedName(feed, companyCardNicknames); + const feedName = getCustomOrFormattedFeedName(feed, customFeedName); expect(feedName).toBe(customFeedName); }); it('Should return formatted name if there is no custom name', () => { const feed = CONST.COMPANY_CARD.FEED_BANK_NAME.VISA; - const companyCardNicknames = cardFeedsCollection.FAKE_ID_3?.settings?.companyCardNicknames; - const feedName = getCustomOrFormattedFeedName(feed, companyCardNicknames); + const customName = undefined; + const feedName = getCustomOrFormattedFeedName(feed, customName); expect(feedName).toBe('Visa cards'); }); it('Should return undefined if no feed provided', () => { const feed = undefined; - const companyCardNicknames = cardFeedsCollection.FAKE_ID_1?.settings?.companyCardNicknames; - const feedName = getCustomOrFormattedFeedName(feed, companyCardNicknames); + const feedName = getCustomOrFormattedFeedName(feed); expect(feedName).toBe(undefined); }); it('Should return feed key name for unknown feed', () => { - const companyCardNicknames = cardFeedsCollection.FAKE_ID_7?.settings?.companyCardNicknames; - const feedName = getCustomOrFormattedFeedName(unknownFeed, companyCardNicknames); + const companyCardNickname = cardFeedsCollection.FAKE_ID_7?.settings?.companyCardNicknames?.[unknownFeed]; + const feedName = getCustomOrFormattedFeedName(unknownFeed, companyCardNickname); expect(feedName).toBe(unknownFeed); }); }); @@ -665,13 +689,13 @@ describe('CardUtils', () => { }); it('Should return filtered direct feed cards list with a single card', () => { - const cardsList = getFilteredCardList(directFeedCardsSingleList, oAuthAccountDetails[CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE], undefined); + const cardsList = getFilteredCardList(directFeedCardsSingleList, oAuthAccountDetails[CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE].accountList, undefined); // eslint-disable-next-line @typescript-eslint/naming-convention expect(cardsList).toStrictEqual({'CREDIT CARD...6607': 'CREDIT CARD...6607'}); }); it('Should return filtered direct feed cards list with multiple cards', () => { - const cardsList = getFilteredCardList(directFeedCardsMultipleList, oAuthAccountDetails[CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE], undefined); + const cardsList = getFilteredCardList(directFeedCardsMultipleList, oAuthAccountDetails[CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE].accountList, undefined); expect(cardsList).toStrictEqual({ // eslint-disable-next-line @typescript-eslint/naming-convention 'CREDIT CARD...1233': 'CREDIT CARD...1233', @@ -785,27 +809,25 @@ describe('CardUtils', () => { }, }, } as unknown as OnyxCollection; - const directFeedWithAssignedCard = { - accountList: [assignedCard1, assignedCard2, unassignedCard], - } as unknown as (typeof oAuthAccountDetails)[typeof CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE]; - const filteredCards = getFilteredCardList(undefined, directFeedWithAssignedCard, mockAllWorkspaceCards); + const accountList = [assignedCard1, assignedCard2, unassignedCard]; + const filteredCards = getFilteredCardList(undefined, accountList, mockAllWorkspaceCards); expect(filteredCards).toStrictEqual({[`${unassignedCard}`]: unassignedCard}); }); }); describe('getFeedType', () => { it('should return the feed name with a consecutive number, if there is already a feed with a number', () => { - const feedType = getFeedType('vcf', cardFeedsCollection.FAKE_ID_4); + const feedType = getFeedType('vcf', companyCardsCustomFeedSettingsWithNumbers as CombinedCardFeeds); expect(feedType).toBe('vcf2'); }); it('should return the feed name with 1, if there is already a feed without a number', () => { - const feedType = getFeedType('vcf', cardFeedsCollection.FAKE_ID_3); + const feedType = getFeedType('vcf', companyCardsCustomFeedSettings); expect(feedType).toBe('vcf1'); }); it('should return the feed name with with the first smallest available number', () => { - const feedType = getFeedType('vcf', cardFeedsCollection.FAKE_ID_5); + const feedType = getFeedType('vcf', companyCardsCustomVisaFeedSettingsWithNumbers as CombinedCardFeeds); expect(feedType).toBe('vcf2'); }); }); @@ -1268,4 +1290,21 @@ describe('CardUtils', () => { expect(sorted.map((r: Card) => r.cardID)).toEqual([11, 10, 99]); }); }); + + describe('getCompanyCardFeed', () => { + it('should extract the original feed from a combined feed key', () => { + const combinedKey: CompanyCardFeedWithDomainID = `${CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE}#22222222`; + const feed = getCompanyCardFeed(combinedKey); + expect(feed).toBe(CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE); + }); + }); + + describe('getCompanyCardFeedWithDomainID', () => { + it('should combine feed name domain ID', () => { + const feedName = CONST.COMPANY_CARD.FEED_BANK_NAME.VISA; + const domainID = 11111111; + const combinedKey = getCompanyCardFeedWithDomainID(feedName, domainID); + expect(combinedKey).toBe(`${feedName}${CONST.COMPANY_CARD.FEED_KEY_SEPARATOR}${domainID}`); + }); + }); }); diff --git a/tests/unit/hooks/useIsAllowedToIssueCompanyCard.test.ts b/tests/unit/hooks/useIsAllowedToIssueCompanyCard.test.ts index b30901cc8754..e8d46bd98cd2 100644 --- a/tests/unit/hooks/useIsAllowedToIssueCompanyCard.test.ts +++ b/tests/unit/hooks/useIsAllowedToIssueCompanyCard.test.ts @@ -6,9 +6,30 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import createRandomPolicy from '../../utils/collections/policies'; +const domainID = 19475968; const mockPolicyID = '123456'; +const workspaceAccountID = 11111111; -const mockPolicy = {...createRandomPolicy(Number(mockPolicyID), CONST.POLICY.TYPE.TEAM, 'TestPolicy'), policyID: mockPolicyID}; +const mockPolicy = {...createRandomPolicy(Number(mockPolicyID), CONST.POLICY.TYPE.TEAM, 'TestPolicy'), policyID: mockPolicyID, workspaceAccountID}; + +const mockedFeeds = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'vcf#19475968': { + liabilityType: 'personal', + pending: false, + domainID, + customFeedName: 'Custom feed name', + feed: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'vcf#11111111': { + liabilityType: 'personal', + pending: false, + domainID: workspaceAccountID, + customFeedName: 'Custom feed name 1', + feed: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + }, +}; jest.mock('@hooks/useCardFeeds', () => ({ // eslint-disable-next-line @typescript-eslint/naming-convention @@ -18,122 +39,37 @@ jest.mock('@hooks/useCardFeeds', () => ({ describe('useIsAllowedToIssueCompanyCard', () => { beforeEach(async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${mockPolicy?.policyID}`, mockPolicy); - await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicy?.policyID}`, 'vcf'); }); it('should return true if domain feed and access is granted', async () => { - const domainID = 19475968; + await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicy?.policyID}`, 'vcf#19475968'); await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${domainID}`, true); - (useCardFeeds as jest.Mock).mockReturnValue([ - { - settings: { - companyCards: { - vcf: { - asrEnabled: false, - country: 'US', - domainID, - forceReimbursable: 'force_no', - liabilityType: 'corporate', - preferredPolicy: '135CA2196CD21C88', - reportTitleFormat: '', - shouldApplyCashbackToBill: true, - statementPeriodEndDay: 'LAST_DAY_OF_MONTH', - uploadLayoutSettings: [], - }, - }, - companyCardNicknames: {}, - oAuthAccountDetails: {}, - }, - }, - {status: 'loaded'}, - ]); + (useCardFeeds as jest.Mock).mockReturnValue([mockedFeeds, {status: 'loaded'}]); const {result} = renderHook(() => useIsAllowedToIssueCompanyCard({policyID: mockPolicyID})); expect(result?.current).toBe(true); }); it('should return false if domain feed and access is not granted', async () => { - const domainID = 19475968; + await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicy?.policyID}`, 'vcf#19475968'); await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${domainID}`, false); - (useCardFeeds as jest.Mock).mockReturnValue([ - { - settings: { - companyCards: { - vcf: { - asrEnabled: false, - country: 'US', - domainID, - forceReimbursable: 'force_no', - liabilityType: 'corporate', - preferredPolicy: '135CA2196CD21C88', - reportTitleFormat: '', - shouldApplyCashbackToBill: true, - statementPeriodEndDay: 'LAST_DAY_OF_MONTH', - uploadLayoutSettings: [], - }, - }, - companyCardNicknames: {}, - oAuthAccountDetails: {}, - }, - }, - {status: 'loaded'}, - ]); + (useCardFeeds as jest.Mock).mockReturnValue([mockedFeeds, {status: 'loaded'}]); const {result} = renderHook(() => useIsAllowedToIssueCompanyCard({policyID: mockPolicyID})); expect(result?.current).toBe(false); }); it('should return true if workspace feed and user is admin', async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicy?.policyID}`, 'vcf#11111111'); await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${mockPolicy?.policyID}`, { role: CONST.POLICY.ROLE.ADMIN, }); - (useCardFeeds as jest.Mock).mockReturnValue([ - { - settings: { - companyCards: { - vcf: { - asrEnabled: false, - country: 'US', - forceReimbursable: 'force_no', - liabilityType: 'corporate', - preferredPolicy: '135CA2196CD21C88', - reportTitleFormat: '', - shouldApplyCashbackToBill: true, - statementPeriodEndDay: 'LAST_DAY_OF_MONTH', - uploadLayoutSettings: [], - }, - }, - companyCardNicknames: {}, - oAuthAccountDetails: {}, - }, - }, - {status: 'loaded'}, - ]); + (useCardFeeds as jest.Mock).mockReturnValue([mockedFeeds, {status: 'loaded'}]); const {result} = renderHook(() => useIsAllowedToIssueCompanyCard({policyID: mockPolicyID})); expect(result?.current).toBe(true); }); it('should return false if workspace feed and user is not an admin', async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicy?.policyID}`, 'vcf#11111111'); await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${mockPolicy?.policyID}`, { role: CONST.POLICY.ROLE.USER, }); - (useCardFeeds as jest.Mock).mockReturnValue([ - { - settings: { - companyCards: { - vcf: { - asrEnabled: false, - country: 'US', - forceReimbursable: 'force_no', - liabilityType: 'corporate', - preferredPolicy: '135CA2196CD21C88', - reportTitleFormat: '', - shouldApplyCashbackToBill: true, - statementPeriodEndDay: 'LAST_DAY_OF_MONTH', - uploadLayoutSettings: [], - }, - }, - companyCardNicknames: {}, - oAuthAccountDetails: {}, - }, - }, - {status: 'loaded'}, - ]); + (useCardFeeds as jest.Mock).mockReturnValue([mockedFeeds, {status: 'loaded'}]); const {result} = renderHook(() => useIsAllowedToIssueCompanyCard({policyID: mockPolicyID})); expect(result?.current).toBe(false); });