diff --git a/assets/images/checkmark-circle.svg b/assets/images/checkmark-circle.svg new file mode 100644 index 000000000000..3497548bc1bc --- /dev/null +++ b/assets/images/checkmark-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 7e81f5ba3814..4dfbd891d9f6 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -38,6 +38,7 @@ import ChatBubbleUnread from '@assets/images/chatbubble-unread.svg'; import ChatBubble from '@assets/images/chatbubble.svg'; import ChatBubbles from '@assets/images/chatbubbles.svg'; import CheckCircle from '@assets/images/check-circle.svg'; +import CheckmarkCircle from '@assets/images/checkmark-circle.svg'; import Checkmark from '@assets/images/checkmark.svg'; import Close from '@assets/images/close.svg'; import ClosedSign from '@assets/images/closed-sign.svg'; @@ -350,4 +351,5 @@ export { DocumentPlus, Clear, CheckCircle, + CheckmarkCircle, }; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index b6f378763659..0fcc12ec50e0 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -360,7 +360,7 @@ function MenuItem( const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const combinedStyle = [style, styles.popoverMenuItem]; + const combinedStyle = [styles.popoverMenuItem, style]; const {shouldUseNarrowLayout} = useResponsiveLayout(); const {isExecuting, singleExecution, waitForNavigate} = useContext(MenuItemGroupContext) ?? {}; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 04fbbe616458..ec52a6158ad7 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -104,7 +104,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); - const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation; + const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation; const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); @@ -120,14 +120,16 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isMoreContentShown = shouldShowNextStep || hasScanningReceipt || (shouldShowAnyButton && shouldUseNarrowLayout); const confirmPayment = (type?: PaymentMethodType | undefined) => { - if (!type) { + if (!type || !chatReport) { return; } setPaymentType(type); setRequestType('pay'); if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { setIsHoldMenuVisible(true); - } else if (chatReport) { + } else if (ReportUtils.isInvoiceReport(moneyRequestReport)) { + IOU.payInvoice(type, chatReport, moneyRequestReport); + } else { IOU.payMoneyRequest(type, chatReport, moneyRequestReport, true); } }; diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index e525d07fa1d3..0bdf83bf4f27 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -1,3 +1,4 @@ +import lodashIsEqual from 'lodash/isEqual'; import type {RefObject} from 'react'; import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; @@ -5,6 +6,7 @@ import type {ModalProps} from 'react-native-modal'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import type {AnchorPosition} from '@src/styles'; @@ -26,6 +28,9 @@ type PopoverMenuItem = MenuItemProps & { /** Sub menu items to be rendered after a menu item is selected */ subMenuItems?: PopoverMenuItem[]; + /** Back button text to be shown if sub menu items are opened */ + backButtonText?: string; + /** Determines whether the menu item is disabled or not */ disabled?: boolean; }; @@ -98,6 +103,7 @@ function PopoverMenu({ shouldEnableNewFocusManagement, }: PopoverMenuProps) { const styles = useThemeStyles(); + const theme = useTheme(); const {isSmallScreenWidth} = useResponsiveLayout(); const selectedItemIndex = useRef(null); @@ -111,6 +117,7 @@ function PopoverMenu({ if (selectedItem?.subMenuItems) { setCurrentMenuItems([...selectedItem.subMenuItems]); setEnteredSubMenuIndexes([...enteredSubMenuIndexes, index]); + setFocusedIndex(-1); } else { selectedItemIndex.current = index; onItemSelected(selectedItem, index); @@ -132,17 +139,22 @@ function PopoverMenu({ const renderBackButtonItem = () => { const previousMenuItems = getPreviousSubMenu(); const previouslySelectedItem = previousMenuItems[enteredSubMenuIndexes[enteredSubMenuIndexes.length - 1]]; + const hasBackButtonText = !!previouslySelectedItem.backButtonText; return ( { setCurrentMenuItems(previousMenuItems); + setFocusedIndex(-1); enteredSubMenuIndexes.splice(-1); }} /> @@ -199,7 +211,7 @@ function PopoverMenu({ shouldEnableNewFocusManagement={shouldEnableNewFocusManagement} > - {!!headerText && {headerText}} + {!!headerText && enteredSubMenuIndexes.length === 0 && {headerText}} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} {currentMenuItems.map((item, menuIndex) => ( + !lodashIsEqual(prevProps.menuItems, nextProps.menuItems) && + prevProps.isVisible === nextProps.isVisible && + lodashIsEqual(prevProps.anchorPosition, nextProps.anchorPosition) && + prevProps.anchorRef === nextProps.anchorRef && + prevProps.headerText === nextProps.headerText && + prevProps.fromSidebarMediumScreen === nextProps.fromSidebarMediumScreen && + lodashIsEqual(prevProps.anchorAlignment, nextProps.anchorAlignment) && + prevProps.animationIn === nextProps.animationIn && + prevProps.animationOut === nextProps.animationOut && + prevProps.animationInTiming === nextProps.animationInTiming && + prevProps.disableAnimation === nextProps.disableAnimation && + prevProps.withoutOverlay === nextProps.withoutOverlay && + prevProps.shouldSetModalVisibility === nextProps.shouldSetModalVisibility, +); export type {PopoverMenuItem, PopoverMenuProps}; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index be3b104018db..4677563d204f 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -227,7 +227,7 @@ function ReportPreview({ const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); - const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(iouReport) && (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage; + const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage; const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID); const shouldShowRBR = !iouSettled && hasErrors; @@ -280,6 +280,17 @@ function ReportPreview({ }; }, [formattedMerchant, formattedDescription, moneyRequestComment, translate, numberOfRequests, numberOfScanningReceipts, numberOfPendingRequests]); + const confirmPayment = (paymentMethodType?: PaymentMethodType) => { + if (!paymentMethodType || !chatReport || !iouReport) { + return; + } + if (ReportUtils.isInvoiceReport(iouReport)) { + IOU.payInvoice(paymentMethodType, chatReport, iouReport); + } else { + IOU.payMoneyRequest(paymentMethodType, chatReport, iouReport); + } + }; + return ( {shouldShowSettlementButton && ( chatReport && iouReport && paymentType && IOU.payMoneyRequest(paymentType, chatReport, iouReport)} + onPress={confirmPayment} enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} addBankAccountRoute={bankAccountRoute} shouldHidePaymentOptions={!shouldShowPayButton} diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index f56c4dd1a863..b6e2a753c829 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -18,6 +18,7 @@ import type {LastPaymentMethod, Policy, Report} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type AnchorAlignment from '@src/types/utils/AnchorAlignment'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import type {PaymentType} from './ButtonWithDropdownMenu/types'; import * as Expensicons from './Icon/Expensicons'; @@ -149,9 +150,10 @@ function SettlementButton({ const session = useSession(); const chatReport = ReportUtils.getReport(chatReportID); + const isInvoiceReport = (!isEmptyObject(iouReport) && ReportUtils.isInvoiceReport(iouReport)) || false; const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport); const shouldShowPaywithExpensifyOption = !isPaidGroupPolicy || (!shouldHidePaymentOptions && ReportUtils.isPayer(session, iouReport as OnyxEntry)); - const shouldShowPayElsewhereOption = !isPaidGroupPolicy || policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL; + const shouldShowPayElsewhereOption = (!isPaidGroupPolicy || policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL) && !isInvoiceReport; const paymentButtonOptions = useMemo(() => { const buttonOptions = []; const isExpenseReport = ReportUtils.isExpenseReport(iouReport); @@ -178,7 +180,7 @@ function SettlementButton({ value: CONST.IOU.REPORT_ACTION_TYPE.APPROVE, disabled: !!shouldDisableApproveButton, }; - const canUseWallet = !isExpenseReport && currency === CONST.CURRENCY.USD; + const canUseWallet = !isExpenseReport && !isInvoiceReport && currency === CONST.CURRENCY.USD; // Only show the Approve button if the user cannot pay the expense if (shouldHidePaymentOptions && shouldShowApproveButton) { @@ -199,6 +201,23 @@ function SettlementButton({ buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]); } + if (isInvoiceReport) { + buttonOptions.push({ + text: translate('iou.settlePersonal', {formattedAmount}), + icon: Expensicons.User, + value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + backButtonText: translate('iou.individual'), + subMenuItems: [ + { + text: translate('iou.payElsewhere', {formattedAmount: ''}), + icon: Expensicons.Cash, + value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.ELSEWHERE), + }, + ], + }); + } + if (shouldShowApproveButton) { buttonOptions.push(approveButtonOption); } @@ -211,6 +230,7 @@ function SettlementButton({ // We don't want to reorder the options when the preferred payment method changes while the button is still visible // eslint-disable-next-line react-hooks/exhaustive-deps }, [currency, formattedAmount, iouReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton, shouldDisableApproveButton]); + const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => { if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) { triggerKYCFlow(event, iouPaymentType); @@ -252,6 +272,10 @@ function SettlementButton({ success buttonRef={buttonRef} + shouldAlwaysShowDropdownMenu={isInvoiceReport} + customText={isInvoiceReport ? translate('iou.settlePayment', {formattedAmount}) : undefined} + menuHeaderText={isInvoiceReport ? translate('workspace.invoices.paymentMethods.chooseInvoiceMethod') : undefined} + isSplitButton={!isInvoiceReport} isDisabled={isDisabled} isLoading={isLoading} onPress={(event, iouPaymentType) => selectPaymentType(event, iouPaymentType, triggerKYCFlow)} diff --git a/src/languages/en.ts b/src/languages/en.ts index ad63a786d3d9..fbdaaeab9903 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -677,7 +677,11 @@ export default { deleteConfirmation: 'Are you sure that you want to delete this expense?', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', + individual: 'Individual', settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} with Expensify` : `Pay with Expensify`), + settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as an individual` : `Pay as an individual`), + settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount}`, + settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as a business` : `Pay as a business`), payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} elsewhere` : `Pay elsewhere`), nextStep: 'Next Steps', finished: 'Finished', @@ -2497,6 +2501,14 @@ export default { viewUnpaidInvoices: 'View unpaid invoices', sendInvoice: 'Send invoice', sendFrom: 'Send from', + paymentMethods: { + personal: 'Personal', + business: 'Business', + chooseInvoiceMethod: 'Choose a payment method below:', + addBankAccount: 'Add bank account', + payingAsIndividual: 'Paying as an individual', + payingAsBusiness: 'Paying as a business', + }, }, travel: { unlockConciergeBookingTravel: 'Unlock Concierge travel booking', diff --git a/src/languages/es.ts b/src/languages/es.ts index 92c1e3f30a1c..033291d5bba2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -670,7 +670,11 @@ export default { deleteConfirmation: '¿Estás seguro de que quieres eliminar esta solicitud?', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', + individual: 'Individual', settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} con Expensify` : `Pagar con Expensify`), + settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pago ${formattedAmount} como individuo` : `Pago individual`), + settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount}`, + settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} como negocio` : `Pagar como empresa`), payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} de otra forma` : `Pagar de otra forma`), nextStep: 'Pasos Siguientes', finished: 'Finalizado', @@ -2536,6 +2540,14 @@ export default { viewUnpaidInvoices: 'Ver facturas emitidas pendientes', sendInvoice: 'Enviar factura', sendFrom: 'Enviar desde', + paymentMethods: { + personal: 'Personal', + business: 'Empresas', + chooseInvoiceMethod: 'Elija un método de pago:', + addBankAccount: 'Añadir cuenta bancaria', + payingAsIndividual: 'Pago individual', + payingAsBusiness: 'Pagar como una empresa', + }, }, travel: { unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', diff --git a/src/libs/API/parameters/PayInvoiceParams.ts b/src/libs/API/parameters/PayInvoiceParams.ts new file mode 100644 index 000000000000..4c6633749adb --- /dev/null +++ b/src/libs/API/parameters/PayInvoiceParams.ts @@ -0,0 +1,9 @@ +import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; + +type PayInvoiceParams = { + reportID: string; + reportActionID: string; + paymentMethodType: PaymentMethodType; +}; + +export default PayInvoiceParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 68097a201120..6fa58f44bd89 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -221,4 +221,5 @@ export type {default as LeavePolicyParams} from './LeavePolicyParams'; export type {default as OpenPolicyAccountingPageParams} from './OpenPolicyAccountingPageParams'; export type {default as SearchParams} from './Search'; export type {default as SendInvoiceParams} from './SendInvoiceParams'; +export type {default as PayInvoiceParams} from './PayInvoiceParams'; export type {default as MarkAsCashParams} from './MarkAsCashParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 897b300f0471..9b77a9b74aa4 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -216,6 +216,7 @@ const WRITE_COMMANDS = { LEAVE_POLICY: 'LeavePolicy', ACCEPT_SPOTNANA_TERMS: 'AcceptSpotnanaTerms', SEND_INVOICE: 'SendInvoice', + PAY_INVOICE: 'PayInvoice', MARK_AS_CASH: 'MarkAsCash', } as const; @@ -433,6 +434,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.LEAVE_POLICY]: Parameters.LeavePolicyParams; [WRITE_COMMANDS.ACCEPT_SPOTNANA_TERMS]: EmptyObject; [WRITE_COMMANDS.SEND_INVOICE]: Parameters.SendInvoiceParams; + [WRITE_COMMANDS.PAY_INVOICE]: Parameters.PayInvoiceParams; [WRITE_COMMANDS.MARK_AS_CASH]: Parameters.MarkAsCashParams; }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 848376fb4b6d..17cde1111e48 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -901,7 +901,7 @@ function isPolicyExpenseChat(report: OnyxEntry | Participant | EmptyObje return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false); } -function isInvoiceRoom(report: OnyxEntry): boolean { +function isInvoiceRoom(report: OnyxEntry | EmptyObject): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 128ee5b85b7b..a98a9c315173 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -14,6 +14,7 @@ import type { DeleteMoneyRequestParams, DetachReceiptParams, EditMoneyRequestParams, + PayInvoiceParams, PayMoneyRequestParams, ReplaceReceiptParams, RequestMoneyParams, @@ -5850,6 +5851,8 @@ function getPayMoneyRequestParams( paymentMethodType: PaymentMethodType, full: boolean, ): PayMoneyRequestData { + const isInvoiceReport = ReportUtils.isInvoiceReport(iouReport); + let total = (iouReport.total ?? 0) - (iouReport.nonReimbursableTotal ?? 0); if (ReportUtils.hasHeldExpenses(iouReport.reportID) && !full && !!iouReport.unheldTotal) { total = iouReport.unheldTotal; @@ -5874,9 +5877,12 @@ function getPayMoneyRequestParams( if (reportPreviewAction) { optimisticReportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction as ReportPreviewAction, true); } - - const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`] ?? null; - const optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithExpensify: paymentMethodType === CONST.IOU.PAYMENT_TYPE.VBBA}); + let currentNextStep = null; + let optimisticNextStep = null; + if (!isInvoiceReport) { + currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`]; + optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithExpensify: paymentMethodType === CONST.IOU.PAYMENT_TYPE.VBBA}); + } const optimisticData: OnyxUpdate[] = [ { @@ -6064,6 +6070,7 @@ function canApproveIOU(iouReport: OnyxEntry | EmptyObject, cha function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) { const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); const isChatReportArchived = ReportUtils.isArchivedRoom(chatReport); + const iouSettled = ReportUtils.isSettled(iouReport?.reportID); if (isEmptyObject(iouReport)) { return false; @@ -6074,10 +6081,12 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat } if (ReportUtils.isInvoiceReport(iouReport)) { + if (iouSettled) { + return false; + } if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { return chatReport?.invoiceReceiver?.accountID === userAccountID; } - return PolicyUtils.getPolicy(chatReport?.invoiceReceiver?.policyID).role === CONST.POLICY.ROLE.ADMIN; } @@ -6090,7 +6099,6 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat ); const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport); - const iouSettled = ReportUtils.isSettled(iouReport?.reportID); const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); @@ -6487,6 +6495,24 @@ function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.R Navigation.dismissModalWithReport(chatReport); } +function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes.Report, invoiceReport: OnyxTypes.Report) { + const recipient = {accountID: invoiceReport.ownerAccountID}; + const { + optimisticData, + successData, + failureData, + params: {reportActionID}, + } = getPayMoneyRequestParams(chatReport, invoiceReport, recipient, paymentMethodType, true); + + const params: PayInvoiceParams = { + reportID: invoiceReport.reportID, + reportActionID, + paymentMethodType, + }; + + API.write(WRITE_COMMANDS.PAY_INVOICE, params, {optimisticData, successData, failureData}); +} + function detachReceipt(transactionID: string) { const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const newTransaction = transaction ? {...transaction, filename: '', receipt: {}} : null; @@ -6871,6 +6897,7 @@ export { initMoneyRequest, navigateToStartStepIfScanFileCannotBeRead, payMoneyRequest, + payInvoice, putOnHold, replaceReceipt, requestMoney, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 4b248bf14131..ea7a970d5494 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -91,6 +91,9 @@ const getDraftMessage = (drafts: OnyxCollection, }; type ReportActionItemOnyxProps = { + /** Get modal status */ + modal: OnyxEntry; + /** IOU report for this action, if any */ iouReport: OnyxEntry; @@ -165,6 +168,7 @@ const isIOUReport = (actionObj: OnyxEntry): actionObj is actionObj?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; function ReportActionItem({ + modal, action, report, transactionThreadReport, @@ -946,6 +950,7 @@ function ReportActionItem({ accessible > @@ -1051,11 +1056,15 @@ export default withOnyx({ key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action as OnyxTypes.OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? 0}`, selector: (transaction: OnyxEntry) => transaction?.errorFields?.route ?? null, }, + modal: { + key: ONYXKEYS.MODAL, + }, })( memo(ReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; const nextParentReportAction = nextProps.parentReportAction; return ( + prevProps.modal?.willAlertModalBecomeVisible === nextProps.modal?.willAlertModalBecomeVisible && prevProps.displayAsGroup === nextProps.displayAsGroup && prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && @@ -1086,7 +1095,8 @@ export default withOnyx({ lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && lodashIsEqual(prevProps.transaction, nextProps.transaction) && lodashIsEqual(prevProps.linkedTransactionRouteError, nextProps.linkedTransactionRouteError) && - lodashIsEqual(prevParentReportAction, nextParentReportAction) + lodashIsEqual(prevParentReportAction, nextParentReportAction) && + prevProps.modal?.willAlertModalBecomeVisible === nextProps.modal?.willAlertModalBecomeVisible ); }), ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 9980a96f64ae..1d8e14733d02 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1696,7 +1696,7 @@ const styles = (theme: ThemeColors) => createMenuHeaderText: { fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, fontSize: variables.fontSizeLabel, - color: theme.heading, + color: theme.textSupporting, }, popoverMenuItem: {