Skip to content
Merged
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
@@ -1,86 +1,38 @@
import Button from '@components/Button';
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';

import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import useReportTransactionViolations from '@hooks/useReportTransactionViolations';
import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';

import {hasHeldExpensesFromTransactions as hasHeldExpensesReportUtils, hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils';

import {approveMoneyRequest} from '@userActions/IOU/ReportWorkflow';
import {hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

import {delegateEmailSelector} from '@selectors/Account';
import {isTrackIntentUserSelector} from '@selectors/Onboarding';
import {personalDetailsLoginSelector} from '@selectors/PersonalDetails';
import React from 'react';

import {useReportPreviewActions, useReportPreviewActionState, useReportPreviewData} from './MoneyRequestReportPreviewContext';
import {useReportPreviewData} from './MoneyRequestReportPreviewContext';
import useConfirmApproveReportAction from './useConfirmApproveReportAction';
import useReportPreviewActionButtonData from './useReportPreviewActionButtonData';

function ApproveActionButton() {
const {translate} = useLocalize();
const currentUserDetails = useCurrentUserPersonalDetails();
const currentUserAccountID = currentUserDetails.accountID;
const currentUserEmail = currentUserDetails.email ?? '';
const {isBetaEnabled} = usePermissions();
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();

const {iouReportID} = useReportPreviewData();
const {shouldShowPayButton} = useReportPreviewActionState();
const {startApprovedAnimation, onHoldMenuOpen} = useReportPreviewActions();

const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`);
const [expenseReportPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${iouReport?.policyID}`);
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReportID}`);
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [delegateEmail] = useOnyx(ONYXKEYS.ACCOUNT, {selector: delegateEmailSelector});
const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(iouReport?.ownerAccountID)}, [iouReport?.ownerAccountID]);
const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector});
const actionButtonData = useReportPreviewActionButtonData(iouReportID);
const {iouReport} = actionButtonData;

const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(iouReport?.reportID);
const allTransactionValues = Object.values(reportTransactions);
const transactions = allTransactionValues;
const transactions = Object.values(reportTransactions);

const [transactionViolations] = useReportTransactionViolations(transactions);

const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
const hasViolations = hasViolationsReportUtils(iouReport?.reportID, transactionViolations, currentUserAccountID, currentUserEmail, undefined, transactions);

const confirmApproval = () => {
if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
} else if (hasHeldExpensesReportUtils(transactions)) {
onHoldMenuOpen(CONST.IOU.REPORT_ACTION_TYPE.APPROVE, undefined, shouldShowPayButton);
} else {
approveMoneyRequest({
expenseReport: iouReport,
expenseReportPolicy,
currentUserAccountIDParam: currentUserAccountID,
currentUserEmailParam: currentUserEmail,
hasViolations,
isASAPSubmitBetaEnabled,
expenseReportCurrentNextStepDeprecated: iouReportNextStep,
betas,
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
ownerLogin,
full: true,
onApproved: startApprovedAnimation,
delegateEmail,
isTrackIntentUser,
});
}
};
const confirmApproval = useConfirmApproveReportAction(actionButtonData, transactions, hasViolations);

return (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useParticipantsInvoiceReport from '@hooks/useParticipantsInvoiceReport';
import usePayChatReportActions from '@hooks/usePayChatReportActions';
import usePermissions from '@hooks/usePermissions';
import usePolicy from '@hooks/usePolicy';
import useReportTransactionsCollection from '@hooks/useReportTransactionsCollection';
import useReportTransactionViolations from '@hooks/useReportTransactionViolations';

import {generateDefaultWorkspaceName} from '@libs/actions/Policy/Policy';
Expand All @@ -28,62 +26,47 @@ import {
} from '@libs/ReportUtils';

import {payInvoice, payMoneyRequest} from '@userActions/IOU/PayMoneyRequest';
import {approveMoneyRequest} from '@userActions/IOU/ReportWorkflow';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Transaction} from '@src/types/onyx';

import {delegateEmailSelector} from '@selectors/Account';
import {hasSeenTourSelector} from '@selectors/Onboarding';
import {personalDetailsLoginSelector} from '@selectors/PersonalDetails';
import React from 'react';

import {useReportPreviewActions, useReportPreviewActionState, useReportPreviewAnimationState, useReportPreviewData, useReportPreviewUIState} from './MoneyRequestReportPreviewContext';
import useConfirmApproveReportAction from './useConfirmApproveReportAction';
import useReportPreviewActionButtonData from './useReportPreviewActionButtonData';

function PayActionButton() {
const {isOffline} = useNetwork();
const {translate} = useLocalize();
const currentUserDetails = useCurrentUserPersonalDetails();
const currentUserAccountID = currentUserDetails.accountID;
const currentUserEmail = currentUserDetails.email ?? '';
const {isBetaEnabled} = usePermissions();
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
const lastWorkspaceNumber = useLastWorkspaceNumber();
const {convertToDisplayString} = useCurrencyListActions();

const {iouReportID, chatReportID, chatReport} = useReportPreviewData();
const {iouReportID, chatReportID, chatReport, transactions} = useReportPreviewData();
const {isPaidAnimationRunning, isApprovedAnimationRunning} = useReportPreviewAnimationState();
const {stopAnimation, startAnimation, startApprovedAnimation, onPaymentOptionsShow, onPaymentOptionsHide, onHoldMenuOpen} = useReportPreviewActions();
const {stopAnimation, startAnimation, onPaymentOptionsShow, onPaymentOptionsHide, onHoldMenuOpen} = useReportPreviewActions();
const {buttonMaxWidth} = useReportPreviewUIState();
const {reportPreviewAction, canIOUBePaid, onlyShowPayElsewhere, shouldShowPayButton} = useReportPreviewActionState();

const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const activePolicy = usePolicy(activePolicyID);
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`);
const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(iouReport?.ownerAccountID)}, [iouReport?.ownerAccountID]);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${iouReport?.policyID}`);
const actionButtonData = useReportPreviewActionButtonData(iouReportID);
const {iouReport, policy, userBillingGracePeriodEnds, iouReportNextStep, amountOwed, ownerBillingGracePeriodEnd} = actionButtonData;
const chatReportPolicy = usePolicy(chatReport?.policyID);
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReportID}`);
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [delegateEmail] = useOnyx(ONYXKEYS.ACCOUNT, {selector: delegateEmailSelector});
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});

