diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8c339e9120ab..a07266738004 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -460,8 +460,8 @@ const ONYXKEYS = { SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END: 'sharedNVP_private_billingGracePeriodEnd_', /** - * Stores the card list for a given fundID and feed in the format: card__ - * So for example: card_12345_Expensify Card + * Stores the card list for a given fundID and feed in the format: cards__ + * So for example: cards_12345_Expensify Card */ WORKSPACE_CARDS_LIST: 'cards_', diff --git a/src/libs/API/parameters/OpenPolicyEditCardLimitTypePageParams.ts b/src/libs/API/parameters/OpenPolicyEditCardLimitTypePageParams.ts new file mode 100644 index 000000000000..21c79803cf01 --- /dev/null +++ b/src/libs/API/parameters/OpenPolicyEditCardLimitTypePageParams.ts @@ -0,0 +1,7 @@ +type OpenPolicyEditCardLimitTypePageParams = { + policyID: string; + authToken: string | null | undefined; + cardID: number; +}; + +export default OpenPolicyEditCardLimitTypePageParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index a422b303a564..fb6802577f1e 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -260,6 +260,7 @@ export type {default as EnablePolicyInvoicingParams} from './EnablePolicyInvoici export type {default as CreateWorkspaceReportFieldListValueParams} from './CreateWorkspaceReportFieldListValueParams'; export type {default as RemoveWorkspaceReportFieldListValueParams} from './RemoveWorkspaceReportFieldListValueParams'; export type {default as OpenPolicyExpensifyCardsPageParams} from './OpenPolicyExpensifyCardsPageParams'; +export type {default as OpenPolicyEditCardLimitTypePageParams} from './OpenPolicyEditCardLimitTypePageParams'; export type {default as RequestExpensifyCardLimitIncreaseParams} from './RequestExpensifyCardLimitIncreaseParams'; export type {default as UpdateNetSuiteGenericTypeParams} from './UpdateNetSuiteGenericTypeParams'; export type {default as CancelBillingSubscriptionParams} from './CancelBillingSubscriptionParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 85dc033161de..eb43bae52af0 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -711,6 +711,7 @@ const READ_COMMANDS = { OPEN_POLICY_TAXES_PAGE: 'OpenPolicyTaxesPage', OPEN_POLICY_REPORT_FIELDS_PAGE: 'OpenPolicyReportFieldsPage', OPEN_POLICY_EXPENSIFY_CARDS_PAGE: 'OpenPolicyExpensifyCardsPage', + OPEN_POLICY_EDIT_CARD_LIMIT_TYPE_PAGE: 'OpenPolicyEditCardLimitTypePage', OPEN_WORKSPACE_INVITE_PAGE: 'OpenWorkspaceInvitePage', OPEN_DRAFT_WORKSPACE_REQUEST: 'OpenDraftWorkspaceRequest', OPEN_POLICY_WORKFLOWS_PAGE: 'OpenPolicyWorkflowsPage', @@ -773,6 +774,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE]: Parameters.OpenPolicyMoreFeaturesPageParams; [READ_COMMANDS.OPEN_POLICY_ACCOUNTING_PAGE]: Parameters.OpenPolicyAccountingPageParams; [READ_COMMANDS.OPEN_POLICY_EXPENSIFY_CARDS_PAGE]: Parameters.OpenPolicyExpensifyCardsPageParams; + [READ_COMMANDS.OPEN_POLICY_EDIT_CARD_LIMIT_TYPE_PAGE]: Parameters.OpenPolicyEditCardLimitTypePageParams; [READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE]: Parameters.OpenPolicyProfilePageParams; [READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE]: Parameters.OpenPolicyInitialPageParams; [READ_COMMANDS.SEARCH]: Parameters.SearchParams; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index e2f1f4f54f68..d914e47e5204 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -20,6 +20,7 @@ import type { EnablePolicyWorkflowsParams, LeavePolicyParams, OpenDraftWorkspaceRequestParams, + OpenPolicyEditCardLimitTypePageParams, OpenPolicyExpensifyCardsPageParams, OpenPolicyInitialPageParams, OpenPolicyMoreFeaturesPageParams, @@ -2076,6 +2077,18 @@ function openPolicyExpensifyCardsPage(policyID: string, workspaceAccountID: numb API.read(READ_COMMANDS.OPEN_POLICY_EXPENSIFY_CARDS_PAGE, params, {optimisticData, successData, failureData}); } +function openPolicyEditCardLimitTypePage(policyID: string, cardID: number) { + const authToken = NetworkStore.getAuthToken(); + + const params: OpenPolicyEditCardLimitTypePageParams = { + policyID, + authToken, + cardID, + }; + + API.read(READ_COMMANDS.OPEN_POLICY_EDIT_CARD_LIMIT_TYPE_PAGE, params); +} + function openWorkspaceInvitePage(policyID: string, clientMemberEmails: string[]) { if (!policyID || !clientMemberEmails) { Log.warn('openWorkspaceInvitePage invalid params', {policyID, clientMemberEmails}); @@ -3346,6 +3359,7 @@ export { createPolicyExpenseChats, upgradeToCorporate, openPolicyExpensifyCardsPage, + openPolicyEditCardLimitTypePage, requestExpensifyCardLimitIncrease, getAdminPoliciesConnectedToNetSuite, getAdminPoliciesConnectedToSageIntacct, diff --git a/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitTypePage.tsx b/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitTypePage.tsx index 441e2e80006b..390c434692d7 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitTypePage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitTypePage.tsx @@ -1,6 +1,8 @@ +import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -15,6 +17,7 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -38,20 +41,43 @@ function WorkspaceEditCardLimitTypePage({route}: WorkspaceEditCardLimitTypePageP const defaultLimitType = areApprovalsConfigured ? CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART : CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY; const initialLimitType = card?.nameValuePairs?.limitType ?? defaultLimitType; const promptTranslationKey = - initialLimitType === CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY - ? 'workspace.expensifyCard.changeCardMonthlyLimitTypeWarning' - : 'workspace.expensifyCard.changeCardSmartLimitTypeWarning'; + initialLimitType === CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY || initialLimitType === CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED + ? 'workspace.expensifyCard.changeCardSmartLimitTypeWarning' + : 'workspace.expensifyCard.changeCardMonthlyLimitTypeWarning'; const [typeSelected, setTypeSelected] = useState(initialLimitType); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const fetchCardLimitTypeData = useCallback(() => { + Policy.openPolicyEditCardLimitTypePage(policyID, Number(cardID)); + }, [policyID, cardID]); + + useFocusEffect(fetchCardLimitTypeData); + const updateCardLimitType = () => { // TODO: add API call when it's supported https://github.com/Expensify/Expensify/issues/407833 }; const submit = () => { - // TODO: update the condition of showing confirm warning when requirements are known - const shouldShowConfirmModal = true; + let shouldShowConfirmModal = false; + if (!!card?.unapprovedSpend && card?.nameValuePairs?.unapprovedExpenseLimit) { + // Spends are coming as negative numbers from the backend and we need to make it positive for the correct expression. + const unapprovedSpend = Math.abs(card.unapprovedSpend); + const isUnapprovedSpendOverLimit = unapprovedSpend >= card.nameValuePairs.unapprovedExpenseLimit; + + const validCombinations = [ + [CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY, CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART], + [CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART, CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY], + [CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED, CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART], + [CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED, CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY], + ]; + // Check if the combination exists in validCombinations + const isValidCombination = validCombinations.some(([limitType, selectedType]) => initialLimitType === limitType && typeSelected === selectedType); + + if (isValidCombination && isUnapprovedSpendOverLimit) { + shouldShowConfirmModal = true; + } + } if (shouldShowConfirmModal) { setIsConfirmModalVisible(true); @@ -62,8 +88,17 @@ function WorkspaceEditCardLimitTypePage({route}: WorkspaceEditCardLimitTypePageP const data = useMemo(() => { const options = []; - // TODO: update the condition of showing the fixed option when requirements are known - const shouldShowFixedOption = true; + let shouldShowFixedOption = true; + + if (card?.totalSpend && card?.nameValuePairs?.unapprovedExpenseLimit) { + const totalSpend = Math.abs(card.totalSpend); + if ( + (initialLimitType === CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY || initialLimitType === CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART) && + totalSpend >= card.nameValuePairs?.unapprovedExpenseLimit + ) { + shouldShowFixedOption = false; + } + } if (areApprovalsConfigured) { options.push({ @@ -96,7 +131,7 @@ function WorkspaceEditCardLimitTypePage({route}: WorkspaceEditCardLimitTypePageP } return options; - }, [translate, typeSelected, areApprovalsConfigured]); + }, [areApprovalsConfigured, card, initialLimitType, translate, typeSelected]); return ( Navigation.goBack(ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.getRoute(policyID, cardID))} /> - setTypeSelected(value)} - sections={[{data}]} - shouldUpdateFocusedIndex - shouldSingleExecuteRowSelect - isAlternateTextMultilineSupported - initiallyFocusedOptionKey={typeSelected} - /> - setIsConfirmModalVisible(false)} - prompt={translate(promptTranslationKey, CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.unapprovedExpenseLimit, CONST.CURRENCY.USD))} - confirmText={translate('workspace.expensifyCard.changeLimitType')} - cancelText={translate('common.cancel')} - danger - shouldEnableNewFocusManagement - /> -