Skip to content

Commit

Permalink
Merge pull request Expensify#47333 from VickyStash/feature/47231-Open…
Browse files Browse the repository at this point in the history
…CardDetailsPage-api-call

[No QA] Implement OpenCardDetailsPage api call
  • Loading branch information
mountiny authored Aug 15, 2024
2 parents 65a3811 + d692b67 commit c880993
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 27 deletions.
6 changes: 6 additions & 0 deletions src/libs/API/parameters/OpenCardDetailsPageParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type OpenCardDetailsPageParams = {
authToken: string;
cardID: number;
};

export default OpenCardDetailsPageParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,4 @@ export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFl
export type {default as ConfigureExpensifyCardsForPolicyParams} from './ConfigureExpensifyCardsForPolicyParams';
export type {default as CreateExpensifyCardParams} from './CreateExpensifyCardParams';
export type {default as UpdateExpensifyCardTitleParams} from './UpdateExpensifyCardTitleParams';
export type {default as OpenCardDetailsPageParams} from './OpenCardDetailsPageParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ const READ_COMMANDS = {
OPEN_SUBSCRIPTION_PAGE: 'OpenSubscriptionPage',
OPEN_DRAFT_DISTANCE_EXPENSE: 'OpenDraftDistanceExpense',
START_ISSUE_NEW_CARD_FLOW: 'StartIssueNewCardFlow',
OPEN_CARD_DETAILS_PAGE: 'OpenCardDetailsPage',
} as const;

type ReadCommand = ValueOf<typeof READ_COMMANDS>;
Expand Down Expand Up @@ -781,6 +782,7 @@ type ReadCommandParameters = {
[READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE]: null;
[READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE]: null;
[READ_COMMANDS.START_ISSUE_NEW_CARD_FLOW]: Parameters.StartIssueNewCardFlowParams;
[READ_COMMANDS.OPEN_CARD_DETAILS_PAGE]: Parameters.OpenCardDetailsPageParams;
};

