diff --git a/src/CONST.ts b/src/CONST.ts index 811dcdfe8b31..f66a6d811bb1 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1226,6 +1226,7 @@ const CONST = { ADD_EXPENSE: 'addExpense', REOPEN: 'reopen', MOVE_EXPENSE: 'moveExpense', + PAY: 'pay', }, PRIMARY_ACTIONS: { SUBMIT: 'submit', diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index dbd87686bfd7..5364c075f67e 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -37,6 +37,7 @@ function ButtonWithDropdownMenu({ onPress, options, onOptionSelected, + onSubItemSelected, onOptionsMenuShow, onOptionsMenuHide, enterKeyEventListenerPriority = 0, @@ -218,7 +219,10 @@ function ButtonWithDropdownMenu({ onOptionsMenuHide?.(); }} onModalShow={onOptionsMenuShow} - onItemSelected={() => setIsMenuVisible(false)} + onItemSelected={(selectedSubitem, index, event) => { + onSubItemSelected?.(selectedSubitem, index, event); + setIsMenuVisible(false); + }} anchorPosition={shouldUseStyleUtilityForAnchorPosition ? styles.popoverButtonDropdownMenuOffset(windowWidth) : popoverAnchorPosition} shouldShowSelectedItemCheck={shouldShowSelectedItemCheck} // eslint-disable-next-line react-compiler/react-compiler diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index 3e9dd2e7b695..39dbe870e110 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -132,6 +132,9 @@ type ButtonWithDropdownMenuProps = { /** The second line text displays under the first line */ secondLineText?: string; + /** Callback to execute when a dropdown submenu option is selected */ + onSubItemSelected?: (selectedItem: PopoverMenuItem, index: number, event?: GestureResponderEvent | KeyboardEvent) => void; + /** Icon for main button */ icon?: IconAsset; }; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index cbd8728f420b..7db4a860f22d 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -9,6 +9,7 @@ import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; import usePaymentAnimations from '@hooks/usePaymentAnimations'; +import usePaymentOptions from '@hooks/usePaymentOptions'; import usePermissions from '@hooks/usePermissions'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -20,9 +21,11 @@ import {deleteAppReport, downloadReportPDF, exportReportToCSV, exportReportToPDF import {getThreadReportIDsForTransactions, getTotalAmountForIOUReportPreviewButton} from '@libs/MoneyRequestReportUtils'; import Navigation from '@libs/Navigation/Navigation'; import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils'; +import {isSecondaryActionAPaymentOption, selectPaymentType} from '@libs/PaymentUtils'; +import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils'; import {getValidConnectedIntegration} from '@libs/PolicyUtils'; import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; -import {getReportPrimaryAction} from '@libs/ReportPrimaryActionUtils'; +import {getAllExpensesToHoldIfApplicable, getReportPrimaryAction} from '@libs/ReportPrimaryActionUtils'; import {getSecondaryReportActions} from '@libs/ReportSecondaryActionUtils'; import { changeMoneyRequestHoldStatus, @@ -95,6 +98,7 @@ import Header from './Header'; import HeaderWithBackButton from './HeaderWithBackButton'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import KYCWall from './KYCWall'; import type {PaymentMethod} from './KYCWall/types'; import LoadingBar from './LoadingBar'; import Modal from './Modal'; @@ -103,6 +107,7 @@ import MoneyReportHeaderStatusBarSkeleton from './MoneyReportHeaderStatusBarSkel import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import {useMoneyRequestReportContext} from './MoneyRequestReportView/MoneyRequestReportContext'; +import type {PopoverMenuItem} from './PopoverMenu'; import type {ActionHandledType} from './ProcessMoneyReportHoldMenu'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import AnimatedSettlementButton from './SettlementButton/AnimatedSettlementButton'; @@ -151,6 +156,7 @@ function MoneyReportHeader({ const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID}`, {canBeMissing: true}); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [nextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${moneyRequestReport?.reportID}`, {canBeMissing: true}); + const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.validated, canBeMissing: true}); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, {canBeMissing: true}); const [reportPDFFilename] = useOnyx(`${ONYXKEYS.COLLECTION.NVP_EXPENSIFY_REPORT_PDF_FILENAME}${moneyRequestReport?.reportID}`, {canBeMissing: true}) ?? null; const [download] = useOnyx(`${ONYXKEYS.COLLECTION.DOWNLOAD}${reportPDFFilename}`, {canBeMissing: true}); @@ -316,7 +322,7 @@ function MoneyReportHeader({ if (isDelegateAccessRestricted) { setIsNoDelegateAccessMenuVisible(true); } else if (isAnyTransactionOnHold) { - setIsHoldMenuVisible(true); + InteractionManager.runAfterInteractions(() => setIsHoldMenuVisible(true)); } else if (isInvoiceReport) { startAnimation(); payInvoice(type, chatReport, moneyRequestReport, payAsBusiness, methodID, paymentMethod); @@ -445,6 +451,27 @@ function MoneyReportHeader({ } }, [connectedIntegration, exportModalStatus, moneyRequestReport?.reportID]); + const getAmount = (actionType: ValueOf) => ({ + formattedAmount: getTotalAmountForIOUReportPreviewButton(moneyRequestReport, policy, actionType), + }); + + const {formattedAmount: payAmount} = getAmount(CONST.REPORT.PRIMARY_ACTIONS.PAY); + const {formattedAmount: totalAmount} = hasOnlyHeldExpenses ? getAmount(CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW) : getAmount(CONST.REPORT.PRIMARY_ACTIONS.PAY); + + const paymentButtonOptions = usePaymentOptions({ + addBankAccountRoute: bankAccountRoute, + currency: moneyRequestReport?.currency, + iouReport: moneyRequestReport, + chatReportID: chatReport?.reportID, + formattedAmount: totalAmount, + policyID: moneyRequestReport?.policyID, + onPress: confirmPayment, + shouldHidePaymentOptions: !shouldShowPayButton, + shouldShowApproveButton, + shouldDisableApproveButton, + onlyShowPayElsewhere, + }); + const addExpenseDropdownOptions: Array>> = useMemo( () => [ { @@ -470,10 +497,6 @@ function MoneyReportHeader({ [moneyRequestReport?.reportID, translate], ); - const getAmount = (actionType: ValueOf) => ({ - formattedAmount: getTotalAmountForIOUReportPreviewButton(moneyRequestReport, policy, actionType), - }); - const primaryActionsImplementation = { [CONST.REPORT.PRIMARY_ACTIONS.SUBMIT]: (