const reportTransactionsCollection = useReportTransactionsCollection(iouReportID);
const transactions = Object.values(reportTransactionsCollection ?? {}).filter(
(t): t is Transaction => !!t && (isOffline || t.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE),
);

const [transactionViolations] = useReportTransactionViolations(transactions);
const isTrackIntentUser = isTrackOnboardingChoice(introSelected?.choice);

const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
const existingB2BInvoiceReport = useParticipantsInvoiceReport(activePolicyID, CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS, chatReport?.policyID);
const getChatReportActions = usePayChatReportActions(chatReport, existingB2BInvoiceReport);
const canAllowSettlement = hasUpdatedTotal(iouReport, policy);
Expand All @@ -94,32 +77,7 @@ function PayActionButton() {

const formattedAmount = getTotalAmountForIOUReportPreviewButton(iouReport, policy, reportPreviewAction, transactions, convertToDisplayString);

const confirmApproval = () => {
if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
} else if (hasHeldExpensesReportUtils(transactions)) {
onHoldMenuOpen(CONST.IOU.REPORT_ACTION_TYPE.APPROVE, undefined, shouldShowPayButton);
} else {
approveMoneyRequest({
expenseReport: iouReport,
expenseReportPolicy: policy,
currentUserAccountIDParam: currentUserAccountID,
currentUserEmailParam: currentUserEmail,
hasViolations,
isASAPSubmitBetaEnabled,
expenseReportCurrentNextStepDeprecated: iouReportNextStep,
betas,
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
ownerLogin,
full: true,
onApproved: startApprovedAnimation,
delegateEmail,
isTrackIntentUser,
});
}
};
const confirmApproval = useConfirmApproveReportAction(actionButtonData, transactions, hasViolations);

