Skip to content

[Internal QA] feat: show domain feed in ND #59538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 9 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
34 changes: 34 additions & 0 deletions src/hooks/useDomainCardsID.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {useOnyx} from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

function useDomainCardsID(policyID: string | undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can technically be multiple domainAccountIDs that we would return here, right? Since there could be multiple domains that have this set as the preferred policy. Maybe it would make sense to call it useDomainCardIDs or useDomainAccountIDs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so I need to refactor this then, for now it takes the first matching entry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed it to FundID like in the other locations, if it's a fundID in the cards_ object

@puneetlath May I work on refactoring it to return an array of the ids in the second PR for selector? Here, without selector it would make things a bit complicated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that makes sense to me.

const [domainCardsID] = useOnyx(ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS, {
selector: (cardSettings) => {
const matchingEntry = Object.entries(cardSettings ?? {}).find(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
([_, settings]) => settings?.preferredPolicy && settings.preferredPolicy === policyID,
);

if (!matchingEntry) {
return CONST.DEFAULT_NUMBER_ID;
}

const key = matchingEntry[0];
const prefix = ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS;

if (!key.startsWith(prefix)) {
return CONST.DEFAULT_NUMBER_ID;
}

const accountIDStr = key.substring(prefix.length);

const accountID = Number(accountIDStr);
return Number.isNaN(accountID) ? CONST.DEFAULT_NUMBER_ID : accountID;
},
});

return domainCardsID;
}

export default useDomainCardsID;
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID';
import {getLatestErrorMessage} from '@libs/ErrorUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ExpensifyCardSettings} from '@src/types/onyx';
import WorkspaceCardsListLabel from './WorkspaceCardsListLabel';

type WorkspaceCardListHeaderProps = {
/** ID of the current policy */
policyID: string;

/** Card settings */
cardSettings: ExpensifyCardSettings | undefined;
};

function WorkspaceCardListHeader({policyID}: WorkspaceCardListHeaderProps) {
function WorkspaceCardListHeader({policyID, cardSettings}: WorkspaceCardListHeaderProps) {
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {shouldUseNarrowLayout, isMediumScreenWidth, isSmallScreenWidth} = useResponsiveLayout();
const styles = useThemeStyles();
Expand All @@ -26,7 +30,6 @@ function WorkspaceCardListHeader({policyID}: WorkspaceCardListHeaderProps) {
const workspaceAccountID = useWorkspaceAccountID(policyID);
const isLessThanMediumScreen = isMediumScreenWidth || isSmallScreenWidth;

const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`);
const [cardManualBilling] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING}${workspaceAccountID}`);

const errorMessage = getLatestErrorMessage(cardSettings) ?? '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import useDomainCardsID from '@hooks/useDomainCardsID';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
Expand All @@ -29,7 +30,7 @@ import Navigation from '@navigation/Navigation';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import variables from '@styles/variables';
import {deactivateCard as deactivateCard_1, openCardDetailsPage} from '@userActions/Card';
import {deactivateCard as deactivateCardAction, openCardDetailsPage} from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -44,6 +45,11 @@ type WorkspaceExpensifyCardDetailsPageProps = PlatformStackScreenProps<
function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetailsPageProps) {
const {policyID, cardID, backTo} = route.params;
const workspaceAccountID = useWorkspaceAccountID(policyID);
const domainCardsID = useDomainCardsID(policyID);

// TODO: add logic for choosing between the domain and workspace feed when both available
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const cardsID = domainCardsID || workspaceAccountID;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice we also have cardID. Are cardsID and cardID different?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, cardID is the id for a particular card, cardsID is the part of the 'private_expensifyCardSettings_' or 'cards_' key saved as fundID in the object - should I rename it for fundID?
Screenshot 2025-04-07 at 16 03 02

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, calling it fundID sounds good to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so I'll rename to fundID


const [isDeactivateModalVisible, setIsDeactivateModalVisible] = useState(false);
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
Expand All @@ -54,7 +60,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail
const styles = useThemeStyles();

const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [cardsList, cardsListResult] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`, {selector: filterInactiveCards});
const [cardsList, cardsListResult] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${cardsID}_${CONST.EXPENSIFY_CARD.BANK}`, {selector: filterInactiveCards});

const isWorkspaceCardRhp = route.name === SCREENS.WORKSPACE.EXPENSIFY_CARD_DETAILS;
const card = cardsList?.[cardID];
Expand All @@ -77,7 +83,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail
setIsDeactivateModalVisible(false);
Navigation.goBack();
InteractionManager.runAfterInteractions(() => {
deactivateCard_1(workspaceAccountID, card);
deactivateCardAction(workspaceAccountID, card);
});
};

