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,
+ });
+ });
+});