const confirmPayment = ({paymentType: type, payAsBusiness, methodID, paymentMethod}: PaymentActionParams) => {
if (!type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import useConfirmModal from '@hooks/useConfirmModal';
import useConfirmPendingRTERAndProceed from '@hooks/useConfirmPendingRTERAndProceed';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import useReportTransactionsCollection from '@hooks/useReportTransactionsCollection';
import useReportTransactionViolations from '@hooks/useReportTransactionViolations';

import {hasDynamicExternalWorkflow, isSubmitPolicy} from '@libs/PolicyUtils';
Expand All @@ -20,14 +18,12 @@ import {markPendingRTERTransactionsAsCash} from '@userActions/Transaction';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import type {Transaction} from '@src/types/onyx';

import {delegateEmailSelector} from '@selectors/Account';
import {isTrackIntentUserSelector} from '@selectors/Onboarding';
import React from 'react';

import {useReportPreviewActions, useReportPreviewAnimationState, useReportPreviewData} from './MoneyRequestReportPreviewContext';
import useReportPreviewActionButtonData from './useReportPreviewActionButtonData';

const ANCHOR_ALIGNMENT = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER,
Expand Down Expand Up @@ -57,25 +53,22 @@ function SubmitActionButtonContent() {
const {isBetaEnabled} = usePermissions();
const openReportSubmitToPopover = useOpenReportSubmitToPopover();

const {iouReportID} = useReportPreviewData();
const {iouReportID, transactions} = useReportPreviewData();
const {isSubmittingAnimationRunning} = useReportPreviewAnimationState();
const {stopAnimation, startSubmittingAnimation} = useReportPreviewActions();

const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${iouReport?.policyID}`);
const [submitterLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(iouReport?.ownerAccountID)}, [iouReport?.ownerAccountID]);
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReportID}`);
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const {
iouReport,
policy,
ownerLogin: submitterLogin,
userBillingGracePeriodEnds,
iouReportNextStep,
amountOwed,
ownerBillingGracePeriodEnd,
delegateEmail,
} = useReportPreviewActionButtonData(iouReportID);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`);
const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector});
const [delegateEmail] = useOnyx(ONYXKEYS.ACCOUNT, {selector: delegateEmailSelector});
const {isOffline} = useNetwork();
const reportTransactionsCollection = useReportTransactionsCollection(iouReportID);
const transactions = Object.values(reportTransactionsCollection ?? {}).filter(
(t): t is Transaction => !!t && (isOffline || t.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE),
);

const [transactionViolations] = useReportTransactionViolations(transactions);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Returns the confirm-approval handler shared by the report-preview Approve and Pay buttons,
* handling delegate-access restrictions, held expenses, and the approveMoneyRequest call.
*/
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';
Comment thread
OlimpiaZurek marked this conversation as resolved.

import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';

import {hasHeldExpensesFromTransactions as hasHeldExpensesReportUtils} from '@libs/ReportUtils';

import {approveMoneyRequest} from '@userActions/IOU/ReportWorkflow';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Transaction} from '@src/types/onyx';

import {isTrackIntentUserSelector} from '@selectors/Onboarding';

import type useReportPreviewActionButtonData from './useReportPreviewActionButtonData';

import {useReportPreviewActions, useReportPreviewActionState} from './MoneyRequestReportPreviewContext';

function useConfirmApproveReportAction(actionButtonData: ReturnType<typeof useReportPreviewActionButtonData>, transactions: Transaction[], hasViolations: boolean) {
const currentUserDetails = useCurrentUserPersonalDetails();
const {isBetaEnabled} = usePermissions();
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
const {shouldShowPayButton} = useReportPreviewActionState();
const {startApprovedAnimation, onHoldMenuOpen} = useReportPreviewActions();
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector});

const {iouReport, policy, ownerLogin, userBillingGracePeriodEnds, iouReportNextStep, amountOwed, ownerBillingGracePeriodEnd, delegateEmail} = actionButtonData;

return () => {
if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
} else if (hasHeldExpensesReportUtils(transactions)) {
onHoldMenuOpen(CONST.IOU.REPORT_ACTION_TYPE.APPROVE, undefined, shouldShowPayButton);
} else {
approveMoneyRequest({
expenseReport: iouReport,
expenseReportPolicy: policy,
currentUserAccountIDParam: currentUserDetails.accountID,
currentUserEmailParam: currentUserDetails.email ?? '',
hasViolations,
isASAPSubmitBetaEnabled: isBetaEnabled(CONST.BETAS.ASAP_SUBMIT),
expenseReportCurrentNextStepDeprecated: iouReportNextStep,
betas,
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
ownerLogin,
full: true,
onApproved: startApprovedAnimation,
delegateEmail,
isTrackIntentUser,
});
}
};
}

export default useConfirmApproveReportAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Subscribes to the Onyx data shared by the money-request report-preview action buttons
* (Submit / Approve / Pay) and returns it as a single object.
*/
import useOnyx from '@hooks/useOnyx';
Comment thread
OlimpiaZurek marked this conversation as resolved.

import ONYXKEYS from '@src/ONYXKEYS';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';

import {delegateEmailSelector} from '@selectors/Account';

function useReportPreviewActionButtonData(iouReportID: string | undefined) {
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`);

@linhvovan29546 linhvovan29546 Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can use const {iouReport, policy} = useReportPreviewData(); instead of useOnyx as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I checked, but these two aren't the same as the context values:

  • The context policy is the chat's policy (report?.policyID), but here we need the previewed report's own policy (iouReport?.policyID)
  • The context iouReport is a trimmed getStableReportSelector version, not the full report the approve/pay/submit actions need.

These are just single-record subscriptions (not a whole collection like transactions), so I'd keep them as useOnyx here

const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${iouReport?.policyID}`);
const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(iouReport?.ownerAccountID)});
Comment thread
OlimpiaZurek marked this conversation as resolved.
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReportID}`);
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const [delegateEmail] = useOnyx(ONYXKEYS.ACCOUNT, {selector: delegateEmailSelector});

return {
Comment thread
OlimpiaZurek marked this conversation as resolved.
iouReport,
policy,
ownerLogin,
userBillingGracePeriodEnds,
iouReportNextStep,
amountOwed,
ownerBillingGracePeriodEnd,
delegateEmail,
};
}

export default useReportPreviewActionButtonData;
Loading
Loading