Expand Down Expand Up @@ -188,7 +194,11 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail
onPress={() => {
Navigation.navigate(
ROUTES.SEARCH_ROOT.getRoute({
query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE, status: CONST.SEARCH.STATUS.EXPENSE.ALL, cardID}),
query: buildCannedSearchQuery({
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
status: CONST.SEARCH.STATUS.EXPENSE.ALL,
cardID,
}),
}),
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ type WorkspaceExpensifyCardListPageProps = {

/** List of Expensify cards */
cardsList: OnyxEntry<WorkspaceCardsList>;

/** Cards ID */
cardsID: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes more sense to me that this would be singular if it's the ID for a single card. Or is this the ID for the card feed?

Suggested change
cardsID: number;
cardID: number;

};

function WorkspaceExpensifyCardListPage({route, cardsList}: WorkspaceExpensifyCardListPageProps) {
function WorkspaceExpensifyCardListPage({route, cardsList, cardsID}: WorkspaceExpensifyCardListPageProps) {
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {translate} = useLocalize();
const styles = useThemeStyles();
Expand All @@ -48,6 +51,7 @@ function WorkspaceExpensifyCardListPage({route, cardsList}: WorkspaceExpensifyCa
const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [cardOnWaitlist] = useOnyx(`${ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST}${policyID}`);
const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${cardsID}`);

const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate});
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
Expand Down Expand Up @@ -115,7 +119,15 @@ function WorkspaceExpensifyCardListPage({route, cardsList}: WorkspaceExpensifyCa
[personalDetails, policyCurrency, policyID, workspaceAccountID, styles],
);

const renderListHeader = useCallback(() => <WorkspaceCardListHeader policyID={policyID} />, [policyID]);
const renderListHeader = useCallback(
() => (
<WorkspaceCardListHeader
policyID={policyID}
cardSettings={cardSettings}
/>
),
[policyID, cardSettings],
);

return (
<ScreenWrapper
Expand Down
47 changes: 32 additions & 15 deletions src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {useFocusEffect} from '@react-navigation/native';
import React, {useCallback} from 'react';
import {ActivityIndicator} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import useDomainCardsID from '@hooks/useDomainCardsID';
import useNetwork from '@hooks/useNetwork';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -25,12 +26,17 @@ function WorkspaceExpensifyCardPage({route}: WorkspaceExpensifyCardPageProps) {

const styles = useThemeStyles();
const theme = useTheme();
const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`);
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`, {selector: filterInactiveCards});
const domainCardsID = useDomainCardsID(policyID);

// TODO: add logic for choosing between the domain and workspace feed when both available
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const cardsID = domainCardsID || workspaceAccountID;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can call this domainAccountID since that's what it is in the db. And technically a workspaceAccountID is a type of domainAccountID. Or maybe we call it the cardAccountID.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think cardsID is easily mixed up with cardID.

const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${cardsID}`);
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${cardsID}_${CONST.EXPENSIFY_CARD.BANK}`, {selector: filterInactiveCards});

const fetchExpensifyCards = useCallback(() => {
openPolicyExpensifyCardsPage(policyID, workspaceAccountID);
}, [policyID, workspaceAccountID]);
openPolicyExpensifyCardsPage(policyID, cardsID);
}, [policyID, cardsID]);

const {isOffline} = useNetwork({onReconnect: fetchExpensifyCards});

Expand All @@ -39,26 +45,37 @@ function WorkspaceExpensifyCardPage({route}: WorkspaceExpensifyCardPageProps) {
const paymentBankAccountID = cardSettings?.paymentBankAccountID ?? CONST.DEFAULT_NUMBER_ID;
const isLoading = !isOffline && (!cardSettings || cardSettings.isLoading);

return (
<AccessOrNotFoundWrapper
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
policyID={route.params.policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED}
>
{!!isLoading && !paymentBankAccountID && (
const renderContent = () => {
if (!!isLoading && !paymentBankAccountID && !domainCardsID) {
return (
<ActivityIndicator
size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
style={styles.flex1}
color={theme.spinner}
/>
)}
{!!paymentBankAccountID && (
);
}
if (!!paymentBankAccountID || domainCardsID) {
return (
<WorkspaceExpensifyCardListPage
cardsList={cardsList}
cardsID={cardsID}
route={route}
/>
)}
{!paymentBankAccountID && !isLoading && <WorkspaceExpensifyCardPageEmptyState route={route} />}
);
}
if (!paymentBankAccountID && !isLoading) {
return <WorkspaceExpensifyCardPageEmptyState route={route} />;
}
};

return (
<AccessOrNotFoundWrapper
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
policyID={route.params.policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED}
>
{renderContent()}
</AccessOrNotFoundWrapper>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useDomainCardsID from '@hooks/useDomainCardsID';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID';
Expand All @@ -34,11 +35,15 @@ function WorkspaceSettlementAccountPage({route}: WorkspaceSettlementAccountPageP
const {translate} = useLocalize();
const policyID = route.params?.policyID;
const workspaceAccountID = useWorkspaceAccountID(policyID);
const domainCardsID = useDomainCardsID(policyID);
// TODO: add logic for choosing between the domain and workspace feed when both available
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const cardsID = domainCardsID || workspaceAccountID;

const [bankAccountsList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`);
const [isUsedContinuousReconciliation] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`);
const [reconciliationConnection] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`);
const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${cardsID}`);
const [isUsedContinuousReconciliation] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${cardsID}`);
const [reconciliationConnection] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${cardsID}`);

const connectionName = reconciliationConnection ?? '';
const connectionParam = getRouteParamForConnection(connectionName as ConnectionName);
Expand Down Expand Up @@ -78,7 +83,7 @@ function WorkspaceSettlementAccountPage({route}: WorkspaceSettlementAccountPageP
}, [eligibleBankAccounts, paymentBankAccountID, styles, translate]);

const updateSettlementAccount = (value: number) => {
updateSettlementAccountCard(workspaceAccountID, policyID, value, paymentBankAccountID);
updateSettlementAccountCard(cardsID, policyID, value, paymentBankAccountID);
Navigation.goBack();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import Text from '@components/Text';
import useDomainCardsID from '@hooks/useDomainCardsID';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID';
Expand All @@ -26,8 +27,13 @@ function WorkspaceSettlementFrequencyPage({route}: WorkspaceSettlementFrequencyP
const {translate} = useLocalize();
const policyID = route.params?.policyID;
const workspaceAccountID = useWorkspaceAccountID(policyID);
const domainCardsID = useDomainCardsID(policyID);

const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`);
// TODO: add logic for choosing between the domain and workspace feed when both available
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const cardsID = domainCardsID || workspaceAccountID;

