Skip to content

Commit c7a4859

Browse files
authored
Merge pull request #87750 from Expensify/claude-pendingCardTransactionSubmitError
Show submit button with error modal for pending card transactions
2 parents c4ac66d + cb53779 commit c7a4859

30 files changed

Lines changed: 156 additions & 44 deletions

src/components/MoneyReportHeaderPrimaryAction/SubmitPrimaryAction.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from 'react';
33
import AnimatedSubmitButton from '@components/AnimatedSubmitButton';
44
import {usePaymentAnimationsContext} from '@components/PaymentAnimationsContext';
55
import {useSearchStateContext} from '@components/Search/SearchContext';
6+
import useConfirmModal from '@hooks/useConfirmModal';
67
import useConfirmPendingRTERAndProceed from '@hooks/useConfirmPendingRTERAndProceed';
78
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
89
import useLocalize from '@hooks/useLocalize';
@@ -17,7 +18,7 @@ import {search} from '@libs/actions/Search';
1718
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
1819
import {getFilteredReportActionsForReportView} from '@libs/ReportActionsUtils';
1920
import {hasViolations as hasViolationsReportUtils, shouldBlockSubmitDueToPreventSelfApproval, shouldBlockSubmitDueToStrictPolicyRules} from '@libs/ReportUtils';
20-
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils} from '@libs/TransactionUtils';
21+
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils, hasOnlyPendingCardTransactions, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
2122
import {submitReport} from '@userActions/IOU/ReportWorkflow';
2223
import {markPendingRTERTransactionsAsCash} from '@userActions/Transaction';
2324
import CONST from '@src/CONST';
@@ -57,6 +58,8 @@ function SubmitPrimaryAction({reportID}: SubmitPrimaryActionProps) {
5758
};
5859
const confirmPendingRTERAndProceed = useConfirmPendingRTERAndProceed(hasAnyPendingRTERViolation, handleMarkPendingRTERTransactionsAsCash);
5960