const SIDE_EFFECT_REQUEST_COMMANDS = {
Expand Down
17 changes: 16 additions & 1 deletion src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type {OnyxValues} from '@src/ONYXKEYS';
import ONYXKEYS from '@src/ONYXKEYS';
import type {BankAccountList, Card, CardList} from '@src/types/onyx';
import type {BankAccountList, Card, CardList, PersonalDetailsList, WorkspaceCardsList} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import localeCompare from './LocaleCompare';
import * as Localize from './Localize';
import * as PersonalDetailsUtils from './PersonalDetailsUtils';

let allCards: OnyxValues[typeof ONYXKEYS.CARD_LIST] = {};
Onyx.connect({
Expand Down Expand Up @@ -166,6 +168,18 @@ function getEligibleBankAccountsForCard(bankAccountsList: OnyxEntry<BankAccountL
return Object.values(bankAccountsList).filter((bankAccount) => bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && bankAccount?.accountData?.allowDebit);
}

function sortCardsByCardholderName(cardsList: OnyxEntry<WorkspaceCardsList>, personalDetails: OnyxEntry<PersonalDetailsList>): Card[] {
return Object.values(cardsList ?? {}).sort((cardA: Card, cardB: Card) => {
const userA = personalDetails?.[cardA.accountID ?? '-1'] ?? {};
const userB = personalDetails?.[cardB.accountID ?? '-1'] ?? {};

const aName = PersonalDetailsUtils.getDisplayNameOrDefault(userA);
const bName = PersonalDetailsUtils.getDisplayNameOrDefault(userB);

return localeCompare(aName, bName);
});
}

export {
isExpensifyCard,
isCorporateCard,
Expand All @@ -180,4 +194,5 @@ export {
getMCardNumberString,
getTranslationKeyForLimitType,
getEligibleBankAccountsForCard,
sortCardsByCardholderName,
};
21 changes: 20 additions & 1 deletion src/libs/actions/Card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {ValueOf} from 'type-fest';
import * as API from '@libs/API';
import type {
ActivatePhysicalExpensifyCardParams,
OpenCardDetailsPageParams,
ReportVirtualExpensifyCardFraudParams,
RequestReplacementExpensifyCardParams,
RevealExpensifyCardDetailsParams,
Expand Down Expand Up @@ -314,7 +315,7 @@ function clearIssueNewCardFlow() {
});
}

function updateExpensifyCardLimit(workspaceAccountID: number, cardID: number, newLimit: number, oldLimit?: number) {
function updateExpensifyCardLimit(workspaceAccountID: number, cardID: number, newLimit: number, newAvailableSpend: number, oldLimit?: number, oldAvailableSpend?: number) {
const authToken = NetworkStore.getAuthToken();

if (!authToken) {
Expand All @@ -327,6 +328,7 @@ function updateExpensifyCardLimit(workspaceAccountID: number, cardID: number, ne
key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`,
value: {
[cardID]: {
availableSpend: newAvailableSpend,
nameValuePairs: {
unapprovedExpenseLimit: newLimit,
},
Expand Down Expand Up @@ -355,6 +357,7 @@ function updateExpensifyCardLimit(workspaceAccountID: number, cardID: number, ne
key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`,
value: {
[cardID]: {
availableSpend: oldAvailableSpend,
nameValuePairs: {
unapprovedExpenseLimit: oldLimit,
},
Expand Down Expand Up @@ -479,6 +482,21 @@ function issueExpensifyCard(policyID: string, feedCountry: string, data?: IssueN
API.write(WRITE_COMMANDS.CREATE_ADMIN_ISSUED_VIRTUAL_CARD, parameters);
}

function openCardDetailsPage(cardID: number) {
const authToken = NetworkStore.getAuthToken();

if (!authToken) {
return;
}

const parameters: OpenCardDetailsPageParams = {
authToken,
cardID,
};

API.read(READ_COMMANDS.OPEN_CARD_DETAILS_PAGE, parameters);
}

export {
requestReplacementExpensifyCard,
activatePhysicalExpensifyCard,
Expand All @@ -494,5 +512,6 @@ export {
startIssueNewCardFlow,
configureExpensifyCardsForPolicy,
issueExpensifyCard,
openCardDetailsPage,
};
export type {ReplacementReason};
21 changes: 14 additions & 7 deletions src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,33 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) {
}
}, [card?.nameValuePairs?.limitType]);

const updateCardLimit = (newLimit: string) => {
const getNewAvailableSpend = (newLimit: number) => {
const currentLimit = card?.nameValuePairs?.unapprovedExpenseLimit ?? 0;
const currentSpend = currentLimit - (card?.availableSpend ?? 0);

return newLimit - currentSpend;
};

const updateCardLimit = (newLimit: number) => {
const newAvailableSpend = getNewAvailableSpend(newLimit);

setIsConfirmModalVisible(false);

Card.updateExpensifyCardLimit(workspaceAccountID, Number(cardID), Number(newLimit) * 100, card?.nameValuePairs?.unapprovedExpenseLimit);
Card.updateExpensifyCardLimit(workspaceAccountID, Number(cardID), newLimit, newAvailableSpend, card?.nameValuePairs?.unapprovedExpenseLimit, card?.availableSpend);

Navigation.goBack();
};

const submit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.EDIT_EXPENSIFY_CARD_LIMIT_FORM>) => {
const currentLimit = card?.nameValuePairs?.unapprovedExpenseLimit ?? 0;
const currentSpend = currentLimit - (card?.availableSpend ?? 0);
const newLimit = Number(values[INPUT_IDS.LIMIT]) * 100;
const newAvailableSpend = newLimit - currentSpend;
const newAvailableSpend = getNewAvailableSpend(newLimit);

if (newAvailableSpend <= 0) {
setIsConfirmModalVisible(true);
return;
}

updateCardLimit(values[INPUT_IDS.LIMIT]);
updateCardLimit(newLimit);
};

const validate = useCallback(
Expand Down Expand Up @@ -126,7 +133,7 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) {
<ConfirmModal
title={translate('workspace.expensifyCard.changeCardLimit')}
isVisible={isConfirmModalVisible}
onConfirm={() => updateCardLimit(inputValues[INPUT_IDS.LIMIT])}
onConfirm={() => updateCardLimit(Number(inputValues[INPUT_IDS.LIMIT]) * 100)}
onCancel={() => setIsConfirmModalVisible(false)}
prompt={translate(getPromptTextKey, CurrencyUtils.convertToDisplayString(Number(inputValues[INPUT_IDS.LIMIT]) * 100, CONST.CURRENCY.USD))}
confirmText={translate('workspace.expensifyCard.changeLimit')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useState} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import ExpensifyCardImage from '@assets/images/expensify-card.svg';
Expand All @@ -14,6 +14,7 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
Expand All @@ -24,6 +25,7 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import variables from '@styles/variables';
import * as Card from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -43,7 +45,6 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`);

// TODO: add an API call to load the card details data: https://github.com/Expensify/App/issues/47231
const card = cardsList?.[cardID];
const cardholder = personalDetails?.[card?.accountID ?? -1];
const isVirtual = !!card?.nameValuePairs?.isVirtual;
Expand All @@ -52,6 +53,14 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail
const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder);
const translationForLimitType = CardUtils.getTranslationKeyForLimitType(card?.nameValuePairs?.limitType);

const fetchCardDetails = useCallback(() => {
Card.openCardDetailsPage(Number(cardID));
}, [cardID]);

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

useEffect(() => fetchCardDetails(), [fetchCardDetails]);

const deactivateCard = () => {
setIsDeactivateModalVisible(false);

Expand Down Expand Up @@ -111,6 +120,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail
title={formattedAvailableSpendAmount}
interactive={false}
titleStyle={styles.newKansasLarge}
containerStyle={isOffline ? styles.buttonOpacityDisabled : null}
/>
<MenuItemWithTopDescription
description={translate('workspace.expensifyCard.cardLimit')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import localeCompare from '@libs/LocaleCompare';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as CardUtils from '@libs/CardUtils';
import Navigation from '@navigation/Navigation';
import type {FullScreenNavigatorParamList} from '@navigation/types';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -48,17 +47,7 @@ function WorkspaceExpensifyCardListPage({route, cardsList}: WorkspaceExpensifyCa

const policyCurrency = useMemo(() => policy?.outputCurrency ?? CONST.CURRENCY.USD, [policy]);

const sortedCards = useMemo(
() =>
Object.values(cardsList ?? {}).sort((cardA: Card, cardB: Card) => {
const userA = personalDetails?.[cardA.accountID ?? '-1'] ?? {};
const userB = personalDetails?.[cardB.accountID ?? '-1'] ?? {};
const aName = PersonalDetailsUtils.getDisplayNameOrDefault(userA);
const bName = PersonalDetailsUtils.getDisplayNameOrDefault(userB);
return localeCompare(aName, bName);
}),
[cardsList, personalDetails],
);
const sortedCards = useMemo(() => CardUtils.sortCardsByCardholderName(cardsList, personalDetails), [cardsList, personalDetails]);

const getHeaderButtons = () => (
<View style={[styles.w100, styles.flexRow, styles.gap2, shouldUseNarrowLayout && styles.mb3]}>
Expand Down
6 changes: 3 additions & 3 deletions src/types/onyx/ExpensifyCardSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import type * as OnyxCommon from './OnyxCommon';
/** Model of Expensify card settings for a workspace */
type ExpensifyCardSettings = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Sum of all posted Expensify Card transactions */
currentBalance: number;
currentBalance?: number;

/** Remaining limit for Expensify Cards on the workspace */
remainingLimit: number;
remainingLimit?: number;

/** The total amount of cash back earned thus far */
earnedCashback: number;
earnedCashback?: number;

/** The date of the last settlement */
monthlySettlementDate: Date;
Expand Down

0 comments on commit c880993

Please sign in to comment.