From 9f353461fda453531395fca79f7d48c423fc0cc0 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 6 May 2026 11:41:16 +0200 Subject: [PATCH 01/23] avoid userBillingFundID prop drilling --- .../MoneyRequestReportActionsList.tsx | 3 --- src/components/Search/SearchList/ListItem/ChatListItem.tsx | 2 -- src/components/Search/SearchList/ListItem/types.ts | 3 --- src/components/Search/SearchList/index.tsx | 3 --- .../Debug/ReportAction/DebugReportActionCreatePage.tsx | 2 -- src/pages/Debug/ReportAction/DebugReportActionPreview.tsx | 2 -- .../TransactionDuplicate/DuplicateTransactionItem.tsx | 2 -- src/pages/inbox/report/PureReportActionItem.tsx | 6 ------ src/pages/inbox/report/ReportActionItem.tsx | 5 ----- src/pages/inbox/report/ReportActionItemParentAction.tsx | 5 ----- src/pages/inbox/report/ReportActionsList.tsx | 3 --- src/pages/inbox/report/ReportActionsListItemRenderer.tsx | 6 ------ .../inbox/report/actionContents/ActionContentRouter.tsx | 5 ----- .../inbox/report/actionContents/ChatActionableButtons.tsx | 7 ++++--- .../inbox/report/actionContents/ChatMessageContent.tsx | 3 --- 15 files changed, 4 insertions(+), 53 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 716966b53d3e..e865683c889a 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -126,7 +126,6 @@ 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; @@ -579,7 +578,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) shouldHideThreadDividerLine linkedReportActionID={linkedReportActionID} personalDetails={personalDetails} - userBillingFundID={userBillingFundID} originalReportID={originalReportID} isReportArchived={isReportArchived} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} @@ -599,7 +597,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) firstVisibleReportActionID, linkedReportActionID, personalDetails, - userBillingFundID, isTryNewDotNVPDismissed, isReportArchived, reportNameValuePairs?.origin, diff --git a/src/components/Search/SearchList/ListItem/ChatListItem.tsx b/src/components/Search/SearchList/ListItem/ChatListItem.tsx index ee913d7a3e27..c1749cd8654f 100644 --- a/src/components/Search/SearchList/ListItem/ChatListItem.tsx +++ b/src/components/Search/SearchList/ListItem/ChatListItem.tsx @@ -26,7 +26,6 @@ function ChatListItem({ onLongPressRow, shouldSyncFocus, personalDetails, - userBillingFundID, }: ChatListItemProps) { const reportActionItem = item as unknown as ReportActionListItemType; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportActionItem?.reportID}`); @@ -86,7 +85,6 @@ function ChatListItem({ shouldShowDraftMessage={false} shouldShowBorder personalDetails={personalDetails} - userBillingFundID={userBillingFundID} /> ); diff --git a/src/components/Search/SearchList/ListItem/types.ts b/src/components/Search/SearchList/ListItem/types.ts index 922dfe03095b..0c913f1b9c2e 100644 --- a/src/components/Search/SearchList/ListItem/types.ts +++ b/src/components/Search/SearchList/ListItem/types.ts @@ -53,9 +53,6 @@ type ChatListItemProps = ListItemProps & { /** Personal details list */ personalDetails: OnyxEntry; - - /** User billing fund ID */ - userBillingFundID: number | undefined; }; type ExpenseReportListItemProps = ListItemProps & diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index 09ee7cbf950d..4bdf0e4d8a79 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -302,7 +302,6 @@ function SearchList({ const hasItemsBeingRemoved = prevDataLength && prevDataLength > data.length; const personalDetails = usePersonalDetails(); - const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); @@ -453,7 +452,6 @@ function SearchList({ userBillingGracePeriodEnds={userBillingGracePeriodEnds} ownerBillingGracePeriodEnd={ownerBillingGracePeriodEnd} personalDetails={personalDetails} - userBillingFundID={userBillingFundID} isOffline={isOffline} violations={violations} nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards} @@ -483,7 +481,6 @@ function SearchList({ canSelectMultiple, columns, personalDetails, - userBillingFundID, isOffline, violations, lastPaymentMethod, diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index cc68738eef12..91d96093923f 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -49,7 +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; @@ -119,7 +118,6 @@ function DebugReportActionCreatePage({ isFirstVisibleReportAction={false} shouldDisplayContextMenu={false} personalDetails={personalDetailsList} - userBillingFundID={userBillingFundID} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ) : ( diff --git a/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx b/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx index e93df5acead1..f5e409851ca4 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx @@ -17,7 +17,6 @@ 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; @@ -34,7 +33,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..df0d1799b165 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -25,7 +25,6 @@ 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); @@ -73,7 +72,6 @@ function DuplicateTransactionItem({transaction, index, onPreviewPressed}: Duplic personalDetails={personalDetails} draftMessage={matchingDraftMessage} linkedTransactionRouteError={linkedTransactionRouteError} - userBillingFundID={userBillingFundID} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index 4c1e1eb5ca2f..c047ad9e75d0 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -159,9 +159,6 @@ type PureReportActionItemProps = { /** 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; @@ -202,7 +199,6 @@ function PureReportActionItem({ originalReport, isArchivedRoom, isClosedExpenseReportWithNoExpenses, - userBillingFundID, shouldShowBorder, shouldHighlight = false, isTryNewDotNVPDismissed = false, @@ -627,7 +623,6 @@ function PureReportActionItem({ isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} shouldShowBorder={shouldShowBorder} isOnSearch={isOnSearch} - userBillingFundID={userBillingFundID} index={index} setIsPaymentMethodPopoverActive={setIsPaymentMethodPopoverActive} /> @@ -715,7 +710,6 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { 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 diff --git a/src/pages/inbox/report/ReportActionItem.tsx b/src/pages/inbox/report/ReportActionItem.tsx index fb3db75190f3..45cd50bf7438 100644 --- a/src/pages/inbox/report/ReportActionItem.tsx +++ b/src/pages/inbox/report/ReportActionItem.tsx @@ -21,9 +21,6 @@ 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; }; @@ -33,7 +30,6 @@ function ReportActionItem({ report, draftMessage, personalDetails, - userBillingFundID, linkedTransactionRouteError: linkedTransactionRouteErrorProp, isTryNewDotNVPDismissed, ...props @@ -70,7 +66,6 @@ function ReportActionItem({ originalReport={originalReport} isArchivedRoom={isArchivedNonExpenseReport(originalReport, isOriginalReportArchived)} isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses(iouReport, transactionsOnIOUReport)} - userBillingFundID={userBillingFundID} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ); diff --git a/src/pages/inbox/report/ReportActionItemParentAction.tsx b/src/pages/inbox/report/ReportActionItemParentAction.tsx index dfd306bf5704..5c1d898d79c2 100644 --- a/src/pages/inbox/report/ReportActionItemParentAction.tsx +++ b/src/pages/inbox/report/ReportActionItemParentAction.tsx @@ -63,9 +63,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; @@ -84,7 +81,6 @@ function ReportActionItemParentAction({ isFirstVisibleReportAction = false, shouldUseThreadDividerLine = false, personalDetails, - userBillingFundID, isTryNewDotNVPDismissed = false, isReportArchived = false, }: ReportActionItemParentActionProps) { @@ -229,7 +225,6 @@ function ReportActionItemParentAction({ personalDetails={personalDetails} draftMessage={matchingDraftMessageString} linkedTransactionRouteError={linkedTransactionRouteError} - userBillingFundID={userBillingFundID} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index ad9887e37cb6..f9ec73bdc12e 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -209,7 +209,6 @@ function ReportActionsList({ const isAnonymousUser = useIsAnonymousUser(); const isReportArchived = useReportIsArchived(report?.reportID); const [reportActionsFromOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${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); @@ -800,7 +799,6 @@ function ReportActionsList({ personalDetails={personalDetailsList} originalReportID={originalReportID} isReportArchived={isReportArchived} - userBillingFundID={userBillingFundID} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} reportNameValuePairsOrigin={reportNameValuePairs?.origin} reportNameValuePairsOriginalID={reportNameValuePairs?.originalID} @@ -829,7 +827,6 @@ function ReportActionsList({ firstVisibleReportActionID, shouldUseThreadDividerLine, personalDetailsList, - userBillingFundID, isTryNewDotNVPDismissed, isReportArchived, reportNameValuePairs?.origin, diff --git a/src/pages/inbox/report/ReportActionsListItemRenderer.tsx b/src/pages/inbox/report/ReportActionsListItemRenderer.tsx index a08b7ba8281b..63bf936cdc0b 100644 --- a/src/pages/inbox/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/inbox/report/ReportActionsListItemRenderer.tsx @@ -58,9 +58,6 @@ type ReportActionsListItemRendererProps = { /** 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 */ @@ -89,7 +86,6 @@ function ReportActionsListItemRenderer({ shouldHighlight = false, parentReportActionForTransactionThread, originalReportID, - userBillingFundID, personalDetails, isTryNewDotNVPDismissed = false, isReportArchived = false, @@ -188,7 +184,6 @@ function ReportActionsListItemRenderer({ isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} personalDetails={personalDetails} - userBillingFundID={userBillingFundID} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} isReportArchived={isReportArchived} /> @@ -212,7 +207,6 @@ function ReportActionsListItemRenderer({ shouldHighlight={shouldHighlight} personalDetails={personalDetails} draftMessage={draftMessage} - userBillingFundID={userBillingFundID} isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} reportNameValuePairsOrigin={reportNameValuePairsOrigin} reportNameValuePairsOriginalID={reportNameValuePairsOriginalID} diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index f2c69de69234..929b593dd9e8 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -127,9 +127,6 @@ type ActionContentRouterProps = { /** 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; @@ -159,7 +156,6 @@ function ActionContentRouter({ isTryNewDotNVPDismissed, shouldShowBorder, isOnSearch, - userBillingFundID, index, setIsPaymentMethodPopoverActive, }: ActionContentRouterProps): React.JSX.Element | null { @@ -497,7 +493,6 @@ function ActionContentRouter({ updateHiddenState={updateHiddenState} isArchivedRoom={isArchivedRoom} isOnSearch={isOnSearch} - userBillingFundID={userBillingFundID} /> ); } diff --git a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx index 979b884a7b97..5b06ee29135d 100644 --- a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx +++ b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx @@ -27,6 +27,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'; @@ -39,10 +40,9 @@ type ChatActionableButtonsProps = { originalReport: OnyxEntry; reportID: string | undefined; originalReportID: string; - userBillingFundID: number | undefined; }; -function ChatActionableButtons({action, report, originalReport, reportID, originalReportID, userBillingFundID}: ChatActionableButtonsProps) { +function ChatActionableButtons({action, report, originalReport, reportID, originalReportID}: ChatActionableButtonsProps) { const styles = useThemeStyles(); const personalDetail = useCurrentUserPersonalDetails(); const {isRestrictedToPreferredPolicy, preferredPolicyID} = usePreferredPolicy(); @@ -50,6 +50,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin 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 +59,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', diff --git a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx index 36ee7e9e3840..0246d0ae0b6a 100644 --- a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx @@ -38,7 +38,6 @@ type ChatMessageContentProps = { updateHiddenState: (isHiddenValue: boolean) => void; isArchivedRoom?: boolean; isOnSearch: boolean; - userBillingFundID: number | undefined; }; function ChatMessageContent({ @@ -54,7 +53,6 @@ function ChatMessageContent({ updateHiddenState, isArchivedRoom, isOnSearch, - userBillingFundID, }: ChatMessageContentProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -108,7 +106,6 @@ function ChatMessageContent({ originalReport={originalReport} reportID={reportID} originalReportID={originalReportID} - userBillingFundID={userBillingFundID} /> )} From 0927cac4ff2b6f5ae4b855661412a9fe39c31734 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 6 May 2026 12:26:24 +0200 Subject: [PATCH 02/23] avoid isTryNewDotNVPDismissed nad personalDetails prop drilling --- .../MoneyRequestReportActionsList.tsx | 4 --- .../DebugReportActionCreatePage.tsx | 3 -- .../ReportAction/DebugReportActionPreview.tsx | 3 -- .../DuplicateTransactionItem.tsx | 3 -- .../inbox/report/PureReportActionItem.tsx | 6 ---- src/pages/inbox/report/ReportActionItem.tsx | 14 +--------- .../report/ReportActionItemParentAction.tsx | 5 ---- src/pages/inbox/report/ReportActionsList.tsx | 4 --- .../report/ReportActionsListItemRenderer.tsx | 5 ---- .../actionContents/ActionContentRouter.tsx | 18 ++++-------- .../IntegrationSyncFailedMessage.tsx | 28 +++++++++++++++++++ .../ReimbursementQueuedContent.tsx | 9 +++--- tests/ui/PureReportActionItemTest.tsx | 4 +-- 13 files changed, 40 insertions(+), 66 deletions(-) create mode 100644 src/pages/inbox/report/actionContents/IntegrationSyncFailedMessage.tsx diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index e865683c889a..4d9ff089d208 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -127,8 +127,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) const parentReportAction = useParentReportAction(report); 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); // reportActions is passed as an array because it's sorted chronologically for FlatList rendering and pagination. @@ -580,7 +578,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) personalDetails={personalDetails} originalReportID={originalReportID} isReportArchived={isReportArchived} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} reportNameValuePairsOrigin={reportNameValuePairs?.origin} reportNameValuePairsOriginalID={reportNameValuePairs?.originalID} /> @@ -597,7 +594,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) firstVisibleReportActionID, linkedReportActionID, personalDetails, - isTryNewDotNVPDismissed, isReportArchived, reportNameValuePairs?.origin, reportNameValuePairs?.originalID, diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index 91d96093923f..40c29b3a6a5b 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -49,8 +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 [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); - const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; const [error, setError] = useState(); @@ -118,7 +116,6 @@ function DebugReportActionCreatePage({ isFirstVisibleReportAction={false} shouldDisplayContextMenu={false} personalDetails={personalDetailsList} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ) : ( {translate('debug.nothingToPreview')} diff --git a/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx b/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx index f5e409851ca4..f9b3213f477c 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionPreview.tsx @@ -18,8 +18,6 @@ type DebugReportActionPreviewProps = { function DebugReportActionPreview({reportAction, reportID}: DebugReportActionPreviewProps) { const personalDetails = usePersonalDetails(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); - const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed; return ( @@ -33,7 +31,6 @@ function DebugReportActionPreview({reportAction, reportID}: DebugReportActionPre isFirstVisibleReportAction={false} shouldDisplayContextMenu={false} personalDetails={personalDetails} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ); diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index df0d1799b165..474e0749d59d 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -27,8 +27,6 @@ function DuplicateTransactionItem({transaction, index, onPreviewPressed}: Duplic 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; @@ -72,7 +70,6 @@ function DuplicateTransactionItem({transaction, index, onPreviewPressed}: Duplic personalDetails={personalDetails} draftMessage={matchingDraftMessage} linkedTransactionRouteError={linkedTransactionRouteError} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index c047ad9e75d0..64efcbde9519 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -165,9 +165,6 @@ type PureReportActionItemProps = { /** 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; @@ -201,7 +198,6 @@ function PureReportActionItem({ isClosedExpenseReportWithNoExpenses, shouldShowBorder, shouldHighlight = false, - isTryNewDotNVPDismissed = false, reportNameValuePairsOrigin, reportNameValuePairsOriginalID, }: PureReportActionItemProps) { @@ -619,8 +615,6 @@ function PureReportActionItem({ isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses} isHarvestCreatedExpenseReport={isHarvestCreatedExpenseReport} reportNameValuePairsOriginalID={reportNameValuePairsOriginalID} - personalDetails={personalDetails} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} shouldShowBorder={shouldShowBorder} isOnSearch={isOnSearch} index={index} diff --git a/src/pages/inbox/report/ReportActionItem.tsx b/src/pages/inbox/report/ReportActionItem.tsx index 45cd50bf7438..78464a4fd6e4 100644 --- a/src/pages/inbox/report/ReportActionItem.tsx +++ b/src/pages/inbox/report/ReportActionItem.tsx @@ -20,20 +20,9 @@ type ReportActionItemProps = PureReportActionItemProps & { /** Personal details list */ personalDetails: OnyxEntry; - - /** Did the user dismiss trying out NewDot? If true, it means they prefer using OldDot */ - isTryNewDotNVPDismissed?: boolean; }; -function ReportActionItem({ - action, - report, - draftMessage, - personalDetails, - linkedTransactionRouteError: linkedTransactionRouteErrorProp, - isTryNewDotNVPDismissed, - ...props -}: ReportActionItemProps) { +function ReportActionItem({action, report, draftMessage, personalDetails, linkedTransactionRouteError: linkedTransactionRouteErrorProp, ...props}: ReportActionItemProps) { const reportID = report?.reportID; const originalReportID = useOriginalReportID(reportID, action); const isOriginalReportArchived = useReportIsArchived(originalReportID); @@ -66,7 +55,6 @@ function ReportActionItem({ originalReport={originalReport} isArchivedRoom={isArchivedNonExpenseReport(originalReport, isOriginalReportArchived)} isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses(iouReport, transactionsOnIOUReport)} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ); } diff --git a/src/pages/inbox/report/ReportActionItemParentAction.tsx b/src/pages/inbox/report/ReportActionItemParentAction.tsx index 5c1d898d79c2..cbebb4793c98 100644 --- a/src/pages/inbox/report/ReportActionItemParentAction.tsx +++ b/src/pages/inbox/report/ReportActionItemParentAction.tsx @@ -63,9 +63,6 @@ type ReportActionItemParentActionProps = { /** Personal details list */ personalDetails: OnyxEntry; - /** 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; }; @@ -81,7 +78,6 @@ function ReportActionItemParentAction({ isFirstVisibleReportAction = false, shouldUseThreadDividerLine = false, personalDetails, - isTryNewDotNVPDismissed = false, isReportArchived = false, }: ReportActionItemParentActionProps) { const styles = useThemeStyles(); @@ -225,7 +221,6 @@ function ReportActionItemParentAction({ personalDetails={personalDetails} draftMessage={matchingDraftMessageString} linkedTransactionRouteError={linkedTransactionRouteError} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} /> ); diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index f9ec73bdc12e..276a1cc50bc9 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -209,8 +209,6 @@ function ReportActionsList({ const isAnonymousUser = useIsAnonymousUser(); const isReportArchived = useReportIsArchived(report?.reportID); const [reportActionsFromOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`); - 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(''); @@ -799,7 +797,6 @@ function ReportActionsList({ personalDetails={personalDetailsList} originalReportID={originalReportID} isReportArchived={isReportArchived} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} reportNameValuePairsOrigin={reportNameValuePairs?.origin} reportNameValuePairsOriginalID={reportNameValuePairs?.originalID} /> @@ -827,7 +824,6 @@ function ReportActionsList({ firstVisibleReportActionID, shouldUseThreadDividerLine, personalDetailsList, - isTryNewDotNVPDismissed, isReportArchived, reportNameValuePairs?.origin, reportNameValuePairs?.originalID, diff --git a/src/pages/inbox/report/ReportActionsListItemRenderer.tsx b/src/pages/inbox/report/ReportActionsListItemRenderer.tsx index 63bf936cdc0b..31198c18a0b9 100644 --- a/src/pages/inbox/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/inbox/report/ReportActionsListItemRenderer.tsx @@ -58,8 +58,6 @@ type ReportActionsListItemRendererProps = { /** Personal details list */ personalDetails: OnyxEntry; - /** 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; @@ -87,7 +85,6 @@ function ReportActionsListItemRenderer({ parentReportActionForTransactionThread, originalReportID, personalDetails, - isTryNewDotNVPDismissed = false, isReportArchived = false, reportNameValuePairsOrigin, reportNameValuePairsOriginalID, @@ -184,7 +181,6 @@ function ReportActionsListItemRenderer({ isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} personalDetails={personalDetails} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} isReportArchived={isReportArchived} /> ); @@ -207,7 +203,6 @@ function ReportActionsListItemRenderer({ shouldHighlight={shouldHighlight} personalDetails={personalDetails} draftMessage={draftMessage} - isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} reportNameValuePairsOrigin={reportNameValuePairsOrigin} reportNameValuePairsOriginalID={reportNameValuePairsOriginalID} /> diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index 929b593dd9e8..9d5efb0b56a0 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,6 +51,7 @@ 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'; @@ -115,12 +115,6 @@ type ActionContentRouterProps = { /** 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; @@ -152,8 +146,6 @@ function ActionContentRouter({ isClosedExpenseReportWithNoExpenses, isHarvestCreatedExpenseReport, reportNameValuePairsOriginalID, - personalDetails, - isTryNewDotNVPDismissed, shouldShowBorder, isOnSearch, index, @@ -247,7 +239,6 @@ function ActionContentRouter({ action={action} report={report} iouReport={iouReport} - personalDetails={personalDetails} /> ); } @@ -435,9 +426,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)) { 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/ReimbursementQueuedContent.tsx b/src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx index 51ff7a5650af..791bb9cc99ff 100644 --- a/src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx @@ -5,6 +5,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import Button from '@components/Button'; import KYCWall from '@components/KYCWall'; import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; +import {usePersonalDetails} from '@components/OnyxListItemProvider'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -18,7 +19,7 @@ import {openPersonalBankAccountSetupView} from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Report, ReportAction} from '@src/types/onyx'; +import type {Report, ReportAction} from '@src/types/onyx'; type ReimbursementQueuedContentProps = { /** The reimbursement queued action */ @@ -29,15 +30,13 @@ type ReimbursementQueuedContentProps = { /** The IOU/Expense report we are paying */ iouReport: OnyxEntry; - - /** Personal details list for resolving the submitter's display name */ - personalDetails: OnyxEntry; }; -function ReimbursementQueuedContent({action, report, iouReport, personalDetails}: ReimbursementQueuedContentProps) { +function ReimbursementQueuedContent({action, report, iouReport}: ReimbursementQueuedContentProps) { const styles = useThemeStyles(); const {translate, formatPhoneNumber} = useLocalize(); const kycWallRef = useContext(KYCWallContext); + const personalDetails = usePersonalDetails(); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID)}`); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index aa133359edca..c692dcb38fe7 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -1564,7 +1564,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 () => { @@ -1603,7 +1603,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 () => { From 066fe9b7529d831c79cb2648e951e690d42d76cc Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 12 May 2026 11:18:18 +0200 Subject: [PATCH 03/23] add actionReport and actionReportID calc in ActionContentRouter --- .../actionContents/ActionContentRouter.tsx | 15 +++++------ .../actionContents/ChatActionableButtons.tsx | 25 ++++++++----------- .../actionContents/ChatMessageContent.tsx | 11 ++++---- .../actionContents/ConfirmWhisperContent.tsx | 10 +++----- .../actionContents/JoinRequestContent.tsx | 10 +++----- .../actionContents/MentionWhisperContent.tsx | 8 +++--- .../ReportMentionWhisperContent.tsx | 12 ++++----- .../unit/WhisperContentMentionContextTest.tsx | 6 ++--- 8 files changed, 44 insertions(+), 53 deletions(-) diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index 9d5efb0b56a0..fd567d638334 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -154,6 +154,9 @@ function ActionContentRouter({ const {translate, formatTravelDate} = useLocalize(); const styles = useThemeStyles(); + const actionReport = originalReport ?? report; + const actionReportID = originalReportID ?? reportID; + 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 @@ -361,8 +364,7 @@ function ActionContentRouter({ return ( ); @@ -382,8 +384,7 @@ function ActionContentRouter({ ); @@ -393,8 +394,7 @@ function ActionContentRouter({ ); @@ -475,7 +475,8 @@ function ActionContentRouter({ ; - originalReport: OnyxEntry; + actionReport: OnyxEntry; + actionReportID: string | undefined; reportID: string | undefined; - originalReportID: string; }; -function ChatActionableButtons({action, report, originalReport, reportID, originalReportID}: ChatActionableButtonsProps) { +function ChatActionableButtons({action, actionReport, actionReportID, reportID}: ChatActionableButtonsProps) { const styles = useThemeStyles(); const personalDetail = useCurrentUserPersonalDetails(); const {isRestrictedToPreferredPolicy, preferredPolicyID} = usePreferredPolicy(); @@ -72,7 +71,6 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin ]; } - const reportActionReport = originalReport ?? report; if (isConciergeCategoryOptions(action)) { const options = getOriginalMessage(action)?.options; if (!options) { @@ -83,7 +81,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin return []; } - if (!reportActionReport) { + if (!actionReport) { return []; } @@ -92,7 +90,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin key: `${action.reportActionID}-conciergeCategoryOptions-${option}`, onPress: () => { resolveConciergeCategoryOptions( - reportActionReport, + actionReport, reportID, action.reportActionID, option, @@ -114,7 +112,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin return []; } - if (!reportActionReport) { + if (!actionReport) { return []; } @@ -123,7 +121,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin key: `${action.reportActionID}-conciergeDescriptionOptions-${option}`, onPress: () => { resolveConciergeDescriptionOptions( - reportActionReport, + actionReport, reportID, action.reportActionID, option, @@ -135,7 +133,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin })); } const messageHtml = getReportActionMessage(action)?.html; - if (messageHtml && reportActionReport) { + if (messageHtml && actionReport) { const followups = parseFollowupsFromHtml(messageHtml); if (followups && followups.length > 0) { return followups.map((followup) => ({ @@ -144,7 +142,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin key: `${action.reportActionID}-followup-${followup.text}`, onPress: () => { resolveSuggestedFollowup( - reportActionReport, + actionReport, reportID, action, followup, @@ -159,9 +157,8 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin } if (isActionableTrackExpense(action)) { - const reportActionReportID = originalReportID ?? reportID; const baseDraftTransactionParams = { - reportID: reportActionReportID, + reportID: actionReportID, reportActionID: action.reportActionID, introSelected, draftTransactionIDs, @@ -199,7 +196,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin text: 'actionableMentionTrackExpense.nothing', key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, onPress: () => { - dismissTrackExpenseActionableWhisper(reportActionReportID, action); + dismissTrackExpenseActionableWhisper(actionReportID, action); }, }); return options; diff --git a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx index 0246d0ae0b6a..3a9ac27fc13b 100644 --- a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx @@ -28,7 +28,8 @@ import ChatActionableButtons from './ChatActionableButtons'; type ChatMessageContentProps = { action: OnyxTypes.ReportAction; report: OnyxEntry; - originalReport: OnyxEntry; + actionReport: OnyxEntry; + actionReportID: string | undefined; reportID: string | undefined; originalReportID: string; displayAsGroup: boolean; @@ -43,7 +44,8 @@ type ChatMessageContentProps = { function ChatMessageContent({ action, report, - originalReport, + actionReport, + actionReportID, reportID, originalReportID, displayAsGroup, @@ -102,10 +104,9 @@ function ChatMessageContent({ {mayHaveActionableButtons && ( )} diff --git a/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx b/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx index 7a2c9d9c4e5b..df5d455563ac 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; + actionReport: OnyxEntry; }; -function ConfirmWhisperContent({action, reportID, originalReportID, report, originalReport}: ConfirmWhisperContentProps) { - const reportActionReport = originalReport ?? report; +function ConfirmWhisperContent({action, reportID, originalReportID, actionReport}: 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(actionReport, action, CONST.REPORT.ACTIONABLE_MENTION_INVITE_TO_SUBMIT_EXPENSE_CONFIRM_WHISPER.DONE, isOriginalReportArchived), isPrimary: true, }, ]; diff --git a/src/pages/inbox/report/actionContents/JoinRequestContent.tsx b/src/pages/inbox/report/actionContents/JoinRequestContent.tsx index 8e5e0b88f8c1..805980f7461d 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; + actionReportID: string | undefined; policyID: string | undefined; }; -function JoinRequestContent({action, reportID, originalReportID, policyID}: JoinRequestContentProps) { +function JoinRequestContent({action, actionReportID, 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(actionReportID, action), isPrimary: true, }, { text: 'actionableMentionJoinWorkspaceOptions.decline', key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - onPress: () => declineJoinRequest(reportActionReportID, action), + onPress: () => declineJoinRequest(actionReportID, action), }, ]; diff --git a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx index 78586ae77809..9e9e2a92ca7e 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 actionReport = 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, + actionReport, 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, + actionReport, 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, + actionReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING, isOriginalReportArchived, diff --git a/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx index 606a9771b858..2bde90d6cb47 100644 --- a/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx @@ -13,15 +13,13 @@ import type {Report, ReportAction} from '@src/types/onyx'; type ReportMentionWhisperContentProps = { action: ReportAction; reportID: string | undefined; - report: OnyxEntry; - originalReport: OnyxEntry; + actionReport: OnyxEntry; isReportArchived: boolean; }; -function ReportMentionWhisperContent({action, reportID, report, originalReport, isReportArchived}: ReportMentionWhisperContentProps) { - const reportActionReport = originalReport ?? report; +function ReportMentionWhisperContent({action, reportID, actionReport, isReportArchived}: ReportMentionWhisperContentProps) { const resolution = getOriginalMessage(action)?.resolution; - const mentionReportContextValue = {currentReportID: report?.reportID, exactlyMatch: true}; + const mentionReportContextValue = {currentReportID: reportID, exactlyMatch: true}; const buttons: ActionableItem[] = resolution ? [] @@ -29,13 +27,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(actionReport, 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(actionReport, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING, isReportArchived), }, ]; diff --git a/tests/unit/WhisperContentMentionContextTest.tsx b/tests/unit/WhisperContentMentionContextTest.tsx index 8b42c3a4ed9c..170d8cffaf1b 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} + actionReport={report} isReportArchived={false} /> , @@ -116,8 +115,7 @@ describe('Whisper content components provide MentionReportContext so room mentio action={action as ReportAction} reportID={REPORT_ID} originalReportID={undefined} - report={report} - originalReport={undefined} + actionReport={report} /> , ); From 05d256ab43cf3074e6f6850d5f5b6fe97bee2cd3 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 12 May 2026 12:22:18 +0200 Subject: [PATCH 04/23] clean up, reuse const and isHarvestCreatedExpenseReport --- .../MoneyRequestReportActionsList.tsx | 6 +- .../CreateHarvestReportAction.tsx | 9 +- .../inbox/report/PureReportActionItem.tsx | 225 +++++++++--------- src/pages/inbox/report/ReportActionsList.tsx | 8 +- .../report/ReportActionsListItemRenderer.tsx | 13 +- .../actionContents/ActionContentRouter.tsx | 25 +- .../actionContents/ModifiedExpenseContent.tsx | 8 +- tests/ui/PureReportActionItemTest.tsx | 3 +- 8 files changed, 139 insertions(+), 158 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 4d9ff089d208..bea043be3509 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -578,8 +578,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) personalDetails={personalDetails} originalReportID={originalReportID} isReportArchived={isReportArchived} - reportNameValuePairsOrigin={reportNameValuePairs?.origin} - reportNameValuePairsOriginalID={reportNameValuePairs?.originalID} + isHarvestCreatedExpenseReport={shouldShowHarvestCreatedAction} /> ); }, @@ -595,8 +594,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps) linkedReportActionID, personalDetails, isReportArchived, - reportNameValuePairs?.origin, - reportNameValuePairs?.originalID, + shouldShowHarvestCreatedAction, ], ); diff --git a/src/components/ReportActionItem/CreateHarvestReportAction.tsx b/src/components/ReportActionItem/CreateHarvestReportAction.tsx index b3037b4e1fb9..dd5d065f0274 100644 --- a/src/components/ReportActionItem/CreateHarvestReportAction.tsx +++ b/src/components/ReportActionItem/CreateHarvestReportAction.tsx @@ -8,13 +8,14 @@ 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(harvestReport?.reportID, harvestReportName, translate)}`; diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index 64efcbde9519..a613ae9ebc9a 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -52,14 +52,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'; @@ -165,11 +158,8 @@ type PureReportActionItemProps = { /** Whether to highlight the action for a few seconds */ shouldHighlight?: 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({ @@ -198,8 +188,7 @@ function PureReportActionItem({ isClosedExpenseReportWithNoExpenses, shouldShowBorder, shouldHighlight = false, - reportNameValuePairsOrigin, - reportNameValuePairsOriginalID, + isHarvestCreatedExpenseReport = false, }: PureReportActionItemProps) { const isConciergeGreeting = action.reportActionID === CONST.CONCIERGE_GREETING_ACTION_ID; const shouldDisplayContextMenuValue = shouldDisplayContextMenu && !isConciergeGreeting; @@ -224,7 +213,6 @@ function PureReportActionItem({ const [isReportActionActive, setIsReportActionActive] = useState(!!isReportActionLinked); const isReportArchived = useReportIsArchived(reportID); - const isHarvestCreatedExpenseReport = isHarvestCreatedExpenseReportUtils(reportNameValuePairsOrigin, reportNameValuePairsOriginalID); const shouldRenderViewBasedOnAction = useTableReportViewActionRenderConditionals(action); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.chatReportID)}`); @@ -540,117 +528,121 @@ function PureReportActionItem({ setIsReportActionActive(!!isReportActionLinked); }} > - {(hovered) => ( - - {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && ( - - )} - {shouldDisplayContextMenuValue && (hovered || !!isEmojiPickerActive || isContextMenuActive) && !hasDraft && !hasErrors && ( - - )} - { + 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 && ( @@ -705,7 +697,6 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { prevProps.isArchivedRoom === nextProps.isArchivedRoom && prevProps.isClosedExpenseReportWithNoExpenses === nextProps.isClosedExpenseReportWithNoExpenses && prevProps.shouldHighlight === nextProps.shouldHighlight && - prevProps.reportNameValuePairsOrigin === nextProps.reportNameValuePairsOrigin && - prevProps.reportNameValuePairsOriginalID === nextProps.reportNameValuePairsOriginalID + prevProps.isHarvestCreatedExpenseReport === nextProps.isHarvestCreatedExpenseReport ); }); diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index 276a1cc50bc9..da669d787e9e 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -54,6 +54,7 @@ import { isArchivedNonExpenseReport, isCanceledTaskReport, isExpenseReport, + isHarvestCreatedExpenseReport, isInvoiceReport, isIOUReport, isMoneyRequestReport, @@ -215,6 +216,7 @@ function ReportActionsList({ 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; @@ -797,8 +799,7 @@ function ReportActionsList({ personalDetails={personalDetailsList} originalReportID={originalReportID} isReportArchived={isReportArchived} - reportNameValuePairsOrigin={reportNameValuePairs?.origin} - reportNameValuePairsOriginalID={reportNameValuePairs?.originalID} + isHarvestCreatedExpenseReport={isHarvestCreatedExpenseReportAction} /> getOriginalMessage(reportAction), [reportAction]); @@ -203,8 +199,7 @@ function ReportActionsListItemRenderer({ shouldHighlight={shouldHighlight} personalDetails={personalDetails} draftMessage={draftMessage} - 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 fd567d638334..0359c83394e5 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -112,9 +112,6 @@ type ActionContentRouterProps = { /** 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; - /** Whether to show border for MoneyRequestReportPreviewContent */ shouldShowBorder?: boolean; @@ -145,7 +142,6 @@ function ActionContentRouter({ isReportArchived, isClosedExpenseReportWithNoExpenses, isHarvestCreatedExpenseReport, - reportNameValuePairsOriginalID, shouldShowBorder, isOnSearch, index, @@ -156,6 +152,7 @@ function ActionContentRouter({ const actionReport = originalReport ?? report; const actionReportID = originalReportID ?? reportID; + const policyID = report?.policyID; if (isIOURequestReportAction(action)) { const moneyRequestOriginalMessage = isMoneyRequestAction(action) ? getOriginalMessage(action) : undefined; @@ -211,7 +208,7 @@ function ActionContentRouter({ return ( ); } @@ -257,7 +254,7 @@ function ActionContentRouter({ return ( ); @@ -266,7 +263,7 @@ function ActionContentRouter({ return ( @@ -276,7 +273,7 @@ function ActionContentRouter({ return ( ); } @@ -311,7 +308,7 @@ function ActionContentRouter({ return ( ); } @@ -365,7 +362,7 @@ function ActionContentRouter({ ); } @@ -406,7 +403,7 @@ function ActionContentRouter({ return ( ); } @@ -428,7 +425,7 @@ function ActionContentRouter({ return ( ); } @@ -447,7 +444,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 ; diff --git a/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx b/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx index 61f879ac98f9..a26c9a770fbe 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)}`); diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index c692dcb38fe7..7447e968c7c6 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -2579,8 +2579,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} - reportNameValuePairsOrigin="harvest" - reportNameValuePairsOriginalID="origReport123" + isHarvestCreatedExpenseReport /> From ae146ba287307b83ec24ef8d3e0d0aa46e628b79 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 12 May 2026 12:39:50 +0200 Subject: [PATCH 05/23] pass only id instead of report to getReimbursedMessage --- src/libs/OptionsListUtils/index.ts | 4 ++-- src/libs/ReportActionsUtils.ts | 4 ++-- src/libs/ReportNameUtils.ts | 2 +- src/libs/ReportUtils.ts | 4 ++-- .../inbox/report/ContextMenu/ContextMenuActions.tsx | 4 ++-- .../inbox/report/actionContents/ActionContentRouter.tsx | 5 +++-- .../inbox/report/actionContents/ReimbursedContent.tsx | 9 ++++----- .../actionContents/ReimbursementDeQueuedContent.tsx | 8 ++++---- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 99b9600ab149..ec24e08888a6 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -727,7 +727,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)) { @@ -735,7 +735,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 9e71cbdbddf0..3de8c8e874b2 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -459,7 +459,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; @@ -488,7 +488,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 b7d90f7f6ec9..f19c49a0bf2e 100644 --- a/src/libs/ReportNameUtils.ts +++ b/src/libs/ReportNameUtils.ts @@ -476,7 +476,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 d6552a75b5ec..9a3e3f2093e7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3985,7 +3985,7 @@ function getReimbursementQueuedActionMessage({ function getReimbursementDeQueuedOrCanceledActionMessage( translate: LocalizedTranslate, reportAction: OnyxEntry>, - report: OnyxEntry, + reportOwnerAccountID: number | undefined, ): string { const originalMessage = getOriginalMessage(reportAction); const amount = originalMessage?.amount; @@ -3994,7 +3994,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/inbox/report/ContextMenu/ContextMenuActions.tsx b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx index 6a99cb8c13f9..36eeb47c5e18 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -872,7 +872,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); @@ -999,7 +999,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/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index 0359c83394e5..fd58f6440fb0 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -153,6 +153,7 @@ function ActionContentRouter({ const actionReport = originalReport ?? report; const actionReportID = originalReportID ?? reportID; const policyID = report?.policyID; + const reportOwnerAccountID = report?.ownerAccountID; if (isIOURequestReportAction(action)) { const moneyRequestOriginalMessage = isMoneyRequestAction(action) ? getOriginalMessage(action) : undefined; @@ -246,7 +247,7 @@ function ActionContentRouter({ return ( ); } @@ -281,7 +282,7 @@ function ActionContentRouter({ return ( ); } diff --git a/src/pages/inbox/report/actionContents/ReimbursedContent.tsx b/src/pages/inbox/report/actionContents/ReimbursedContent.tsx index 2699c1d6b14d..57ec9b3fab63 100644 --- a/src/pages/inbox/report/actionContents/ReimbursedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursedContent.tsx @@ -1,20 +1,19 @@ 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 ; } diff --git a/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx b/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx index 39992f810f67..bda6fbca89b3 100644 --- a/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx @@ -4,19 +4,19 @@ 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 ; From 7d8bc163d4860b4d3031dd55754eb120c681b3f4 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 12 May 2026 12:49:08 +0200 Subject: [PATCH 06/23] remove report prop from SimpleMessageContent --- .../actionContents/ActionContentRouter.tsx | 12 ++++++++---- .../ReceiptScanFailedContent.tsx | 18 ++++++++++++++++++ .../actionContents/SimpleMessageContent.tsx | 14 +------------- 3 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index fd58f6440fb0..27182db38f59 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -57,6 +57,7 @@ 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'; @@ -286,14 +287,17 @@ function ActionContentRouter({ /> ); } - 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) { diff --git a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx new file mode 100644 index 000000000000..dbcf28cccf25 --- /dev/null +++ b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import {getReportAction, wasActionTakenByCurrentUser} from '@libs/ReportActionsUtils'; +import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; + +type ReceiptScanFailedContentProps = { + parentReportID: string | undefined; + parentReportActionID: string | undefined; +}; + +// RECEIPT_SCAN_FAILED is submitted by Concierge, so use the parent IOU action to determine edit permission. +function ReceiptScanFailedContent({parentReportID, parentReportActionID}: ReceiptScanFailedContentProps) { + const {translate} = useLocalize(); + const iouAction = getReportAction(parentReportID, parentReportActionID); + return ; +} + +export default ReceiptScanFailedContent; diff --git a/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx b/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx index e768dbb1e278..78794c7162c7 100644 --- a/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import { getActionableCard3DSTransactionApprovalMessage, @@ -9,12 +8,10 @@ import { getMessageOfOldDotReportAction, getOriginalMessage, getRemovedFromApprovalChainMessage, - getReportAction, getReportActionText, isActionOfType, isRejectedAction, isUnapprovedAction, - wasActionTakenByCurrentUser, } from '@libs/ReportActionsUtils'; import {getDeletedTransactionMessage, getPolicyChangeMessage} from '@libs/ReportUtils'; import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; @@ -23,7 +20,6 @@ import type * as OnyxTypes from '@src/types/onyx'; type SimpleMessageContentProps = { action: OnyxTypes.ReportAction; - report: OnyxEntry; }; const SIMPLE_MESSAGE_ACTION_TYPES = new Set([ @@ -40,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, @@ -51,7 +46,7 @@ function isSimpleMessageAction(action: OnyxTypes.ReportAction): boolean { return SIMPLE_MESSAGE_ACTION_TYPES.has(action.actionName) || isUnapprovedAction(action) || isRejectedAction(action); } -function SimpleMessageContent({action, report}: SimpleMessageContentProps) { +function SimpleMessageContent({action}: SimpleMessageContentProps) { const {translate} = useLocalize(); if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED)) { @@ -93,11 +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)) { - // RECEIPT_SCAN_FAILED is submitted by Concierge, so use the IOU action to determine edit permission - const iouAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - return ; - } if (isUnapprovedAction(action)) { return ; } @@ -120,7 +110,5 @@ function SimpleMessageContent({action, report}: SimpleMessageContentProps) { return null; } -SimpleMessageContent.displayName = 'SimpleMessageContent'; - export default SimpleMessageContent; export {isSimpleMessageAction}; From 4a1637beb351369ddf9053bd3def8c8961577102 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 11:29:13 +0200 Subject: [PATCH 07/23] remove isOffline change --- src/components/Search/SearchList/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index d79e0d6c78e8..c01db4ee3120 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -451,7 +451,6 @@ function SearchList({ personalPolicyID={personalPolicyID} userBillingGracePeriodEnds={userBillingGracePeriodEnds} ownerBillingGracePeriodEnd={ownerBillingGracePeriodEnd} - isOffline={isOffline} nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards} onFocus={onFocus} newTransactionID={newTransactionID} @@ -477,7 +476,6 @@ function SearchList({ onCheckboxPress, canSelectMultiple, columns, - isOffline, lastPaymentMethod, personalPolicyID, userBillingGracePeriodEnds, From f3892bc0c62e64b8fef146208996e4b13e295f31 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 11:30:34 +0200 Subject: [PATCH 08/23] prettier fix --- src/pages/inbox/report/ReportActionItemParentAction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/inbox/report/ReportActionItemParentAction.tsx b/src/pages/inbox/report/ReportActionItemParentAction.tsx index a5b555c69202..edb4123eff12 100644 --- a/src/pages/inbox/report/ReportActionItemParentAction.tsx +++ b/src/pages/inbox/report/ReportActionItemParentAction.tsx @@ -83,7 +83,7 @@ function ReportActionItemParentAction({ 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; From febb20f9b46c7da9dc755bf557fd95d131c9ffdb Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 11:50:32 +0200 Subject: [PATCH 09/23] clean up displayName based on current guideline --- src/pages/inbox/report/actionContents/ApprovalFlowContent.tsx | 2 -- .../inbox/report/actionContents/CardBrokenConnectionContent.tsx | 2 -- src/pages/inbox/report/actionContents/ChatActionableButtons.tsx | 2 -- src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx | 2 -- src/pages/inbox/report/actionContents/FraudAlertContent.tsx | 2 -- src/pages/inbox/report/actionContents/JoinRequestContent.tsx | 2 -- src/pages/inbox/report/actionContents/MentionWhisperContent.tsx | 2 -- .../inbox/report/actionContents/ModifiedExpenseContent.tsx | 2 -- src/pages/inbox/report/actionContents/PaymentContent.tsx | 2 -- .../inbox/report/actionContents/PolicyChangeLogContent.tsx | 2 -- src/pages/inbox/report/actionContents/ReimbursedContent.tsx | 2 -- .../report/actionContents/ReimbursementDeQueuedContent.tsx | 2 -- .../inbox/report/actionContents/ReportMentionWhisperContent.tsx | 2 -- 13 files changed, 26 deletions(-) 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 3d097c9edca3..a0eed34c7655 100644 --- a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx +++ b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx @@ -228,6 +228,4 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: ); } -ChatActionableButtons.displayName = 'ChatActionableButtons'; - export default ChatActionableButtons; diff --git a/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx b/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx index df5d455563ac..381915e6c94c 100644 --- a/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx @@ -49,6 +49,4 @@ function ConfirmWhisperContent({action, reportID, originalReportID, actionReport ); } -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/JoinRequestContent.tsx b/src/pages/inbox/report/actionContents/JoinRequestContent.tsx index 805980f7461d..c4162a4a0b38 100644 --- a/src/pages/inbox/report/actionContents/JoinRequestContent.tsx +++ b/src/pages/inbox/report/actionContents/JoinRequestContent.tsx @@ -53,6 +53,4 @@ function JoinRequestContent({action, actionReportID, policyID}: JoinRequestConte ); } -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 9e9e2a92ca7e..aa7b274d3844 100644 --- a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx @@ -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 a26c9a770fbe..55d96661019f 100644 --- a/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx +++ b/src/pages/inbox/report/actionContents/ModifiedExpenseContent.tsx @@ -51,6 +51,4 @@ function ModifiedExpenseContent({action, policyID, originalReport}: ModifiedExpe ); } -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/ReimbursedContent.tsx b/src/pages/inbox/report/actionContents/ReimbursedContent.tsx index 57ec9b3fab63..b351c7c1109c 100644 --- a/src/pages/inbox/report/actionContents/ReimbursedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursedContent.tsx @@ -18,6 +18,4 @@ function ReimbursedContent({action, reportOwnerAccountID}: ReimbursedContentProp 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 bda6fbca89b3..365cd942474a 100644 --- a/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx @@ -22,6 +22,4 @@ function ReimbursementDeQueuedContent({action, reportOwnerAccountID}: Reimbursem 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 c746a2dde606..7d3b35b5c00a 100644 --- a/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx @@ -58,6 +58,4 @@ function ReportMentionWhisperContent({action, reportID, actionReport}: ReportMen ); } -ReportMentionWhisperContent.displayName = 'ReportMentionWhisperContent'; - export default ReportMentionWhisperContent; From 2a3af7db52aeb390a437be3902064321151d6a1c Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 12:37:46 +0200 Subject: [PATCH 10/23] Revert personalDetails leaf subscription from ReimbursementQueuedContent --- src/pages/inbox/report/PureReportActionItem.tsx | 1 + .../inbox/report/actionContents/ActionContentRouter.tsx | 5 +++++ .../report/actionContents/ReimbursementQueuedContent.tsx | 9 +++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index d265967e5185..ff08bc5d5bea 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -610,6 +610,7 @@ function PureReportActionItem({ isArchivedRoom={isArchivedRoom} isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses} isHarvestCreatedExpenseReport={isHarvestCreatedExpenseReport} + personalDetails={personalDetails} shouldShowBorder={shouldShowBorder} isOnSearch={isOnSearch} index={index} diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index 2f3de691cb87..a3e4b0bb4eb0 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -110,6 +110,9 @@ type ActionContentRouterProps = { /** Whether the report action is the "Created" action of a harvest-created expense report */ isHarvestCreatedExpenseReport: boolean; + /** Personal details list */ + personalDetails?: OnyxTypes.PersonalDetailsList; + /** Whether to show border for MoneyRequestReportPreviewContent */ shouldShowBorder?: boolean; @@ -139,6 +142,7 @@ function ActionContentRouter({ isArchivedRoom, isClosedExpenseReportWithNoExpenses, isHarvestCreatedExpenseReport, + personalDetails, shouldShowBorder, isOnSearch, index, @@ -237,6 +241,7 @@ function ActionContentRouter({ action={action} report={report} iouReport={iouReport} + personalDetails={personalDetails} /> ); } diff --git a/src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx b/src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx index 791bb9cc99ff..51ff7a5650af 100644 --- a/src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReimbursementQueuedContent.tsx @@ -5,7 +5,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import Button from '@components/Button'; import KYCWall from '@components/KYCWall'; import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; -import {usePersonalDetails} from '@components/OnyxListItemProvider'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -19,7 +18,7 @@ import {openPersonalBankAccountSetupView} from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Report, ReportAction} from '@src/types/onyx'; +import type {PersonalDetailsList, Report, ReportAction} from '@src/types/onyx'; type ReimbursementQueuedContentProps = { /** The reimbursement queued action */ @@ -30,13 +29,15 @@ type ReimbursementQueuedContentProps = { /** The IOU/Expense report we are paying */ iouReport: OnyxEntry; + + /** Personal details list for resolving the submitter's display name */ + personalDetails: OnyxEntry; }; -function ReimbursementQueuedContent({action, report, iouReport}: ReimbursementQueuedContentProps) { +function ReimbursementQueuedContent({action, report, iouReport, personalDetails}: ReimbursementQueuedContentProps) { const styles = useThemeStyles(); const {translate, formatPhoneNumber} = useLocalize(); const kycWallRef = useContext(KYCWallContext); - const personalDetails = usePersonalDetails(); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID)}`); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); From f438dd04f7718ad295e8e9e0c84f399c82460559 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 13:25:27 +0200 Subject: [PATCH 11/23] rename actionReport to actionOwnerReport --- .../actionContents/ActionContentRouter.tsx | 15 +++++++------ .../actionContents/ChatActionableButtons.tsx | 22 +++++++++---------- .../actionContents/ChatMessageContent.tsx | 12 +++++----- .../actionContents/ConfirmWhisperContent.tsx | 6 ++--- .../actionContents/JoinRequestContent.tsx | 8 +++---- .../actionContents/MentionWhisperContent.tsx | 8 +++---- .../ReportMentionWhisperContent.tsx | 8 +++---- .../unit/WhisperContentMentionContextTest.tsx | 4 ++-- 8 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index a3e4b0bb4eb0..d7e70a3577d5 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -151,8 +151,9 @@ function ActionContentRouter({ const {translate, formatTravelDate} = useLocalize(); const styles = useThemeStyles(); - const actionReport = originalReport ?? report; - const actionReportID = originalReportID ?? reportID; + // 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; @@ -370,7 +371,7 @@ function ActionContentRouter({ return ( ); @@ -390,7 +391,7 @@ function ActionContentRouter({ ); } @@ -399,7 +400,7 @@ function ActionContentRouter({ ); @@ -480,8 +481,8 @@ function ActionContentRouter({ ; - actionReportID: string | undefined; + actionOwnerReport: OnyxEntry; + actionOwnerReportID: string | undefined; reportID: string | undefined; }; -function ChatActionableButtons({action, actionReport, actionReportID, reportID}: ChatActionableButtonsProps) { +function ChatActionableButtons({action, actionOwnerReport, actionOwnerReportID, reportID}: ChatActionableButtonsProps) { const styles = useThemeStyles(); const personalDetail = useCurrentUserPersonalDetails(); const {isRestrictedToPreferredPolicy, preferredPolicyID} = usePreferredPolicy(); @@ -81,7 +81,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: return []; } - if (!actionReport) { + if (!actionOwnerReport) { return []; } @@ -90,7 +90,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: key: `${action.reportActionID}-conciergeCategoryOptions-${option}`, onPress: () => { resolveConciergeCategoryOptions( - actionReport, + actionOwnerReport, reportID, action.reportActionID, option, @@ -112,7 +112,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: return []; } - if (!actionReport) { + if (!actionOwnerReport) { return []; } @@ -121,7 +121,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: key: `${action.reportActionID}-conciergeDescriptionOptions-${option}`, onPress: () => { resolveConciergeDescriptionOptions( - actionReport, + actionOwnerReport, reportID, action.reportActionID, option, @@ -133,7 +133,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: })); } const messageHtml = getReportActionMessage(action)?.html; - if (messageHtml && actionReport) { + if (messageHtml && actionOwnerReport) { const followups = parseFollowupsFromHtml(messageHtml); if (followups && followups.length > 0) { return followups.map((followup) => ({ @@ -142,7 +142,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: key: `${action.reportActionID}-followup-${followup.text}`, onPress: () => { resolveSuggestedFollowup( - actionReport, + actionOwnerReport, reportID, action, followup, @@ -158,7 +158,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: if (isActionableTrackExpense(action)) { const baseDraftTransactionParams = { - reportID: actionReportID, + reportID: actionOwnerReportID, reportActionID: action.reportActionID, introSelected, draftTransactionIDs, @@ -196,7 +196,7 @@ function ChatActionableButtons({action, actionReport, actionReportID, reportID}: text: 'actionableMentionTrackExpense.nothing', key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, onPress: () => { - dismissTrackExpenseActionableWhisper(actionReportID, action); + dismissTrackExpenseActionableWhisper(actionOwnerReportID, action); }, }); return options; diff --git a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx index d2f78c8032c9..a7fabac755a9 100644 --- a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx @@ -29,8 +29,8 @@ import ChatActionableButtons from './ChatActionableButtons'; type ChatMessageContentProps = { action: OnyxTypes.ReportAction; report: OnyxEntry; - actionReport: OnyxEntry; - actionReportID: string | undefined; + actionOwnerReport: OnyxEntry; + actionOwnerReportID: string | undefined; reportID: string | undefined; originalReportID: string; displayAsGroup: boolean; @@ -45,8 +45,8 @@ type ChatMessageContentProps = { function ChatMessageContent({ action, report, - actionReport, - actionReportID, + actionOwnerReport, + actionOwnerReportID, reportID, originalReportID, displayAsGroup, @@ -117,8 +117,8 @@ function ChatMessageContent({ {mayHaveActionableButtons && ( )} diff --git a/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx b/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx index 381915e6c94c..a8d2c4029b67 100644 --- a/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/ConfirmWhisperContent.tsx @@ -14,10 +14,10 @@ type ConfirmWhisperContentProps = { action: ReportAction; reportID: string | undefined; originalReportID: string | undefined; - actionReport: OnyxEntry; + actionOwnerReport: OnyxEntry; }; -function ConfirmWhisperContent({action, reportID, originalReportID, actionReport}: ConfirmWhisperContentProps) { +function ConfirmWhisperContent({action, reportID, originalReportID, actionOwnerReport}: ConfirmWhisperContentProps) { const isOriginalReportArchived = useReportIsArchived(originalReportID); const mentionReportContextValue = {currentReportID: reportID, exactlyMatch: true}; @@ -26,7 +26,7 @@ function ConfirmWhisperContent({action, reportID, originalReportID, actionReport text: 'common.buttonConfirm', key: `${action.reportActionID}-actionableReportMentionConfirmWhisper-${CONST.REPORT.ACTIONABLE_MENTION_INVITE_TO_SUBMIT_EXPENSE_CONFIRM_WHISPER.DONE}`, onPress: () => - resolveActionableMentionConfirmWhisper(actionReport, 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, }, ]; diff --git a/src/pages/inbox/report/actionContents/JoinRequestContent.tsx b/src/pages/inbox/report/actionContents/JoinRequestContent.tsx index c4162a4a0b38..57596ffd8431 100644 --- a/src/pages/inbox/report/actionContents/JoinRequestContent.tsx +++ b/src/pages/inbox/report/actionContents/JoinRequestContent.tsx @@ -14,11 +14,11 @@ import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; type JoinRequestContentProps = { action: ReportAction; - actionReportID: string | undefined; + actionOwnerReportID: string | undefined; policyID: string | undefined; }; -function JoinRequestContent({action, actionReportID, policyID}: JoinRequestContentProps) { +function JoinRequestContent({action, actionOwnerReportID, policyID}: JoinRequestContentProps) { const {translate} = useLocalize(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); @@ -29,13 +29,13 @@ function JoinRequestContent({action, actionReportID, policyID}: JoinRequestConte { text: 'actionableMentionJoinWorkspaceOptions.accept', key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, - onPress: () => acceptJoinRequest(actionReportID, action), + onPress: () => acceptJoinRequest(actionOwnerReportID, action), isPrimary: true, }, { text: 'actionableMentionJoinWorkspaceOptions.decline', key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - onPress: () => declineJoinRequest(actionReportID, action), + onPress: () => declineJoinRequest(actionOwnerReportID, action), }, ]; diff --git a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx index aa7b274d3844..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 actionReport = 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( - actionReport, + 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( - actionReport, + 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( - actionReport, + actionOwnerReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING, isOriginalReportArchived, diff --git a/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx index 7d3b35b5c00a..007ca5c8b280 100644 --- a/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/ReportMentionWhisperContent.tsx @@ -14,10 +14,10 @@ import type {Report, ReportAction} from '@src/types/onyx'; type ReportMentionWhisperContentProps = { action: ReportAction; reportID: string | undefined; - actionReport: OnyxEntry; + actionOwnerReport: OnyxEntry; }; -function ReportMentionWhisperContent({action, reportID, actionReport}: ReportMentionWhisperContentProps) { +function ReportMentionWhisperContent({action, reportID, actionOwnerReport}: ReportMentionWhisperContentProps) { const isReportArchived = useReportIsArchived(reportID); const resolution = getOriginalMessage(action)?.resolution; const mentionReportContextValue = {currentReportID: reportID, exactlyMatch: true}; @@ -28,13 +28,13 @@ function ReportMentionWhisperContent({action, reportID, actionReport}: ReportMen { text: 'common.yes', key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - onPress: () => resolveActionableReportMentionWhisper(actionReport, 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(actionReport, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING, isReportArchived), + onPress: () => resolveActionableReportMentionWhisper(actionOwnerReport, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING, isReportArchived), }, ]; diff --git a/tests/unit/WhisperContentMentionContextTest.tsx b/tests/unit/WhisperContentMentionContextTest.tsx index 0474a60c7e2e..4dc39778e597 100644 --- a/tests/unit/WhisperContentMentionContextTest.tsx +++ b/tests/unit/WhisperContentMentionContextTest.tsx @@ -95,7 +95,7 @@ describe('Whisper content components provide MentionReportContext so room mentio } reportID={REPORT_ID} - actionReport={report} + actionOwnerReport={report} /> , ); @@ -114,7 +114,7 @@ describe('Whisper content components provide MentionReportContext so room mentio action={action as ReportAction} reportID={REPORT_ID} originalReportID={undefined} - actionReport={report} + actionOwnerReport={report} /> , ); From b17c66f3d7ce69f344522ed3d57f2a0691d886b4 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 14:37:14 +0200 Subject: [PATCH 12/23] fix unit tests --- tests/ui/PureReportActionItemTest.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index 7447e968c7c6..ade416dbd397 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>) => ({ @@ -1433,6 +1441,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1470,6 +1479,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1518,6 +1528,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1554,6 +1565,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> @@ -1594,6 +1606,7 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} + personalDetails={testPersonalDetailsList} /> From f33d7050e02f5eba9951ffc7fb131d85c2a95678 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 15:41:44 +0200 Subject: [PATCH 13/23] clean up ChatMessageContent --- .../inbox/report/PureReportActionItem.tsx | 6 ----- src/pages/inbox/report/ReportActionItem.tsx | 5 +--- .../report/ReportActionItemMessageEdit.tsx | 20 +++++++++------ .../actionContents/ActionContentRouter.tsx | 9 +------ .../actionContents/ChatActionableButtons.tsx | 10 +++++--- .../actionContents/ChatMessageContent.tsx | 25 ++++--------------- tests/ui/ReportActionItemMessageEditTest.tsx | 1 - .../ui/ReportActionMessageEditLayoutTest.tsx | 1 - 8 files changed, 26 insertions(+), 51 deletions(-) diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index ff08bc5d5bea..07e8cf5cde50 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -147,9 +147,6 @@ 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; @@ -185,7 +182,6 @@ function PureReportActionItem({ personalDetails, originalReportID = '-1', originalReport, - isArchivedRoom, isClosedExpenseReportWithNoExpenses, shouldShowBorder, shouldHighlight = false, @@ -607,7 +603,6 @@ function PureReportActionItem({ hovered={isHoveredOrActive} isHidden={isHidden} updateHiddenState={updateHiddenState} - isArchivedRoom={isArchivedRoom} isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses} isHarvestCreatedExpenseReport={isHarvestCreatedExpenseReport} personalDetails={personalDetails} @@ -699,7 +694,6 @@ 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.shouldHighlight === nextProps.shouldHighlight && prevProps.isHarvestCreatedExpenseReport === nextProps.isHarvestCreatedExpenseReport diff --git a/src/pages/inbox/report/ReportActionItem.tsx b/src/pages/inbox/report/ReportActionItem.tsx index 473df5b0ce53..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'; @@ -23,7 +22,6 @@ type ReportActionItemProps = PureReportActionItemProps & { 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)}`); @@ -54,7 +52,6 @@ function ReportActionItem({action, report, draftMessage: draftMessageProp, perso personalDetails={personalDetails} originalReportID={originalReportID} originalReport={originalReport} - isArchivedRoom={isArchivedNonExpenseReport(originalReport, isOriginalReportArchived)} isClosedExpenseReportWithNoExpenses={isClosedExpenseReportWithNoExpenses(iouReport, transactionsOnIOUReport)} /> ); 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/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index d7e70a3577d5..9d960499acc2 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -101,9 +101,6 @@ 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; @@ -139,7 +136,6 @@ function ActionContentRouter({ hovered, isHidden, updateHiddenState, - isArchivedRoom, isClosedExpenseReportWithNoExpenses, isHarvestCreatedExpenseReport, personalDetails, @@ -480,9 +476,7 @@ function ActionContentRouter({ return ( ); diff --git a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx index 1610d7770e03..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'; @@ -36,13 +35,16 @@ import type * as OnyxTypes from '@src/types/onyx'; type ChatActionableButtonsProps = { action: OnyxTypes.ReportAction; - actionOwnerReport: OnyxEntry; - actionOwnerReportID: string | undefined; + originalReportID: string | undefined; reportID: string | undefined; }; -function ChatActionableButtons({action, actionOwnerReport, actionOwnerReportID, reportID}: 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(); diff --git a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx index a7fabac755a9..a9cce758d30b 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,19 +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; - actionOwnerReport: OnyxEntry; - actionOwnerReportID: string | undefined; + policyID: string | undefined; reportID: string | undefined; originalReportID: string; displayAsGroup: boolean; @@ -38,15 +32,12 @@ type ChatMessageContentProps = { index: number; isHidden: boolean; updateHiddenState: (isHiddenValue: boolean) => void; - isArchivedRoom?: boolean; isOnSearch: boolean; }; function ChatMessageContent({ action, - report, - actionOwnerReport, - actionOwnerReportID, + policyID, reportID, originalReportID, displayAsGroup, @@ -54,18 +45,15 @@ function ChatMessageContent({ index, isHidden, updateHiddenState, - isArchivedRoom, 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 +74,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,8 +103,7 @@ function ChatMessageContent({ {mayHaveActionableButtons && ( )} 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} /> )} From 5c74fb215399399436435dc90113b3d1c354beca Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 15:44:30 +0200 Subject: [PATCH 14/23] add unit test for ChatActionableButtons --- tests/ui/ChatActionableButtonsTest.tsx | 101 +++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/ui/ChatActionableButtonsTest.tsx 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(); + }); +}); From 7822f9caca00d7cf4f228f0c3df0564d2ad7376b Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 15:47:56 +0200 Subject: [PATCH 15/23] prettier fix --- .../report/actionContents/ChatMessageContent.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx index a9cce758d30b..9dd0b1a090ea 100644 --- a/src/pages/inbox/report/actionContents/ChatMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/ChatMessageContent.tsx @@ -35,18 +35,7 @@ type ChatMessageContentProps = { isOnSearch: boolean; }; -function ChatMessageContent({ - action, - policyID, - reportID, - originalReportID, - displayAsGroup, - draftMessage, - index, - isHidden, - updateHiddenState, - isOnSearch, -}: ChatMessageContentProps) { +function ChatMessageContent({action, policyID, reportID, originalReportID, displayAsGroup, draftMessage, index, isHidden, updateHiddenState, isOnSearch}: ChatMessageContentProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); From 80a7e3029c177f1184e7af9b5c1d2fc81fb95130 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 20 May 2026 16:02:35 +0200 Subject: [PATCH 16/23] fix PureReportActionItem test --- tests/ui/PureReportActionItemTest.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index ade416dbd397..d24ff18ef982 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -603,6 +603,10 @@ describe('PureReportActionItem', () => { type: CONST.REPORT.TYPE.CHAT, }; + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + }); + render( @@ -610,6 +614,7 @@ describe('PureReportActionItem', () => { { type: CONST.REPORT.TYPE.CHAT, }; + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + }); + render( @@ -666,6 +675,7 @@ describe('PureReportActionItem', () => { Date: Thu, 21 May 2026 08:57:26 +0200 Subject: [PATCH 17/23] fix selector perf issue with ReceiptScanFailedContent --- .../ReceiptScanFailedContent.tsx | 31 +--- src/selectors/ReportAction.ts | 48 +++++- tests/unit/selectors/ReportActionTest.ts | 155 ++++++++++++++++++ 3 files changed, 208 insertions(+), 26 deletions(-) create mode 100644 tests/unit/selectors/ReportActionTest.ts diff --git a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx index a10b42e587fe..703b9b9fd958 100644 --- a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx @@ -1,9 +1,9 @@ +import {getReceiptScanFailedIouActionDataSelector} from '@selectors/ReportAction'; 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 {getLinkedTransactionID, isActionOfType, wasActionTakenByCurrentUser} from '@libs/ReportActionsUtils'; import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -23,33 +23,16 @@ function ReceiptScanFailedContent({reportID, actionReportID, parentReportID, par const isIouReport = reportType === CONST.REPORT.TYPE.IOU || reportType === CONST.REPORT.TYPE.EXPENSE || reportType === CONST.REPORT.TYPE.INVOICE; const iouReportID = isIouReport ? reportID : parentReportID; - // 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 receiptScanFailedIouActionDataSelector = (reportActions: OnyxEntry) => + getReceiptScanFailedIouActionDataSelector(reportActions, isIouReport, parentReportActionID, actionReportID); + const [{transactionID, canEdit} = {transactionID: undefined, canEdit: false}] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(iouReportID)}`, { + selector: receiptScanFailedIouActionDataSelector, + }); 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 ; + return ; } export default ReceiptScanFailedContent; diff --git a/src/selectors/ReportAction.ts b/src/selectors/ReportAction.ts index 8c46a3772a26..93665b38d6c4 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, wasActionTakenByCurrentUser} 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; canEdit: boolean} { + const iouAction = findIouActionForReceiptScanFailed(reportActions, isIouReport, parentReportActionID, actionReportID); + + return { + transactionID: getLinkedTransactionID(iouAction), + canEdit: wasActionTakenByCurrentUser(iouAction), + }; +} + +export {getParentReportActionSelector, getLastClosedReportAction, getReportActionsForReportIDs, getReportActionByIDSelector, getReceiptScanFailedIouActionDataSelector}; diff --git a/tests/unit/selectors/ReportActionTest.ts b/tests/unit/selectors/ReportActionTest.ts new file mode 100644 index 000000000000..d2b73fcb187d --- /dev/null +++ b/tests/unit/selectors/ReportActionTest.ts @@ -0,0 +1,155 @@ +import {getReceiptScanFailedIouActionDataSelector} from '@selectors/ReportAction'; +import Onyx from 'react-native-onyx'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportAction, ReportActions} from '@src/types/onyx'; +import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; + +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', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + return Onyx.merge(ONYXKEYS.SESSION, {accountID: CURRENT_USER_ACCOUNT_ID}).then(() => waitForBatchedUpdates()); + }); + + it('returns undefined transactionID and canEdit false when reportActions is undefined', () => { + expect(getReceiptScanFailedIouActionDataSelector(undefined, false, 'iou1', 'thread1')).toEqual({ + transactionID: undefined, + canEdit: false, + }); + }); + + it('returns transactionID and canEdit 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, + canEdit: true, + }); + }); + + 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, + canEdit: true, + }); + }); + + 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, + canEdit: true, + }); + }); + + it('returns transactionID and canEdit 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, + canEdit: true, + }); + }); + + it('returns transactionID and canEdit 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, + canEdit: true, + }); + }); + + it('returns undefined transactionID and canEdit false 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, + canEdit: false, + }); + }); + + it('returns undefined transactionID and canEdit false 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, + canEdit: false, + }); + }); + + it('returns canEdit false when the matched IOU action 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, + canEdit: false, + }); + }); +}); From 121d01a47812f283b11f18e903383e894bbbea6a Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 21 May 2026 09:24:03 +0200 Subject: [PATCH 18/23] refactor to not use deprecated wasActionTakenByCurrentUser --- .../ReceiptScanFailedContent.tsx | 6 ++- src/selectors/ReportAction.ts | 6 +-- tests/unit/selectors/ReportActionTest.ts | 40 ++++++++----------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx index 703b9b9fd958..30382291e3cb 100644 --- a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx @@ -1,6 +1,7 @@ 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'; @@ -19,15 +20,18 @@ type ReceiptScanFailedContentProps = { 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 [{transactionID, canEdit} = {transactionID: undefined, canEdit: false}] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(iouReportID)}`, { + 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 ?? []; diff --git a/src/selectors/ReportAction.ts b/src/selectors/ReportAction.ts index 93665b38d6c4..05e39c9e2833 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, getLinkedTransactionID, getSortedReportActions, isActionOfType, wasActionTakenByCurrentUser} 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'; @@ -90,12 +90,12 @@ function getReceiptScanFailedIouActionDataSelector( isIouReport: boolean, parentReportActionID: string | undefined, actionReportID: string | undefined, -): {transactionID: string | undefined; canEdit: boolean} { +): {transactionID: string | undefined; actorAccountID: number | undefined} { const iouAction = findIouActionForReceiptScanFailed(reportActions, isIouReport, parentReportActionID, actionReportID); return { transactionID: getLinkedTransactionID(iouAction), - canEdit: wasActionTakenByCurrentUser(iouAction), + actorAccountID: iouAction?.actorAccountID, }; } diff --git a/tests/unit/selectors/ReportActionTest.ts b/tests/unit/selectors/ReportActionTest.ts index d2b73fcb187d..1c6c92903e95 100644 --- a/tests/unit/selectors/ReportActionTest.ts +++ b/tests/unit/selectors/ReportActionTest.ts @@ -1,9 +1,6 @@ import {getReceiptScanFailedIouActionDataSelector} from '@selectors/ReportAction'; -import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction, ReportActions} from '@src/types/onyx'; -import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; const CURRENT_USER_ACCOUNT_ID = 123456789; const OTHER_USER_ACCOUNT_ID = 987654321; @@ -26,19 +23,14 @@ const createIouAction = (reportActionID: string, overrides: Partial { - beforeAll(() => { - Onyx.init({keys: ONYXKEYS}); - return Onyx.merge(ONYXKEYS.SESSION, {accountID: CURRENT_USER_ACCOUNT_ID}).then(() => waitForBatchedUpdates()); - }); - - it('returns undefined transactionID and canEdit false when reportActions is undefined', () => { + it('returns undefined transactionID and actorAccountID when reportActions is undefined', () => { expect(getReceiptScanFailedIouActionDataSelector(undefined, false, 'iou1', 'thread1')).toEqual({ transactionID: undefined, - canEdit: false, + actorAccountID: undefined, }); }); - it('returns transactionID and canEdit for the IOU action matching parentReportActionID on a transaction thread report', () => { + 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', { @@ -54,7 +46,7 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { expect(getReceiptScanFailedIouActionDataSelector(reportActions, false, 'iou1', 'thread1')).toEqual({ transactionID: TRANSACTION_ID, - canEdit: true, + actorAccountID: CURRENT_USER_ACCOUNT_ID, }); }); @@ -74,7 +66,7 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { expect(getReceiptScanFailedIouActionDataSelector(reportActions, true, 'parentIou', 'thread1')).toEqual({ transactionID: OTHER_TRANSACTION_ID, - canEdit: true, + actorAccountID: CURRENT_USER_ACCOUNT_ID, }); }); @@ -86,11 +78,11 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { expect(getReceiptScanFailedIouActionDataSelector(reportActions, false, 'comment1', 'thread1')).toEqual({ transactionID: TRANSACTION_ID, - canEdit: true, + actorAccountID: CURRENT_USER_ACCOUNT_ID, }); }); - it('returns transactionID and canEdit for the IOU action whose childReportID matches actionReportID', () => { + it('returns transactionID and actorAccountID for the IOU action whose childReportID matches actionReportID', () => { const reportActions: ReportActions = { iou1: createIouAction('iou1', {childReportID: 'thread1'}), iou2: createIouAction('iou2', { @@ -106,20 +98,20 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { expect(getReceiptScanFailedIouActionDataSelector(reportActions, true, undefined, 'thread1')).toEqual({ transactionID: TRANSACTION_ID, - canEdit: true, + actorAccountID: CURRENT_USER_ACCOUNT_ID, }); }); - it('returns transactionID and canEdit for the only IOU action on one-transaction reports', () => { + 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, - canEdit: true, + actorAccountID: CURRENT_USER_ACCOUNT_ID, }); }); - it('returns undefined transactionID and canEdit false when multiple IOU actions exist and none match actionReportID', () => { + 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'}), @@ -127,29 +119,29 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { expect(getReceiptScanFailedIouActionDataSelector(reportActions, true, undefined, 'thread3')).toEqual({ transactionID: undefined, - canEdit: false, + actorAccountID: undefined, }); }); - it('returns undefined transactionID and canEdit false when no IOU actions exist', () => { + 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, - canEdit: false, + actorAccountID: undefined, }); }); - it('returns canEdit false when the matched IOU action belongs to another user', () => { + 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, - canEdit: false, + actorAccountID: OTHER_USER_ACCOUNT_ID, }); }); }); From 1822fbe38f5a7f89de5c639c5b9cba774b52e440 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 22 May 2026 09:54:58 +0200 Subject: [PATCH 19/23] fix ts --- src/components/ReportActionItem/CreateHarvestReportAction.tsx | 2 +- tests/ui/PureReportActionItemTest.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/CreateHarvestReportAction.tsx b/src/components/ReportActionItem/CreateHarvestReportAction.tsx index 424edc02d2de..35714fde0ac4 100644 --- a/src/components/ReportActionItem/CreateHarvestReportAction.tsx +++ b/src/components/ReportActionItem/CreateHarvestReportAction.tsx @@ -17,7 +17,7 @@ function CreateHarvestReportAction({reportID}: CreateHarvestReportActionProps) { 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/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index 31a978e0db46..aa4af9ffbcce 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -2639,8 +2639,6 @@ describe('PureReportActionItem', () => { shouldDisplayNewMarker={false} index={0} isFirstVisibleReportAction={false} - reportNameValuePairsOrigin="harvest" - reportNameValuePairsOriginalID="origReport123" /> From 7eb6ca536523829fd5498da7d283641133f7f7b8 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 22 May 2026 10:41:11 +0200 Subject: [PATCH 20/23] fix unit tests set up --- tests/ui/PureReportActionItemTest.tsx | 28 +++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index aa4af9ffbcce..4d2d985fcf4d 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -2587,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( @@ -2595,7 +2606,7 @@ 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', }); }); @@ -2632,13 +2647,14 @@ describe('PureReportActionItem', () => { @@ -2649,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 () => { From 22421f74cf433df17227dcb055fca991c7c00c74 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Mon, 25 May 2026 09:27:14 +0200 Subject: [PATCH 21/23] fix type IOU --- .../ReceiptScanFailedContent.tsx | 16 +++--- src/selectors/ReportAction.ts | 12 ++--- tests/ui/PureReportActionItemTest.tsx | 2 +- tests/unit/selectors/ReportActionTest.ts | 50 +++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx index 30382291e3cb..bb86a612c152 100644 --- a/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx +++ b/src/pages/inbox/report/actionContents/ReceiptScanFailedContent.tsx @@ -1,4 +1,4 @@ -import {getReceiptScanFailedIouActionDataSelector} from '@selectors/ReportAction'; +import {getReceiptScanFailedIOUActionDataSelector} from '@selectors/ReportAction'; import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {useSession} from '@components/OnyxListItemProvider'; @@ -22,15 +22,15 @@ function ReceiptScanFailedContent({reportID, actionReportID, parentReportID, par 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 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 receiptScanFailedIOUActionDataSelector = (reportActions: OnyxEntry) => + getReceiptScanFailedIOUActionDataSelector(reportActions, isIOUReport, parentReportActionID, actionReportID); + const [receiptScanFailedIOUActionData] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(IOUReportID)}`, { + selector: receiptScanFailedIOUActionDataSelector, }); - const {transactionID, actorAccountID} = receiptScanFailedIouActionData ?? {}; + 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); diff --git a/src/selectors/ReportAction.ts b/src/selectors/ReportAction.ts index 05e39c9e2833..2bd22ef7c6f6 100644 --- a/src/selectors/ReportAction.ts +++ b/src/selectors/ReportAction.ts @@ -62,11 +62,11 @@ function getReportActionByIDSelector(reportActions: OnyxEntry, re */ function findIouActionForReceiptScanFailed( reportActions: OnyxEntry, - isIouReport: boolean, + isIOUReport: boolean, parentReportActionID: string | undefined, actionReportID: string | undefined, ): OnyxEntry { - if (!isIouReport && parentReportActionID) { + if (!isIOUReport && parentReportActionID) { const candidate = reportActions?.[parentReportActionID]; if (isActionOfType(candidate, CONST.REPORT.ACTIONS.TYPE.IOU)) { return candidate; @@ -85,13 +85,13 @@ function findIouActionForReceiptScanFailed( } /** Resolves the IOU action for a RECEIPT_SCAN_FAILED message and returns only the primitive fields the UI needs. */ -function getReceiptScanFailedIouActionDataSelector( +function getReceiptScanFailedIOUActionDataSelector( reportActions: OnyxEntry, - isIouReport: boolean, + isIOUReport: boolean, parentReportActionID: string | undefined, actionReportID: string | undefined, ): {transactionID: string | undefined; actorAccountID: number | undefined} { - const iouAction = findIouActionForReceiptScanFailed(reportActions, isIouReport, parentReportActionID, actionReportID); + const iouAction = findIouActionForReceiptScanFailed(reportActions, isIOUReport, parentReportActionID, actionReportID); return { transactionID: getLinkedTransactionID(iouAction), @@ -99,4 +99,4 @@ function getReceiptScanFailedIouActionDataSelector( }; } -export {getParentReportActionSelector, getLastClosedReportAction, getReportActionsForReportIDs, getReportActionByIDSelector, getReceiptScanFailedIouActionDataSelector}; +export {getParentReportActionSelector, getLastClosedReportAction, getReportActionsForReportIDs, getReportActionByIDSelector, getReceiptScanFailedIOUActionDataSelector}; diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index 4d2d985fcf4d..847c4f1256e0 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -2753,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/unit/selectors/ReportActionTest.ts b/tests/unit/selectors/ReportActionTest.ts index 1c6c92903e95..4e938c7a7951 100644 --- a/tests/unit/selectors/ReportActionTest.ts +++ b/tests/unit/selectors/ReportActionTest.ts @@ -1,4 +1,4 @@ -import {getReceiptScanFailedIouActionDataSelector} from '@selectors/ReportAction'; +import {getReceiptScanFailedIOUActionDataSelector} from '@selectors/ReportAction'; import CONST from '@src/CONST'; import type {ReportAction, ReportActions} from '@src/types/onyx'; @@ -7,7 +7,7 @@ const OTHER_USER_ACCOUNT_ID = 987654321; const TRANSACTION_ID = 'txn123'; const OTHER_TRANSACTION_ID = 'txn456'; -const createIouAction = (reportActionID: string, overrides: Partial = {}): ReportAction => ({ +const createIOUAction = (reportActionID: string, overrides: Partial = {}): ReportAction => ({ reportActionID, actionName: CONST.REPORT.ACTIONS.TYPE.IOU, actorAccountID: CURRENT_USER_ACCOUNT_ID, @@ -22,9 +22,9 @@ const createIouAction = (reportActionID: string, overrides: Partial { +describe('getReceiptScanFailedIOUActionDataSelector', () => { it('returns undefined transactionID and actorAccountID when reportActions is undefined', () => { - expect(getReceiptScanFailedIouActionDataSelector(undefined, false, 'iou1', 'thread1')).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(undefined, false, 'iou1', 'thread1')).toEqual({ transactionID: undefined, actorAccountID: undefined, }); @@ -32,8 +32,8 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { 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', { + iou1: createIOUAction('iou1'), + other: createIOUAction('other', { childReportID: 'thread1', originalMessage: { type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, @@ -44,7 +44,7 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { }), }; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, false, 'iou1', 'thread1')).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'iou1', 'thread1')).toEqual({ transactionID: TRANSACTION_ID, actorAccountID: CURRENT_USER_ACCOUNT_ID, }); @@ -52,8 +52,8 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { it('skips parentReportActionID lookup when the report is an IOU report', () => { const reportActions: ReportActions = { - parentIou: createIouAction('parentIou'), - childMatch: createIouAction('childMatch', { + parentIOU: createIOUAction('parentIOU'), + childMatch: createIOUAction('childMatch', { childReportID: 'thread1', originalMessage: { type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, @@ -64,7 +64,7 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { }), }; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, true, 'parentIou', 'thread1')).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, true, 'parentIOU', 'thread1')).toEqual({ transactionID: OTHER_TRANSACTION_ID, actorAccountID: CURRENT_USER_ACCOUNT_ID, }); @@ -72,11 +72,11 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { 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'}), + comment1: createIOUAction('comment1', {actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT}), + iou1: createIOUAction('iou1', {childReportID: 'thread1'}), }; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, false, 'comment1', 'thread1')).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'comment1', 'thread1')).toEqual({ transactionID: TRANSACTION_ID, actorAccountID: CURRENT_USER_ACCOUNT_ID, }); @@ -84,8 +84,8 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { it('returns transactionID and actorAccountID for the IOU action whose childReportID matches actionReportID', () => { const reportActions: ReportActions = { - iou1: createIouAction('iou1', {childReportID: 'thread1'}), - iou2: createIouAction('iou2', { + iou1: createIOUAction('iou1', {childReportID: 'thread1'}), + iou2: createIOUAction('iou2', { childReportID: 'thread2', originalMessage: { type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, @@ -96,16 +96,16 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { }), }; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, true, undefined, 'thread1')).toEqual({ + 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')}; + const reportActions: ReportActions = {iou1: createIOUAction('iou1')}; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, true, undefined, undefined)).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, true, undefined, undefined)).toEqual({ transactionID: TRANSACTION_ID, actorAccountID: CURRENT_USER_ACCOUNT_ID, }); @@ -113,11 +113,11 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { 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'}), + iou1: createIOUAction('iou1', {childReportID: 'thread1'}), + iou2: createIOUAction('iou2', {childReportID: 'thread2'}), }; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, true, undefined, 'thread3')).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, true, undefined, 'thread3')).toEqual({ transactionID: undefined, actorAccountID: undefined, }); @@ -125,10 +125,10 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { it('returns undefined transactionID and actorAccountID when no IOU actions exist', () => { const reportActions: ReportActions = { - comment1: createIouAction('comment1', {actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT}), + comment1: createIOUAction('comment1', {actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT}), }; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, false, 'comment1', 'thread1')).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'comment1', 'thread1')).toEqual({ transactionID: undefined, actorAccountID: undefined, }); @@ -136,10 +136,10 @@ describe('getReceiptScanFailedIouActionDataSelector', () => { 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}), + iou1: createIOUAction('iou1', {actorAccountID: OTHER_USER_ACCOUNT_ID}), }; - expect(getReceiptScanFailedIouActionDataSelector(reportActions, false, 'iou1', 'thread1')).toEqual({ + expect(getReceiptScanFailedIOUActionDataSelector(reportActions, false, 'iou1', 'thread1')).toEqual({ transactionID: TRANSACTION_ID, actorAccountID: OTHER_USER_ACCOUNT_ID, }); From 61707c4199212f10a35868e09cb8a80203595851 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Mon, 25 May 2026 09:29:48 +0200 Subject: [PATCH 22/23] fix type IOU --- src/selectors/ReportAction.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/selectors/ReportAction.ts b/src/selectors/ReportAction.ts index 2bd22ef7c6f6..71914e032da1 100644 --- a/src/selectors/ReportAction.ts +++ b/src/selectors/ReportAction.ts @@ -60,7 +60,7 @@ function getReportActionByIDSelector(reportActions: OnyxEntry, re * 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( +function findIOUActionForReceiptScanFailed( reportActions: OnyxEntry, isIOUReport: boolean, parentReportActionID: string | undefined, @@ -72,16 +72,16 @@ function findIouActionForReceiptScanFailed( return candidate; } } - const iouActions = Object.values(reportActions ?? {}).filter((action): action is ReportAction => + 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); + const match = IOUActions.find((action) => action.childReportID === actionReportID); if (match) { return match; } } - return iouActions.length === 1 ? iouActions.at(0) : undefined; + 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. */ @@ -91,11 +91,11 @@ function getReceiptScanFailedIOUActionDataSelector( parentReportActionID: string | undefined, actionReportID: string | undefined, ): {transactionID: string | undefined; actorAccountID: number | undefined} { - const iouAction = findIouActionForReceiptScanFailed(reportActions, isIOUReport, parentReportActionID, actionReportID); + const IOUAction = findIOUActionForReceiptScanFailed(reportActions, isIOUReport, parentReportActionID, actionReportID); return { - transactionID: getLinkedTransactionID(iouAction), - actorAccountID: iouAction?.actorAccountID, + transactionID: getLinkedTransactionID(IOUAction), + actorAccountID: IOUAction?.actorAccountID, }; } From f8454471157f5c22d7a246360cc0b4277f061740 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Mon, 25 May 2026 10:09:39 +0200 Subject: [PATCH 23/23] fix ts after revert --- src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx | 5 ----- 1 file changed, 5 deletions(-) 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} />