61+
const {showConfirmModal} = useConfirmModal();
62+
6063
const isBlockSubmitDueToPreventSelfApproval = shouldBlockSubmitDueToPreventSelfApproval(moneyRequestReport, policy);
6164
const isBlockSubmitDueToStrictPolicyRules = shouldBlockSubmitDueToStrictPolicyRules(
6265
moneyRequestReport?.reportID,
@@ -75,6 +78,12 @@ function SubmitPrimaryAction({reportID}: SubmitPrimaryActionProps) {
7578
if (!moneyRequestReport || shouldBlockSubmit) {
7679
return;
7780
}
81+
82+
if (hasOnlyPendingCardTransactions(transactions)) {
83+
showPendingCardTransactionsBlockModal(showConfirmModal, translate);
84+
return;
85+
}
86+
7887
confirmPendingRTERAndProceed(() => {
7988
submitReport({
8089
expenseReport: moneyRequestReport,

src/components/MoneyReportHeaderStatusBarSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ function MoneyReportHeaderStatusBarSection({reportID, statusBarType, iouTransact
122122
return (
123123
<MoneyRequestHeaderStatusBar
124124
icon={getStatusIcon(expensifyIcons.CreditCardHourglass)}
125-
description={translate('iou.transactionPendingDescription')}
125+
description={translate('iou.allTransactionsPendingNextStep')}
126126
/>
127127
);
128128
}

src/components/MoneyRequestHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
116116
}
117117

118118
if (isExpensifyCardTransaction(transaction) && isPending(transaction)) {
119-
return {icon: getStatusIcon(icons.CreditCardHourglass), description: translate('iou.transactionPendingDescription')};
119+
return {icon: getStatusIcon(icons.CreditCardHourglass), description: translate('iou.allTransactionsPendingNextStep')};
120120
}
121121
if (!!transaction?.transactionID && !!transactionViolations.length && shouldShowBrokenConnectionViolation) {
122122
const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION);

src/components/ReportActionItem/MoneyRequestReportPreview/SubmitActionButton.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {delegateEmailSelector} from '@selectors/Account';
22
import React from 'react';
33
import AnimatedSubmitButton from '@components/AnimatedSubmitButton';
4+
import useConfirmModal from '@hooks/useConfirmModal';
45
import useConfirmPendingRTERAndProceed from '@hooks/useConfirmPendingRTERAndProceed';
56
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
67
import useLocalize from '@hooks/useLocalize';
@@ -9,7 +10,7 @@ import useOnyx from '@hooks/useOnyx';
910
import usePermissions from '@hooks/usePermissions';
1011
import useReportTransactionsCollection from '@hooks/useReportTransactionsCollection';
1112
import {hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils';
12-
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils} from '@libs/TransactionUtils';
13+
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils, hasOnlyPendingCardTransactions, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
1314
import {submitReport} from '@userActions/IOU/ReportWorkflow';
1415
import {markPendingRTERTransactionsAsCash} from '@userActions/Transaction';
1516
import CONST from '@src/CONST';
@@ -25,6 +26,7 @@ type SubmitActionButtonProps = {
2526

2627
function SubmitActionButton({iouReportID, isSubmittingAnimationRunning, stopAnimation, startSubmittingAnimation}: SubmitActionButtonProps) {
2728
const {translate} = useLocalize();
29+
const {showConfirmModal} = useConfirmModal();
2830
const currentUserDetails = useCurrentUserPersonalDetails();
2931
const currentUserAccountID = currentUserDetails.accountID;
3032
const currentUserEmail = currentUserDetails.email ?? '';
@@ -60,6 +62,10 @@ function SubmitActionButton({iouReportID, isSubmittingAnimationRunning, stopAnim
6062
success
6163
text={translate('common.submit')}
6264
onPress={() => {
65+
if (hasOnlyPendingCardTransactions(transactions)) {
66+
showPendingCardTransactionsBlockModal(showConfirmModal, translate);
67+
return;
68+
}
6369
confirmPendingRTERAndProceed(() => {
6470
submitReport({
6571
expenseReport: iouReport,

src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import BaseListItem from '@components/SelectionList/ListItem/BaseListItem';
1212
import type {ListItem} from '@components/SelectionList/types';
1313
import Text from '@components/Text';
1414
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
15+
import useConfirmModal from '@hooks/useConfirmModal';
1516
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
1617
import useHoldMenuModal from '@hooks/useHoldMenuModal';
1718
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
@@ -27,7 +28,7 @@ import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils';
2728
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
2829
import {isAttendeeTrackingEnabled} from '@libs/PolicyUtils';
2930
import {getNonHeldAndFullAmount, isInvoiceReport, isOpenExpenseReport, isProcessingReport, isReportPendingDelete} from '@libs/ReportUtils';
30-
import {isOnHold, isViolationDismissed, shouldShowViolation} from '@libs/TransactionUtils';
31+
import {isOnHold, isViolationDismissed, shouldShowViolation, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
3132
import variables from '@styles/variables';
3233
import CONST from '@src/CONST';
3334
import ONYXKEYS from '@src/ONYXKEYS';
@@ -142,6 +143,7 @@ function ExpenseReportListItem<TItem extends ListItem>({
142143
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
143144
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
144145
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
146+
const {showConfirmModal} = useConfirmModal();
145147
const {showHoldMenu} = useHoldMenuModal();
146148
const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportItem.reportID);
147149
const liveReportTransactions = useMemo(() => Object.values(reportTransactions), [reportTransactions]);
@@ -187,6 +189,7 @@ function ExpenseReportListItem<TItem extends ListItem>({
187189
},
188190
ownerBillingGracePeriodEnd,
189191
amountOwed,
192+
onPendingCardTransactionsBlock: () => showPendingCardTransactionsBlockModal(showConfirmModal, translate),
190193
});
191194
}, [
192195
currentSearchHash,
@@ -208,6 +211,8 @@ function ExpenseReportListItem<TItem extends ListItem>({
208211
liveReportTransactions,
209212
ownerBillingGracePeriodEnd,
210213
amountOwed,
214+
showConfirmModal,
215+
translate,
211216
]);
212217

213218
const handleSelectionButtonPress = useCallback(() => {

src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import {PressableWithFeedback} from '@components/Pressable';
88
import ReportSearchHeader from '@components/ReportSearchHeader';
99
import {useSearchStateContext} from '@components/Search/SearchContext';
1010
import type {ListItem} from '@components/SelectionList/types';
11+
import useConfirmModal from '@hooks/useConfirmModal';
1112
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
13+
import useLocalize from '@hooks/useLocalize';
1214
import useOnyx from '@hooks/useOnyx';
1315
import useResponsiveLayout from '@hooks/useResponsiveLayout';
1416
import useStyleUtils from '@hooks/useStyleUtils';
1517
import useTheme from '@hooks/useTheme';
1618
import useThemeStyles from '@hooks/useThemeStyles';
1719
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
1820
import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab';
21+
import {showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
1922
import {handleActionButtonPress} from '@userActions/Search';
2023
import CONST from '@src/CONST';
2124
import ONYXKEYS from '@src/ONYXKEYS';
@@ -225,6 +228,8 @@ function ReportListItemHeader<TItem extends ListItem>({
225228
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
226229
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
227230
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
231+
const {translate} = useLocalize();
232+
const {showConfirmModal} = useConfirmModal();
228233
const avatarBorderColor =
229234
StyleUtils.getItemBackgroundColorStyle(!!reportItem.isSelected, !!isFocused || !!isHovered, !!isDisabled, theme.activeComponentBG, theme.hoverComponentBG)?.backgroundColor ??
230235
theme.highlightBG;
@@ -245,6 +250,7 @@ function ReportListItemHeader<TItem extends ListItem>({
245250
personalPolicyID,
246251
ownerBillingGracePeriodEnd,
247252
amountOwed,
253+
onPendingCardTransactionsBlock: () => showPendingCardTransactionsBlockModal(showConfirmModal, translate),
248254
});
249255
};
250256
return !isLargeScreenWidth ? (

src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {useSearchStateContext} from '@components/Search/SearchContext';
1212
import type {TransactionListItemProps, TransactionListItemType} from '@components/Search/SearchList/ListItem/types';
1313
import type {ListItem} from '@components/SelectionList/types';
1414
import {useEditingCellState} from '@components/TransactionItemRow/EditableCell';
15+
import useConfirmModal from '@hooks/useConfirmModal';
1516
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
17+
import useLocalize from '@hooks/useLocalize';
1618
import useOnyx from '@hooks/useOnyx';
1719
import useResponsiveLayout from '@hooks/useResponsiveLayout';
1820
import useTransactionInlineEdit from '@hooks/useTransactionInlineEdit';
@@ -22,7 +24,13 @@ import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils';
2224
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
2325
import {isAttendeeTrackingEnabled} from '@libs/PolicyUtils';
2426
import {isInvoiceReport} from '@libs/ReportUtils';
25-
import {isDeletedTransaction as isDeletedTransactionUtil, isViolationDismissed, mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils';
27+
import {
28+
isDeletedTransaction as isDeletedTransactionUtil,
29+
isViolationDismissed,
30+
mergeProhibitedViolations,
31+
shouldShowViolation,
32+
showPendingCardTransactionsBlockModal,
33+
} from '@libs/TransactionUtils';
2634
import CONST from '@src/CONST';
2735
import ONYXKEYS from '@src/ONYXKEYS';
2836
import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
@@ -129,6 +137,8 @@ function TransactionListItem<TItem extends ListItem>({
129137

130138
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
131139
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
140+
const {translate} = useLocalize();
141+
const {showConfirmModal} = useConfirmModal();
132142
const {isEditingCell, wasRecentlyEditingCell} = useEditingCellState();
133143
const [shouldDisableHoverStyle, setShouldDisableHoverStyle] = useState(false);
134144

@@ -207,6 +217,7 @@ function TransactionListItem<TItem extends ListItem>({
207217
ownerBillingGracePeriodEnd,
208218
amountOwed,
209219
onUndelete: () => onUndelete?.(transactionItem),
220+
onPendingCardTransactionsBlock: () => showPendingCardTransactionsBlockModal(showConfirmModal, translate),
210221
});
211222
};
212223

src/hooks/useLifecycleActions.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
isReportOwner,
2020
shouldBlockSubmitDueToStrictPolicyRules,
2121
} from '@libs/ReportUtils';
22-
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils} from '@libs/TransactionUtils';
22+
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils, hasOnlyPendingCardTransactions, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
2323
import {cancelPayment} from '@userActions/IOU/PayMoneyRequest';
2424
import {approveMoneyRequest, reopenReport, retractReport, submitReport, unapproveExpenseReport} from '@userActions/IOU/ReportWorkflow';
2525
import {markPendingRTERTransactionsAsCash} from '@userActions/Transaction';
@@ -179,6 +179,11 @@ function useLifecycleActions({reportID, startApprovedAnimation, startSubmittingA
179179
return;
180180
}
181181

182+
if (hasOnlyPendingCardTransactions(transactions)) {
183+
showPendingCardTransactionsBlockModal(showConfirmModal, translate);
184+
return;
185+
}
186+
182187
const doSubmit = () => {
183188
submitReport({
184189
expenseReport: moneyRequestReport,
@@ -227,6 +232,12 @@ function useLifecycleActions({reportID, startApprovedAnimation, startSubmittingA
227232
if (!moneyRequestReport) {
228233
return;
229234
}
235+
236+
if (hasOnlyPendingCardTransactions(transactions)) {
237+
showPendingCardTransactionsBlockModal(showConfirmModal, translate);
238+
return;
239+
}
240+
230241
confirmPendingRTERAndProceed(() => {
231242
submitReport({
232243
expenseReport: moneyRequestReport,

src/hooks/useSearchBulkActions.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,15 @@ import {navigateToSearchRHP, shouldShowDeleteOption} from '@libs/SearchUIUtils';
5757
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
5858
import {
5959
hasCustomUnitOutOfPolicyViolation,
60+
hasOnlyPendingCardTransactions,
6061
hasTransactionBeenRejected,
6162
isDeletedTransaction,
6263
isDistanceRequest,
6364
isManagedCardTransaction,
6465
isPerDiemRequest,
6566
isScanning,
6667
shouldRedirectDeleteToSplitExpenseEdit,
68+
showPendingCardTransactionsBlockModal,
6769
} from '@libs/TransactionUtils';
6870
import variables from '@styles/variables';
6971
import {initBulkEditDraftTransaction} from '@userActions/IOU/BulkEdit';
@@ -1275,6 +1277,17 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
12751277
return;
12761278
}
12771279

1280+
const allSelectedTransactionsList = selectedReports.length
1281+
? Object.values(allTransactions ?? {}).filter((t): t is NonNullable<typeof t> => !!t && selectedReports.some((report) => report.reportID === t.reportID))
1282+
: selectedTransactionsKeys
1283+
.map((id) => selectedTransactions[id]?.transaction ?? allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`])
1284+
.filter((t): t is NonNullable<typeof t> => !!t);
1285+
1286+
if (hasOnlyPendingCardTransactions(allSelectedTransactionsList)) {
1287+
showPendingCardTransactionsBlockModal(showConfirmModal, translate);
1288+
return;
1289+
}
1290+
12781291
for (const item of itemList) {
12791292
const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.policyID}`];
12801293
if (policy) {

src/hooks/useSelectionModeReportActions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ import {
3939
isReportOwner,
4040
shouldBlockSubmitDueToStrictPolicyRules,
4141
} from '@libs/ReportUtils';
42-
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils, isExpensifyCardTransaction, isPending} from '@libs/TransactionUtils';
42+
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils, hasOnlyPendingCardTransactions, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
4343
import {markPendingRTERTransactionsAsCash} from '@userActions/Transaction';
4444
import CONST from '@src/CONST';
4545
import ONYXKEYS from '@src/ONYXKEYS';
4646
import type * as OnyxTypes from '@src/types/onyx';
4747
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
4848
import useActiveAdminPolicies from './useActiveAdminPolicies';
49+
import useConfirmModal from './useConfirmModal';
4950
import useConfirmPendingRTERAndProceed from './useConfirmPendingRTERAndProceed';
5051
import {useCurrencyListActions} from './useCurrencyList';
5152
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
@@ -84,6 +85,7 @@ function useSelectionModeReportActions({
8485
selectedTransactionIDs,
8586
}: UseSelectionModeReportActionsParams) {
8687
const {translate, localeCompare} = useLocalize();
88+
const {showConfirmModal} = useConfirmModal();
8789
const {accountID: currentUserAccountID, login: currentUserLogin, localCurrencyCode} = useCurrentUserPersonalDetails();
8890
const {isBetaEnabled} = usePermissions();
8991
const {areStrictPolicyRulesEnabled} = useStrictPolicyRules();
@@ -159,7 +161,7 @@ function useSelectionModeReportActions({
159161
const isAnyTransactionOnHold = hasHeldExpensesReportUtils(transactions);
160162
const isInvoiceReport = isInvoiceReportUtil(report);
161163

162-
const hasOnlyPendingTransactions = !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t));
164+
const hasOnlyPendingTransactions = hasOnlyPendingCardTransactions(transactions);
163165
const nonPendingDeleteTransactions = transactions.filter((t): t is OnyxTypes.Transaction => !!t && (isOffline || t.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE));
164166

165167
const getCanIOUBePaid = (onlyShowPayElsewhere = false) =>
@@ -283,6 +285,10 @@ function useSelectionModeReportActions({
283285
if (!report || shouldBlockSubmit) {
284286
return;
285287
}
288+
if (hasOnlyPendingTransactions) {
289+
showPendingCardTransactionsBlockModal(showConfirmModal, translate);
290+
return;
291+
}
286292
const doSubmit = () => {
287293
submitReport({
288294
expenseReport: report,

0 commit comments

Comments
 (0)