const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${cardsID}`);

const shouldShowMonthlyOption = cardSettings?.isMonthlySettlementAllowed ?? false;
const selectedFrequency = cardSettings?.monthlySettlementDate ? CONST.EXPENSIFY_CARD.FREQUENCY_SETTING.MONTHLY : CONST.EXPENSIFY_CARD.FREQUENCY_SETTING.DAILY;
Expand Down Expand Up @@ -56,7 +62,7 @@ function WorkspaceSettlementFrequencyPage({route}: WorkspaceSettlementFrequencyP
}, [translate, shouldShowMonthlyOption, selectedFrequency]);

const updateSettlementFrequency = (value: ValueOf<typeof CONST.EXPENSIFY_CARD.FREQUENCY_SETTING>) => {
updateSettlementFrequencyUtil(workspaceAccountID, value, cardSettings?.monthlySettlementDate);
updateSettlementFrequencyUtil(cardsID, value, cardSettings?.monthlySettlementDate);
};

return (
Expand Down
6 changes: 6 additions & 0 deletions src/types/onyx/ExpensifyCardSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ type ExpensifyCardSettings = OnyxCommon.OnyxValueWithOfflineFeedback<{

/** Whether the request was successful */
isSuccess?: boolean;

/** The preferred policy for the domain card */
preferredPolicy?: string;

/** The Marqeta business token */
marqetaBusinessToken?: number;
}>;

export default ExpensifyCardSettings;