Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3382,6 +3382,7 @@ const CONST = {
AMEX_FILE_DOWNLOAD: 'americanexpressfd.us',
CSV: 'ccupload',
},
FEED_KEY_SEPARATOR: '#',
STEP_NAMES: ['1', '2', '3', '4'],
STEP: {
BANK_CONNECTION: 'BankConnection',
Expand Down
2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean;
[ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeed;
[ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeedWithDomainID;
[ONYXKEYS.COLLECTION.LAST_SELECTED_EXPENSIFY_CARD_FEED]: OnyxTypes.FundID;
[ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST]: OnyxTypes.CardOnWaitlist;
[ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard;
Expand Down
57 changes: 31 additions & 26 deletions src/hooks/useCardFeeds.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import {useMemo} from 'react';
import type {OnyxCollection, ResultMetadata} from 'react-native-onyx';
import {getCompanyCardFeedWithDomainID} from '@libs/CardUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {CardFeeds, CompanyCardFeed} from '@src/types/onyx';
import type {CardFeeds, CompanyCardFeed, CompanyCardFeedWithDomainID} from '@src/types/onyx';
import type {CustomCardFeedData, DirectCardFeedData} from '@src/types/onyx/CardFeeds';
import useOnyx from './useOnyx';
import useWorkspaceAccountID from './useWorkspaceAccountID';

type CombinedCardFeed = CustomCardFeedData &
Partial<DirectCardFeedData> & {
/** Custom feed name, originally coming from settings.companyCardNicknames */
customFeedName?: string;

/** Feed name */
feed: CompanyCardFeed;
};

type CombinedCardFeeds = Record<CompanyCardFeedWithDomainID, CombinedCardFeed>;

/**
* 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 -
Expand All @@ -20,7 +32,7 @@ import useWorkspaceAccountID from './useWorkspaceAccountID';
* 2. The result metadata from the Onyx collection fetch.
* 3. Card feeds specific to the given policyID (or `undefined` if unavailable).
*/
const useCardFeeds = (policyID: string | undefined): [CardFeeds | undefined, ResultMetadata<OnyxCollection<CardFeeds>>, CardFeeds | undefined] => {
const useCardFeeds = (policyID: string | undefined): [CombinedCardFeeds | undefined, ResultMetadata<OnyxCollection<CardFeeds>>, 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}`];
Expand All @@ -30,18 +42,9 @@ const useCardFeeds = (policyID: string | undefined): [CardFeeds | undefined, Res
return undefined;
}

const {companyCards = {}, companyCardNicknames = {}, oAuthAccountDetails = {}} = defaultFeed?.settings ?? {};
const result: CombinedCardFeeds = {};

const result: CardFeeds & {settings: Required<CardFeeds['settings']>} = {
settings: {
companyCards: {...companyCards},
companyCardNicknames: {...companyCardNicknames},
oAuthAccountDetails: {...oAuthAccountDetails},
},
isLoading: defaultFeed?.isLoading,
};

return Object.entries(allFeeds).reduce<CardFeeds & {settings: Required<CardFeeds['settings']>}>((acc, [onyxKey, feed]) => {
return Object.entries(allFeeds).reduce<CombinedCardFeeds>((acc, [onyxKey, feed]) => {
if (!feed?.settings?.companyCards) {
return acc;
}
Expand All @@ -50,29 +53,31 @@ const useCardFeeds = (policyID: string | undefined): [CardFeeds | undefined, Res
Object.entries(feed.settings.companyCards).forEach(([key, feedSettings]) => {
const feedName = key as CompanyCardFeed;
const feedOAuthAccountDetails = feed.settings.oAuthAccountDetails?.[feedName];
const feedCompanyCardNicknames = feed.settings.companyCardNicknames?.[feedName];
const feedCompanyCardNickname = feed.settings.companyCardNicknames?.[feedName];
const domainID = onyxKey.split('_').at(-1);
const shouldAddFeed = domainID && (feedSettings.preferredPolicy ? feedSettings.preferredPolicy === policyID : domainID === workspaceAccountID.toString());

if (feedSettings.preferredPolicy !== policyID || acc.settings.companyCards[feedName]) {
if (!shouldAddFeed) {
return;
}

const domainID = onyxKey.split('_').at(-1);

acc.settings.companyCards[feedName] = {...feedSettings, domainID: domainID ? Number(domainID) : undefined};
const combinedFeedKey = getCompanyCardFeedWithDomainID(feedName, domainID);

if (feedOAuthAccountDetails) {
acc.settings.oAuthAccountDetails[feedName] = feedOAuthAccountDetails;
}
if (feedCompanyCardNicknames) {
acc.settings.companyCardNicknames[feedName] = feedCompanyCardNicknames;
}
acc[combinedFeedKey] = {
...feedSettings,
...feedOAuthAccountDetails,
customFeedName: feedCompanyCardNickname,
domainID: Number(domainID),
feed: feedName,
};
});

return acc;
}, result);
}, [allFeeds, defaultFeed?.isLoading, defaultFeed?.settings, policyID]);
}, [allFeeds, policyID, workspaceAccountID]);

return [workspaceFeeds, allFeedsResult, defaultFeed];
};

export default useCardFeeds;
export type {CombinedCardFeeds, CompanyCardFeedWithDomainID, CombinedCardFeed};
18 changes: 7 additions & 11 deletions src/hooks/useCardsList.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import type {ResultMetadata} from 'react-native-onyx';
import {filterInactiveCards, getCompanyFeeds, getDomainOrWorkspaceAccountID} from '@libs/CardUtils';
import {filterInactiveCards} from '@libs/CardUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {CardList, CompanyCardFeed} from '@src/types/onyx';
import useCardFeeds from './useCardFeeds';
import type {CardList, CompanyCardFeedWithDomainID} from '@src/types/onyx';
import useOnyx from './useOnyx';
import useWorkspaceAccountID from './useWorkspaceAccountID';

/* Custom hook that retrieves a list of company cards for the given policy and selected feed. */
const useCardsList = (policyID: string | undefined, selectedFeed: CompanyCardFeed | undefined): [CardList | undefined, ResultMetadata<CardList>] => {
const workspaceAccountID = useWorkspaceAccountID(policyID);
const [cardFeeds] = useCardFeeds(policyID);
const companyCards = getCompanyFeeds(cardFeeds);
const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, selectedFeed ? companyCards[selectedFeed] : undefined);
const [cardsList, cardsListMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${selectedFeed}`, {
/* Custom hook that retrieves a list of company cards for the given selected feed. */
const useCardsList = (selectedFeed: CompanyCardFeedWithDomainID | undefined): [CardList | undefined, ResultMetadata<CardList>] => {
const [feed, domainOrWorkspaceAccountID] = selectedFeed?.split(CONST.COMPANY_CARD.FEED_KEY_SEPARATOR) ?? [];
const [cardsList, cardsListMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${feed}`, {
selector: filterInactiveCards,
canBeMissing: true,
});
Expand Down
6 changes: 5 additions & 1 deletion src/hooks/useIsAllowedToIssueCompanyCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ function useIsAllowedToIssueCompanyCard({policyID}: {policyID?: string}) {
const selectedFeedData = selectedFeed && companyCards[selectedFeed];
const [adminAccess] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${selectedFeedData?.domainID}`, {canBeMissing: true});

return selectedFeedData?.domainID ? !!adminAccess : isPolicyAdmin;
if (selectedFeedData?.domainID === policy?.workspaceAccountID) {
return isPolicyAdmin;
}

return !!adminAccess;
}

export default useIsAllowedToIssueCompanyCard;
11 changes: 2 additions & 9 deletions src/hooks/useIsBlockedToAddFeed.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import {useEffect, useState} from 'react';
import {getCompanyFeeds, getSelectedFeed} from '@libs/CardUtils';
import {getCompanyFeeds} from '@libs/CardUtils';
import {isCollectPolicy} from '@libs/PolicyUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import useCardFeeds from './useCardFeeds';
import useCardsList from './useCardsList';
import useOnyx from './useOnyx';
import usePolicy from './usePolicy';

/**
Expand All @@ -23,15 +19,12 @@ import usePolicy from './usePolicy';
function useIsBlockedToAddFeed(policyID?: string) {
const policy = usePolicy(policyID);
const [cardFeeds, allFeedsResult, defaultFeed] = useCardFeeds(policyID);
const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true});
const companyFeeds = getCompanyFeeds(cardFeeds, true);
const isCollect = isCollectPolicy(policy);
const isAllFeedsResultLoading = isLoadingOnyxValue(allFeedsResult);
const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds);
const [cardsList] = useCardsList(policyID, selectedFeed);
const [prevCompanyFeedsLength, setPrevCompanyFeedsLength] = useState(0);

const isLoading = !cardFeeds || (!!cardFeeds.isLoading && isEmptyObject(cardsList)) || !!defaultFeed?.isLoading;
const isLoading = !cardFeeds || !!defaultFeed?.isLoading;

useEffect(() => {
if (isLoading) {
Expand Down
10 changes: 5 additions & 5 deletions src/hooks/useUpdateFeedBrokenConnection.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {useCallback} from 'react';
import {checkIfFeedConnectionIsBroken, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFeedConnectionBrokenCard} from '@libs/CardUtils';
import {checkIfFeedConnectionIsBroken, getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getFeedConnectionBrokenCard} from '@libs/CardUtils';
import {updateWorkspaceCompanyCard} from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import type {CompanyCardFeed} from '@src/types/onyx';
import type {CompanyCardFeedWithDomainID} from '@src/types/onyx';
import useCardFeeds from './useCardFeeds';
import useCardsList from './useCardsList';
import usePolicy from './usePolicy';

export default function useUpdateFeedBrokenConnection({policyID, feed}: {policyID?: string; feed?: CompanyCardFeed}) {
const [cardsList] = useCardsList(policyID, feed);
export default function useUpdateFeedBrokenConnection({policyID, feed}: {policyID?: string; feed?: CompanyCardFeedWithDomainID}) {
const [cardsList] = useCardsList(feed);
const policy = usePolicy(policyID);
const [cardFeeds] = useCardFeeds(policyID);
const companyFeeds = getCompanyFeeds(cardFeeds);
Expand All @@ -23,7 +23,7 @@ export default function useUpdateFeedBrokenConnection({policyID, feed}: {policyI
if (!brokenCardId || !feed) {
return;
}
updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, brokenCardId, feed, brokenCard?.lastScrapeResult);
updateWorkspaceCompanyCard(domainOrWorkspaceAccountID, brokenCardId, getCompanyCardFeed(feed), brokenCard?.lastScrapeResult);
}, [brokenCard?.lastScrapeResult, brokenCardId, domainOrWorkspaceAccountID, feed]);

return {updateBrokenConnection, isFeedConnectionBroken};
Expand Down
10 changes: 5 additions & 5 deletions src/libs/CardFeedUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {
getBankName,
getCardFeedIcon,
getCompanyFeeds,
getCustomOrFormattedFeedName,
getOriginalCompanyFeeds,
getPlaidInstitutionIconUrl,
getPlaidInstitutionId,
isCard,
Expand Down Expand Up @@ -425,7 +425,7 @@ function getCardFeedsForDisplay(allCardFeeds: OnyxCollection<CardFeeds>, allCard
}

// eslint-disable-next-line unicorn/no-array-for-each
Object.keys(getCompanyFeeds(cardFeeds, true, true)).forEach((key) => {
Object.keys(getOriginalCompanyFeeds(cardFeeds)).forEach((key) => {
const feed = key as CompanyCardFeed;
const id = `${fundID}_${feed}`;

Expand All @@ -437,7 +437,7 @@ function getCardFeedsForDisplay(allCardFeeds: OnyxCollection<CardFeeds>, allCard
id,
feed,
fundID,
name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames, false) ?? feed,
name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames?.[feed], false) ?? feed,
};
});
}
Expand Down Expand Up @@ -480,7 +480,7 @@ function getCardFeedsForDisplayPerPolicy(allCardFeeds: OnyxCollection<CardFeeds>
}

// eslint-disable-next-line unicorn/no-array-for-each
Object.entries(getCompanyFeeds(cardFeeds, true, true)).forEach(([key, feedData]) => {
Object.entries(getOriginalCompanyFeeds(cardFeeds)).forEach(([key, feedData]) => {
const preferredPolicy = 'preferredPolicy' in feedData ? (feedData.preferredPolicy ?? '') : '';
const feed = key as CompanyCardFeed;
const id = `${fundID}_${feed}`;
Expand All @@ -489,7 +489,7 @@ function getCardFeedsForDisplayPerPolicy(allCardFeeds: OnyxCollection<CardFeeds>
id,
feed,
fundID,
name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames, false) ?? feed,
name: getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames?.[feed], false) ?? feed,
});
});
}
Expand Down
Loading
Loading