Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0abbf92
Update useCardFeeds hook to support domain/workspace feed with the sa…
VickyStash Nov 4, 2025
04388f6
Update the rest of the files
VickyStash Nov 5, 2025
ceaf2c5
Simplify updates
VickyStash Nov 5, 2025
15e6f0a
Update existing tests
VickyStash Nov 5, 2025
f3871ca
Merge branch 'main' into VickyStash/refactor/73380-support-domain-wor…
VickyStash Nov 5, 2025
455f5a2
Adjustments after merging main
VickyStash Nov 5, 2025
5b85efb
Add FEED_KEY_SEPARATOR const
VickyStash Nov 6, 2025
52910c1
Clean up pt1
VickyStash Nov 6, 2025
938d1ff
Rename function
VickyStash Nov 6, 2025
d739cc7
Rename variable
VickyStash Nov 6, 2025
79a5a89
Clean up pt2
VickyStash Nov 6, 2025
7401ba4
Clean up pt3
VickyStash Nov 6, 2025
71f5e28
Minor improvements
VickyStash Nov 6, 2025
9d0fb3f
Minor improvements
VickyStash Nov 6, 2025
b1e492c
Merge branch 'main' into VickyStash/refactor/73380-support-domain-wor…
VickyStash Nov 7, 2025
cfb417b
Remove extra file
VickyStash Nov 7, 2025
0420ba7
Add getOriginalFeed, getCombinedFeedKey, getOriginalCompanyFeeds unit…
VickyStash Nov 7, 2025
38e8d6f
Add getCompanyFeeds unit tests
VickyStash Nov 7, 2025
179b032
Fix card details overview
VickyStash Nov 7, 2025
4f45079
Add domain name display
VickyStash Nov 7, 2025
283c69f
Lint fix
VickyStash Nov 7, 2025
d259783
Merge branch 'main' into VickyStash/refactor/73380-support-domain-wor…
VickyStash Nov 12, 2025
89a71df
Merge branch 'main' into VickyStash/refactor/73380-support-domain-wor…
VickyStash Nov 13, 2025
39aae2b
Fix TS issues and tests
VickyStash Nov 13, 2025
0c50c62
Rename CombinedFeedKey type to CompanyCardFeedWithDomainID
VickyStash Nov 14, 2025
020e930
Rename getOriginalFeed function
VickyStash Nov 14, 2025
5b6c22e
Fix prettier check
VickyStash Nov 14, 2025
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 @@ -3345,6 +3345,7 @@ const CONST = {
AMEX_FILE_DOWNLOAD: 'americanexpressfd.us',
CSV: 'ccupload',
},
FEED_KEY_SEPARATOR: '#',
STEP_NAMES: ['1', '2', '3', '4'],
STEP: {
BANK_CONNECTION: 'BankConnection',
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
56 changes: 30 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,48 +42,40 @@ 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;
}

Object.entries(feed.settings.companyCards).forEach(([key, feedSettings]) => {
const feedName = key as CompanyCardFeed;
const feedOAuthAccountDetails = feed.settings.oAuthAccountDetails?.[feedName];
const feedCompanyCardNicknames = feed.settings.companyCardNicknames?.[feedName];
const feedCompanyCardNickname = feed.settings.companyCardNicknames?.[feedName];
const domainID = onyxKey.split('_').at(-1);

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

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

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

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

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

return [workspaceFeeds, allFeedsResult, defaultFeed];
};

export default useCardFeeds;
export type {CombinedCardFeeds, CompanyCardFeedWithDomainID, CombinedCardFeed};
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;
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 @@ -424,7 +424,7 @@ function getCardFeedsForDisplay(allCardFeeds: OnyxCollection<CardFeeds>, allCard
return;
}

Object.keys(getCompanyFeeds(cardFeeds, true, true)).forEach((key) => {
Object.keys(getOriginalCompanyFeeds(cardFeeds)).forEach((key) => {
const feed = key as CompanyCardFeed;
const id = `${fundID}_${feed}`;

Expand All @@ -436,7 +436,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 @@ -478,7 +478,7 @@ function getCardFeedsForDisplayPerPolicy(allCardFeeds: OnyxCollection<CardFeeds>
return;
}

Object.entries(getCompanyFeeds(cardFeeds, true, true)).forEach(([key, feedData]) => {
Object.entries(getOriginalCompanyFeeds(cardFeeds)).forEach(([key, feedData]) => {
const preferredPolicy = 'preferredPolicy' in feedData ? (feedData.preferredPolicy ?? '') : '';
const feed = key as CompanyCardFeed;
const id = `${fundID}_${feed}`;
Expand All @@ -487,7 +487,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