From b5c3f395b8f8920ca8da3d36a45a3a3d4b62f63a Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 17 Nov 2025 22:25:43 +0000 Subject: [PATCH 1/3] Update version to 9.2.59-5 (cherry picked from commit 52031fa40f0f3e66d374f6c32e0b31bee48d545b) (cherry-picked to staging by puneetlath) --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- ios/ShareViewController/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 186dc8642803..d1c3a126035d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -114,8 +114,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009025904 - versionName "9.2.59-4" + versionCode 1009025905 + versionName "9.2.59-5" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index bc83c7154e51..843bce0ec8a2 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -44,7 +44,7 @@ CFBundleVersion - 9.2.59.4 + 9.2.59.5 FullStory OrgId diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 38c7d1995c04..e7f8d1e4692b 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.59 CFBundleVersion - 9.2.59.4 + 9.2.59.5 NSExtension NSExtensionPointIdentifier diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist index e46151733deb..50ac8e1d3cab 100644 --- a/ios/ShareViewController/Info.plist +++ b/ios/ShareViewController/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.59 CFBundleVersion - 9.2.59.4 + 9.2.59.5 NSExtension NSExtensionAttributes diff --git a/package-lock.json b/package-lock.json index 345d68716e86..3af8cb69ccc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.2.59-4", + "version": "9.2.59-5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.2.59-4", + "version": "9.2.59-5", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 60b0d8f3fa21..7855ad71b272 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.2.59-4", + "version": "9.2.59-5", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 7960c9a4380fff995ee7e92576c6067ff6992c7a Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 17 Nov 2025 22:26:24 +0000 Subject: [PATCH 2/3] Update Mobile-Expensify submodule version to 9.2.59-5 --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 469e94ca2505..1ddab27f66cb 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 469e94ca25059ffbe06547089a5976c8880e7609 +Subproject commit 1ddab27f66cb2778f4a29d75707ce794fe3ab65e From 80df068d41f736766aecda079a2df42a7b1dfcdf Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Mon, 17 Nov 2025 17:20:59 -0500 Subject: [PATCH 3/3] Merge pull request #75356 from Expensify/revert-74356 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Merge pull request #74356 from callstack-internal/VickyStash/… (cherry picked from commit 06aea8a32e4023616ac926b658a5c40133385cf3) (cherry-picked to staging by puneetlath) --- src/CONST/index.ts | 1 - src/ONYXKEYS.ts | 2 +- src/hooks/useCardFeeds.tsx | 56 +++-- src/hooks/useCardsList.tsx | 18 +- src/hooks/useIsAllowedToIssueCompanyCard.ts | 6 +- src/hooks/useUpdateFeedBrokenConnection.ts | 10 +- src/libs/CardFeedUtils.ts | 10 +- src/libs/CardUtils.ts | 68 ++---- src/libs/actions/Card.ts | 4 +- src/libs/actions/CompanyCards.ts | 34 +-- .../BankConnection/index.native.tsx | 18 +- .../companyCards/BankConnection/index.tsx | 18 +- ...kspaceCompanyCardAccountSelectCardPage.tsx | 12 +- .../WorkspaceCompanyCardDetailsPage.tsx | 23 +- .../WorkspaceCompanyCardEditCardNamePage.tsx | 10 +- .../WorkspaceCompanyCardFeedSelectorPage.tsx | 33 +-- ...spaceCompanyCardStatementCloseDatePage.tsx | 15 +- ...WorkspaceCompanyCardsErrorConfirmation.tsx | 12 +- .../WorkspaceCompanyCardsList.tsx | 8 +- ...WorkspaceCompanyCardsListHeaderButtons.tsx | 28 +-- .../WorkspaceCompanyCardsPage.tsx | 26 +-- ...kspaceCompanyCardsSettingsFeedNamePage.tsx | 9 +- .../WorkspaceCompanyCardsSettingsPage.tsx | 21 +- .../addNew/CardInstructionsStep.tsx | 6 +- .../addNew/DirectStatementCloseDatePage.tsx | 15 +- .../addNew/PlaidConnectionStep.tsx | 4 +- .../assignCard/AssignCardFeedPage.tsx | 4 +- .../companyCards/assignCard/AssigneeStep.tsx | 6 +- .../assignCard/CardSelectionStep.tsx | 12 +- .../assignCard/ConfirmationStep.tsx | 10 +- .../members/WorkspaceMemberDetailsPage.tsx | 23 +- .../members/WorkspaceMemberNewCardPage.tsx | 50 ++--- src/types/onyx/AssignCard.ts | 3 +- src/types/onyx/CardFeeds.ts | 7 +- src/types/onyx/index.ts | 3 +- tests/unit/CardUtilsTest.ts | 205 +++++++----------- .../useIsAllowedToIssueCompanyCard.test.ts | 124 ++++++++--- 37 files changed, 419 insertions(+), 495 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index f95edd054da7..31db9ce7a341 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3349,7 +3349,6 @@ 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 22af17fee262..fb7c1526f162 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.CompanyCardFeedWithDomainID; + [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeed; [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 46282e29dae8..a926c0a9db18 100644 --- a/src/hooks/useCardFeeds.tsx +++ b/src/hooks/useCardFeeds.tsx @@ -1,29 +1,17 @@ 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, CompanyCardFeedWithDomainID} from '@src/types/onyx'; -import type {CustomCardFeedData, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; +import type {CardFeeds, CompanyCardFeed} from '@src/types/onyx'; 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 - @@ -32,7 +20,7 @@ type CombinedCardFeeds = Record; * 2. The result metadata from the Onyx collection fetch. * 3. Card feeds specific to the given policyID (or `undefined` if unavailable). */ -const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefined, ResultMetadata>, CardFeeds | undefined] => { +const useCardFeeds = (policyID: string | undefined): [CardFeeds | 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}`]; @@ -42,9 +30,18 @@ const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefi return undefined; } - const result: CombinedCardFeeds = {}; + const {companyCards = {}, companyCardNicknames = {}, oAuthAccountDetails = {}} = defaultFeed?.settings ?? {}; - return Object.entries(allFeeds).reduce((acc, [onyxKey, feed]) => { + const result: CardFeeds & {settings: Required} = { + settings: { + companyCards: {...companyCards}, + companyCardNicknames: {...companyCardNicknames}, + oAuthAccountDetails: {...oAuthAccountDetails}, + }, + isLoading: defaultFeed?.isLoading, + }; + + return Object.entries(allFeeds).reduce}>((acc, [onyxKey, feed]) => { if (!feed?.settings?.companyCards) { return acc; } @@ -52,30 +49,29 @@ const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefi Object.entries(feed.settings.companyCards).forEach(([key, feedSettings]) => { const feedName = key as CompanyCardFeed; const feedOAuthAccountDetails = feed.settings.oAuthAccountDetails?.[feedName]; - const feedCompanyCardNickname = feed.settings.companyCardNicknames?.[feedName]; - const domainID = onyxKey.split('_').at(-1); + const feedCompanyCardNicknames = feed.settings.companyCardNicknames?.[feedName]; - if (feedSettings.preferredPolicy !== policyID || !domainID) { + if (feedSettings.preferredPolicy !== policyID || acc.settings.companyCards[feedName]) { return; } - const combinedFeedKey = getCompanyCardFeedWithDomainID(feedName, domainID); + const domainID = onyxKey.split('_').at(-1); + + acc.settings.companyCards[feedName] = {...feedSettings, domainID: domainID ? Number(domainID) : undefined}; - acc[combinedFeedKey] = { - ...feedSettings, - ...feedOAuthAccountDetails, - customFeedName: feedCompanyCardNickname, - domainID: Number(domainID), - feed: feedName, - }; + if (feedOAuthAccountDetails) { + acc.settings.oAuthAccountDetails[feedName] = feedOAuthAccountDetails; + } + if (feedCompanyCardNicknames) { + acc.settings.companyCardNicknames[feedName] = feedCompanyCardNicknames; + } }); return acc; }, result); - }, [allFeeds, policyID]); + }, [allFeeds, defaultFeed?.isLoading, defaultFeed?.settings, 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 934d1cb63b6d..769144a1a02b 100644 --- a/src/hooks/useCardsList.tsx +++ b/src/hooks/useCardsList.tsx @@ -1,14 +1,18 @@ import type {ResultMetadata} from 'react-native-onyx'; -import {filterInactiveCards} from '@libs/CardUtils'; -import CONST from '@src/CONST'; +import {filterInactiveCards, getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CardList, CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CardList, CompanyCardFeed} from '@src/types/onyx'; +import useCardFeeds from './useCardFeeds'; import useOnyx from './useOnyx'; +import useWorkspaceAccountID from './useWorkspaceAccountID'; -/* 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}`, { +/* 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}`, { selector: filterInactiveCards, canBeMissing: true, }); diff --git a/src/hooks/useIsAllowedToIssueCompanyCard.ts b/src/hooks/useIsAllowedToIssueCompanyCard.ts index 050506c12151..148dc0433065 100644 --- a/src/hooks/useIsAllowedToIssueCompanyCard.ts +++ b/src/hooks/useIsAllowedToIssueCompanyCard.ts @@ -17,11 +17,7 @@ function useIsAllowedToIssueCompanyCard({policyID}: {policyID?: string}) { const selectedFeedData = selectedFeed && companyCards[selectedFeed]; const [adminAccess] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${selectedFeedData?.domainID}`, {canBeMissing: true}); - if (selectedFeedData?.domainID === policy?.workspaceAccountID) { - return isPolicyAdmin; - } - - return !!adminAccess; + return selectedFeedData?.domainID ? !!adminAccess : isPolicyAdmin; } export default useIsAllowedToIssueCompanyCard; diff --git a/src/hooks/useUpdateFeedBrokenConnection.ts b/src/hooks/useUpdateFeedBrokenConnection.ts index 77fdc8ffe815..1c4daab40368 100644 --- a/src/hooks/useUpdateFeedBrokenConnection.ts +++ b/src/hooks/useUpdateFeedBrokenConnection.ts @@ -1,14 +1,14 @@ import {useCallback} from 'react'; -import {checkIfFeedConnectionIsBroken, getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFeedConnectionBrokenCard} from '@libs/CardUtils'; +import {checkIfFeedConnectionIsBroken, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFeedConnectionBrokenCard} from '@libs/CardUtils'; import {updateWorkspaceCompanyCard} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; -import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} 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?: CompanyCardFeedWithDomainID}) { - const [cardsList] = useCardsList(feed); +export default function useUpdateFeedBrokenConnection({policyID, feed}: {policyID?: string; feed?: CompanyCardFeed}) { + const [cardsList] = useCardsList(policyID, 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, getCompanyCardFeed(feed), brokenCard?.lastScrapeResult); + updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, brokenCardId, feed, brokenCard?.lastScrapeResult); }, [brokenCard?.lastScrapeResult, brokenCardId, domainOrWorkspaceAccountID, feed]); return {updateBrokenConnection, isFeedConnectionBroken}; diff --git a/src/libs/CardFeedUtils.ts b/src/libs/CardFeedUtils.ts index 913395fd3af8..a36199da7444 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(getOriginalCompanyFeeds(cardFeeds)).forEach((key) => { + Object.keys(getCompanyFeeds(cardFeeds, true, true)).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?.[feed], false) ?? feed, + name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames, false) ?? feed, }; }); }); @@ -478,7 +478,7 @@ function getCardFeedsForDisplayPerPolicy(allCardFeeds: OnyxCollection return; } - Object.entries(getOriginalCompanyFeeds(cardFeeds)).forEach(([key, feedData]) => { + Object.entries(getCompanyFeeds(cardFeeds, true, true)).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?.[feed], false) ?? feed, + name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames, false) ?? feed, }); }); }); diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 9a5ea851e745..f70e3308e321 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -4,7 +4,6 @@ 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'; @@ -12,7 +11,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, CompanyCardFeedWithDomainID, CompanyCardFeedWithNumber, CompanyFeeds} from '@src/types/onyx/CardFeeds'; +import type {CardFeedData, CompanyCardFeedWithNumber, CompanyCardNicknames, CompanyFeeds, DirectCardFeedData} 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 @@ -345,27 +344,16 @@ 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 getOriginalCompanyFeeds(cardFeeds: OnyxEntry): CompanyFeeds { +function getCompanyFeeds(cardFeeds: OnyxEntry, shouldFilterOutRemovedFeeds = false, shouldFilterOutPendingFeeds = false): 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 !value.feed.includes(CONST.EXPENSIFY_CARD.BANK); + return key !== CONST.EXPENSIFY_CARD.BANK; }), ); } @@ -418,11 +406,13 @@ const getBankCardDetailsImage = (bank: ValueOf return iconMap[bank]; }; -function getCustomOrFormattedFeedName(feed?: CompanyCardFeed, customFeedName?: string, shouldAddCardsSuffix = true): string | undefined { +function getCustomOrFormattedFeedName(feed?: CompanyCardFeed, companyCardNicknames?: CompanyCardNicknames, shouldAddCardsSuffix = true): string | undefined { if (!feed) { return; } + const customFeedName = companyCardNicknames?.[feed]; + if (customFeedName && typeof customFeedName !== 'string') { return ''; } @@ -524,24 +514,21 @@ function getCorrectStepForPlaidSelectedBank(selectedBank: ValueOf, 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 getSelectedFeed(lastSelectedFeed: OnyxEntry, cardFeeds: OnyxEntry): CompanyCardFeed | undefined { + const defaultFeed = Object.keys(getCompanyFeeds(cardFeeds, true)).at(0) as CompanyCardFeed | undefined; + return lastSelectedFeed ?? defaultFeed; } -function getCompanyCardFeedWithDomainID(feedName: CompanyCardFeed, domainID: number | string): CompanyCardFeedWithDomainID { - return `${feedName}${CONST.COMPANY_CARD.FEED_KEY_SEPARATOR}${domainID}`; -} +function isSelectedFeedExpired(directFeed: DirectCardFeedData | undefined): boolean { + if (!directFeed || !directFeed.expiration) { + return false; + } -function isSelectedFeedExpired(cardFeed: CombinedCardFeed | undefined): boolean { - return cardFeed?.expiration ? isBefore(fromUnixTime(cardFeed.expiration), new Date()) : false; + return isBefore(fromUnixTime(directFeed.expiration), new Date()); } /** Returns list of cards which can be assigned */ -function getFilteredCardList(list: WorkspaceCardsList | undefined, accountList: string[] | undefined, workspaceCardFeeds: OnyxCollection) { +function getFilteredCardList(list: WorkspaceCardsList | undefined, directFeed: DirectCardFeedData | undefined, workspaceCardFeeds: OnyxCollection) { const {cardList: customFeedCardsToAssign, ...cards} = list ?? {}; const assignedCards = new Set(Object.values(cards).map((card) => card.cardName)); @@ -560,8 +547,8 @@ function getFilteredCardList(list: WorkspaceCardsList | undefined, accountList: }); }); - if (accountList) { - const unassignedDirectFeedCards = accountList.filter((cardNumber) => !assignedCards.has(cardNumber) && !allWorkspaceAssignedCards.has(cardNumber)); + if (directFeed) { + const unassignedDirectFeedCards = directFeed.accountList.filter((cardNumber) => !assignedCards.has(cardNumber) && !allWorkspaceAssignedCards.has(cardNumber)); return Object.fromEntries(unassignedDirectFeedCards.map((cardNumber) => [cardNumber, cardNumber])); } @@ -585,7 +572,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 CompanyCardFeedWithDomainID | undefined, + newFeed: currentFeeds.find((feed) => !prevFeeds.includes(feed)) as CompanyCardFeed | undefined, }; } @@ -597,11 +584,11 @@ function filterInactiveCards(cards: CardList | undefined): CardList { function getAllCardsForWorkspace( workspaceAccountID: number, allCardList: OnyxCollection, - cardFeeds?: CombinedCardFeeds, + cardFeeds?: CardFeeds, expensifyCardSettings?: OnyxCollection, ): CardList { const cards = {}; - const companyCardsDomainFeeds = Object.entries(cardFeeds ?? {}).map(([feedName, feedData]) => ({domainID: feedData.domainID, feedName})); + const companyCardsDomainFeeds = Object.entries(cardFeeds?.settings?.companyCards ?? {}).map(([feedName, feedData]) => ({domainID: feedData.domainID, feedName})); const expensifyCardsDomainIDs = Object.keys(expensifyCardSettings ?? {}) .map((key) => key.split('_').at(-1)) .filter((id): id is string => !!id); @@ -624,11 +611,9 @@ 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 ?? {}) - .filter((str) => str.includes(feedKey)) - .map((str) => getCompanyCardFeed(str as CompanyCardFeedWithDomainID)); + const filteredFeeds = Object.keys(cardFeeds?.settings?.companyCards ?? {}).filter((str) => str.includes(feedKey)); const feedNumbers = filteredFeeds.map((str) => parseInt(str.replace(feedKey, ''), 10)).filter(Boolean); feedNumbers.sort((a, b) => a - b); @@ -735,12 +720,6 @@ 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, @@ -794,8 +773,5 @@ export { getPlaidInstitutionId, getFeedConnectionBrokenCard, getCorrectStepForPlaidSelectedBank, - getOriginalCompanyFeeds, - getCompanyCardFeed, - getCompanyCardFeedWithDomainID, getEligibleBankAccountsForUkEuCard, }; diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index bde8d1f86abc..51d1b1d33441 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, CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {Card, CompanyCardFeed} 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: CompanyCardFeedWithDomainID, policyID: string | undefined) { +function updateSelectedFeed(feed: CompanyCardFeed, policyID: string | undefined) { if (!policyID) { return; } diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 3d5c9d1c2c25..1e671f59571e 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -1,6 +1,5 @@ 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, @@ -21,18 +20,9 @@ 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} from '@src/types/onyx'; +import type {Card, CardFeeds} from '@src/types/onyx'; import type {AssignCard, AssignCardData} from '@src/types/onyx/AssignCard'; -import type { - AddNewCardFeedData, - AddNewCardFeedStep, - CardFeedData, - CardFeedDetails, - CompanyCardFeed, - CompanyCardFeedWithDomainID, - StatementPeriodEnd, - StatementPeriodEndDay, -} from '@src/types/onyx/CardFeeds'; +import type {AddNewCardFeedData, AddNewCardFeedStep, CardFeedData, CardFeedDetails, CompanyCardFeed, StatementPeriodEnd, StatementPeriodEndDay} from '@src/types/onyx/CardFeeds'; import type {OnyxData} from '@src/types/onyx/Request'; type AddNewCompanyCardFlowData = { @@ -73,10 +63,10 @@ function addNewCompanyCardsFeed( policyID: string | undefined, cardFeed: CompanyCardFeed, feedDetails: CardFeedDetails, - cardFeeds: OnyxEntry, + cardFeeds: OnyxEntry, statementPeriodEnd: StatementPeriodEnd | undefined, statementPeriodEndDay: StatementPeriodEndDay | undefined, - lastSelectedFeed?: CompanyCardFeedWithDomainID, + lastSelectedFeed?: CompanyCardFeed, ) { const authToken = NetworkStore.getAuthToken(); const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); @@ -163,7 +153,7 @@ function addNewCompanyCardsFeed( API.write(WRITE_COMMANDS.REQUEST_FEED_SETUP, parameters, {optimisticData, failureData, successData, finallyData}); } -function setWorkspaceCompanyCardFeedName(policyID: string, domainOrWorkspaceAccountID: number, bankName: CompanyCardFeed, userDefinedName: string) { +function setWorkspaceCompanyCardFeedName(policyID: string, domainOrWorkspaceAccountID: number, bankName: string, userDefinedName: string) { const authToken = NetworkStore.getAuthToken(); const onyxData: OnyxData = { optimisticData: [ @@ -220,7 +210,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?: CompanyCardFeedWithDomainID) { +function deleteWorkspaceCompanyCardFeed(policyID: string, domainOrWorkspaceAccountID: number, bankName: CompanyCardFeed, cardIDs: string[], feedToOpen?: CompanyCardFeed) { const authToken = NetworkStore.getAuthToken(); const isCustomFeed = CardUtils.isCustomFeed(bankName); const optimisticFeedUpdates = {[bankName]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}}; @@ -385,7 +375,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(getCompanyCardFeed(feed)) : (bankNameFromRoute ?? addNewCard?.data?.plaidConnectedFeed ?? selectedBank); + const bankName = feed ? getBankName(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); - const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.[feed]) : false; + const prevFeedsData = usePrevious(cardFeeds?.settings?.oAuthAccountDetails); + const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.settings?.oAuthAccountDetails?.[feed]) : false; const {isNewFeedConnected, newFeed} = useMemo( - () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds ?? {}, addNewCard?.data?.plaidConnectedFeed), - [addNewCard?.data?.plaidConnectedFeed, cardFeeds, prevFeedsData], + () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds?.settings?.oAuthAccountDetails ?? {}, addNewCard?.data?.plaidConnectedFeed), + [addNewCard?.data?.plaidConnectedFeed, cardFeeds?.settings?.oAuthAccountDetails, 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?.[newFeed]?.errors); + const isNewFeedHasError = !!(newFeed && cardFeeds?.settings?.oAuthAccountDetails?.[newFeed]?.errors); const renderLoading = () => ; diff --git a/src/pages/workspace/companyCards/BankConnection/index.tsx b/src/pages/workspace/companyCards/BankConnection/index.tsx index d956ec2edc02..37f4daace2e5 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, getCompanyCardFeed, isSelectedFeedExpired} from '@libs/CardUtils'; +import {checkIfNewFeedConnected, getBankName, 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 {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} 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?: CompanyCardFeedWithDomainID; + feed?: CompanyCardFeed; /** 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); + const prevFeedsData = usePrevious(cardFeeds?.settings?.oAuthAccountDetails); const [shouldBlockWindowOpen, setShouldBlockWindowOpen] = useState(false); const selectedBank = addNewCard?.data?.selectedBank; - const bankName = feed ? getBankName(getCompanyCardFeed(feed)) : (bankNameFromRoute ?? addNewCard?.data?.plaidConnectedFeed ?? selectedBank); + const bankName = feed ? getBankName(feed) : (bankNameFromRoute ?? addNewCard?.data?.plaidConnectedFeed ?? selectedBank); const {isNewFeedConnected, newFeed} = useMemo( - () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds ?? {}, addNewCard?.data?.plaidConnectedFeed), - [addNewCard?.data?.plaidConnectedFeed, cardFeeds, prevFeedsData], + () => checkIfNewFeedConnected(prevFeedsData ?? {}, cardFeeds?.settings?.oAuthAccountDetails ?? {}, addNewCard?.data?.plaidConnectedFeed), + [addNewCard?.data?.plaidConnectedFeed, cardFeeds?.settings?.oAuthAccountDetails, 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?.[feed]) : false; + const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.settings?.oAuthAccountDetails?.[feed]) : false; const headerTitleAddCards = !backTo ? translate('workspace.companyCards.addCards') : undefined; const headerTitle = feed ? translate('workspace.companyCards.assignCard') : headerTitleAddCards; - const isNewFeedHasError = !!(newFeed && cardFeeds?.[newFeed]?.errors); + const isNewFeedHasError = !!(newFeed && cardFeeds?.settings?.oAuthAccountDetails?.[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 b0691d963b23..40598d869b91 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 {getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; +import {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 {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} 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) as CompanyCardFeedWithDomainID; + const bank = decodeURIComponent(route.params.bank); const policy = usePolicy(policyID); const workspaceAccountID = useWorkspaceAccountID(policyID); const [searchText, setSearchText] = useState(''); - const [allBankCards] = useCardsList(bank); + const [allBankCards] = useCardsList(policyID, bank as CompanyCardFeed); 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]); + const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, companyFeeds[bank as CompanyCardFeed]); 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, getCompanyCardFeed(bank)); + setCompanyCardExportAccount(policyID, domainOrWorkspaceAccountID, cardID, exportMenuItem.exportType, exportValue, 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 41b5799e4fd0..43961fd46334 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, getCompanyCardFeed, getCompanyFeeds, getDefaultCardName, getDomainOrWorkspaceAccountID, getPlaidInstitutionIconUrl, maskCardNumber} from '@libs/CardUtils'; +import {getCardFeedIcon, 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, CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} from '@src/types/onyx'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import {getExportMenuItem} from './utils'; @@ -47,8 +47,7 @@ type WorkspaceCompanyCardDetailsPageProps = PlatformStackScreenProps { setIsUnassignModalVisible(false); if (card) { - unassignWorkspaceCompanyCard(domainOrWorkspaceAccountID, feed, card); + unassignWorkspaceCompanyCard(domainOrWorkspaceAccountID, bank, card); } Navigation.goBack(); }; const updateCard = () => { - updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, cardID, feed, card?.lastScrapeResult); + updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, cardID, bank as CompanyCardFeed, card?.lastScrapeResult); }; const lastScrape = useMemo(() => { @@ -155,7 +154,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag @@ -163,7 +162,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag pendingAction={card?.nameValuePairs?.pendingFields?.cardTitle} errorRowStyles={[styles.ph5, styles.mb3]} errors={getLatestErrorField(card?.nameValuePairs ?? {}, 'cardTitle')} - onClose={() => clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, feed, 'cardTitle')} + onClose={() => clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'cardTitle')} > clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, feed, 'lastScrape', true)} + onClose={() => clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'lastScrape', true)} > ; @@ -33,7 +33,7 @@ type WorkspaceCompanyCardEditCardNamePageProps = PlatformStackScreenProps) => { - updateCompanyCardName(domainOrWorkspaceAccountID, cardID, values[INPUT_IDS.NAME], getCompanyCardFeed(bank), defaultValue); + updateCompanyCardName(domainOrWorkspaceAccountID, cardID, values[INPUT_IDS.NAME], 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 69de0bdc40d1..6edc5d99120b 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx @@ -1,4 +1,3 @@ -import {Str} from 'expensify-common'; import React from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; @@ -10,7 +9,6 @@ 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'; @@ -41,11 +39,8 @@ import type SCREENS from '@src/SCREENS'; import type {CompanyCardFeed} from '@src/types/onyx'; type CardFeedListItem = ListItem & { - /** Combined feed key */ - value: CompanyCardFeedWithDomainID; - /** Card feed value */ - feed: CompanyCardFeed; + value: CompanyCardFeed; }; type WorkspaceCompanyCardFeedSelectorPageProps = PlatformStackScreenProps; @@ -56,7 +51,6 @@ 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); @@ -66,24 +60,21 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS const companyFeeds = getCompanyFeeds(cardFeeds); const isCollect = isCollectPolicy(policy); - const feeds: CardFeedListItem[] = (Object.entries(companyFeeds) as Array<[CompanyCardFeedWithDomainID, CombinedCardFeed]>).map(([key, feedSettings]) => { + const feeds: CardFeedListItem[] = Object.entries(companyFeeds).map(([key, feedSettings]) => { + const feed = key as CompanyCardFeed; const filteredFeedCards = filterInactiveCards( - allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${getDomainOrWorkspaceAccountID(workspaceAccountID, feedSettings)}_${feedSettings.feed}`], + allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${getDomainOrWorkspaceAccountID(workspaceAccountID, feedSettings)}_${feed}`], ); const isFeedConnectionBroken = checkIfFeedConnectionIsBroken(filteredFeedCards); - const plaidUrl = getPlaidInstitutionIconUrl(feedSettings.feed); - const domain = allDomains?.[`${ONYXKEYS.COLLECTION.DOMAIN}${feedSettings.domainID}`]; - const domainName = domain?.email ? Str.extractEmailDomain(domain.email) : undefined; + const plaidUrl = getPlaidInstitutionIconUrl(feed); return { - 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, + 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, brickRoadIndicator: isFeedConnectionBroken ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, canShowSeveralIndicators: isFeedConnectionBroken, leftElement: plaidUrl ? ( @@ -93,7 +84,7 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS /> ) : ( { const isChangedValue = (newStatementPeriodEndDay ?? newStatementPeriodEnd) !== statementPeriodEndDay; - if (feed && isChangedValue) { - setFeedStatementPeriodEndDay(policyID, feed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); + if (selectedFeed && isChangedValue) { + setFeedStatementPeriodEndDay(policyID, selectedFeed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); } Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.getRoute(policyID)); }, - [policyID, feed, statementPeriodEndDay, domainOrWorkspaceAccountID], + [policyID, selectedFeed, statementPeriodEndDay, domainOrWorkspaceAccountID], ); const goBack = useCallback(() => { @@ -65,12 +64,12 @@ function WorkspaceCompanyCardStatementCloseDatePage({ }, [policyID]); const clearError = useCallback(() => { - if (!feed) { + if (!selectedFeed) { return; } - clearErrorField(feed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); - }, [feed, domainOrWorkspaceAccountID]); + clearErrorField(selectedFeed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); + }, [selectedFeed, domainOrWorkspaceAccountID]); if (isLoadingOnyxValue(cardFeedsResult) || isLoadingOnyxValue(lastSelectedFeedResult)) { return ; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsErrorConfirmation.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsErrorConfirmation.tsx index bc3d851a72d4..ac2885e169f1 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 {getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils'; +import {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 {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} from '@src/types/onyx'; type WorkspaceCompanyCardsErrorConfirmationProps = { policyID?: string; - newFeed?: CompanyCardFeedWithDomainID; + newFeed?: CompanyCardFeed; }; 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(newFeed); + const [cardsList] = useCardsList(policyID, 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 CompanyCardFeedWithDomainID[]).find( + const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeed[]).find( (feed) => feed !== newFeed && companyFeeds[feed]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); - deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, getCompanyCardFeed(newFeed), cardIDs, feedToOpen); + deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, newFeed, cardIDs, feedToOpen); }; const onButtonPress = () => { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx index 5f1c5cda7520..978adbf95316 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, getCompanyCardFeedWithDomainID, getDefaultCardName, sortCardsByCardholderName} from '@libs/CardUtils'; +import {filterCardsByPersonalDetails, getCardsByCardholderName, 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, CompanyCardFeed, WorkspaceCardsList} from '@src/types/onyx'; +import type {Card, 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 || !item.fundID) { + if (!cardID || !item?.accountID) { return; } - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, getCompanyCardFeedWithDomainID(item.bank as CompanyCardFeed, item.fundID))); + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, item.bank)); }} > {({hovered}) => ( diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx index 474f7ce79ff8..1157d3d81a0e 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx @@ -1,4 +1,3 @@ -import {Str} from 'expensify-common'; import React, {useMemo} from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; @@ -8,7 +7,6 @@ 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'; @@ -25,7 +23,6 @@ import { flatAllCardsList, getBankName, getCardFeedIcon, - getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, @@ -39,7 +36,7 @@ import {setAddNewCompanyCardStepAndData, setAssignCardStepAndData} from '@userAc import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {CurrencyList} from '@src/types/onyx'; +import type {CompanyCardFeed, CurrencyList} from '@src/types/onyx'; import type {AssignCardData} from '@src/types/onyx/AssignCard'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; @@ -48,7 +45,7 @@ type WorkspaceCompanyCardsListHeaderButtonsProps = { policyID: string; /** Currently selected feed */ - selectedFeed: CompanyCardFeedWithDomainID; + selectedFeed: CompanyCardFeed; /** Whether to show assign card button */ shouldShowAssignCardButton?: boolean; @@ -70,24 +67,22 @@ 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 feed = getCompanyCardFeed(selectedFeed); - const formattedFeedName = getCustomOrFormattedFeedName(feed, cardFeeds?.[selectedFeed]?.customFeedName); + const formattedFeedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); const isCommercialFeed = isCustomFeed(selectedFeed); const plaidUrl = getPlaidInstitutionIconUrl(selectedFeed); const companyFeeds = getCompanyFeeds(cardFeeds); const currentFeedData = companyFeeds?.[selectedFeed]; - const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(feed); + const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(selectedFeed); const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeedData); const filteredFeedCards = filterInactiveCards(allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${selectedFeed}`]); - const hasFeedError = !!cardFeeds?.[selectedFeed]?.errors; + const hasFeedError = !!cardFeeds?.settings?.oAuthAccountDetails?.[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: feed, + bankName: selectedFeed, }; if (institutionId) { const country = getPlaidCountry(policy?.outputCurrency, currencyList, countryByIp); @@ -120,23 +115,16 @@ 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(feed, illustrations)} + cardIcon={getCardFeedIcon(selectedFeed, illustrations)} shouldChangeLayout={shouldChangeLayout} feedName={formattedFeedName} - supportingText={supportingText} + supportingText={translate(isCommercialFeed ? 'workspace.companyCards.commercialFeed' : 'workspace.companyCards.directFeed')} shouldShowRBR={checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, domainOrWorkspaceAccountID), selectedFeed)} /> diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index 86dc3915da99..34367b7e228c 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -14,7 +14,6 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import { checkIfFeedConnectionIsBroken, - getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFilteredCardList, @@ -57,10 +56,9 @@ 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, , defaultFeed] = useCardFeeds(policyID); + const [cardFeeds] = useCardFeeds(policyID); const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds); - const feed = selectedFeed ? getCompanyCardFeed(selectedFeed) : undefined; - const [cardsList] = useCardsList(selectedFeed); + const [cardsList] = useCardsList(policyID, selectedFeed); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const {isBetaEnabled} = usePermissions(); @@ -70,7 +68,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); - const filteredCardList = getFilteredCardList(cardsList, selectedFeed ? cardFeeds?.[selectedFeed]?.accountList : undefined, workspaceCardFeeds); + const filteredCardList = getFilteredCardList(cardsList, selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined, workspaceCardFeeds); const companyCards = getCompanyFeeds(cardFeeds); const selectedFeedData = selectedFeed && companyCards[selectedFeed]; @@ -86,7 +84,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { }, [policyID, domainOrWorkspaceAccountID]); const {isOffline} = useNetwork({onReconnect: fetchCompanyCards}); - const isLoading = !isOffline && (!cardFeeds || (!!defaultFeed?.isLoading && isEmptyObject(cardsList))); + const isLoading = !isOffline && (!cardFeeds || (!!cardFeeds.isLoading && isEmptyObject(cardsList))); const isGB = countryByIp === CONST.COUNTRY.GB; const shouldShowGBDisclaimer = isGB && isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && (isNoFeed || hasNoAssignedCard); const isAllowedToIssueCompanyCard = useIsAllowedToIssueCompanyCard({policyID}); @@ -96,12 +94,12 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { }, [fetchCompanyCards]); useEffect(() => { - if (isLoading || !feed || isPending) { + if (isLoading || !selectedFeed || isPending) { return; } - openPolicyCompanyCardsFeed(domainOrWorkspaceAccountID, policyID, feed); - }, [feed, isLoading, policyID, isPending, domainOrWorkspaceAccountID]); + openPolicyCompanyCardsFeed(domainOrWorkspaceAccountID, policyID, selectedFeed); + }, [selectedFeed, isLoading, policyID, isPending, domainOrWorkspaceAccountID]); const handleAssignCard = () => { if (isActingAsDelegate) { @@ -122,17 +120,19 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { } const data: Partial = { - bankName: feed, + bankName: selectedFeed, }; let currentStep: AssignCardStep = CONST.COMPANY_CARD.STEP.ASSIGNEE; const employeeList = Object.values(policy?.employeeList ?? {}).filter((employee) => !isDeletedPolicyEmployee(employee, isOffline)); - const isFeedExpired = isSelectedFeedExpired(selectedFeedData); - const plaidAccessToken = selectedFeedData?.plaidAccessToken; + const selectedFeedCompanyCardsData = selectedFeed ? cardFeeds?.settings?.companyCards?.[selectedFeed] : undefined; + const selectedFeedOAuthData = selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined; + const isFeedExpired = isSelectedFeedExpired(selectedFeedOAuthData); + const plaidAccessToken = selectedFeedCompanyCardsData?.plaidAccessToken; // Refetch plaid card list if (!isFeedExpired && plaidAccessToken) { - const country = selectedFeedData?.country ?? ''; + const country = selectedFeedCompanyCardsData?.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 1e2cd87bd7de..14c67362dabb 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 {getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; +import {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,9 +44,8 @@ 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 = selectedFeed ? getCustomOrFormattedFeedName(feed, cardFeeds?.[selectedFeed]?.customFeedName) : undefined; + const feedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, selectedFeed ? companyFeeds[selectedFeed] : undefined); const validate = useCallback( @@ -69,8 +68,8 @@ function WorkspaceCompanyCardsSettingsFeedNamePage({ ); const submit = ({name}: WorkspaceCompanyCardFeedName) => { - if (feed) { - setWorkspaceCompanyCardFeedName(policyID, domainOrWorkspaceAccountID, feed, name); + if (selectedFeed) { + setWorkspaceCompanyCardFeedName(policyID, domainOrWorkspaceAccountID, selectedFeed, 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 4a638ecb1ff5..30d73c609019 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 {getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; +import {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 {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} from '@src/types/onyx'; type WorkspaceCompanyCardsSettingsPageProps = PlatformStackScreenProps; @@ -45,10 +45,9 @@ 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(selectedFeed); - const feedName = selectedFeed ? getCustomOrFormattedFeedName(feed, cardFeeds?.[selectedFeed]?.customFeedName) : undefined; + const [cardsList] = useCardsList(policyID, selectedFeed); + const feedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); const companyFeeds = getCompanyFeeds(cardFeeds); const selectedFeedData = selectedFeed ? companyFeeds[selectedFeed] : undefined; const liabilityType = selectedFeedData?.liabilityType; @@ -78,27 +77,27 @@ function WorkspaceCompanyCardsSettingsPage({ const deleteCompanyCardFeed = () => { setDeleteCompanyCardConfirmModalVisible(false); Navigation.goBack(); - if (feed) { + if (selectedFeed) { const {cardList, ...cards} = cardsList ?? {}; const cardIDs = Object.keys(cards); - const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeedWithDomainID[]).find( - (feedWithDomainID) => feedWithDomainID !== selectedFeed && companyFeeds[feedWithDomainID]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeed[]).find( + (feed) => feed !== selectedFeed && companyFeeds[feed]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, feed, cardIDs, feedToOpen); + deleteWorkspaceCompanyCardFeed(policyID, domainOrWorkspaceAccountID, selectedFeed, cardIDs, feedToOpen); }); } }; const onToggleLiability = (isOn: boolean) => { - if (!feed) { + if (!selectedFeed) { return; } setWorkspaceCompanyCardTransactionLiability( domainOrWorkspaceAccountID, policyID, - feed, + selectedFeed, 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 92f7bce11d76..ef882baee50e 100644 --- a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx @@ -11,10 +11,9 @@ 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, getCompanyCardFeedWithDomainID} from '@libs/CardUtils'; +import {getBankName} from '@libs/CardUtils'; import Parser from '@libs/Parser'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; @@ -51,13 +50,12 @@ 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(getCompanyCardFeedWithDomainID(feedProvider, workspaceAccountID), policyID); + updateSelectedFeed(feedProvider, policyID); Navigation.goBack(); return; } diff --git a/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx b/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx index 79a36cc0203c..ae70fbe3136f 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 {getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; +import {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,7 +22,6 @@ 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; @@ -52,22 +51,22 @@ function DirectStatementCloseDateStep({policyID}: DirectStatementCloseDateStepPr return; } const isChangedValue = (newStatementPeriodEndDay ?? newStatementPeriodEnd) !== statementPeriodEndDay; - if (feed && isChangedValue) { - setFeedStatementPeriodEndDay(policyID, feed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); + if (selectedFeed && isChangedValue) { + setFeedStatementPeriodEndDay(policyID, selectedFeed, domainOrWorkspaceAccountID, newStatementPeriodEnd, newStatementPeriodEndDay, statementPeriodEndDay); } goBack(); }, - [policyID, statementPeriodEndDay, goBack, feed, domainOrWorkspaceAccountID], + [policyID, statementPeriodEndDay, selectedFeed, goBack, domainOrWorkspaceAccountID], ); const clearError = useCallback(() => { - if (!feed) { + if (!selectedFeed) { return; } - clearErrorField(feed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); - }, [feed, domainOrWorkspaceAccountID]); + clearErrorField(selectedFeed, domainOrWorkspaceAccountID, 'statementPeriodEndDay'); + }, [selectedFeed, 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 2846ddf95777..a025a80bfa90 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 {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeedWithDomainID; policyID?: string; onExit?: () => void}) { +function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeed; 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 b8b240dcfb85..2d0f4dabe373 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 {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} 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 CompanyCardFeedWithDomainID; + const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeed; 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 c165629db143..529899fdb651 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.CompanyCardFeedWithDomainID; + feed: OnyxTypes.CompanyCardFeed; }; 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(feed); + const [list] = useCardsList(policy?.id, feed); const [cardFeeds] = useCardFeeds(policy?.id); - const filteredCardList = getFilteredCardList(list, cardFeeds?.[feed]?.accountList, workspaceCardFeeds); + const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[feed], workspaceCardFeeds); const isEditing = assignCard?.isEditing; diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 89d8fbc4dd28..bdba42f1da2f 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, getCompanyCardFeed, getFilteredCardList, getPlaidInstitutionIconUrl, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; +import {getCardFeedIcon, 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 {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} from '@src/types/onyx'; type CardSelectionStepProps = { /** Selected feed */ - feed: CompanyCardFeedWithDomainID; + feed: CompanyCardFeed; /** 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(feed); + const [list] = useCardsList(policyID, 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?.[feed]?.accountList, workspaceCardFeeds); + const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[feed], 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 ?? getCompanyCardFeed(feed); + const bankName = (assignCard?.data?.bankName as CompanyCardFeed | undefined) ?? feed; const [cardFeeds] = useCardFeeds(policyID); const data = assignCard?.data; @@ -74,7 +74,7 @@ function ConfirmationStep({policyID, feed, backTo}: ConfirmationStepProps) { return; } - const isFeedExpired = isSelectedFeedExpired(cardFeeds?.[feed]); + const isFeedExpired = isSelectedFeedExpired(bankName ? cardFeeds?.settings?.oAuthAccountDetails?.[bankName] : undefined); const institutionId = !!getPlaidInstitutionId(bankName); if (isFeedExpired) { diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index c7a18cf6f41f..8f2551386431 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -27,16 +27,7 @@ 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, - getCompanyCardFeedWithDomainID, - getCompanyFeeds, - getPlaidInstitutionIconUrl, - isExpensifyCardFullySetUp, - lastFourNumbersFromCardName, - maskCardNumber, -} from '@libs/CardUtils'; +import {getAllCardsForWorkspace, getCardFeedIcon, 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'; @@ -285,17 +276,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.getRoute(policyID, card.cardID.toString(), Navigation.getActiveRoute())); return; } - 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(), - ), - ); + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, card.cardID.toString(), card.bank, Navigation.getActiveRoute())); }, [policyID], ); diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index b4cdc4796946..f022b1a09705 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -7,7 +7,6 @@ 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'; @@ -17,7 +16,6 @@ import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import { getCardFeedIcon, - getCompanyCardFeed, getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, @@ -42,15 +40,12 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed, CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type {CompanyCardFeed} from '@src/types/onyx'; import type {AssignCardData, AssignCardStep} from '@src/types/onyx/AssignCard'; type CardFeedListItem = ListItem & { - /** Combined feed key */ - value: CompanyCardFeedWithDomainID; - /** Card feed value */ - feed: CompanyCardFeed; + value: string; }; type WorkspaceMemberNewCardPageProps = WithPolicyAndFullscreenLoadingProps & PlatformStackScreenProps; @@ -66,7 +61,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew const styles = useThemeStyles(); const lazyIllustrations = useMemoizedLazyIllustrations(['ExpensifyCardImage'] as const); const illustrations = useThemeIllustrations(); - const [cardFeeds, , defaultFeed] = useCardFeeds(policyID); + const [cardFeeds] = useCardFeeds(policyID); const [selectedFeed, setSelectedFeed] = useState(''); const [shouldShowError, setShouldShowError] = useState(false); const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, {canBeMissing: true}); @@ -76,12 +71,11 @@ 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 currentFeed = selectedFeed ? cardFeeds?.[selectedFeed as CompanyCardFeedWithDomainID] : undefined; - const isFeedExpired = isSelectedFeedExpired(currentFeed); - const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeed); + const isFeedExpired = isSelectedFeedExpired((selectedFeed as CompanyCardFeed) ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed as CompanyCardFeed] : undefined); + const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, companyFeeds[selectedFeed as CompanyCardFeed]); - const [list] = useCardsList(selectedFeed as CompanyCardFeedWithDomainID); - const filteredCardList = getFilteredCardList(list, currentFeed?.accountList, workspaceCardFeeds); + const [list] = useCardsList(policyID, selectedFeed as CompanyCardFeed); + const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed as CompanyCardFeed], workspaceCardFeeds); const shouldShowExpensifyCard = isExpensifyCardFullySetUp(policy, cardSettings); @@ -104,7 +98,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew } else { const data: Partial = { email: memberLogin, - bankName: getCompanyCardFeed(selectedFeed as CompanyCardFeedWithDomainID), + bankName: selectedFeed, cardName: `${memberName}'s card`, }; let currentStep: AssignCardStep = CONST.COMPANY_CARD.STEP.CARD; @@ -128,26 +122,25 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew } }; - const handleSelectFeed = ({value, feed}: CardFeedListItem) => { - setSelectedFeed(value); - const workspaceCards = workspaceCardFeeds?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed}`] ?? {}; + const handleSelectFeed = (feed: CardFeedListItem) => { + setSelectedFeed(feed.value); + const workspaceCards = workspaceCardFeeds?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed.value as CompanyCardFeed}`] ?? {}; const hasAllCardsData = !!workspaceCards.cardList; - if (isCustomFeed(feed) && !hasAllCardsData) { - openAssignFeedCardPage(policyID, feed, domainOrWorkspaceAccountID); + if (isCustomFeed(feed.value as CompanyCardFeed) && !hasAllCardsData) { + openAssignFeedCardPage(policyID, feed.value as CompanyCardFeed, domainOrWorkspaceAccountID); } setShouldShowError(false); }; - const companyCardFeeds: CardFeedListItem[] = (Object.entries(companyFeeds) as Array<[CompanyCardFeedWithDomainID, CombinedCardFeed]>).map(([key, value]) => { - const plaidUrl = getPlaidInstitutionIconUrl(value.feed); + const companyCardFeeds: CardFeedListItem[] = (Object.keys(companyFeeds) as CompanyCardFeed[]).map((key) => { + const plaidUrl = getPlaidInstitutionIconUrl(key); return { value: key, - feed: value.feed, - text: getCustomOrFormattedFeedName(value.feed, value.customFeedName), + text: getCustomOrFormattedFeedName(key, cardFeeds?.settings?.companyCardNicknames), keyForList: key, - isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - pendingAction: value.pendingAction, + isDisabled: companyFeeds[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingAction: companyFeeds[key]?.pendingAction, isSelected: selectedFeed === key, leftElement: plaidUrl ? ( @@ -157,7 +150,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew /> ) : ( diff --git a/src/types/onyx/AssignCard.ts b/src/types/onyx/AssignCard.ts index 18415925ceef..dd0ba64f8683 100644 --- a/src/types/onyx/AssignCard.ts +++ b/src/types/onyx/AssignCard.ts @@ -2,7 +2,6 @@ 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; @@ -19,7 +18,7 @@ type AssignCardData = { cardNumber: string; /** The name of the feed */ - bankName: CompanyCardFeed; + bankName: string; /** The name of the card */ cardName: string; diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index 31ee7d1ac767..33a179bf1f9a 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -7,11 +7,8 @@ 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}` | CompanyCardFeedWithDomainID; +type CompanyCardFeedWithNumber = CompanyCardFeed | `${CompanyCardFeed}${number}`; /** Statement period end */ type StatementPeriodEnd = Exclude, typeof CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH>; @@ -228,8 +225,6 @@ 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 4fd8c653057d..6cab4c8f6799 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, CompanyCardFeedWithDomainID, FundID} from './CardFeeds'; +import type {AddNewCompanyCardFeed, CompanyCardFeed, FundID} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; import type {CorpayFields, CorpayFormField} from './CorpayFields'; @@ -165,7 +165,6 @@ export type { IssueNewCard, AddNewCompanyCardFeed, CompanyCardFeed, - CompanyCardFeedWithDomainID, LastExportMethod, Locale, LockAccountDetails, diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 0461e7945543..170015cd9d32 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -3,7 +3,6 @@ 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, @@ -18,14 +17,11 @@ import { getCardFeedIcon, getCardsByCardholderName, getCompanyCardDescription, - getCompanyCardFeed, - getCompanyCardFeedWithDomainID, getCompanyFeeds, getCustomOrFormattedFeedName, getFeedType, getFilteredCardList, getMonthFromExpirationDateString, - getOriginalCompanyFeeds, getSelectedFeed, getYearFromExpirationDateString, hasIssuedExpensifyCard, @@ -36,7 +32,7 @@ import { maskCardNumber, sortCardsByCardholderName, } from '@src/libs/CardUtils'; -import type {Card, CardFeeds, CardList, CompanyCardFeed, CompanyCardFeedWithDomainID, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; +import type {Card, CardFeeds, CardList, CompanyCardFeed, 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'; @@ -63,7 +59,7 @@ const directFeedBanks = [ const companyCardsCustomFeedSettings = { [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: { - pending: false, + pending: true, }, [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: { liabilityType: 'personal', @@ -88,7 +84,14 @@ 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', @@ -99,7 +102,7 @@ const companyCardsDirectFeedSettings = { }; const companyCardsSettingsWithoutExpensifyBank = { [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: { - pending: false, + pending: true, }, [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: { liabilityType: 'personal', @@ -107,16 +110,13 @@ const companyCardsSettingsWithoutExpensifyBank = { ...companyCardsDirectFeedSettings, }; -const companyCardsSettingsWithPendingRemovedFeeds = { +const companyCardsSettingsWithOnePendingFeed = { [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,49 +226,6 @@ 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; @@ -296,10 +253,36 @@ const cardFeedsCollection: OnyxCollection = { oAuthAccountDetails, }, }, - // Policy with pending and removed feeds + // Policy with direct feeds only FAKE_ID_2: { settings: { - companyCards: companyCardsSettingsWithPendingRemovedFeeds, + 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, }, }, // Policy with unknown feed @@ -446,65 +429,56 @@ describe('CardUtils', () => { }); }); - describe('getOriginalCompanyFeeds', () => { + describe('getCompanyFeeds', () => { it('Should return both custom and direct feeds with filtered out "Expensify Card" bank', () => { - const companyFeeds = getOriginalCompanyFeeds(cardFeedsCollection.FAKE_ID_1); + const companyFeeds = getCompanyFeeds(cardFeedsCollection.FAKE_ID_1); expect(companyFeeds).toStrictEqual(companyCardsSettingsWithoutExpensifyBank); }); - 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 direct feeds only since custom feeds are not exist', () => { + const companyFeeds = getCompanyFeeds(cardFeedsCollection.FAKE_ID_2); + expect(companyFeeds).toStrictEqual(companyCardsDirectFeedSettings); }); - 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 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 = 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: CompanyCardFeedWithDomainID = `${CONST.COMPANY_CARD.FEED_BANK_NAME.VISA}#12345`; - const selectedFeed = getSelectedFeed(lastSelectedCustomFeed, combinedCardFeeds); + const lastSelectedCustomFeed = CONST.COMPANY_CARD.FEED_BANK_NAME.VISA; + const selectedFeed = getSelectedFeed(lastSelectedCustomFeed, cardFeedsCollection.FAKE_ID_1); expect(selectedFeed).toBe(lastSelectedCustomFeed); }); it('Should return last selected direct feed', () => { - const lastSelectedDirectFeed: CompanyCardFeedWithDomainID = `${CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE}#12345`; - const selectedFeed = getSelectedFeed(lastSelectedDirectFeed, combinedCardFeeds); + const lastSelectedDirectFeed = CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE; + const selectedFeed = getSelectedFeed(lastSelectedDirectFeed, cardFeedsCollection.FAKE_ID_1); expect(selectedFeed).toBe(lastSelectedDirectFeed); }); - it('Should return the first available feed if lastSelectedFeed is undefined', () => { + it('Should return the first available custom feed if lastSelectedFeed is undefined', () => { const lastSelectedFeed = undefined; - const selectedFeed = getSelectedFeed(lastSelectedFeed, combinedCardFeeds); - expect(selectedFeed).toBe(`${CONST.COMPANY_CARD.FEED_BANK_NAME.VISA}#11111111`); + 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); }); it('Should return undefined if lastSelectedFeed is undefined and there is no card feeds', () => { @@ -522,26 +496,28 @@ describe('CardUtils', () => { }); it('Should return custom name if exists', () => { const feed = CONST.COMPANY_CARD.FEED_BANK_NAME.VISA; - const feedName = getCustomOrFormattedFeedName(feed, customFeedName); + const companyCardNicknames = cardFeedsCollection.FAKE_ID_1?.settings?.companyCardNicknames; + const feedName = getCustomOrFormattedFeedName(feed, companyCardNicknames); 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 customName = undefined; - const feedName = getCustomOrFormattedFeedName(feed, customName); + const companyCardNicknames = cardFeedsCollection.FAKE_ID_3?.settings?.companyCardNicknames; + const feedName = getCustomOrFormattedFeedName(feed, companyCardNicknames); expect(feedName).toBe('Visa cards'); }); it('Should return undefined if no feed provided', () => { const feed = undefined; - const feedName = getCustomOrFormattedFeedName(feed); + const companyCardNicknames = cardFeedsCollection.FAKE_ID_1?.settings?.companyCardNicknames; + const feedName = getCustomOrFormattedFeedName(feed, companyCardNicknames); expect(feedName).toBe(undefined); }); it('Should return feed key name for unknown feed', () => { - const companyCardNickname = cardFeedsCollection.FAKE_ID_7?.settings?.companyCardNicknames?.[unknownFeed]; - const feedName = getCustomOrFormattedFeedName(unknownFeed, companyCardNickname); + const companyCardNicknames = cardFeedsCollection.FAKE_ID_7?.settings?.companyCardNicknames; + const feedName = getCustomOrFormattedFeedName(unknownFeed, companyCardNicknames); expect(feedName).toBe(unknownFeed); }); }); @@ -689,13 +665,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].accountList, undefined); + const cardsList = getFilteredCardList(directFeedCardsSingleList, oAuthAccountDetails[CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE], 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].accountList, undefined); + const cardsList = getFilteredCardList(directFeedCardsMultipleList, oAuthAccountDetails[CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE], undefined); expect(cardsList).toStrictEqual({ // eslint-disable-next-line @typescript-eslint/naming-convention 'CREDIT CARD...1233': 'CREDIT CARD...1233', @@ -809,25 +785,27 @@ describe('CardUtils', () => { }, }, } as unknown as OnyxCollection; - const accountList = [assignedCard1, assignedCard2, unassignedCard]; - const filteredCards = getFilteredCardList(undefined, accountList, mockAllWorkspaceCards); + 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); 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', companyCardsCustomFeedSettingsWithNumbers as CombinedCardFeeds); + const feedType = getFeedType('vcf', cardFeedsCollection.FAKE_ID_4); 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', companyCardsCustomFeedSettings); + const feedType = getFeedType('vcf', cardFeedsCollection.FAKE_ID_3); expect(feedType).toBe('vcf1'); }); it('should return the feed name with with the first smallest available number', () => { - const feedType = getFeedType('vcf', companyCardsCustomVisaFeedSettingsWithNumbers as CombinedCardFeeds); + const feedType = getFeedType('vcf', cardFeedsCollection.FAKE_ID_5); expect(feedType).toBe('vcf2'); }); }); @@ -1290,21 +1268,4 @@ 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 e8d46bd98cd2..b30901cc8754 100644 --- a/tests/unit/hooks/useIsAllowedToIssueCompanyCard.test.ts +++ b/tests/unit/hooks/useIsAllowedToIssueCompanyCard.test.ts @@ -6,30 +6,9 @@ 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, 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, - }, -}; +const mockPolicy = {...createRandomPolicy(Number(mockPolicyID), CONST.POLICY.TYPE.TEAM, 'TestPolicy'), policyID: mockPolicyID}; jest.mock('@hooks/useCardFeeds', () => ({ // eslint-disable-next-line @typescript-eslint/naming-convention @@ -39,37 +18,122 @@ 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 () => { - await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicy?.policyID}`, 'vcf#19475968'); + const domainID = 19475968; await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${domainID}`, true); - (useCardFeeds as jest.Mock).mockReturnValue([mockedFeeds, {status: 'loaded'}]); + (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'}, + ]); const {result} = renderHook(() => useIsAllowedToIssueCompanyCard({policyID: mockPolicyID})); expect(result?.current).toBe(true); }); it('should return false if domain feed and access is not granted', async () => { - await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicy?.policyID}`, 'vcf#19475968'); + const domainID = 19475968; await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${domainID}`, false); - (useCardFeeds as jest.Mock).mockReturnValue([mockedFeeds, {status: 'loaded'}]); + (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'}, + ]); 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([mockedFeeds, {status: 'loaded'}]); + (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'}, + ]); 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([mockedFeeds, {status: 'loaded'}]); + (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'}, + ]); const {result} = renderHook(() => useIsAllowedToIssueCompanyCard({policyID: mockPolicyID})); expect(result?.current).toBe(false); });