diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 336674915180..6770e00840a0 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -125,10 +125,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) const parentReportAction = useParentReportAction(report); - const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const personalDetails = usePersonalDetails(); - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); - const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [betas] = useOnyx(ONYXKEYS.BETAS); @@ -568,11 +565,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) shouldHideThreadDividerLine linkedReportActionID={linkedReportActionID} personalDetails={personalDetails} - userBillingFundID={userBillingFundID} - isReportArchived={isReportArchived} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} - reportNameValuePairsOrigin={reportNameValuePairs?.origin} - reportNameValuePairsOriginalID={reportNameValuePairs?.originalID} + isHarvestCreatedExpenseReport={shouldShowHarvestCreatedAction} /> ); }, @@ -586,11 +579,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) firstVisibleReportActionID, linkedReportActionID, personalDetails, - userBillingFundID, - isTryNewDotNVPDismissed, - isReportArchived, - reportNameValuePairs?.origin, - reportNameValuePairs?.originalID, + shouldShowHarvestCreatedAction, ], ); diff --git a/src/components/ReportActionItem/CreateHarvestReportAction.tsx b/src/components/ReportActionItem/CreateHarvestReportAction.tsx index 010590220dee..35714fde0ac4 100644 --- a/src/components/ReportActionItem/CreateHarvestReportAction.tsx +++ b/src/components/ReportActionItem/CreateHarvestReportAction.tsx @@ -8,15 +8,16 @@ import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBa import ONYXKEYS from '@src/ONYXKEYS'; type CreateHarvestReportActionProps = { - /** The original ID of the report */ - reportNameValuePairsOriginalID: string | undefined; + /** ID of the chat report the harvest "Created" action belongs to */ + reportID: string | undefined; }; -function CreateHarvestReportAction({reportNameValuePairsOriginalID}: CreateHarvestReportActionProps) { +function CreateHarvestReportAction({reportID}: CreateHarvestReportActionProps) { const {translate} = useLocalize(); - const [harvestReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportNameValuePairsOriginalID}`); + const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`); + const [harvestReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportNameValuePairs?.originalID}`); const harvestReportName = getReportName(harvestReport); - const htmlContent = `${getHarvestCreatedExpenseReportMessage(reportNameValuePairsOriginalID, harvestReportName, translate)}`; + const htmlContent = `${getHarvestCreatedExpenseReportMessage(reportNameValuePairs?.originalID, harvestReportName, translate)}`; return ( diff --git a/src/components/Search/SearchList/ListItem/ChatListItem.tsx b/src/components/Search/SearchList/ListItem/ChatListItem.tsx index e0413ccfd04a..8c4bcf224c6a 100644 --- a/src/components/Search/SearchList/ListItem/ChatListItem.tsx +++ b/src/components/Search/SearchList/ListItem/ChatListItem.tsx @@ -30,7 +30,6 @@ function ChatListItem({ const reportActionItem = item as unknown as ReportActionListItemType; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportActionItem?.reportID}`); const personalDetails = usePersonalDetails(); - const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const styles = useThemeStyles(); const theme = useTheme(); const animatedHighlightStyle = useAnimatedHighlightStyle({ @@ -86,7 +85,6 @@ function ChatListItem({ shouldDisplayContextMenu={false} shouldShowBorder personalDetails={personalDetails} - userBillingFundID={userBillingFundID} /> ); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index f01b1bf73865..c06d88347d4b 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -774,7 +774,7 @@ function getLastMessageTextForReport({ } else if (isReimbursementQueuedAction(lastReportAction)) { lastMessageTextFromReport = getReimbursementQueuedActionMessage({reportAction: lastReportAction, translate, formatPhoneNumber: formatPhoneNumberPhoneUtils, report}); } else if (isReimbursementDeQueuedOrCanceledAction(lastReportAction)) { - lastMessageTextFromReport = getReimbursementDeQueuedOrCanceledActionMessage(translate, lastReportAction, report); + lastMessageTextFromReport = getReimbursementDeQueuedOrCanceledActionMessage(translate, lastReportAction, report?.ownerAccountID); } else if (isDeletedParentAction(lastReportAction) && reportUtilsIsChatReport(report)) { lastMessageTextFromReport = getDeletedParentActionMessageForChatReport(lastReportAction); } else if (isPendingRemove(lastReportAction) && report?.reportID && isThreadParentMessage(lastReportAction, report.reportID)) { @@ -782,7 +782,7 @@ function getLastMessageTextForReport({ } else if (isActionOfType(lastReportAction, CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED)) { lastMessageTextFromReport = getMarkedReimbursedMessage(translate, lastReportAction); } else if (isActionOfType(lastReportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSED)) { - lastMessageTextFromReport = getReimbursedMessage(translate, lastReportAction, report); + lastMessageTextFromReport = getReimbursedMessage(translate, lastReportAction, report?.ownerAccountID); } else if (isReportMessageAttachment({text: report?.lastMessageText ?? '', html: report?.lastMessageHtml, type: ''})) { lastMessageTextFromReport = `[${translate('common.attachment')}]`; } else if (isModifiedExpenseAction(lastReportAction)) { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index a0bb12e50a64..3df89dc42754 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -463,7 +463,7 @@ function getMarkedReimbursedMessage(translate: LocalizedTranslate, reportAction: return translate('iou.paidElsewhere', {comment: originalMessage?.message?.trim()}); } -function getReimbursedMessage(translate: LocalizedTranslate, reportAction: OnyxInputOrEntry, report: OnyxEntry, currentUserAccountID?: number): string { +function getReimbursedMessage(translate: LocalizedTranslate, reportAction: OnyxInputOrEntry, reportOwnerAccountID: number | undefined, currentUserAccountID?: number): string { const effectiveCurrentUserAccountID = currentUserAccountID ?? deprecatedCurrentUserAccountID ?? CONST.DEFAULT_NUMBER_ID; const originalMessage = getOriginalMessage(reportAction) as OriginalMessageReimbursed | undefined; @@ -492,7 +492,7 @@ function getReimbursedMessage(translate: LocalizedTranslate, reportAction: OnyxI const {debitBankAccountLast4, creditBankAccountLast4, expectedDate, isInvoiceOrBill, isSubmitterAddingBankAccount, stripePaymentType} = originalMessage; // Resolve submitter from report owner - const submitterAccountID = report?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; + const submitterAccountID = reportOwnerAccountID ?? CONST.DEFAULT_NUMBER_ID; const submitterLogin = getPersonalDetailsByIDs({accountIDs: [submitterAccountID], currentUserAccountID: effectiveCurrentUserAccountID}).at(0)?.login ?? ''; const isCurrentUser = submitterAccountID === effectiveCurrentUserAccountID; diff --git a/src/libs/ReportNameUtils.ts b/src/libs/ReportNameUtils.ts index 646d7a662e08..390612711f96 100644 --- a/src/libs/ReportNameUtils.ts +++ b/src/libs/ReportNameUtils.ts @@ -478,7 +478,7 @@ function computeReportNameBasedOnReportAction( } if (isReimbursementDeQueuedOrCanceledAction(parentReportAction)) { - return getReimbursementDeQueuedOrCanceledActionMessage(translate, parentReportAction, parentReport); + return getReimbursementDeQueuedOrCanceledActionMessage(translate, parentReportAction, parentReport?.ownerAccountID); } if (isRejectedAction(parentReportAction)) { return translate('iou.rejectedThisReport'); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e8f991f82665..ed930f2c85e4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3991,7 +3991,7 @@ function getReimbursementQueuedActionMessage({ function getReimbursementDeQueuedOrCanceledActionMessage( translate: LocalizedTranslate, reportAction: OnyxEntry>, - report: OnyxEntry, + reportOwnerAccountID: number | undefined, ): string { const originalMessage = getOriginalMessage(reportAction); const amount = originalMessage?.amount; @@ -4000,7 +4000,7 @@ function getReimbursementDeQueuedOrCanceledActionMessage( if (originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN || originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.USER) { return translate('iou.adminCanceledRequest'); } - const submitterDisplayName = getDisplayNameForParticipant({accountID: report?.ownerAccountID, shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils}) ?? ''; + const submitterDisplayName = getDisplayNameForParticipant({accountID: reportOwnerAccountID, shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils}) ?? ''; return translate('iou.canceledRequest', formattedAmount, submitterDisplayName); } diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index cc68738eef12..40c29b3a6a5b 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -49,9 +49,6 @@ function DebugReportActionCreatePage({ const [session] = useOnyx(ONYXKEYS.SESSION); const [personalDetailsList] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [draftReportAction, setDraftReportAction] = useState(() => getInitialReportAction(reportID, session, personalDetailsList)); - const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); - const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; const [error, setError] = useState(); @@ -119,8 +116,6 @@ function DebugReportActionCreatePage({ isFirstVisibleReportAction={false} shouldDisplayContextMenu={false} personalDetails={personalDetailsList} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ) : ( {translate('debug.nothingToPreview')} diff --git a/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx b/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx index e93df5acead1..f9b3213f477c 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx @@ -17,10 +17,7 @@ type DebugReportActionPreviewProps = { function DebugReportActionPreview({reportAction, reportID}: DebugReportActionPreviewProps) { const personalDetails = usePersonalDetails(); - const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); - const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; return ( @@ -34,8 +31,6 @@ function DebugReportActionPreview({reportAction, reportID}: DebugReportActionPre isFirstVisibleReportAction={false} shouldDisplayContextMenu={false} personalDetails={personalDetails} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ); diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index 0561a026a1a7..474e0749d59d 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -25,11 +25,8 @@ function DuplicateTransactionItem({transaction, index, onPreviewPressed}: Duplic const styles = useThemeStyles(); const personalDetails = usePersonalDetails(); - const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`); - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); - const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; const action = Object.values(reportActions ?? {})?.find((reportAction) => { const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : CONST.DEFAULT_NUMBER_ID; @@ -73,8 +70,6 @@ function DuplicateTransactionItem({transaction, index, onPreviewPressed}: Duplic personalDetails={personalDetails} draftMessage={matchingDraftMessage} linkedTransactionRouteError={linkedTransactionRouteError} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> diff --git a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx index 788fdd625ea2..42d1fb281caf 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -876,7 +876,7 @@ const ContextMenuActions: ContextMenuAction[] = [ const modifyExpenseMessage = Parser.htmlToMarkdown(modifyExpenseMessageWithHTML); Clipboard.setString(modifyExpenseMessage); } else if (isReimbursementDeQueuedOrCanceledAction(reportAction)) { - const displayMessage = getReimbursementDeQueuedOrCanceledActionMessage(translate, reportAction, report); + const displayMessage = getReimbursementDeQueuedOrCanceledActionMessage(translate, reportAction, report?.ownerAccountID); Clipboard.setString(displayMessage); } else if (isMoneyRequestAction(reportAction)) { const displayMessage = getIOUReportActionDisplayMessage(translate, reportAction, transaction, report, bankAccountList); @@ -1003,7 +1003,7 @@ const ContextMenuActions: ContextMenuAction[] = [ } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED)) { Clipboard.setString(getMarkedReimbursedMessage(translate, reportAction)); } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSED)) { - Clipboard.setString(getReimbursedMessage(translate, reportAction, report, currentUserPersonalDetails.accountID)); + Clipboard.setString(getReimbursedMessage(translate, reportAction, report?.ownerAccountID, currentUserPersonalDetails.accountID)); } else if (isReimbursementQueuedAction(reportAction)) { Clipboard.setString( getReimbursementQueuedActionMessage({reportAction, translate, formatPhoneNumber: formatPhoneNumberPhoneUtils, report, shouldUseShortDisplayName: false}), diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index f67cb244e544..07e8cf5cde50 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -53,14 +53,7 @@ import { isWhisperActionTargetedToOthers, useTableReportViewActionRenderConditionals, } from '@libs/ReportActionsUtils'; -import { - canWriteInReport, - getTransactionsWithReceipts, - isCompletedTaskReport, - isHarvestCreatedExpenseReport as isHarvestCreatedExpenseReportUtils, - isTaskReport, - shouldDisplayThreadReplies as shouldDisplayThreadRepliesUtils, -} from '@libs/ReportUtils'; +import {canWriteInReport, getTransactionsWithReceipts, isCompletedTaskReport, isTaskReport, shouldDisplayThreadReplies as shouldDisplayThreadRepliesUtils} from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; import {ReactionListContext} from '@pages/inbox/ReportScreenContext'; import AttachmentModalContext from '@pages/media/AttachmentModalScreen/AttachmentModalContext'; @@ -154,29 +147,17 @@ type PureReportActionItemProps = { /** Original report from which the given reportAction is first created */ originalReport?: OnyxTypes.Report; - /** Whether the room is archived */ - isArchivedRoom?: boolean; - /** Whether the provided report is a closed expense report with no expenses */ isClosedExpenseReportWithNoExpenses?: boolean; - /** User payment card ID */ - userBillingFundID?: number; - /** Whether to show border for MoneyRequestReportPreviewContent */ shouldShowBorder?: boolean; /** Whether to highlight the action for a few seconds */ shouldHighlight?: boolean; - /** Did the user dismiss trying out NewDot? If true, it means they prefer using OldDot */ - isTryNewDotNVPDismissed?: boolean; - - /** Report name value pairs origin */ - reportNameValuePairsOrigin?: string; - - /** Report name value pairs originalID */ - reportNameValuePairsOriginalID?: string; + /** Whether the action is the "Created" action of a harvest-created expense report */ + isHarvestCreatedExpenseReport?: boolean; }; function PureReportActionItem({ @@ -201,14 +182,10 @@ function PureReportActionItem({ personalDetails, originalReportID = '-1', originalReport, - isArchivedRoom, isClosedExpenseReportWithNoExpenses, - userBillingFundID, shouldShowBorder, shouldHighlight = false, - isTryNewDotNVPDismissed = false, - reportNameValuePairsOrigin, - reportNameValuePairsOriginalID, + isHarvestCreatedExpenseReport = false, }: PureReportActionItemProps) { const isConciergeGreeting = action.reportActionID === CONST.CONCIERGE_GREETING_ACTION_ID; const shouldDisplayContextMenuValue = shouldDisplayContextMenu && !isConciergeGreeting; @@ -232,7 +209,6 @@ function PureReportActionItem({ const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; const [isReportActionActive, setIsReportActionActive] = useState(!!isReportActionLinked); - const isHarvestCreatedExpenseReport = isHarvestCreatedExpenseReportUtils(reportNameValuePairsOrigin, reportNameValuePairsOriginalID); const shouldRenderViewBasedOnAction = useTableReportViewActionRenderConditionals(action); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.chatReportID)}`); @@ -553,118 +529,120 @@ function PureReportActionItem({ setIsReportActionActive(!!isReportActionLinked); }} > - {(hovered) => ( - - {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && ( - - )} - {shouldDisplayContextMenuValue && (hovered || !!isEmojiPickerActive || isContextMenuActive) && !hasDraft && !hasActionErrors && ( - - )} - { + const isHoveredOrActive = !!hovered || !!isReportActionLinked || isContextMenuActive || !!isEmojiPickerActive; + + return ( + + {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && ( + )} - > - + )} + - - {isWhisper && } - {isEmpty ? ( - - ) : ( - - + {isWhisper && } + {isEmpty ? ( + + ) : ( + - {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( - - !isEmptyObject(item))} /> - - )} - {!isOnSearch && !isMessageDeleted(action) && ( - - )} - {shouldDisplayThreadReplies && ( - + - )} - - )} - - + {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( + + !isEmptyObject(item))} /> + + )} + {!isOnSearch && !isMessageDeleted(action) && ( + + )} + {shouldDisplayThreadReplies && ( + + )} + + )} + + + - - )} + ); + }} {!!action.error && ( @@ -716,11 +694,8 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { deepEqual(prevProps.personalDetails, nextProps.personalDetails) && prevProps.originalReportID === nextProps.originalReportID && deepEqual(prevProps.originalReport?.participants, nextProps.originalReport?.participants) && - prevProps.isArchivedRoom === nextProps.isArchivedRoom && prevProps.isClosedExpenseReportWithNoExpenses === nextProps.isClosedExpenseReportWithNoExpenses && - prevProps.userBillingFundID === nextProps.userBillingFundID && prevProps.shouldHighlight === nextProps.shouldHighlight && - prevProps.reportNameValuePairsOrigin === nextProps.reportNameValuePairsOrigin && - prevProps.reportNameValuePairsOriginalID === nextProps.reportNameValuePairsOriginalID + prevProps.isHarvestCreatedExpenseReport === nextProps.isHarvestCreatedExpenseReport ); }); diff --git a/src/pages/inbox/report/ReportActionItem.tsx b/src/pages/inbox/report/ReportActionItem.tsx index 839c78a909bb..886bb611134b 100644 --- a/src/pages/inbox/report/ReportActionItem.tsx +++ b/src/pages/inbox/report/ReportActionItem.tsx @@ -2,10 +2,9 @@ import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import useOnyx from '@hooks/useOnyx'; import useOriginalReportID from '@hooks/useOriginalReportID'; -import useReportIsArchived from '@hooks/useReportIsArchived'; import useReportTransactions from '@hooks/useReportTransactions'; import {getIOUReportIDFromReportActionPreview, getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; -import {isArchivedNonExpenseReport, isClosedExpenseReportWithNoExpenses} from '@libs/ReportUtils'; +import {isClosedExpenseReportWithNoExpenses} from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Transaction} from '@src/types/onyx'; import type {PureReportActionItemProps} from './PureReportActionItem'; @@ -18,27 +17,11 @@ type ReportActionItemProps = PureReportActionItemProps & { /** Personal details list */ personalDetails: OnyxEntry; - - /** User billing fund ID */ - userBillingFundID: number | undefined; - - /** Did the user dismiss trying out NewDot? If true, it means they prefer using OldDot */ - isTryNewDotNVPDismissed?: boolean; }; -function ReportActionItem({ - action, - report, - draftMessage: draftMessageProp, - personalDetails, - userBillingFundID, - linkedTransactionRouteError: linkedTransactionRouteErrorProp, - isTryNewDotNVPDismissed, - ...props -}: ReportActionItemProps) { +function ReportActionItem({action, report, draftMessage: draftMessageProp, personalDetails, linkedTransactionRouteError: linkedTransactionRouteErrorProp, ...props}: ReportActionItemProps) { const reportID = report?.reportID; const originalReportID = useOriginalReportID(reportID, action); - const isOriginalReportArchived = useReportIsArchived(originalReportID); const [originalReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${originalReportID}`); const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getIOUReportIDFromReportActionPreview(action)}`); @@ -69,10 +52,7 @@ function ReportActionItem({ personalDetails={personalDetails} originalReportID={originalReportID} originalReport={originalReport} - isArchivedRoom={isArchivedNonExpenseReport(originalReport, isOriginalReportArchived)} isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses(iouReport, transactionsOnIOUReport)} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ); } diff --git a/src/pages/inbox/report/ReportActionItemMessageEdit.tsx b/src/pages/inbox/report/ReportActionItemMessageEdit.tsx index 864b9bc4b4b7..ef06b1b8fb2e 100644 --- a/src/pages/inbox/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/inbox/report/ReportActionItemMessageEdit.tsx @@ -10,11 +10,13 @@ import Composer from '@components/Composer'; import type {ComposerRef, TextSelection} from '@components/Composer/types'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; +import {useBlockedFromConcierge} from '@components/OnyxListItemProvider'; import useIsScrollLikelyLayoutTriggered from '@hooks/useIsScrollLikelyLayoutTriggered'; import useKeyboardState from '@hooks/useKeyboardState'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import useReportIsArchived from '@hooks/useReportIsArchived'; import useReportScrollManager from '@hooks/useReportScrollManager'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useScrollBlocker from '@hooks/useScrollBlocker'; @@ -29,9 +31,12 @@ import DomUtils from '@libs/DomUtils'; import {extractEmojis, getTextVSCursorOffset, insertTextVSBetweenDigitAndEmoji, replaceAndExtractEmojis} from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import type {Selection} from '@libs/focusComposerWithDelay/types'; +import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import reportActionItemEventHandler from '@libs/ReportActionItemEventHandler'; import {isDeletedAction} from '@libs/ReportActionsUtils'; +import {chatIncludesConcierge, isArchivedNonExpenseReport} from '@libs/ReportUtils'; +import {isBlockedFromConcierge} from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -67,12 +72,6 @@ type ReportActionItemMessageEditProps = { /** Position index of the report action in the overall report FlatList view */ index: number; - /** Whether or not the emoji picker is disabled */ - shouldDisableEmojiPicker?: boolean; - - /** Whether report is from group policy */ - isGroupPolicyReport: boolean; - /** Reference to the outer element */ ref?: React.Ref; }; @@ -84,8 +83,15 @@ const DEFAULT_MODAL_VALUE = { isVisible: false, }; -function ReportActionItemMessageEdit({action, reportID, originalReportID, policyID, index, isGroupPolicyReport, shouldDisableEmojiPicker = false, ref}: ReportActionItemMessageEditProps) { +function ReportActionItemMessageEdit({action, reportID, originalReportID, policyID, index, ref}: ReportActionItemMessageEditProps) { const [preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`); + const isOriginalReportArchived = useReportIsArchived(originalReportID); + const [originalReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(originalReportID)}`); + const blockedFromConcierge = useBlockedFromConcierge(); + const isGroupPolicyReport = !!policyID && policyID !== CONST.POLICY.ID_FAKE; + const isArchivedRoom = isArchivedNonExpenseReport(originalReport, isOriginalReportArchived); + const shouldDisableEmojiPicker = (chatIncludesConcierge(report) && isBlockedFromConcierge(blockedFromConcierge)) || isArchivedNonExpenseReport(report, isArchivedRoom); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const containerRef = useRef(null); diff --git a/src/pages/inbox/report/ReportActionItemParentAction.tsx b/src/pages/inbox/report/ReportActionItemParentAction.tsx index 991cd6485895..edb4123eff12 100644 --- a/src/pages/inbox/report/ReportActionItemParentAction.tsx +++ b/src/pages/inbox/report/ReportActionItemParentAction.tsx @@ -7,6 +7,7 @@ import useAncestors from '@hooks/useAncestors'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import useReportIsArchived from '@hooks/useReportIsArchived'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; @@ -61,15 +62,6 @@ type ReportActionItemParentActionProps = { /** Personal details list */ personalDetails: OnyxEntry; - - /** User billing fund ID */ - userBillingFundID: number | undefined; - - /** Did the user dismiss trying out NewDot? If true, it means they prefer using OldDot */ - isTryNewDotNVPDismissed: boolean | undefined; - - /** Whether the report is archived */ - isReportArchived: boolean; }; function ReportActionItemParentAction({ @@ -83,9 +75,6 @@ function ReportActionItemParentAction({ isFirstVisibleReportAction = false, shouldUseThreadDividerLine = false, personalDetails, - userBillingFundID, - isTryNewDotNVPDismissed = false, - isReportArchived = false, }: ReportActionItemParentActionProps) { const styles = useThemeStyles(); const ancestors = useAncestors(report, shouldExcludeAncestorReportAction); @@ -93,6 +82,8 @@ function ReportActionItemParentAction({ const {isInNarrowPaneModal} = useResponsiveLayout(); const transactionID = isMoneyRequestAction(action) && getOriginalMessage(action)?.IOUTransactionID; const [allBetas] = useOnyx(ONYXKEYS.BETAS); + const isReportArchived = useReportIsArchived(report?.reportID); + const currentUserPersonalDetail = useCurrentUserPersonalDetails(); const {accountID: currentUserAccountID} = currentUserPersonalDetail; const conciergePersonalDetail = personalDetails ? Object.values(personalDetails).find((detail) => detail?.login === CONST.EMAIL.CONCIERGE) : undefined; @@ -208,8 +199,6 @@ function ReportActionItemParentAction({ isThreadReportParentAction personalDetails={personalDetails} linkedTransactionRouteError={linkedTransactionRouteError} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ); diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index 6b96286c2f10..b1fa59b4d916 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -51,6 +51,7 @@ import { isArchivedNonExpenseReport, isCanceledTaskReport, isExpenseReport, + isHarvestCreatedExpenseReport, isInvoiceReport, isIOUReport, isMoneyRequestReport, @@ -202,15 +203,13 @@ function ReportActionsList({ const isAnonymousUser = useIsAnonymousUser(); const isReportArchived = useReportIsArchived(report?.reportID); - const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); - const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [betas] = useOnyx(ONYXKEYS.BETAS); const [actionIdToHighlight, setActionIdToHighlight] = useState(''); const [reportLoadingState] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${report.reportID}`); const prevIsLoadingInitialReportActions = usePrevious(reportLoadingState?.isLoadingInitialReportActions); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`); + const isHarvestCreatedExpenseReportAction = isHarvestCreatedExpenseReport(reportNameValuePairs?.origin, reportNameValuePairs?.originalID); const backTo = route?.params?.backTo as string; const linkedReportActionID = route?.params?.reportActionID; @@ -758,11 +757,7 @@ function ReportActionsList({ isFirstVisibleReportAction={firstVisibleReportActionID === reportAction.reportActionID} shouldUseThreadDividerLine={shouldUseThreadDividerLine} personalDetails={personalDetailsList} - isReportArchived={isReportArchived} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} - reportNameValuePairsOrigin={reportNameValuePairs?.origin} - reportNameValuePairsOriginalID={reportNameValuePairs?.originalID} + isHarvestCreatedExpenseReport={isHarvestCreatedExpenseReportAction} /> ; - /** User billing fund ID */ - userBillingFundID: number | undefined; - - /** Did the user dismiss trying out NewDot? If true, it means they prefer using OldDot */ - isTryNewDotNVPDismissed: boolean | undefined; - /** Whether the report is archived */ - isReportArchived: boolean; - - /** Report name value pairs origin */ - reportNameValuePairsOrigin?: string; - - /** Report name value pairs originalID */ - reportNameValuePairsOriginalID?: string; + /** Whether the action is the "Created" action of a harvest-created expense report */ + isHarvestCreatedExpenseReport?: boolean; }; function ReportActionsListItemRenderer({ @@ -83,12 +72,8 @@ function ReportActionsListItemRenderer({ shouldUseThreadDividerLine = false, shouldHighlight = false, parentReportActionForTransactionThread, - userBillingFundID, personalDetails, - isTryNewDotNVPDismissed = false, - isReportArchived = false, - reportNameValuePairsOrigin, - reportNameValuePairsOriginalID, + isHarvestCreatedExpenseReport = false, }: ReportActionsListItemRendererProps) { const originalMessage = useMemo(() => getOriginalMessage(reportAction), [reportAction]); @@ -179,9 +164,6 @@ function ReportActionsListItemRenderer({ isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} personalDetails={personalDetails} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} - isReportArchived={isReportArchived} /> ); } @@ -202,10 +184,7 @@ function ReportActionsListItemRenderer({ shouldUseThreadDividerLine={shouldUseThreadDividerLine} shouldHighlight={shouldHighlight} personalDetails={personalDetails} - userBillingFundID={userBillingFundID} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} - reportNameValuePairsOrigin={reportNameValuePairsOrigin} - reportNameValuePairsOriginalID={reportNameValuePairsOriginalID} + isHarvestCreatedExpenseReport={isHarvestCreatedExpenseReport} /> ); } diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index 8667431d0d38..9d960499acc2 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -17,7 +17,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import { getChangedApproverActionMessage, getCompanyCardConnectionBrokenMessage, - getIntegrationSyncFailedMessage, getIOUReportIDFromReportActionPreview, getOriginalMessage, getPlaidBalanceFailureMessage, @@ -52,11 +51,13 @@ import ChatMessageContent from './ChatMessageContent'; import ChatTransactionPreview from './ChatTransactionPreview'; import ConfirmWhisperContent from './ConfirmWhisperContent'; import FraudAlertContent from './FraudAlertContent'; +import IntegrationSyncFailedMessage from './IntegrationSyncFailedMessage'; import JoinRequestContent from './JoinRequestContent'; import MentionWhisperContent from './MentionWhisperContent'; import ModifiedExpenseContent from './ModifiedExpenseContent'; import PaymentContent from './PaymentContent'; import PolicyChangeLogContent, {isHandledPolicyChangeLogAction} from './PolicyChangeLogContent'; +import ReceiptScanFailedContent from './ReceiptScanFailedContent'; import ReimbursedContent from './ReimbursedContent'; import ReimbursementDeQueuedContent from './ReimbursementDeQueuedContent'; import ReimbursementQueuedContent from './ReimbursementQueuedContent'; @@ -100,33 +101,21 @@ type ActionContentRouterProps = { /** Toggle the hidden state of the message */ updateHiddenState: (isHiddenValue: boolean) => void; - /** Whether the room is archived */ - isArchivedRoom?: boolean; - /** Whether the provided report is a closed expense report with no expenses */ isClosedExpenseReportWithNoExpenses?: boolean; /** Whether the report action is the "Created" action of a harvest-created expense report */ isHarvestCreatedExpenseReport: boolean; - /** The originalID component of report name value pairs (used by the Created action of harvest reports) */ - reportNameValuePairsOriginalID?: string; - /** Personal details list */ personalDetails?: OnyxTypes.PersonalDetailsList; - /** Did the user dismiss trying out NewDot? */ - isTryNewDotNVPDismissed?: boolean; - /** Whether to show border for MoneyRequestReportPreviewContent */ shouldShowBorder?: boolean; /** Whether the search-page UI is active */ isOnSearch: boolean; - /** User payment card ID */ - userBillingFundID?: number; - /** Position index of the report action in the overall report FlatList view */ index: number; @@ -147,21 +136,23 @@ function ActionContentRouter({ hovered, isHidden, updateHiddenState, - isArchivedRoom, isClosedExpenseReportWithNoExpenses, isHarvestCreatedExpenseReport, - reportNameValuePairsOriginalID, personalDetails, - isTryNewDotNVPDismissed, shouldShowBorder, isOnSearch, - userBillingFundID, index, setIsPaymentMethodPopoverActive, }: ActionContentRouterProps): React.JSX.Element | null { const {translate, formatTravelDate} = useLocalize(); const styles = useThemeStyles(); + // Report that owns this action for mutations (thread / merged-list cases use originalReport). + const actionOwnerReport = originalReport ?? report; + const actionOwnerReportID = originalReportID ?? reportID; + const policyID = report?.policyID; + const reportOwnerAccountID = report?.ownerAccountID; + if (isIOURequestReportAction(action)) { const moneyRequestOriginalMessage = isMoneyRequestAction(action) ? getOriginalMessage(action) : undefined; // If originalMessage.iouReportID is set, this is a 1:1 IOU expense in a DM chat whose reportID is report.chatReportID @@ -216,7 +207,7 @@ function ActionContentRouter({ return ( ); } @@ -255,7 +246,7 @@ function ActionContentRouter({ return ( ); } @@ -263,7 +254,7 @@ function ActionContentRouter({ return ( ); @@ -272,7 +263,7 @@ function ActionContentRouter({ return ( @@ -282,7 +273,7 @@ function ActionContentRouter({ return ( ); } @@ -290,18 +281,24 @@ function ActionContentRouter({ return ( ); } - if (isSimpleMessageAction(action)) { + if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.RECEIPT_SCAN_FAILED)) { return ( - ); } + if (isSimpleMessageAction(action)) { + return ; + } if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.FORWARDED)) { const wasAutoForwarded = getOriginalMessage(action)?.automaticAction ?? false; if (wasAutoForwarded) { @@ -317,7 +314,7 @@ function ActionContentRouter({ return ( ); } @@ -370,9 +367,8 @@ function ActionContentRouter({ return ( ); } @@ -391,8 +387,7 @@ function ActionContentRouter({ ); } @@ -401,8 +396,7 @@ function ActionContentRouter({ ); @@ -414,7 +408,7 @@ function ActionContentRouter({ return ( ); } @@ -434,9 +428,10 @@ function ActionContentRouter({ } if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED)) { return ( - - ${getIntegrationSyncFailedMessage(translate, action, report?.policyID, isTryNewDotNVPDismissed)}`} /> - + ); } if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.COMPANY_CARD_CONNECTION_BROKEN)) { @@ -454,7 +449,7 @@ function ActionContentRouter({ ); } if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.CREATED) && isHarvestCreatedExpenseReport) { - return ; + return ; } if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.CREATED_REPORT_FOR_UNAPPROVED_TRANSACTIONS)) { return ; @@ -481,8 +476,7 @@ function ActionContentRouter({ return ( ); } diff --git a/src/pages/inbox/report/actionContents/ApprovalFlowContent.tsx b/src/pages/inbox/report/actionContents/ApprovalFlowContent.tsx index 52d3d46652c9..80a93e1d8353 100644 --- a/src/pages/inbox/report/actionContents/ApprovalFlowContent.tsx +++ b/src/pages/inbox/report/actionContents/ApprovalFlowContent.tsx @@ -101,7 +101,5 @@ function ApprovalFlowContent({action, policyID, reportID, originalReport}: Appro return null; } -ApprovalFlowContent.displayName = 'ApprovalFlowContent'; - export default ApprovalFlowContent; export {isApprovalFlowAction}; diff --git a/src/pages/inbox/report/actionContents/CardBrokenConnectionContent.tsx b/src/pages/inbox/report/actionContents/CardBrokenConnectionContent.tsx index bb7b14ea63f7..39a0c70c865b 100644 --- a/src/pages/inbox/report/actionContents/CardBrokenConnectionContent.tsx +++ b/src/pages/inbox/report/actionContents/CardBrokenConnectionContent.tsx @@ -35,6 +35,4 @@ function CardBrokenConnectionContent({action}: CardBrokenConnectionContentProps) ); } -CardBrokenConnectionContent.displayName = 'CardBrokenConnectionContent'; - export default CardBrokenConnectionContent; diff --git a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx index 979b884a7b97..a0ce18000260 100644 --- a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx +++ b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx @@ -1,6 +1,5 @@ import {validTransactionDraftIDsSelector} from '@selectors/TransactionDraft'; import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import type {ActionableItem} from '@components/ReportActionItem/ActionableItemButtons'; import ActionableItemButtons from '@components/ReportActionItem/ActionableItemButtons'; import useActivePolicy from '@hooks/useActivePolicy'; @@ -27,6 +26,7 @@ import { import type {CreateDraftTransactionParams} from '@libs/ReportUtils'; import {createDraftTransactionAndNavigateToParticipantSelector} from '@libs/ReportUtils'; import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; +import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; import {dismissTrackExpenseActionableWhisper, resolveConciergeCategoryOptions, resolveConciergeDescriptionOptions} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -35,21 +35,23 @@ import type * as OnyxTypes from '@src/types/onyx'; type ChatActionableButtonsProps = { action: OnyxTypes.ReportAction; - report: OnyxEntry; - originalReport: OnyxEntry; + originalReportID: string | undefined; reportID: string | undefined; - originalReportID: string; - userBillingFundID: number | undefined; }; -function ChatActionableButtons({action, report, originalReport, reportID, originalReportID, userBillingFundID}: ChatActionableButtonsProps) { +function ChatActionableButtons({action, originalReportID, reportID}: ChatActionableButtonsProps) { const styles = useThemeStyles(); + const actionOwnerReportID = originalReportID ?? reportID; + const [originalReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(originalReportID)}`); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`); + const actionOwnerReport = originalReport ?? report; const personalDetail = useCurrentUserPersonalDetails(); const {isRestrictedToPreferredPolicy, preferredPolicyID} = usePreferredPolicy(); const activePolicy = useActivePolicy(); const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); @@ -58,7 +60,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin const delegateAccountID = useDelegateAccountID(); const actionableItemButtons = ((): ActionableItem[] => { - if (isActionableAddPaymentCard(action) && userBillingFundID === undefined && shouldRenderAddPaymentCard()) { + if (isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded(userBillingFundID) && shouldRenderAddPaymentCard()) { return [ { text: 'subscription.cardSection.addCardButton', @@ -71,7 +73,6 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin ]; } - const reportActionReport = originalReport ?? report; if (isConciergeCategoryOptions(action)) { const options = getOriginalMessage(action)?.options; if (!options) { @@ -82,7 +83,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin return []; } - if (!reportActionReport) { + if (!actionOwnerReport) { return []; } @@ -91,7 +92,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin key: `${action.reportActionID}-conciergeCategoryOptions-${option}`, onPress: () => { resolveConciergeCategoryOptions( - reportActionReport, + actionOwnerReport, reportID, action.reportActionID, option, @@ -113,7 +114,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin return []; } - if (!reportActionReport) { + if (!actionOwnerReport) { return []; } @@ -122,7 +123,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin key: `${action.reportActionID}-conciergeDescriptionOptions-${option}`, onPress: () => { resolveConciergeDescriptionOptions( - reportActionReport, + actionOwnerReport, reportID, action.reportActionID, option, @@ -134,7 +135,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin })); } const messageHtml = getReportActionMessage(action)?.html; - if (messageHtml && reportActionReport) { + if (messageHtml && actionOwnerReport) { const followups = parseFollowupsFromHtml(messageHtml); if (followups && followups.length > 0) { return followups.map((followup) => ({ @@ -143,7 +144,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin key: `${action.reportActionID}-followup-${followup.text}`, onPress: () => { resolveSuggestedFollowup( - reportActionReport, + actionOwnerReport, reportID, action, followup, @@ -158,9 +159,8 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin } if (isActionableTrackExpense(action)) { - const reportActionReportID = originalReportID ?? reportID; const baseDraftTransactionParams = { - reportID: reportActionReportID, + reportID: actionOwnerReportID, reportActionID: action.reportActionID, introSelected, draftTransactionIDs, @@ -198,7 +198,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin text: 'actionableMentionTrackExpense.nothing', key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, onPress: () => { - dismissTrackExpenseActionableWhisper(reportActionReportID, action); + dismissTrackExpenseActionableWhisper(actionOwnerReportID, action); }, }); return options; @@ -230,6 +230,4 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin ); } -ChatActionableButtons.displayName = 'ChatActionableButtons'; - export default ChatActionableButtons; diff --git a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx index 1b4755788cc8..9dd0b1a090ea 100644 --- a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx @@ -1,10 +1,8 @@ import React from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; -import {useBlockedFromConcierge} from '@components/OnyxListItemProvider'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -18,18 +16,15 @@ import { isConciergeCategoryOptions, isConciergeDescriptionOptions, } from '@libs/ReportActionsUtils'; -import {chatIncludesConcierge, isArchivedNonExpenseReport} from '@libs/ReportUtils'; import ReportActionItemMessage from '@pages/inbox/report/ReportActionItemMessage'; import ReportActionItemMessageEdit from '@pages/inbox/report/ReportActionItemMessageEdit'; -import {isBlockedFromConcierge} from '@userActions/User'; import CONST from '@src/CONST'; import type * as OnyxTypes from '@src/types/onyx'; import ChatActionableButtons from './ChatActionableButtons'; type ChatMessageContentProps = { action: OnyxTypes.ReportAction; - report: OnyxEntry; - originalReport: OnyxEntry; + policyID: string | undefined; reportID: string | undefined; originalReportID: string; displayAsGroup: boolean; @@ -37,35 +32,17 @@ type ChatMessageContentProps = { index: number; isHidden: boolean; updateHiddenState: (isHiddenValue: boolean) => void; - isArchivedRoom?: boolean; isOnSearch: boolean; - userBillingFundID: number | undefined; }; -function ChatMessageContent({ - action, - report, - originalReport, - reportID, - originalReportID, - displayAsGroup, - draftMessage, - index, - isHidden, - updateHiddenState, - isArchivedRoom, - isOnSearch, - userBillingFundID, -}: ChatMessageContentProps) { +function ChatMessageContent({action, policyID, reportID, originalReportID, displayAsGroup, draftMessage, index, isHidden, updateHiddenState, isOnSearch}: ChatMessageContentProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const blockedFromConcierge = useBlockedFromConcierge(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const isEditingInline = !shouldUseNarrowLayout && draftMessage !== undefined; - const mentionReportContextValue = {currentReportID: report?.reportID, exactlyMatch: true}; - + const mentionReportContextValue = {currentReportID: reportID, exactlyMatch: true}; const attachmentContextValue = isOnSearch ? {type: CONST.ATTACHMENT_TYPE.SEARCH} : {reportID, type: CONST.ATTACHMENT_TYPE.REPORT}; const {hasBeenFlagged} = getModerationFlagState(action); @@ -86,10 +63,8 @@ function ChatMessageContent({ action={action} reportID={reportID} originalReportID={originalReportID} - policyID={report?.policyID} + policyID={policyID} index={index} - shouldDisableEmojiPicker={(chatIncludesConcierge(report) && isBlockedFromConcierge(blockedFromConcierge)) || isArchivedNonExpenseReport(report, isArchivedRoom)} - isGroupPolicyReport={!!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE} /> ) : ( @@ -117,11 +92,8 @@ function ChatMessageContent({ {mayHaveActionableButtons && ( )} diff --git a/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx b/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx index 7a2c9d9c4e5b..a8d2c4029b67 100644 --- a/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx @@ -14,21 +14,19 @@ type ConfirmWhisperContentProps = { action: ReportAction; reportID: string | undefined; originalReportID: string | undefined; - report: OnyxEntry; - originalReport: OnyxEntry; + actionOwnerReport: OnyxEntry; }; -function ConfirmWhisperContent({action, reportID, originalReportID, report, originalReport}: ConfirmWhisperContentProps) { - const reportActionReport = originalReport ?? report; +function ConfirmWhisperContent({action, reportID, originalReportID, actionOwnerReport}: ConfirmWhisperContentProps) { const isOriginalReportArchived = useReportIsArchived(originalReportID); - const mentionReportContextValue = {currentReportID: report?.reportID, exactlyMatch: true}; + const mentionReportContextValue = {currentReportID: reportID, exactlyMatch: true}; const buttons: ActionableItem[] = [ { text: 'common.buttonConfirm', key: `${action.reportActionID}-actionableReportMentionConfirmWhisper-${CONST.REPORT.ACTIONABLE_MENTION_INVITE_TO_SUBMIT_EXPENSE_CONFIRM_WHISPER.DONE}`, onPress: () => - resolveActionableMentionConfirmWhisper(reportActionReport, action, CONST.REPORT.ACTIONABLE_MENTION_INVITE_TO_SUBMIT_EXPENSE_CONFIRM_WHISPER.DONE, isOriginalReportArchived), + resolveActionableMentionConfirmWhisper(actionOwnerReport, action, CONST.REPORT.ACTIONABLE_MENTION_INVITE_TO_SUBMIT_EXPENSE_CONFIRM_WHISPER.DONE, isOriginalReportArchived), isPrimary: true, }, ]; @@ -51,6 +49,4 @@ function ConfirmWhisperContent({action, reportID, originalReportID, report, orig ); } -ConfirmWhisperContent.displayName = 'ConfirmWhisperContent'; - export default ConfirmWhisperContent; diff --git a/src/pages/inbox/report/actionContents/FraudAlertContent.tsx b/src/pages/inbox/report/actionContents/FraudAlertContent.tsx index b8cd6005b2f2..3a05400c42fe 100644 --- a/src/pages/inbox/report/actionContents/FraudAlertContent.tsx +++ b/src/pages/inbox/report/actionContents/FraudAlertContent.tsx @@ -65,6 +65,4 @@ function FraudAlertContent({action, reportID}: FraudAlertContentProps) { ); } -FraudAlertContent.displayName = 'FraudAlertContent'; - export default FraudAlertContent; diff --git a/src/pages/inbox/report/actionContents/IntegrationSyncFailedMessage.tsx b/src/pages/inbox/report/actionContents/IntegrationSyncFailedMessage.tsx new file mode 100644 index 000000000000..e9968e169ece --- /dev/null +++ b/src/pages/inbox/report/actionContents/IntegrationSyncFailedMessage.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import RenderHTML from '@components/RenderHTML'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import {getIntegrationSyncFailedMessage} from '@libs/ReportActionsUtils'; +import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; + +type IntegrationSyncFailedMessageProps = { + action: OnyxEntry; + policyID: string | undefined; +}; + +function IntegrationSyncFailedMessage({action, policyID}: IntegrationSyncFailedMessageProps) { + const {translate} = useLocalize(); + const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); + const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; + + return ( + + ${getIntegrationSyncFailedMessage(translate, action, policyID, isTryNewDotNVPDismissed)}`} /> + + ); +} + +export default IntegrationSyncFailedMessage; diff --git a/src/pages/inbox/report/actionContents/JoinRequestContent.tsx b/src/pages/inbox/report/actionContents/JoinRequestContent.tsx index 8e5e0b88f8c1..57596ffd8431 100644 --- a/src/pages/inbox/report/actionContents/JoinRequestContent.tsx +++ b/src/pages/inbox/report/actionContents/JoinRequestContent.tsx @@ -14,16 +14,14 @@ import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; type JoinRequestContentProps = { action: ReportAction; - reportID: string | undefined; - originalReportID: string; + actionOwnerReportID: string | undefined; policyID: string | undefined; }; -function JoinRequestContent({action, reportID, originalReportID, policyID}: JoinRequestContentProps) { +function JoinRequestContent({action, actionOwnerReportID, policyID}: JoinRequestContentProps) { const {translate} = useLocalize(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - const reportActionReportID = originalReportID ?? reportID; const buttons: ActionableItem[] = getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution) ? [] @@ -31,13 +29,13 @@ function JoinRequestContent({action, reportID, originalReportID, policyID}: Join { text: 'actionableMentionJoinWorkspaceOptions.accept', key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, - onPress: () => acceptJoinRequest(reportActionReportID, action), + onPress: () => acceptJoinRequest(actionOwnerReportID, action), isPrimary: true, }, { text: 'actionableMentionJoinWorkspaceOptions.decline', key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - onPress: () => declineJoinRequest(reportActionReportID, action), + onPress: () => declineJoinRequest(actionOwnerReportID, action), }, ]; @@ -55,6 +53,4 @@ function JoinRequestContent({action, reportID, originalReportID, policyID}: Join ); } -JoinRequestContent.displayName = 'JoinRequestContent'; - export default JoinRequestContent; diff --git a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx index 78586ae77809..b4598e50d48b 100644 --- a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx @@ -28,7 +28,7 @@ function MentionWhisperContent({action, report, originalReport, originalReportID const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); - const reportActionReport = originalReport ?? report; + const actionOwnerReport = originalReport ?? report; const reportPolicyID = report?.policyID; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${reportPolicyID}`); @@ -42,7 +42,7 @@ function MentionWhisperContent({action, report, originalReport, originalReportID key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE}`, onPress: () => resolveActionableMentionWhisper( - reportActionReport, + actionOwnerReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE, isOriginalReportArchived, @@ -56,7 +56,7 @@ function MentionWhisperContent({action, report, originalReport, originalReportID key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, onPress: () => resolveActionableMentionWhisper( - reportActionReport, + actionOwnerReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE, isOriginalReportArchived, @@ -68,7 +68,7 @@ function MentionWhisperContent({action, report, originalReport, originalReportID key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, onPress: () => resolveActionableMentionWhisper( - reportActionReport, + actionOwnerReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING, isOriginalReportArchived, @@ -91,6 +91,4 @@ function MentionWhisperContent({action, report, originalReport, originalReportID ); } -MentionWhisperContent.displayName = 'MentionWhisperContent'; - export default MentionWhisperContent; diff --git a/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx b/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx index 61f879ac98f9..55d96661019f 100644 --- a/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx +++ b/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx @@ -13,20 +13,20 @@ import type {Report, ReportAction} from '@src/types/onyx'; type ModifiedExpenseContentProps = { action: ReportAction; - report: OnyxEntry; + policyID: string | undefined; originalReport: OnyxEntry; }; -function ModifiedExpenseContent({action, report, originalReport}: ModifiedExpenseContentProps) { +function ModifiedExpenseContent({action, policyID, originalReport}: ModifiedExpenseContentProps) { const {translate} = useLocalize(); const {email: currentUserEmail} = useCurrentUserPersonalDetails(); const {policyForMovingExpensesID} = usePolicyForMovingExpenses(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const [childReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(action.childReportID)}`); // When expense is moved from self-DM to workspace, policyID is temporarily OWNER_EMAIL_FAKE. // Fall back to policyForMovingExpensesID (actual destination workspace) for correct tag list. - const policyIDForTags = report?.policyID === CONST.POLICY.OWNER_EMAIL_FAKE && policyForMovingExpensesID ? policyForMovingExpensesID : report?.policyID; + const policyIDForTags = policyID === CONST.POLICY.OWNER_EMAIL_FAKE && policyForMovingExpensesID ? policyForMovingExpensesID : policyID; const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyIDForTags}`); const [movedFromReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(action, CONST.REPORT.MOVE_TYPE.FROM)}`); const [movedToReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(action, CONST.REPORT.MOVE_TYPE.TO)}`); @@ -51,6 +51,4 @@ function ModifiedExpenseContent({action, report, originalReport}: ModifiedExpens ); } -ModifiedExpenseContent.displayName = 'ModifiedExpenseContent'; - export default ModifiedExpenseContent; diff --git a/src/pages/inbox/report/actionContents/PaymentContent.tsx b/src/pages/inbox/report/actionContents/PaymentContent.tsx index 08ef8ee29c6e..1424cdcb746a 100644 --- a/src/pages/inbox/report/actionContents/PaymentContent.tsx +++ b/src/pages/inbox/report/actionContents/PaymentContent.tsx @@ -72,6 +72,4 @@ function PaymentContent({action, policyID}: PaymentContentProps) { return ; } -PaymentContent.displayName = 'PaymentContent'; - export default PaymentContent; diff --git a/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx b/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx index 63ac39058150..08011af7d79c 100644 --- a/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx +++ b/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx @@ -246,7 +246,5 @@ function PolicyChangeLogContent({action, policyID}: PolicyChangeLogContentProps) return ; } -PolicyChangeLogContent.displayName = 'PolicyChangeLogContent'; - export {isHandledPolicyChangeLogAction, HANDLED_POLICY_CHANGE_LOG_ACTIONS}; export default PolicyChangeLogContent; diff --git a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx new file mode 100644 index 000000000000..bb86a612c152 --- /dev/null +++ b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx @@ -0,0 +1,42 @@ +import {getReceiptScanFailedIOUActionDataSelector} from '@selectors/ReportAction'; +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {useSession} from '@components/OnyxListItemProvider'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; +import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; + +type ReceiptScanFailedContentProps = { + reportID: string | undefined; + actionReportID: string | undefined; + parentReportID: string | undefined; + parentReportActionID: string | undefined; + reportType: string | undefined; +}; + +function ReceiptScanFailedContent({reportID, actionReportID, parentReportID, parentReportActionID, reportType}: ReceiptScanFailedContentProps) { + const {translate} = useLocalize(); + const session = useSession(); + // IOU action lives in the IOU report's actions — `report` itself if it's IOU/Expense/Invoice, else its parent. + const isIOUReport = reportType === CONST.REPORT.TYPE.IOU || reportType === CONST.REPORT.TYPE.EXPENSE || reportType === CONST.REPORT.TYPE.INVOICE; + const IOUReportID = isIOUReport ? reportID : parentReportID; + + const receiptScanFailedIOUActionDataSelector = (reportActions: OnyxEntry) => + getReceiptScanFailedIOUActionDataSelector(reportActions, isIOUReport, parentReportActionID, actionReportID); + const [receiptScanFailedIOUActionData] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(IOUReportID)}`, { + selector: receiptScanFailedIOUActionDataSelector, + }); + const {transactionID, actorAccountID} = receiptScanFailedIOUActionData ?? {}; + const canEdit = !!actorAccountID && actorAccountID === session?.accountID; + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${getNonEmptyStringOnyxID(transactionID)}`); + const smartscanFailedViolation = transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.SMARTSCAN_FAILED); + const missingFields = smartscanFailedViolation?.data?.missingFields ?? []; + + return ; +} + +export default ReceiptScanFailedContent; diff --git a/src/pages/inbox/report/actionContents/ReimbursedContent.tsx b/src/pages/inbox/report/actionContents/ReimbursedContent.tsx index 2699c1d6b14d..b351c7c1109c 100644 --- a/src/pages/inbox/report/actionContents/ReimbursedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursedContent.tsx @@ -1,24 +1,21 @@ import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import {getReimbursedMessage} from '@libs/ReportActionsUtils'; import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; -import type {Report, ReportAction} from '@src/types/onyx'; +import type {ReportAction} from '@src/types/onyx'; type ReimbursedContentProps = { action: ReportAction; - report: OnyxEntry; + reportOwnerAccountID: number | undefined; }; -function ReimbursedContent({action, report}: ReimbursedContentProps) { +function ReimbursedContent({action, reportOwnerAccountID}: ReimbursedContentProps) { const {translate} = useLocalize(); const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); - const message = getReimbursedMessage(translate, action, report, currentUserAccountID); + const message = getReimbursedMessage(translate, action, reportOwnerAccountID, currentUserAccountID); return ; } -ReimbursedContent.displayName = 'ReimbursedContent'; - export default ReimbursedContent; diff --git a/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx b/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx index 39992f810f67..365cd942474a 100644 --- a/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx @@ -4,24 +4,22 @@ import useLocalize from '@hooks/useLocalize'; import {getReimbursementDeQueuedOrCanceledActionMessage} from '@libs/ReportUtils'; import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; import type CONST from '@src/CONST'; -import type {Report, ReportAction} from '@src/types/onyx'; +import type {ReportAction} from '@src/types/onyx'; type ReimbursementDeQueuedContentProps = { action: ReportAction; - report: OnyxEntry; + reportOwnerAccountID: number | undefined; }; -function ReimbursementDeQueuedContent({action, report}: ReimbursementDeQueuedContentProps) { +function ReimbursementDeQueuedContent({action, reportOwnerAccountID}: ReimbursementDeQueuedContentProps) { const {translate} = useLocalize(); const message = getReimbursementDeQueuedOrCanceledActionMessage( translate, action as OnyxEntry>, - report, + reportOwnerAccountID, ); return ; } -ReimbursementDeQueuedContent.displayName = 'ReimbursementDeQueuedContent'; - export default ReimbursementDeQueuedContent; diff --git a/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx index 3087174fd4ef..007ca5c8b280 100644 --- a/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx @@ -14,15 +14,13 @@ import type {Report, ReportAction} from '@src/types/onyx'; type ReportMentionWhisperContentProps = { action: ReportAction; reportID: string | undefined; - report: OnyxEntry; - originalReport: OnyxEntry; + actionOwnerReport: OnyxEntry; }; -function ReportMentionWhisperContent({action, reportID, report, originalReport}: ReportMentionWhisperContentProps) { +function ReportMentionWhisperContent({action, reportID, actionOwnerReport}: ReportMentionWhisperContentProps) { const isReportArchived = useReportIsArchived(reportID); - const reportActionReport = originalReport ?? report; const resolution = getOriginalMessage(action)?.resolution; - const mentionReportContextValue = {currentReportID: report?.reportID, exactlyMatch: true}; + const mentionReportContextValue = {currentReportID: reportID, exactlyMatch: true}; const buttons: ActionableItem[] = resolution ? [] @@ -30,13 +28,13 @@ function ReportMentionWhisperContent({action, reportID, report, originalReport}: { text: 'common.yes', key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - onPress: () => resolveActionableReportMentionWhisper(reportActionReport, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE, isReportArchived), + onPress: () => resolveActionableReportMentionWhisper(actionOwnerReport, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE, isReportArchived), isPrimary: true, }, { text: 'common.no', key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => resolveActionableReportMentionWhisper(reportActionReport, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING, isReportArchived), + onPress: () => resolveActionableReportMentionWhisper(actionOwnerReport, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING, isReportArchived), }, ]; @@ -60,6 +58,4 @@ function ReportMentionWhisperContent({action, reportID, report, originalReport}: ); } -ReportMentionWhisperContent.displayName = 'ReportMentionWhisperContent'; - export default ReportMentionWhisperContent; diff --git a/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx b/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx index bee743e93a6f..78794c7162c7 100644 --- a/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx @@ -1,13 +1,9 @@ import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import { getActionableCard3DSTransactionApprovalMessage, getDemotedFromWorkspaceMessage, getDismissedViolationMessageText, - getLinkedTransactionID, getMarkedReimbursedMessage, getMessageOfOldDotReportAction, getOriginalMessage, @@ -16,17 +12,14 @@ import { isActionOfType, isRejectedAction, isUnapprovedAction, - wasActionTakenByCurrentUser, } from '@libs/ReportActionsUtils'; import {getDeletedTransactionMessage, getPolicyChangeMessage} from '@libs/ReportUtils'; import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; type SimpleMessageContentProps = { action: OnyxTypes.ReportAction; - report: OnyxEntry; }; const SIMPLE_MESSAGE_ACTION_TYPES = new Set([ @@ -43,7 +36,6 @@ const SIMPLE_MESSAGE_ACTION_TYPES = new Set([ CONST.REPORT.ACTIONS.TYPE.MERGED_WITH_CASH_TRANSACTION, CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION, CONST.REPORT.ACTIONS.TYPE.RESOLVED_DUPLICATES, - CONST.REPORT.ACTIONS.TYPE.RECEIPT_SCAN_FAILED, CONST.REPORT.ACTIONS.TYPE.DEMOTED_FROM_WORKSPACE, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_CARD_3DS_TRANSACTION_APPROVAL, CONST.REPORT.ACTIONS.TYPE.REMOVED_FROM_APPROVAL_CHAIN, @@ -54,43 +46,7 @@ function isSimpleMessageAction(action: OnyxTypes.ReportAction): boolean { return SIMPLE_MESSAGE_ACTION_TYPES.has(action.actionName) || isUnapprovedAction(action) || isRejectedAction(action); } -function ReceiptScanFailedContent({action, report}: {action: OnyxTypes.ReportAction; report: OnyxEntry}) { - const {translate} = useLocalize(); - // IOU action lives in the IOU report's actions — `report` itself if it's IOU/Expense/Invoice, else its parent. - const isIouReport = report?.type === CONST.REPORT.TYPE.IOU || report?.type === CONST.REPORT.TYPE.EXPENSE || report?.type === CONST.REPORT.TYPE.INVOICE; - const iouReportID = isIouReport ? report?.reportID : report?.parentReportID; - const parentReportActionID = report?.parentReportActionID; - const actionReportID = action.reportID; - // Prefer parentReportActionID (specific IOU action when `report` is a transaction thread). - // Fall back to childReportID match, then to the only IOU action for one-transaction reports. - const getIouActionSelector = (reportActions: OnyxEntry): OnyxTypes.ReportAction | undefined => { - if (!isIouReport && parentReportActionID) { - const candidate = reportActions?.[parentReportActionID]; - if (isActionOfType(candidate, CONST.REPORT.ACTIONS.TYPE.IOU)) { - return candidate; - } - } - const iouActions = Object.values(reportActions ?? {}).filter((a): a is OnyxTypes.ReportAction => - isActionOfType(a, CONST.REPORT.ACTIONS.TYPE.IOU), - ); - if (actionReportID) { - const match = iouActions.find((a) => a.childReportID === actionReportID); - if (match) { - return match; - } - } - return iouActions.length === 1 ? iouActions.at(0) : undefined; - }; - const [iouAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(iouReportID)}`, {selector: getIouActionSelector}); - const transactionID = getLinkedTransactionID(iouAction); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${getNonEmptyStringOnyxID(transactionID)}`); - const smartscanFailedViolation = transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.SMARTSCAN_FAILED); - const missingFields = smartscanFailedViolation?.data?.missingFields ?? []; - - return ; -} - -function SimpleMessageContent({action, report}: SimpleMessageContentProps) { +function SimpleMessageContent({action}: SimpleMessageContentProps) { const {translate} = useLocalize(); if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED)) { @@ -132,14 +88,6 @@ function SimpleMessageContent({action, report}: SimpleMessageContentProps) { if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.RESOLVED_DUPLICATES)) { return ; } - if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.RECEIPT_SCAN_FAILED)) { - return ( - - ); - } if (isUnapprovedAction(action)) { return ; } @@ -162,7 +110,5 @@ function SimpleMessageContent({action, report}: SimpleMessageContentProps) { return null; } -SimpleMessageContent.displayName = 'SimpleMessageContent'; - export default SimpleMessageContent; export {isSimpleMessageAction}; diff --git a/src/selectors/ReportAction.ts b/src/selectors/ReportAction.ts index 8c46a3772a26..71914e032da1 100644 --- a/src/selectors/ReportAction.ts +++ b/src/selectors/ReportAction.ts @@ -1,6 +1,6 @@ import lodashFindLast from 'lodash/findLast'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {filterOutDeprecatedReportActions, getSortedReportActions} from '@libs/ReportActionsUtils'; +import {filterOutDeprecatedReportActions, getLinkedTransactionID, getSortedReportActions, isActionOfType} from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction, ReportActions} from '@src/types/onyx'; @@ -55,4 +55,48 @@ function getReportActionByIDSelector(reportActions: OnyxEntry, re return reportActions[reportActionID]; } -export {getParentReportActionSelector, getLastClosedReportAction, getReportActionsForReportIDs, getReportActionByIDSelector}; +/** + * Finds the IOU action associated with a RECEIPT_SCAN_FAILED report action. + * Prefer parentReportActionID (specific IOU action when `report` is a transaction thread). + * Fall back to childReportID match, then to the only IOU action for one-transaction reports. + */ +function findIOUActionForReceiptScanFailed( + reportActions: OnyxEntry, + isIOUReport: boolean, + parentReportActionID: string | undefined, + actionReportID: string | undefined, +): OnyxEntry { + if (!isIOUReport && parentReportActionID) { + const candidate = reportActions?.[parentReportActionID]; + if (isActionOfType(candidate, CONST.REPORT.ACTIONS.TYPE.IOU)) { + return candidate; + } + } + const IOUActions = Object.values(reportActions ?? {}).filter((action): action is ReportAction => + isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.IOU), + ); + if (actionReportID) { + const match = IOUActions.find((action) => action.childReportID === actionReportID); + if (match) { + return match; + } + } + return IOUActions.length === 1 ? IOUActions.at(0) : undefined; +} + +/** Resolves the IOU action for a RECEIPT_SCAN_FAILED message and returns only the primitive fields the UI needs. */ +function getReceiptScanFailedIOUActionDataSelector( + reportActions: OnyxEntry, + isIOUReport: boolean, + parentReportActionID: string | undefined, + actionReportID: string | undefined, +): {transactionID: string | undefined; actorAccountID: number | undefined} { + const IOUAction = findIOUActionForReceiptScanFailed(reportActions, isIOUReport, parentReportActionID, actionReportID); + + return { + transactionID: getLinkedTransactionID(IOUAction), + actorAccountID: IOUAction?.actorAccountID, + }; +} + +export {getParentReportActionSelector, getLastClosedReportAction, getReportActionsForReportIDs, getReportActionByIDSelector, getReceiptScanFailedIOUActionDataSelector}; diff --git a/tests/ui/ChatActionableButtonsTest.tsx b/tests/ui/ChatActionableButtonsTest.tsx new file mode 100644 index 000000000000..c423d263476f --- /dev/null +++ b/tests/ui/ChatActionableButtonsTest.tsx @@ -0,0 +1,101 @@ +import {act, render, screen} from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import ComposeProviders from '@components/ComposeProviders'; +import {LocaleContextProvider} from '@components/LocaleContextProvider'; +import OnyxListItemProvider from '@components/OnyxListItemProvider'; +import ChatActionableButtons from '@pages/inbox/report/actionContents/ChatActionableButtons'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report, ReportAction} from '@src/types/onyx'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; +import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; + +const PARENT_REPORT_ID = 'parent-report-100'; +const THREAD_REPORT_ID = 'thread-report-200'; +const CURRENT_USER_ACCOUNT_ID = 1; + +function createConciergeCategoryOptionsAction(options: string[]): ReportAction { + return { + reportActionID: 'concierge-category-action-1', + actorAccountID: CURRENT_USER_ACCOUNT_ID, + created: '2025-07-12 09:03:17.653', + actionName: CONST.REPORT.ACTIONS.TYPE.CONCIERGE_CATEGORY_OPTIONS, + automatic: true, + shouldShow: true, + avatar: '', + person: [{type: 'TEXT', style: 'strong', text: 'Concierge'}], + message: [{type: 'COMMENT', html: 'Pick a category', text: 'Pick a category'}], + originalMessage: { + options, + }, + } as ReportAction; +} + +function renderChatActionableButtons(action: ReportAction, originalReportID: string | undefined, reportID: string | undefined) { + return render( + + + , + ); +} + +describe('ChatActionableButtons', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + }); + }); + + beforeEach(async () => { + wrapOnyxWithWaitForBatchedUpdates(Onyx); + await act(async () => { + await Onyx.merge(ONYXKEYS.SESSION, {accountID: CURRENT_USER_ACCOUNT_ID, email: 'test@test.com'}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [CURRENT_USER_ACCOUNT_ID]: { + accountID: CURRENT_USER_ACCOUNT_ID, + login: 'test@test.com', + displayName: 'Test User', + }, + }); + }); + await waitForBatchedUpdatesWithAct(); + }); + + afterEach(async () => { + await act(async () => { + await Onyx.clear(); + }); + await waitForBatchedUpdatesWithAct(); + }); + + it('renders concierge category buttons using visible report when original report is not in Onyx', async () => { + const action = createConciergeCategoryOptionsAction(['Travel', 'Meals']); + + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${THREAD_REPORT_ID}`, { + reportID: THREAD_REPORT_ID, + type: CONST.REPORT.TYPE.CHAT, + } as Report); + }); + + renderChatActionableButtons(action, PARENT_REPORT_ID, THREAD_REPORT_ID); + await waitForBatchedUpdatesWithAct(); + + expect(screen.getByText('1 - Travel')).toBeOnTheScreen(); + expect(screen.getByText('2 - Meals')).toBeOnTheScreen(); + }); + + it('does not render concierge category buttons when neither original nor visible report is in Onyx', async () => { + const action = createConciergeCategoryOptionsAction(['Travel']); + + renderChatActionableButtons(action, PARENT_REPORT_ID, THREAD_REPORT_ID); + await waitForBatchedUpdatesWithAct(); + + expect(screen.queryByText('1 - Travel')).not.toBeOnTheScreen(); + }); +}); diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index 242b61379145..847c4f1256e0 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -44,6 +44,14 @@ jest.mock('@hooks/useCardFeedsForDisplay', () => jest.fn(() => ({defaultCardFeed const ACTOR_ACCOUNT_ID = 123456789; const actorEmail = 'test@test.com'; +const testPersonalDetailsList = { + [ACTOR_ACCOUNT_ID]: { + accountID: ACTOR_ACCOUNT_ID, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_9.png', + displayName: actorEmail, + login: actorEmail, + }, +}; const createReportAction = (actionName: ReportActionName, originalMessageExtras: Partial>) => ({ @@ -595,6 +603,10 @@ describe('PureReportActionItem', () => { type: CONST.REPORT.TYPE.CHAT, }; + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + }); + render( @@ -602,6 +614,7 @@ describe('PureReportActionItem', () => { { type: CONST.REPORT.TYPE.CHAT, }; + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + }); + render( @@ -658,6 +675,7 @@ describe('PureReportActionItem', () => { { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1470,6 +1489,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1518,6 +1538,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1554,6 +1575,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1564,7 +1586,7 @@ describe('PureReportActionItem', () => { expect(screen.queryByText(translateLocal('bankAccount.addBankAccount'))).toBeNull(); expect(screen.queryByText(translateLocal('iou.enableWallet'))).toBeNull(); - expect(screen.getByText(translateLocal('iou.waitingOnBankAccount', 'Hidden'))).toBeOnTheScreen(); + expect(screen.getByText(translateLocal('iou.waitingOnBankAccount', actorEmail))).toBeOnTheScreen(); }); it('REIMBURSEMENT_QUEUED with a Gold wallet tier hides the enable wallet button', async () => { @@ -1594,6 +1616,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1603,7 +1626,7 @@ describe('PureReportActionItem', () => { await waitForBatchedUpdatesWithAct(); expect(screen.queryByText(translateLocal('iou.enableWallet'))).toBeNull(); - expect(screen.getByText(translateLocal('iou.waitingOnEnabledWallet', 'Hidden'))).toBeOnTheScreen(); + expect(screen.getByText(translateLocal('iou.waitingOnEnabledWallet', actorEmail))).toBeOnTheScreen(); }); it('IOU PAY VBBA manual renders business bank account message with last 4 digits', async () => { @@ -2564,7 +2587,18 @@ describe('PureReportActionItem', () => { expect(screen.getByTestId('MoneyRequestReportPreviewContent-wrapper')).toBeOnTheScreen(); }); + const HARVEST_REPORT_ID = 'harvestReport123'; + const ORIGINAL_REPORT_ID = 'origReport123'; + it('CREATED harvest renders CreateHarvestReportAction via BasicMessage', async () => { + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${HARVEST_REPORT_ID}`, { + origin: 'harvest', + originalID: ORIGINAL_REPORT_ID, + }); + }); + await waitForBatchedUpdatesWithAct(); + const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.CREATED, {}); render( @@ -2572,15 +2606,14 @@ describe('PureReportActionItem', () => { @@ -2592,12 +2625,16 @@ describe('PureReportActionItem', () => { expect(screen.getByText(/created this report to hold/i)).toBeOnTheScreen(); // When the original report is not loaded in Onyx, the link must fall back to "#" // instead of rendering an empty hyperlink (regression guard for issue #90422). - expect(screen.getByText('#origReport123')).toBeOnTheScreen(); + expect(screen.getByText(`#${ORIGINAL_REPORT_ID}`)).toBeOnTheScreen(); }); it('CREATED harvest renders the original report name when it is loaded in Onyx', async () => { await act(async () => { - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}origReport123`, { - reportID: 'origReport123', + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${HARVEST_REPORT_ID}`, { + origin: 'harvest', + originalID: ORIGINAL_REPORT_ID, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${ORIGINAL_REPORT_ID}`, { + reportID: ORIGINAL_REPORT_ID, reportName: 'Q1 Expenses', }); }); @@ -2610,15 +2647,14 @@ describe('PureReportActionItem', () => { @@ -2629,7 +2665,7 @@ describe('PureReportActionItem', () => { expect(screen.getByText(/created this report to hold/i)).toBeOnTheScreen(); expect(screen.getByText('Q1 Expenses')).toBeOnTheScreen(); - expect(screen.queryByText('#origReport123')).not.toBeOnTheScreen(); + expect(screen.queryByText(`#${ORIGINAL_REPORT_ID}`)).not.toBeOnTheScreen(); }); it('isWhisperActionTargetedToOthers returns null', async () => { await act(async () => { @@ -2717,7 +2753,7 @@ describe('PureReportActionItem', () => { // Path: No FE flow creates IOU + reject/cancel/delete/approve actions; this defensive path is only // reachable from BE-emitted actions - describe('IouReportActionMessage edge subtypes', () => { + describe('IOUReportActionMessage edge subtypes', () => { const TEST_REPORT_ID = 'iouEdgeReport123'; const TEST_TRANSACTION_ID = 'iouEdgeTx456'; diff --git a/tests/ui/ReportActionItemMessageEditTest.tsx b/tests/ui/ReportActionItemMessageEditTest.tsx index 589e6513aa1f..bae83f035db0 100644 --- a/tests/ui/ReportActionItemMessageEditTest.tsx +++ b/tests/ui/ReportActionItemMessageEditTest.tsx @@ -71,7 +71,6 @@ const defaultProps: ReportActionItemMessageEditProps = { reportID: defaultReport.reportID, originalReportID: defaultReport.reportID, index: 0, - isGroupPolicyReport: false, }; function ReportActionEditMessageContextProviderForReport({children}: PropsWithChildren) { diff --git a/tests/ui/ReportActionMessageEditLayoutTest.tsx b/tests/ui/ReportActionMessageEditLayoutTest.tsx index c3fbb0b6d199..90be86b546a4 100644 --- a/tests/ui/ReportActionMessageEditLayoutTest.tsx +++ b/tests/ui/ReportActionMessageEditLayoutTest.tsx @@ -160,7 +160,6 @@ function MessageEditLayoutHost({layout}: {layout: LayoutMode}) { reportID={defaultReport.reportID} originalReportID={defaultReport.reportID} index={0} - isGroupPolicyReport={false} /> )} diff --git a/tests/unit/WhisperContentMentionContextTest.tsx b/tests/unit/WhisperContentMentionContextTest.tsx index 1887c6a01eb3..4dc39778e597 100644 --- a/tests/unit/WhisperContentMentionContextTest.tsx +++ b/tests/unit/WhisperContentMentionContextTest.tsx @@ -95,8 +95,7 @@ describe('Whisper content components provide MentionReportContext so room mentio } reportID={REPORT_ID} - report={report} - originalReport={undefined} + actionOwnerReport={report} /> , ); @@ -115,8 +114,7 @@ describe('Whisper content components provide MentionReportContext so room mentio action={action as ReportAction} reportID={REPORT_ID} originalReportID={undefined} - report={report} - originalReport={undefined} + actionOwnerReport={report} /> , ); diff --git a/tests/unit/selectors/ReportActionTest.ts b/tests/unit/selectors/ReportActionTest.ts new file mode 100644 index 000000000000..4e938c7a7951 --- /dev/null +++ b/tests/unit/selectors/ReportActionTest.ts @@ -0,0 +1,147 @@ +import {getReceiptScanFailedIOUActionDataSelector} from '@selectors/ReportAction'; +import CONST from '@src/CONST'; +import type {ReportAction, ReportActions} from '@src/types/onyx'; + +const CURRENT_USER_ACCOUNT_ID = 123456789; +const OTHER_USER_ACCOUNT_ID = 987654321; +const TRANSACTION_ID = 'txn123'; +const OTHER_TRANSACTION_ID = 'txn456'; + +const createIOUAction = (reportActionID: string, overrides: Partial = {}): ReportAction => ({ + reportActionID, + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + actorAccountID: CURRENT_USER_ACCOUNT_ID, + created: '2025-01-01 00:00:00.000', + message: [{type: 'COMMENT', html: '', text: ''}], + originalMessage: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 100, + currency: 'USD', + IOUTransactionID: TRANSACTION_ID, + }, + ...overrides, +}); + +describe('getReceiptScanFailedIOUActionDataSelector', () => { + it('returns undefined transactionID and actorAccountID when reportActions is undefined', () => { + expect(getReceiptScanFailedIOUActionDataSelector(undefined, false, 'iou1', 'thread1')).toEqual({ + transactionID: undefined, + actorAccountID: undefined, + }); + }); + + it('returns transactionID and actorAccountID for the IOU action matching parentReportActionID on a transaction thread report', () => { + const reportActions: ReportActions = { + iou1: createIOUAction('iou1'), + other: createIOUAction('other', { + childReportID: 'thread1', + originalMessage: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 100, + currency: 'USD', + IOUTransactionID: OTHER_TRANSACTION_ID, + }, + }), + }; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'iou1', 'thread1')).toEqual({ + transactionID: TRANSACTION_ID, + actorAccountID: CURRENT_USER_ACCOUNT_ID, + }); + }); + + it('skips parentReportActionID lookup when the report is an IOU report', () => { + const reportActions: ReportActions = { + parentIOU: createIOUAction('parentIOU'), + childMatch: createIOUAction('childMatch', { + childReportID: 'thread1', + originalMessage: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 100, + currency: 'USD', + IOUTransactionID: OTHER_TRANSACTION_ID, + }, + }), + }; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, true, 'parentIOU', 'thread1')).toEqual({ + transactionID: OTHER_TRANSACTION_ID, + actorAccountID: CURRENT_USER_ACCOUNT_ID, + }); + }); + + it('falls back to childReportID match when parentReportActionID does not point to an IOU action', () => { + const reportActions: ReportActions = { + comment1: createIOUAction('comment1', {actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT}), + iou1: createIOUAction('iou1', {childReportID: 'thread1'}), + }; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'comment1', 'thread1')).toEqual({ + transactionID: TRANSACTION_ID, + actorAccountID: CURRENT_USER_ACCOUNT_ID, + }); + }); + + it('returns transactionID and actorAccountID for the IOU action whose childReportID matches actionReportID', () => { + const reportActions: ReportActions = { + iou1: createIOUAction('iou1', {childReportID: 'thread1'}), + iou2: createIOUAction('iou2', { + childReportID: 'thread2', + originalMessage: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 100, + currency: 'USD', + IOUTransactionID: OTHER_TRANSACTION_ID, + }, + }), + }; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, true, undefined, 'thread1')).toEqual({ + transactionID: TRANSACTION_ID, + actorAccountID: CURRENT_USER_ACCOUNT_ID, + }); + }); + + it('returns transactionID and actorAccountID for the only IOU action on one-transaction reports', () => { + const reportActions: ReportActions = {iou1: createIOUAction('iou1')}; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, true, undefined, undefined)).toEqual({ + transactionID: TRANSACTION_ID, + actorAccountID: CURRENT_USER_ACCOUNT_ID, + }); + }); + + it('returns undefined transactionID and actorAccountID when multiple IOU actions exist and none match actionReportID', () => { + const reportActions: ReportActions = { + iou1: createIOUAction('iou1', {childReportID: 'thread1'}), + iou2: createIOUAction('iou2', {childReportID: 'thread2'}), + }; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, true, undefined, 'thread3')).toEqual({ + transactionID: undefined, + actorAccountID: undefined, + }); + }); + + it('returns undefined transactionID and actorAccountID when no IOU actions exist', () => { + const reportActions: ReportActions = { + comment1: createIOUAction('comment1', {actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT}), + }; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'comment1', 'thread1')).toEqual({ + transactionID: undefined, + actorAccountID: undefined, + }); + }); + + it('returns actorAccountID for the matched IOU action when it belongs to another user', () => { + const reportActions: ReportActions = { + iou1: createIOUAction('iou1', {actorAccountID: OTHER_USER_ACCOUNT_ID}), + }; + + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'iou1', 'thread1')).toEqual({ + transactionID: TRANSACTION_ID, + actorAccountID: OTHER_USER_ACCOUNT_ID, + }); + }); +});