Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ function MoneyReportHeaderSelectionDropdown({reportID, primaryAction, isReportIn
iouReport={moneyRequestReport}
onPaymentSelect={onSelectionModePaymentSelect}
onWorkspacePolicySelect={(selectedPolicy, triggerKYCFlow) => {
if (shouldBlockAction()) {
if (shouldBlockAction(undefined, true)) {
return;
}
triggerKYCFlow({policy: selectedPolicy});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ function SelectionToolbar({reportID, transactions, reportActions}: SelectionTool
onSelectionModePaymentSelect={onSelectionModePaymentSelect}
selectionModeKYCSuccess={selectionModeKYCSuccess}
onWorkspacePolicySelect={(selectedPolicy, triggerKYCFlow) => {
if (shouldBlockAction()) {
if (shouldBlockAction(undefined, true)) {
return;
}
triggerKYCFlow({policy: selectedPolicy});
Expand Down
32 changes: 23 additions & 9 deletions src/hooks/useSelectionModePayment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {useSearchQueryContext, useSearchResultsContext} from '@components/Search
import type {PaymentActionParams} from '@components/SettlementButton/types';
import {payInvoice, payMoneyRequest} from '@libs/actions/IOU/PayMoneyRequest';
import {generateDefaultWorkspaceName} from '@libs/actions/Policy/Policy';
import deferModalPresentationAfterPopoverDismiss from '@libs/deferModalPresentationAfterPopoverDismiss';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -119,17 +120,25 @@ function useSelectionModePayment({
const isInvoiceReport = isInvoiceReportUtil(moneyRequestReport);
const isAnyTransactionOnHold = hasHeldExpensesFromTransactions(transactions);

const shouldBlockAction = (paymentMethodType?: PaymentMethodType) => {
const presentBlockingAction = (action: () => void, deferBlockingPresentation: boolean) => {
if (deferBlockingPresentation) {
deferModalPresentationAfterPopoverDismiss(action);
} else {
action();
}
};

const shouldBlockAction = (paymentMethodType?: PaymentMethodType, deferBlockingPresentation = false) => {
if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
presentBlockingAction(showDelegateNoAccessModal, deferBlockingPresentation);
return true;
}
if (isAccountLocked) {
showLockedAccountModal();
presentBlockingAction(showLockedAccountModal, deferBlockingPresentation);
return true;
}
if (!isUserValidated && paymentMethodType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE) {
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.VERIFY_ACCOUNT.path));
presentBlockingAction(() => Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.VERIFY_ACCOUNT.path)), deferBlockingPresentation);
return true;
}
return false;
Expand Down Expand Up @@ -242,7 +251,7 @@ function useSelectionModePayment({
})();

const handleWorkspaceSelected = (wp: OnyxTypes.Policy) => {
if (shouldBlockAction()) {
if (shouldBlockAction(undefined, true)) {
return;
}
kycWallRef.current?.continueAction?.({policy: wp});
Expand All @@ -269,10 +278,7 @@ function useSelectionModePayment({
return result;
})();

const onSelectionModePaymentSelect = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
if (shouldBlockAction(iouPaymentType)) {
return;
}
const invokePaymentSelect = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
selectPaymentType({
event,
iouPaymentType,
Expand All @@ -296,6 +302,13 @@ function useSelectionModePayment({
});
};

const onSelectionModePaymentSelect = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
if (shouldBlockAction(iouPaymentType, true)) {
return;
}
invokePaymentSelect(event, iouPaymentType, triggerKYCFlow);
};

const selectionModeKYCSuccess = (type?: PaymentMethodType) => {
confirmPayment({paymentType: type});
};
Expand All @@ -306,6 +319,7 @@ function useSelectionModePayment({
return {
confirmPayment,
shouldBlockAction,
invokePaymentSelect,
onSelectionModePaymentSelect,
selectionModeKYCSuccess,
paymentSubMenuItems,
Expand Down
63 changes: 27 additions & 36 deletions src/hooks/useSelectionModeReportActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,46 +177,37 @@ function useSelectionModeReportActions({
const effectiveShouldBlockSubmit = shouldBlockSubmit || isBlockSubmitDueToSelectedTransactionsOnSubmitPolicy;

// Shared payment hook
const {
confirmPayment,
shouldBlockAction,
onSelectionModePaymentSelect: basePaymentSelect,
selectionModeKYCSuccess,
paymentSubMenuItems,
hasPayInSelectionMode,
isAnyTransactionOnHold,
isInvoiceReport,
kycWallRef,
} = useSelectionModePayment({
reportID: report?.reportID,
transactions,
formattedAmount: totalAmount,
shouldHidePaymentOptions: !shouldShowPayButton,
onlyShowPayElsewhere,
hasPayAction,
allExpensesSelected,
onHoldMenuOpen: ({requestType: rt, paymentType: pt, methodID}) => {
setRequestType(rt);
setPaymentType(pt);
setSelectedVBBAToPayFromHoldMenu(methodID);
setIsHoldMenuVisible(true);
},
onPaymentComplete: () => {
clearSelectedTransactions(true);
turnOffMobileSelectionMode();
},
confirmApproval,
});
const {confirmPayment, shouldBlockAction, invokePaymentSelect, selectionModeKYCSuccess, paymentSubMenuItems, hasPayInSelectionMode, isAnyTransactionOnHold, isInvoiceReport, kycWallRef} =
useSelectionModePayment({
reportID: report?.reportID,
transactions,
formattedAmount: totalAmount,
shouldHidePaymentOptions: !shouldShowPayButton,
onlyShowPayElsewhere,
hasPayAction,
allExpensesSelected,
onHoldMenuOpen: ({requestType: rt, paymentType: pt, methodID}) => {
setRequestType(rt);
setPaymentType(pt);
setSelectedVBBAToPayFromHoldMenu(methodID);
setIsHoldMenuVisible(true);
},
onPaymentComplete: () => {
clearSelectedTransactions(true);
turnOffMobileSelectionMode();
},
confirmApproval,
});

// Defer payment select until the popover dismiss animation completes. Blocking modals are shown
// synchronously inside the callback (popover already closed) to avoid double-defer on Android.
const onSelectionModePaymentSelect = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
if (shouldBlockAction(iouPaymentType)) {
return;
}
// This callback fires via onSubItemSelected before the popover closes. Defer heavy payment
// work so the dropdown dismiss animation completes first, avoiding perceived UI lag.
TransitionTracker.runAfterTransitions({
callback: () => {
basePaymentSelect(event, iouPaymentType, triggerKYCFlow);
if (shouldBlockAction(iouPaymentType)) {
return;
}
invokePaymentSelect(event, iouPaymentType, triggerKYCFlow);
},
waitForUpcomingTransition: true,
});
Expand Down
5 changes: 3 additions & 2 deletions src/libs/actions/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {
} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import {getCommandURL} from '@libs/ApiUtils';
import deferModalPresentationAfterPopoverDismiss from '@libs/deferModalPresentationAfterPopoverDismiss';
import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
import fileDownload from '@libs/fileDownload';
import {getExportFileName} from '@libs/fileDownload/FileUtils';
Expand Down Expand Up @@ -1640,12 +1641,12 @@ function handleBulkPayItemSelected(params: {
}

if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
deferModalPresentationAfterPopoverDismiss(showDelegateNoAccessModal);
return;
}

if (isAccountLocked) {
showLockedAccountModal();
deferModalPresentationAfterPopoverDismiss(showLockedAccountModal);
return;
}

Expand Down
14 changes: 14 additions & 0 deletions src/libs/deferModalPresentationAfterPopoverDismiss/index.ios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import TransitionTracker from '@libs/Navigation/TransitionTracker';

/**
* Presents a blocking modal after the payment popover dismisses.
* On iOS, presenting a modal while another modal is dismissing freezes the app.
*/
function deferModalPresentationAfterPopoverDismiss(presentModal: () => void) {
TransitionTracker.runAfterTransitions({
callback: presentModal,
waitForUpcomingTransition: true,
});
}

export default deferModalPresentationAfterPopoverDismiss;
8 changes: 8 additions & 0 deletions src/libs/deferModalPresentationAfterPopoverDismiss/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Presents a blocking modal after the payment popover dismisses.
*/
function deferModalPresentationAfterPopoverDismiss(presentModal: () => void) {
presentModal();
}

export default deferModalPresentationAfterPopoverDismiss;
4 changes: 4 additions & 0 deletions tests/unit/Search/handleActionButtonPressTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import type {LastPaymentMethod, Policy, Report, SearchResults} from '@src/types/
import createRandomPolicy from '../../utils/collections/policies';

jest.mock('@src/components/ConfirmedRoute.tsx');
jest.mock('@libs/deferModalPresentationAfterPopoverDismiss', () => ({
__esModule: true,
default: (callback: () => void) => callback(),
}));
jest.mock('@src/libs/Navigation/Navigation', () => ({
navigate: jest.fn(),
dismissModal: jest.fn(),
Expand Down
Loading