diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index 89577aa736c7..623a3f3494b5 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -8349,6 +8349,7 @@ const CONST = {
},
HTML_RENDERER: {
IMAGE: 'HTMLRenderer-Image',
+ PRE: 'HTMLRenderer-Pre',
},
RECEIPT: {
IMAGE: 'Receipt-Image',
diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
index 22ad207ba9e8..d43c203b50d3 100644
--- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
+++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import AttachmentView from '@components/Attachments/AttachmentView';
import {useSession} from '@components/OnyxListItemProvider';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
-import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
+import {showContextMenuForReport, useShowContextMenuActions, useShowContextMenuState} from '@components/ShowContextMenuContext';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
@@ -39,45 +39,44 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', onP
const encryptedAuthToken = session?.encryptedAuthToken ?? '';
const sourceURLWithAuth = addEncryptedAuthTokenToURL(source, encryptedAuthToken);
+ const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState();
+ const {checkIfContextMenuActive} = useShowContextMenuActions();
+
return (
-
- {({anchor, report, isReportArchived, action, checkIfContextMenuActive, isDisabled, shouldDisplayContextMenu}) => (
- {
- if (isDownloading || isOffline || !sourceID) {
- return;
- }
- setDownload(sourceID, true);
- fileDownload(translate, sourceURLWithAuth, displayName, '', isMobileSafari()).then(() => setDownload(sourceID, false));
- }}
- onPressIn={onPressIn}
- onPressOut={onPressOut}
- onLongPress={(event) => {
- if (isDisabled || !shouldDisplayContextMenu) {
- return;
- }
- showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived));
- }}
- shouldUseHapticsOnLongPress
- accessibilityLabel={displayName}
- role={CONST.ROLE.BUTTON}
- sentryLabel={CONST.SENTRY_LABEL.BASE_ANCHOR_FOR_ATTACHMENTS_ONLY.DOWNLOAD_BUTTON}
- >
-
-
- )}
-
+ {
+ if (isDownloading || isOffline || !sourceID) {
+ return;
+ }
+ setDownload(sourceID, true);
+ fileDownload(translate, sourceURLWithAuth, displayName, '', isMobileSafari()).then(() => setDownload(sourceID, false));
+ }}
+ onPressIn={onPressIn}
+ onPressOut={onPressOut}
+ onLongPress={(event) => {
+ if (isDisabled || !shouldDisplayContextMenu) {
+ return;
+ }
+ showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived));
+ }}
+ shouldUseHapticsOnLongPress
+ accessibilityLabel={displayName}
+ role={CONST.ROLE.BUTTON}
+ sentryLabel={CONST.SENTRY_LABEL.BASE_ANCHOR_FOR_ATTACHMENTS_ONLY.DOWNLOAD_BUTTON}
+ >
+
+
);
}
diff --git a/src/components/ConnectToNetSuiteFlow/index.tsx b/src/components/ConnectToNetSuiteFlow/index.tsx
index fd45055656b6..e0c48c442c9d 100644
--- a/src/components/ConnectToNetSuiteFlow/index.tsx
+++ b/src/components/ConnectToNetSuiteFlow/index.tsx
@@ -7,7 +7,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import {isAuthenticationError} from '@libs/actions/connections';
import {getAdminPoliciesConnectedToNetSuite} from '@libs/actions/Policy/Policy';
import Navigation from '@libs/Navigation/Navigation';
-import {useAccountingContext} from '@pages/workspace/accounting/AccountingContext';
+import {useAccountingState} from '@pages/workspace/accounting/AccountingContext';
import {getInitialSubPageForNetsuiteTokenInput} from '@pages/workspace/accounting/netsuite/utils';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
@@ -26,7 +26,7 @@ function ConnectToNetSuiteFlow({policyID}: ConnectToNetSuiteFlowProps) {
const [isReuseConnectionsPopoverOpen, setIsReuseConnectionsPopoverOpen] = useState(false);
const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState({horizontal: 0, vertical: 0});
- const {popoverAnchorRefs} = useAccountingContext();
+ const {popoverAnchorRefs} = useAccountingState();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const icons = useMemoizedLazyExpensifyIcons(['Copy', 'LinkCopy'] as const);
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx
index 56247d66db95..c2ae94febd35 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx
@@ -4,7 +4,7 @@ import {AttachmentContext} from '@components/AttachmentContext';
import {getButtonRole} from '@components/Button/utils';
import {isDeletedNode} from '@components/HTMLEngineProvider/htmlEngineUtils';
import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus';
-import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
+import {showContextMenuForReport, useShowContextMenuActions, useShowContextMenuState} from '@components/ShowContextMenuContext';
import ThumbnailImage from '@components/ThumbnailImage';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
@@ -90,53 +90,52 @@ function ImageRenderer({tnode}: CustomRendererProps) {
/>
);
+ const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState();
+ const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions();
+
return imagePreviewModalDisabled ? (
thumbnailImageComponent
) : (
-
- {({onShowContextMenu, anchor, report, isReportArchived, action, checkIfContextMenuActive, isDisabled, shouldDisplayContextMenu}) => (
-
- {({reportID, accountID, type}) => (
- {
- if (!source || !type) {
- return;
- }
+
+ {({reportID, accountID, type}) => (
+ {
+ if (!source || !type) {
+ return;
+ }
- const attachmentLink = tnode.parent?.attributes?.href;
- const route = ROUTES.REPORT_ATTACHMENTS?.getRoute({
- attachmentID,
- reportID,
- type,
- source,
- accountID,
- isAuthTokenRequired: isAttachmentOrReceipt,
- originalFileName: fileName,
- attachmentLink,
- });
- Navigation.navigate(route);
- }}
- onLongPress={(event) => {
- if (isDisabled || !shouldDisplayContextMenu) {
- return;
- }
- return onShowContextMenu(() =>
- showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)),
- );
- }}
- isNested
- shouldUseHapticsOnLongPress
- role={getButtonRole(true)}
- accessibilityLabel={translate('accessibilityHints.viewAttachment')}
- sentryLabel={CONST.SENTRY_LABEL.HTML_RENDERER.IMAGE}
- >
- {thumbnailImageComponent}
-
- )}
-
+ const attachmentLink = tnode.parent?.attributes?.href;
+ const route = ROUTES.REPORT_ATTACHMENTS?.getRoute({
+ attachmentID,
+ reportID,
+ type,
+ source,
+ accountID,
+ isAuthTokenRequired: isAttachmentOrReceipt,
+ originalFileName: fileName,
+ attachmentLink,
+ });
+ Navigation.navigate(route);
+ }}
+ onLongPress={(event) => {
+ if (isDisabled || !shouldDisplayContextMenu) {
+ return;
+ }
+ return onShowContextMenu(() =>
+ showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)),
+ );
+ }}
+ isNested
+ shouldUseHapticsOnLongPress
+ role={getButtonRole(true)}
+ accessibilityLabel={translate('accessibilityHints.viewAttachment')}
+ sentryLabel={CONST.SENTRY_LABEL.HTML_RENDERER.IMAGE}
+ >
+ {thumbnailImageComponent}
+
)}
-
+
);
}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx
index acbfb286920a..c0a17325bb89 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx
@@ -2,7 +2,6 @@ import React, {useContext, useMemo} from 'react';
import type {TextStyle} from 'react-native';
import {StyleSheet} from 'react-native';
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import {useCurrentReportIDState} from '@hooks/useCurrentReportID';
import useOnyx from '@hooks/useOnyx';
@@ -52,32 +51,28 @@ function MentionReportRenderer({style, tnode, TDefaultRenderer, ...defaultRender
const {color, ...styleWithoutColor} = flattenStyle;
return (
-
- {() => (
- {
- event.preventDefault();
- Navigation.navigate(navigationRoute);
- }
- : undefined
- }
- role={isGroupPolicyReport ? CONST.ROLE.LINK : undefined}
- accessibilityLabel={isGroupPolicyReport ? `/${navigationRoute}` : undefined}
- >
- #{mentionDisplayText}
-
- )}
-
+ {
+ event.preventDefault();
+ Navigation.navigate(navigationRoute);
+ }
+ : undefined
+ }
+ role={isGroupPolicyReport ? CONST.ROLE.LINK : undefined}
+ accessibilityLabel={isGroupPolicyReport ? `/${navigationRoute}` : undefined}
+ >
+ #{mentionDisplayText}
+
);
}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
index fac38983df18..540d0b66cab9 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx
@@ -7,7 +7,7 @@ import type {TextStyle} from 'react-native';
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
import {TNodeChildrenRenderer} from 'react-native-render-html';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
-import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
+import {showContextMenuForReport, useShowContextMenuActions, useShowContextMenuState} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import UserDetailsTooltip from '@components/UserDetailsTooltip';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
@@ -33,6 +33,8 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
const htmlAttribAccountID = tnode.attributes.accountid;
const personalDetails = usePersonalDetails();
const htmlAttributeAccountID = tnode.attributes.accountid;
+ const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState();
+ const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions();
let accountID: number;
let mentionDisplayText: string;
@@ -74,56 +76,52 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
const {color, ...styleWithoutColor} = flattenStyle;
return (
-
- {({onShowContextMenu, anchor, report, isReportArchived, action, checkIfContextMenuActive, isDisabled, shouldDisplayContextMenu}) => (
+ {
+ if (isDisabled || !shouldDisplayContextMenu) {
+ return;
+ }
+ return onShowContextMenu(() =>
+ showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)),
+ );
+ }}
+ onPress={(event) => {
+ event.preventDefault();
+ if (!isEmpty(htmlAttribAccountID)) {
+ Navigation.navigate(ROUTES.PROFILE.getRoute(parseInt(htmlAttribAccountID, 10), Navigation.getReportRHPActiveRoute()));
+ return;
+ }
+ Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getReportRHPActiveRoute(), mentionDisplayText));
+ }}
+ role={CONST.ROLE.LINK}
+ accessibilityLabel={`/${navigationRoute}`}
+ >
+
{
- if (isDisabled || !shouldDisplayContextMenu) {
- return;
- }
- return onShowContextMenu(() =>
- showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)),
- );
- }}
- onPress={(event) => {
- event.preventDefault();
- if (!isEmpty(htmlAttribAccountID)) {
- Navigation.navigate(ROUTES.PROFILE.getRoute(parseInt(htmlAttribAccountID, 10), Navigation.getReportRHPActiveRoute()));
- return;
- }
- Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getReportRHPActiveRoute(), mentionDisplayText));
- }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...defaultRendererProps}
+ style={[
+ styles.link,
+ styleWithoutColor,
+ StyleUtils.getMentionStyle(isOurMention),
+ {color: StyleUtils.getMentionTextColor(isOurMention)},
+ styles.breakWord,
+ styles.textWrap,
+ ]}
role={CONST.ROLE.LINK}
- accessibilityLabel={`/${navigationRoute}`}
+ testID="mention-user"
+ href={`/${navigationRoute}`}
>
-
-
- {htmlAttribAccountID ? `@${mentionDisplayText}` : }
-
-
+ {htmlAttribAccountID ? `@${mentionDisplayText}` : }
- )}
-
+
+
);
}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx
index 8392bfefc4cf..122666021263 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx
@@ -4,7 +4,7 @@ import type {GestureResponderEvent} from 'react-native';
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
-import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
+import {showContextMenuForReport, useShowContextMenuActions, useShowContextMenuState} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
@@ -33,6 +33,8 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
+ const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState();
+ const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions();
const isLast = defaultRendererProps.renderIndex === defaultRendererProps.renderLength - 1;
const isChildOfTaskTitle = HTMLEngineUtils.isChildOfTaskTitle(defaultRendererProps.tnode);
@@ -51,33 +53,30 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d
return (
-
- {({onShowContextMenu, anchor, report, isReportArchived, action, checkIfContextMenuActive, isDisabled, shouldDisplayContextMenu}) => (
- {})}
- onPressIn={onPressIn}
- onPressOut={onPressOut}
- onLongPress={(event) => {
- onShowContextMenu(() => {
- if (isDisabled || !shouldDisplayContextMenu) {
- return;
- }
- return showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived));
- });
- }}
- shouldUseHapticsOnLongPress
- role={CONST.ROLE.PRESENTATION}
- accessibilityLabel={translate('accessibilityHints.preStyledText')}
- >
-
-
- {/* eslint-disable-next-line react/jsx-props-no-spreading */}
-
-
-
-
- )}
-
+ {})}
+ onPressIn={onPressIn}
+ onPressOut={onPressOut}
+ onLongPress={(event) => {
+ onShowContextMenu(() => {
+ if (isDisabled || !shouldDisplayContextMenu) {
+ return;
+ }
+ return showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived));
+ });
+ }}
+ shouldUseHapticsOnLongPress
+ role={CONST.ROLE.PRESENTATION}
+ accessibilityLabel={translate('accessibilityHints.preStyledText')}
+ >
+
+
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
+
+
+
+
);
}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
index 2fd4075892b2..0eb1536b2770 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
import {AttachmentContext} from '@components/AttachmentContext';
import {isDeletedNode} from '@components/HTMLEngineProvider/htmlEngineUtils';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
+import {useShowContextMenuState} from '@components/ShowContextMenuContext';
import VideoPlayerPreview from '@components/VideoPlayerPreview';
import {getFileName} from '@libs/fileDownload/FileUtils';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
@@ -27,41 +27,39 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
const isDeleted = isDeletedNode(tnode);
const attachmentID = htmlAttribs[CONST.ATTACHMENT_ID_ATTRIBUTE];
+ const {report} = useShowContextMenuState();
+
return (
-
- {({report}) => (
-
- {({accountID, type, hashKey, reportID}) => (
- {
- if (!sourceURL || !type) {
- return;
- }
- const isAuthTokenRequired = !!htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE];
- const route = ROUTES.REPORT_ATTACHMENTS.getRoute({
- attachmentID,
- reportID: report?.reportID,
- type,
- source: sourceURL,
- accountID,
- isAuthTokenRequired,
- hashKey,
- });
- Navigation.navigate(route);
- }}
- />
- )}
-
+
+ {({accountID, type, hashKey, reportID}) => (
+ {
+ if (!sourceURL || !type) {
+ return;
+ }
+ const isAuthTokenRequired = !!htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE];
+ const route = ROUTES.REPORT_ATTACHMENTS.getRoute({
+ attachmentID,
+ reportID: report?.reportID,
+ type,
+ source: sourceURL,
+ accountID,
+ isAuthTokenRequired,
+ hashKey,
+ });
+ Navigation.navigate(route);
+ }}
+ />
)}
-
+
);
}
diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx
index 96cf0f05b3f2..5fe4b94b526e 100644
--- a/src/components/MoneyRequestConfirmationListFooter.tsx
+++ b/src/components/MoneyRequestConfirmationListFooter.tsx
@@ -69,7 +69,7 @@ import PDFThumbnail from './PDFThumbnail';
import PressableWithoutFocus from './Pressable/PressableWithoutFocus';
import ReceiptEmptyState from './ReceiptEmptyState';
import ReceiptImage from './ReceiptImage';
-import {ShowContextMenuContext} from './ShowContextMenuContext';
+import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from './ShowContextMenuContext';
import Text from './Text';
type MoneyRequestConfirmationListFooterProps = {
@@ -450,20 +450,26 @@ function MoneyRequestConfirmationListFooter({
// Time requests appear as regular expenses after they're created, with editable amount and merchant, not hours and rate
const shouldShowTimeRequestFields = isTimeRequest && action === CONST.IOU.ACTION.CREATE;
- const contextMenuContextValue = useMemo(
+ const contextMenuStateValue = useMemo(
() => ({
anchor: null,
report: undefined,
isReportArchived: false,
action: undefined,
- checkIfContextMenuActive: () => {},
- onShowContextMenu: () => {},
isDisabled: true,
shouldDisplayContextMenu: false,
}),
[],
);
+ const contextMenuActionsValue = useMemo(
+ () => ({
+ checkIfContextMenuActive: () => {},
+ onShowContextMenu: () => {},
+ }),
+ [],
+ );
+
const tagVisibility = useMemo(
() =>
getTagVisibility({
@@ -522,33 +528,35 @@ function MoneyRequestConfirmationListFooter({
{
item: (
-
-
- {
- if (!transactionID) {
- return;
- }
-
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID),
- );
- }}
- style={[styles.moneyRequestMenuItem]}
- titleStyle={styles.flex1}
- disabled={didConfirm}
- interactive={!isReadOnly}
- numberOfLinesTitle={2}
- rightLabel={isDescriptionRequired ? translate('common.required') : ''}
- sentryLabel={CONST.SENTRY_LABEL.REQUEST_CONFIRMATION_LIST.DESCRIPTION_FIELD}
- />
-
-
+
+
+
+ {
+ if (!transactionID) {
+ return;
+ }
+
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID),
+ );
+ }}
+ style={[styles.moneyRequestMenuItem]}
+ titleStyle={styles.flex1}
+ disabled={didConfirm}
+ interactive={!isReadOnly}
+ numberOfLinesTitle={2}
+ rightLabel={isDescriptionRequired ? translate('common.required') : ''}
+ sentryLabel={CONST.SENTRY_LABEL.REQUEST_CONFIRMATION_LIST.DESCRIPTION_FIELD}
+ />
+
+
+
),
shouldShow: true,
diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx
index 7edc41301348..1f41300d8b84 100644
--- a/src/components/ReportActionItem/MoneyRequestAction.tsx
+++ b/src/components/ReportActionItem/MoneyRequestAction.tsx
@@ -1,6 +1,6 @@
import {useRoute} from '@react-navigation/native';
import lodashIsEmpty from 'lodash/isEmpty';
-import React, {useContext, useMemo} from 'react';
+import React, {useMemo} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import RenderHTML from '@components/RenderHTML';
import useLocalize from '@hooks/useLocalize';
@@ -24,7 +24,7 @@ import {
} from '@libs/ReportActionsUtils';
import type {ContextMenuAnchor} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
import {contextMenuRef} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
-import ReportActionItemContext from '@pages/inbox/report/ReportActionItemContext';
+import {useReportActionItemActions, useReportActionItemState} from '@pages/inbox/report/ReportActionItemContext';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -82,7 +82,8 @@ function MoneyRequestAction({
isWhisper = false,
shouldDisplayContextMenu = true,
}: MoneyRequestActionProps) {
- const {shouldOpenReportInRHP, onPreviewPressed} = useContext(ReportActionItemContext);
+ const {shouldOpenReportInRHP} = useReportActionItemState();
+ const {onPreviewPressed} = useReportActionItemActions();
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`);
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, {canEvict: false});
diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx
index 31d61306dc56..3b756aa774ed 100644
--- a/src/components/ReportActionItem/ReportActionItemImage.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImage.tsx
@@ -9,7 +9,7 @@ import type {IconSize} from '@components/EReceiptThumbnail';
import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus';
import type {ReceiptImageProps} from '@components/ReceiptImage';
import ReceiptImage from '@components/ReceiptImage';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
+import {useShowContextMenuState} from '@components/ShowContextMenuContext';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -114,6 +114,7 @@ function ReportActionItemImage({
const styles = useThemeStyles();
const {translate} = useLocalize();
const icons = useMemoizedLazyExpensifyIcons(['Receipt']);
+ const {report: contextReport, transactionThreadReport} = useShowContextMenuState();
const isMapDistanceRequest = !!transaction && isDistanceRequest(transaction) && !isManualDistanceRequest(transaction);
const hasPendingWaypoints = transaction && isFetchingWaypointsFromServer(transaction);
const hasErrors = !isEmptyObject(transaction?.errors) || !isEmptyObject(transaction?.errorFields?.route) || !isEmptyObject(transaction?.errorFields?.waypoints);
@@ -178,34 +179,30 @@ function ReportActionItemImage({
if (enablePreviewModal) {
return (
-
- {({report, transactionThreadReport}) => (
-
- Navigation.navigate(
- ROUTES.TRANSACTION_RECEIPT.getRoute(
- transactionThreadReport?.reportID ?? report?.reportID ?? reportProp?.reportID ?? getReportIDForExpense(transaction),
- transaction?.transactionID,
- readonly,
- mergeTransactionID,
- ),
- )
- }
- accessibilityLabel={translate('accessibilityHints.viewAttachment')}
- accessibilityRole={CONST.ROLE.BUTTON}
- sentryLabel={CONST.SENTRY_LABEL.RECEIPT.IMAGE}
- >
-
-
- )}
-
+
+ Navigation.navigate(
+ ROUTES.TRANSACTION_RECEIPT.getRoute(
+ transactionThreadReport?.reportID ?? contextReport?.reportID ?? reportProp?.reportID ?? getReportIDForExpense(transaction),
+ transaction?.transactionID,
+ readonly,
+ mergeTransactionID,
+ ),
+ )
+ }
+ accessibilityLabel={translate('accessibilityHints.viewAttachment')}
+ accessibilityRole={CONST.ROLE.BUTTON}
+ sentryLabel={CONST.SENTRY_LABEL.RECEIPT.IMAGE}
+ >
+
+
);
}
diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx
index de9160b0e818..b5ec3c895e40 100644
--- a/src/components/ReportActionItem/TaskView.tsx
+++ b/src/components/ReportActionItem/TaskView.tsx
@@ -11,7 +11,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
import RenderHTML from '@components/RenderHTML';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
+import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useHasOutstandingChildTask from '@hooks/useHasOutstandingChildTask';
@@ -82,151 +82,159 @@ function TaskView({report, parentReport, action}: TaskViewProps) {
const disableState = !isTaskModifiable;
const isDisableInteractive = disableState || !isOpen;
const accountID = currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID;
- const contextValue = useMemo(
+ const contextMenuStateValue = useMemo(
() => ({
anchor: null,
report,
isReportArchived: false,
action,
transactionThreadReport: undefined,
- checkIfContextMenuActive: () => {},
isDisabled: true,
- onShowContextMenu: (callback: () => void) => callback(),
shouldDisplayContextMenu: false,
}),
[report, action],
);
+ const contextMenuActionsValue = useMemo(
+ () => ({
+ checkIfContextMenuActive: () => {},
+ onShowContextMenu: (callback: () => void) => callback(),
+ }),
+ [],
+ );
+
const attachmentContextValue = useMemo(() => ({type: CONST.ATTACHMENT_TYPE.ONBOARDING, accountID}), [accountID]);
return (
-
-
- clearTaskErrors(report, conciergeReportID, accountID)}
- errorRowStyles={styles.ph5}
- >
-
- {(hovered) => (
- {
- if (isDisableInteractive) {
- return;
- }
- if (e && e.type === 'click') {
- (e.currentTarget as HTMLElement).blur();
- }
+
+
+
+ clearTaskErrors(report, conciergeReportID, accountID)}
+ errorRowStyles={styles.ph5}
+ >
+
+ {(hovered) => (
+ {
+ if (isDisableInteractive) {
+ return;
+ }
+ if (e && e.type === 'click') {
+ (e.currentTarget as HTMLElement).blur();
+ }
- Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()));
- })}
- style={({pressed}) => [
- styles.ph5,
- styles.pv2,
- StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true),
- isDisableInteractive && styles.cursorDefault,
- ]}
- accessibilityLabel={taskTitle || translate('task.task')}
- disabled={isDisableInteractive}
- sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_TITLE}
- >
- {({pressed}) => (
-
- {translate('task.title')}
-
- {
- // If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page.
- if (isActiveTaskEditRoute(report?.reportID)) {
- return;
- }
- if (isCompleted) {
- reopenTask(report, parentReport, currentUserPersonalDetails.accountID);
- } else {
- completeTask(report, parentReport?.hasOutstandingChildTask ?? false, hasOutstandingChildTask, parentReportAction);
- }
- })}
- isChecked={isCompleted}
- style={styles.taskMenuItemCheckbox}
- containerSize={24}
- containerBorderRadius={8}
- caretSize={16}
- accessibilityLabel={taskTitle || translate('task.task')}
- disabled={!isTaskActionable}
- sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_CHECKBOX}
- />
+ Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()));
+ })}
+ style={({pressed}) => [
+ styles.ph5,
+ styles.pv2,
+ StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true),
+ isDisableInteractive && styles.cursorDefault,
+ ]}
+ accessibilityLabel={taskTitle || translate('task.task')}
+ disabled={isDisableInteractive}
+ sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_TITLE}
+ >
+ {({pressed}) => (
+
+ {translate('task.title')}
-
-
- {!isDisableInteractive && (
-
-
+ {
+ // If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page.
+ if (isActiveTaskEditRoute(report?.reportID)) {
+ return;
+ }
+ if (isCompleted) {
+ reopenTask(report, parentReport, currentUserPersonalDetails.accountID);
+ } else {
+ completeTask(report, parentReport?.hasOutstandingChildTask ?? false, hasOutstandingChildTask, parentReportAction);
+ }
+ })}
+ isChecked={isCompleted}
+ style={styles.taskMenuItemCheckbox}
+ containerSize={24}
+ containerBorderRadius={8}
+ caretSize={16}
+ accessibilityLabel={taskTitle || translate('task.task')}
+ disabled={!isTaskActionable}
+ sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_CHECKBOX}
+ />
+
+
- )}
-
-
- )}
-
- )}
-
-
- Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))}
- shouldShowRightIcon={!isDisableInteractive}
- disabled={disableState}
- wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
- shouldGreyOutWhenDisabled={false}
- numberOfLinesTitle={0}
- interactive={!isDisableInteractive}
- shouldUseDefaultCursorWhenDisabled
- sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_DESCRIPTION}
- />
-
-
- {report?.managerID ? (
-
+ )}
+
+ )}
+
+
Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))}
+ shouldRenderAsHTML
+ description={translate('task.description')}
+ title={report?.description ?? ''}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))}
shouldShowRightIcon={!isDisableInteractive}
disabled={disableState}
- wrapperStyle={[styles.pv2]}
+ wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
shouldGreyOutWhenDisabled={false}
+ numberOfLinesTitle={0}
interactive={!isDisableInteractive}
shouldUseDefaultCursorWhenDisabled
- sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_ASSIGNEE}
+ sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_DESCRIPTION}
/>
- )}
+
+
+ {report?.managerID ? (
+
-
-
-
+
+
+
);
}
diff --git a/src/components/ShowContextMenuContext/default.ts b/src/components/ShowContextMenuContext/default.ts
new file mode 100644
index 000000000000..fb961ad6b127
--- /dev/null
+++ b/src/components/ShowContextMenuContext/default.ts
@@ -0,0 +1,18 @@
+import type {ShowContextMenuActionsContextType, ShowContextMenuStateContextType} from './types';
+
+const defaultShowContextMenuStateContextValue: ShowContextMenuStateContextType = {
+ anchor: null,
+ report: undefined,
+ isReportArchived: false,
+ action: undefined,
+ transactionThreadReport: undefined,
+ isDisabled: true,
+ shouldDisplayContextMenu: true,
+};
+
+const defaultShowContextMenuActionsContextValue: ShowContextMenuActionsContextType = {
+ checkIfContextMenuActive: () => {},
+ onShowContextMenu: (callback) => callback(),
+};
+
+export {defaultShowContextMenuStateContextValue, defaultShowContextMenuActionsContextValue};
diff --git a/src/components/ShowContextMenuContext.ts b/src/components/ShowContextMenuContext/index.tsx
similarity index 61%
rename from src/components/ShowContextMenuContext.ts
rename to src/components/ShowContextMenuContext/index.tsx
index f56e6e62e8ec..d6e7aa6183ee 100644
--- a/src/components/ShowContextMenuContext.ts
+++ b/src/components/ShowContextMenuContext/index.tsx
@@ -1,4 +1,4 @@
-import {createContext} from 'react';
+import {createContext, useContext} from 'react';
// eslint-disable-next-line no-restricted-imports
import type {GestureResponderEvent} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
@@ -7,31 +7,20 @@ import {getOriginalReportID} from '@libs/ReportUtils';
import {showContextMenu} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
import type {ContextMenuAnchor} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
import CONST from '@src/CONST';
-import type {Report, ReportAction} from '@src/types/onyx';
+import type {ReportAction} from '@src/types/onyx';
+import {defaultShowContextMenuActionsContextValue, defaultShowContextMenuStateContextValue} from './default';
+import type {ShowContextMenuActionsContextType, ShowContextMenuStateContextType} from './types';
-type ShowContextMenuContextProps = {
- anchor: ContextMenuAnchor;
- report: OnyxEntry;
- isReportArchived: boolean;
- action: OnyxEntry;
- transactionThreadReport?: OnyxEntry;
- checkIfContextMenuActive: () => void;
- onShowContextMenu: (callback: () => void) => void;
- isDisabled: boolean;
- shouldDisplayContextMenu?: boolean;
-};
+const ShowContextMenuStateContext = createContext(defaultShowContextMenuStateContextValue);
+const ShowContextMenuActionsContext = createContext(defaultShowContextMenuActionsContextValue);
-const ShowContextMenuContext = createContext({
- anchor: null,
- onShowContextMenu: (callback) => callback(),
- report: undefined,
- isReportArchived: false,
- action: undefined,
- transactionThreadReport: undefined,
- checkIfContextMenuActive: () => {},
- isDisabled: true,
- shouldDisplayContextMenu: true,
-});
+function useShowContextMenuState(): ShowContextMenuStateContextType {
+ return useContext(ShowContextMenuStateContext);
+}
+
+function useShowContextMenuActions(): ShowContextMenuActionsContextType {
+ return useContext(ShowContextMenuActionsContext);
+}
/**
* Show the report action context menu.
@@ -75,5 +64,5 @@ function showContextMenuForReport(
});
}
-export {ShowContextMenuContext, showContextMenuForReport};
-export type {ShowContextMenuContextProps};
+export {ShowContextMenuStateContext, ShowContextMenuActionsContext, useShowContextMenuState, useShowContextMenuActions, showContextMenuForReport};
+export type {ShowContextMenuActionsContextType, ShowContextMenuStateContextType} from './types';
diff --git a/src/components/ShowContextMenuContext/types.ts b/src/components/ShowContextMenuContext/types.ts
new file mode 100644
index 000000000000..a75c3376bde7
--- /dev/null
+++ b/src/components/ShowContextMenuContext/types.ts
@@ -0,0 +1,20 @@
+import type {OnyxEntry} from 'react-native-onyx';
+import type {ContextMenuAnchor} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
+import type {Report, ReportAction} from '@src/types/onyx';
+
+type ShowContextMenuStateContextType = {
+ anchor: ContextMenuAnchor;
+ report: OnyxEntry;
+ isReportArchived: boolean;
+ action: OnyxEntry;
+ transactionThreadReport?: OnyxEntry;
+ isDisabled: boolean;
+ shouldDisplayContextMenu?: boolean;
+};
+
+type ShowContextMenuActionsContextType = {
+ checkIfContextMenuActive: () => void;
+ onShowContextMenu: (callback: () => void) => void;
+};
+
+export type {ShowContextMenuStateContextType, ShowContextMenuActionsContextType};
diff --git a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx
index a7a031dd372f..285ffe861f6d 100644
--- a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx
+++ b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx
@@ -5,7 +5,7 @@ import AttachmentDeletedIndicator from '@components/AttachmentDeletedIndicator';
import Icon from '@components/Icon';
import Image from '@components/Image';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
-import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
+import {showContextMenuForReport, useShowContextMenuActions, useShowContextMenuState} from '@components/ShowContextMenuContext';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useThemeStyles from '@hooks/useThemeStyles';
import ControlSelection from '@libs/ControlSelection';
@@ -31,6 +31,8 @@ type VideoPlayerThumbnailProps = {
function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel, isDeleted}: VideoPlayerThumbnailProps) {
const styles = useThemeStyles();
const icons = useMemoizedLazyExpensifyIcons(['Play'] as const);
+ const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState();
+ const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions();
return (
@@ -45,37 +47,33 @@ function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel, isDele
)}
{!isDeleted ? (
-
- {({anchor, report, isReportArchived, action, checkIfContextMenuActive, isDisabled, onShowContextMenu, shouldDisplayContextMenu}) => (
- canUseTouchScreen() && ControlSelection.block()}
- onPressOut={() => ControlSelection.unblock()}
- onLongPress={(event) => {
- if (isDisabled || !shouldDisplayContextMenu) {
- return;
- }
- onShowContextMenu(() => {
- showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived));
- });
- }}
- shouldUseHapticsOnLongPress
- sentryLabel={CONST.SENTRY_LABEL.VIDEO_PLAYER.THUMBNAIL}
- >
-
-
-
-
- )}
-
+ canUseTouchScreen() && ControlSelection.block()}
+ onPressOut={() => ControlSelection.unblock()}
+ onLongPress={(event) => {
+ if (isDisabled || !shouldDisplayContextMenu) {
+ return;
+ }
+ onShowContextMenu(() => {
+ showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived));
+ });
+ }}
+ shouldUseHapticsOnLongPress
+ sentryLabel={CONST.SENTRY_LABEL.VIDEO_PLAYER.THUMBNAIL}
+ >
+
+
+
+
) : (
)}
diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx
index 6afebaa856b8..04f7538d1043 100644
--- a/src/pages/TransactionDuplicate/Confirmation.tsx
+++ b/src/pages/TransactionDuplicate/Confirmation.tsx
@@ -10,7 +10,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import MoneyRequestView from '@components/ReportActionItem/MoneyRequestView';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
+import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import {useWideRHPState} from '@components/WideRHPContextProvider';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
@@ -92,13 +92,11 @@ function Confirmation() {
Navigation.dismissToSuperWideRHP();
}, [transactionsMergeParams]);
- const contextValue = useMemo(
+ const contextMenuStateValue = useMemo(
() => ({
transactionThreadReport: report,
action: reportAction,
report,
- checkIfContextMenuActive: () => {},
- onShowContextMenu: () => {},
isReportArchived: false,
anchor: null,
isDisabled: false,
@@ -106,6 +104,14 @@ function Confirmation() {
[report, reportAction],
);
+ const contextMenuActionsValue = useMemo(
+ () => ({
+ checkIfContextMenuActive: () => {},
+ onShowContextMenu: () => {},
+ }),
+ [],
+ );
+
const doesTransactionBelongToReport = reviewDuplicates?.transactionID === transactionID || (transactionID && reviewDuplicates?.duplicates.includes(transactionID));
const isDismissingRef = useRef(false);
@@ -144,17 +150,19 @@ function Confirmation() {
{translate('violations.confirmDuplicatesInfo')}
{/* We need that provider here because MoneyRequestView component requires that */}
-
- }
- isFromReviewDuplicates
- />
-
+
+
+ }
+ isFromReviewDuplicates
+ />
+
+
+ )}
+ {/**
These are the actionable buttons that appear at the bottom of a Concierge message
for example: Invite a user mentioned but not a member of the room
https://github.com/Expensify/App/issues/32741
*/}
- {actionableItemButtons.length > 0 && (
-
- )}
-
- ) : (
-
- )}
-
-
+ {actionableItemButtons.length > 0 && (
+
+ )}
+
+ ) : (
+
+ )}
+
+
+
);
}
@@ -1930,7 +1940,8 @@ function PureReportActionItem({
return (
);
- }, [contextValue, parentReportAction, parentReport, draftMessage, shouldHideThreadDividerLine, parentReportActionForTransactionThread]);
+ }, [contextMenuStateValue, contextMenuActionsValue, parentReportAction, parentReport, draftMessage, shouldHideThreadDividerLine, parentReportActionForTransactionThread]);
if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && !isHarvestCreatedExpenseReport) {
return createdActionContent;
diff --git a/src/pages/inbox/report/ReportActionItemContentCreated.tsx b/src/pages/inbox/report/ReportActionItemContentCreated.tsx
index 7eb2cfc0709c..aab07cc262f4 100644
--- a/src/pages/inbox/report/ReportActionItemContentCreated.tsx
+++ b/src/pages/inbox/report/ReportActionItemContentCreated.tsx
@@ -6,8 +6,8 @@ import RenderHTML from '@components/RenderHTML';
import MoneyReportView from '@components/ReportActionItem/MoneyReportView';
import MoneyRequestView from '@components/ReportActionItem/MoneyRequestView';
import TaskView from '@components/ReportActionItem/TaskView';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
-import type {ShowContextMenuContextProps} from '@components/ShowContextMenuContext';
+import type {ShowContextMenuActionsContextType, ShowContextMenuStateContextType} from '@components/ShowContextMenuContext';
+import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext';
import SpacerView from '@components/SpacerView';
import UnreadActionIndicator from '@components/UnreadActionIndicator';
import useLocalize from '@hooks/useLocalize';
@@ -28,8 +28,11 @@ import ReportActionItemCreated from './ReportActionItemCreated';
import ReportActionItemSingle from './ReportActionItemSingle';
type ReportActionItemContentCreatedProps = {
- /** The context value containing the report and action data, along with the show context menu props */
- contextValue: ShowContextMenuContextProps;
+ /** The state context value containing the report and action data */
+ contextMenuStateValue: ShowContextMenuStateContextType;
+
+ /** The actions context value containing the show context menu callbacks */
+ contextMenuActionsValue: ShowContextMenuActionsContextType;
/** The parent report */
parentReport: OnyxEntry;
@@ -47,10 +50,18 @@ type ReportActionItemContentCreatedProps = {
shouldHideThreadDividerLine: boolean;
};
-function ReportActionItemContentCreated({contextValue, parentReport, parentReportAction, transactionID, draftMessage, shouldHideThreadDividerLine}: ReportActionItemContentCreatedProps) {
+function ReportActionItemContentCreated({
+ contextMenuStateValue,
+ contextMenuActionsValue,
+ parentReport,
+ parentReportAction,
+ transactionID,
+ draftMessage,
+ shouldHideThreadDividerLine,
+}: ReportActionItemContentCreatedProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
- const {report, action, transactionThreadReport} = contextValue;
+ const {report, action, transactionThreadReport} = contextMenuStateValue;
const policy = usePolicy(report?.policyID === CONST.POLICY.OWNER_EMAIL_FAKE ? undefined : report?.policyID);
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`);
@@ -72,7 +83,7 @@ function ReportActionItemContentCreated({contextValue, parentReport, parentRepor
[shouldHideThreadDividerLine, report?.reportID, styles.reportHorizontalRule],
);
- const contextMenuValue = useMemo(() => ({...contextValue, isDisabled: true}), [contextValue]);
+ const disabledStateValue = useMemo(() => ({...contextMenuStateValue, isDisabled: true}), [contextMenuStateValue]);
if (isTransactionThread(parentReportAction)) {
const isReversedTransaction = isReversedTransactionReportActionsUtils(parentReportAction);
@@ -105,17 +116,19 @@ function ReportActionItemContentCreated({contextValue, parentReport, parentRepor
return (
-
-
-
- {renderThreadDivider}
-
-
+
+
+
+
+ {renderThreadDivider}
+
+
+
);
}
@@ -169,17 +182,19 @@ function ReportActionItemContentCreated({contextValue, parentReport, parentRepor
shouldHideThreadDividerLine={false}
shouldShowAnimatedBackground={false}
/>
-
-
-
- {renderThreadDivider}
-
-
+
+
+
+
+ {renderThreadDivider}
+
+
+
) : (
- prevProps.contextValue === nextProps.contextValue &&
+ prevProps.contextMenuStateValue === nextProps.contextMenuStateValue &&
+ prevProps.contextMenuActionsValue === nextProps.contextMenuActionsValue &&
prevProps.parentReportAction === nextProps.parentReportAction &&
prevProps.transactionID === nextProps.transactionID &&
prevProps.draftMessage === nextProps.draftMessage &&
diff --git a/src/pages/inbox/report/ReportActionItemContext.tsx b/src/pages/inbox/report/ReportActionItemContext.tsx
deleted file mode 100644
index 0ed3bfef9e40..000000000000
--- a/src/pages/inbox/report/ReportActionItemContext.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import {createContext} from 'react';
-
-type ReportActionItemContextType = {
- shouldOpenReportInRHP: boolean;
- onPreviewPressed?: (reportID: string) => void;
-};
-
-const ReportActionItemContext = createContext({shouldOpenReportInRHP: false});
-
-export default ReportActionItemContext;
diff --git a/src/pages/inbox/report/ReportActionItemContext/default.ts b/src/pages/inbox/report/ReportActionItemContext/default.ts
new file mode 100644
index 000000000000..3e771ff5326a
--- /dev/null
+++ b/src/pages/inbox/report/ReportActionItemContext/default.ts
@@ -0,0 +1,11 @@
+import type {ReportActionItemActionsContextType, ReportActionItemStateContextType} from './types';
+
+const defaultReportActionItemStateContextValue: ReportActionItemStateContextType = {
+ shouldOpenReportInRHP: false,
+};
+
+const defaultReportActionItemActionsContextValue: ReportActionItemActionsContextType = {
+ onPreviewPressed: undefined,
+};
+
+export {defaultReportActionItemStateContextValue, defaultReportActionItemActionsContextValue};
diff --git a/src/pages/inbox/report/ReportActionItemContext/index.tsx b/src/pages/inbox/report/ReportActionItemContext/index.tsx
new file mode 100644
index 000000000000..f302bad8d24c
--- /dev/null
+++ b/src/pages/inbox/report/ReportActionItemContext/index.tsx
@@ -0,0 +1,17 @@
+import {createContext, useContext} from 'react';
+import {defaultReportActionItemActionsContextValue, defaultReportActionItemStateContextValue} from './default';
+import type {ReportActionItemActionsContextType, ReportActionItemStateContextType} from './types';
+
+const ReportActionItemStateContext = createContext(defaultReportActionItemStateContextValue);
+const ReportActionItemActionsContext = createContext(defaultReportActionItemActionsContextValue);
+
+function useReportActionItemState(): ReportActionItemStateContextType {
+ return useContext(ReportActionItemStateContext);
+}
+
+function useReportActionItemActions(): ReportActionItemActionsContextType {
+ return useContext(ReportActionItemActionsContext);
+}
+
+export {ReportActionItemStateContext, ReportActionItemActionsContext, useReportActionItemState, useReportActionItemActions};
+export type {ReportActionItemActionsContextType, ReportActionItemStateContextType} from './types';
diff --git a/src/pages/inbox/report/ReportActionItemContext/types.ts b/src/pages/inbox/report/ReportActionItemContext/types.ts
new file mode 100644
index 000000000000..c506bf97bd0a
--- /dev/null
+++ b/src/pages/inbox/report/ReportActionItemContext/types.ts
@@ -0,0 +1,9 @@
+type ReportActionItemStateContextType = {
+ shouldOpenReportInRHP: boolean;
+};
+
+type ReportActionItemActionsContextType = {
+ onPreviewPressed?: (reportID: string) => void;
+};
+
+export type {ReportActionItemStateContextType, ReportActionItemActionsContextType};
diff --git a/src/pages/workspace/accounting/AccountingContext/default.ts b/src/pages/workspace/accounting/AccountingContext/default.ts
new file mode 100644
index 000000000000..b79523a0ec57
--- /dev/null
+++ b/src/pages/workspace/accounting/AccountingContext/default.ts
@@ -0,0 +1,26 @@
+import type {RefObject} from 'react';
+import type {View} from 'react-native';
+import CONST from '@src/CONST';
+import type {ConnectionName} from '@src/types/onyx/Policy';
+import type {AccountingActionsContextType, AccountingStateContextType} from './types';
+
+const popoverAnchorRefsInitialValue = Object.values(CONST.POLICY.CONNECTIONS.NAME).reduce(
+ (acc, key) => {
+ acc[key] = {current: null};
+ return acc;
+ },
+ {} as Record>,
+);
+
+const defaultAccountingStateContextValue: AccountingStateContextType = {
+ activeIntegration: undefined,
+ popoverAnchorRefs: {
+ current: popoverAnchorRefsInitialValue,
+ },
+};
+
+const defaultAccountingActionsContextValue: AccountingActionsContextType = {
+ startIntegrationFlow: () => {},
+};
+
+export {defaultAccountingStateContextValue, defaultAccountingActionsContextValue, popoverAnchorRefsInitialValue};
diff --git a/src/pages/workspace/accounting/AccountingContext.tsx b/src/pages/workspace/accounting/AccountingContext/index.tsx
similarity index 57%
rename from src/pages/workspace/accounting/AccountingContext.tsx
rename to src/pages/workspace/accounting/AccountingContext/index.tsx
index 1a9b44bb122e..c4e2c43bb29f 100644
--- a/src/pages/workspace/accounting/AccountingContext.tsx
+++ b/src/pages/workspace/accounting/AccountingContext/index.tsx
@@ -1,5 +1,5 @@
+import React, {createContext, useCallback, useContext, useMemo, useRef, useState} from 'react';
import type {RefObject} from 'react';
-import React, {useContext, useMemo, useRef, useState} from 'react';
import type {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
@@ -8,61 +8,28 @@ import useLocalize from '@hooks/useLocalize';
import {removePolicyConnection} from '@libs/actions/connections';
import Navigation from '@libs/Navigation/Navigation';
import {isControlPolicy} from '@libs/PolicyUtils';
-import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
-import type {ConnectionName} from '@src/types/onyx/Policy';
import type Policy from '@src/types/onyx/Policy';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
-import {getAccountingIntegrationData} from './utils';
+import {getAccountingIntegrationData} from '../utils';
+import {defaultAccountingActionsContextValue, defaultAccountingStateContextValue, popoverAnchorRefsInitialValue} from './default';
+import type {AccountingActionsContextType, AccountingStateContextType, ActiveIntegration, ActiveIntegrationState} from './types';
-type ActiveIntegration = {
- name: ConnectionName;
- shouldDisconnectIntegrationBeforeConnecting?: boolean;
- integrationToDisconnect?: ConnectionName;
-};
-
-type ActiveIntegrationState = ActiveIntegration & {key: number};
-
-type AccountingContextType = {
- activeIntegration?: ActiveIntegration;
- startIntegrationFlow: (activeIntegration: ActiveIntegration) => void;
-
- /*
- * This stores refs to integration buttons, so the PopoverMenu can be positioned correctly
- */
- popoverAnchorRefs: RefObject>>;
-};
-
-const popoverAnchorRefsInitialValue = Object.values(CONST.POLICY.CONNECTIONS.NAME).reduce(
- (acc, key) => {
- acc[key] = {current: null};
- return acc;
- },
- {} as Record>,
-);
-
-const defaultAccountingContext = {
- activeIntegration: undefined,
- startIntegrationFlow: () => {},
- popoverAnchorRefs: {
- current: popoverAnchorRefsInitialValue,
- },
-};
-
-const AccountingContext = React.createContext(defaultAccountingContext);
+const AccountingStateContext = createContext(defaultAccountingStateContextValue);
+const AccountingActionsContext = createContext(defaultAccountingActionsContextValue);
type AccountingContextProviderProps = ChildrenProps & {
policy: OnyxEntry;
};
function AccountingContextProvider({children, policy}: AccountingContextProviderProps) {
- const popoverAnchorRefs = useRef>>(defaultAccountingContext.popoverAnchorRefs.current);
+ const popoverAnchorRefs = useRef>>(popoverAnchorRefsInitialValue);
const [activeIntegration, setActiveIntegration] = useState();
const {translate} = useLocalize();
const policyID = policy?.id;
const accountingIcons = useMemoizedLazyExpensifyIcons(['IntacctSquare', 'QBOSquare', 'XeroSquare', 'NetSuiteSquare', 'QBDSquare']);
- const startIntegrationFlow = React.useCallback(
+ const startIntegrationFlow = useCallback(
(newActiveIntegration: ActiveIntegration) => {
if (!policyID) {
return;
@@ -107,13 +74,19 @@ function AccountingContextProvider({children, policy}: AccountingContextProvider
});
};
- const accountingContext = useMemo(
+ const stateValue = useMemo(
() => ({
activeIntegration,
- startIntegrationFlow,
popoverAnchorRefs,
}),
- [activeIntegration, startIntegrationFlow],
+ [activeIntegration],
+ );
+
+ const actionsValue = useMemo(
+ () => ({
+ startIntegrationFlow,
+ }),
+ [startIntegrationFlow],
);
const renderActiveIntegration = () => {
@@ -128,31 +101,38 @@ function AccountingContextProvider({children, policy}: AccountingContextProvider
const shouldShowConfirmationModal = !!activeIntegration?.shouldDisconnectIntegrationBeforeConnecting && !!activeIntegration?.integrationToDisconnect;
return (
-
- {children}
- {!shouldShowConfirmationModal && renderActiveIntegration()}
- {shouldShowConfirmationModal && (
- {
- if (!policyID || !activeIntegration?.integrationToDisconnect) {
- return;
- }
- removePolicyConnection(policy, activeIntegration?.integrationToDisconnect);
- closeConfirmationModal();
- }}
- integrationToConnect={activeIntegration?.name}
- onCancel={() => {
- setActiveIntegration(undefined);
- }}
- />
- )}
-
+
+
+ {children}
+ {!shouldShowConfirmationModal && renderActiveIntegration()}
+ {shouldShowConfirmationModal && (
+ {
+ if (!policyID || !activeIntegration?.integrationToDisconnect) {
+ return;
+ }
+ removePolicyConnection(policy, activeIntegration?.integrationToDisconnect);
+ closeConfirmationModal();
+ }}
+ integrationToConnect={activeIntegration?.name}
+ onCancel={() => {
+ setActiveIntegration(undefined);
+ }}
+ />
+ )}
+
+
);
}
-function useAccountingContext() {
- return useContext(AccountingContext);
+function useAccountingState(): AccountingStateContextType {
+ return useContext(AccountingStateContext);
+}
+
+function useAccountingActions(): AccountingActionsContextType {
+ return useContext(AccountingActionsContext);
}
-export default AccountingContext;
-export {AccountingContextProvider, useAccountingContext};
+export default AccountingContextProvider;
+export {AccountingStateContext, AccountingActionsContext, AccountingContextProvider, useAccountingState, useAccountingActions};
+export type {AccountingActionsContextType, AccountingStateContextType, ActiveIntegration} from './types';
diff --git a/src/pages/workspace/accounting/AccountingContext/types.ts b/src/pages/workspace/accounting/AccountingContext/types.ts
new file mode 100644
index 000000000000..9499e98cae1f
--- /dev/null
+++ b/src/pages/workspace/accounting/AccountingContext/types.ts
@@ -0,0 +1,22 @@
+import type {RefObject} from 'react';
+import type {View} from 'react-native';
+import type {ConnectionName} from '@src/types/onyx/Policy';
+
+type ActiveIntegration = {
+ name: ConnectionName;
+ shouldDisconnectIntegrationBeforeConnecting?: boolean;
+ integrationToDisconnect?: ConnectionName;
+};
+
+type ActiveIntegrationState = ActiveIntegration & {key: number};
+
+type AccountingStateContextType = {
+ activeIntegration?: ActiveIntegration;
+ popoverAnchorRefs: RefObject>>;
+};
+
+type AccountingActionsContextType = {
+ startIntegrationFlow: (activeIntegration: ActiveIntegration) => void;
+};
+
+export type {ActiveIntegration, ActiveIntegrationState, AccountingStateContextType, AccountingActionsContextType};
diff --git a/src/pages/workspace/accounting/ClaimOfferPage.tsx b/src/pages/workspace/accounting/ClaimOfferPage.tsx
index aebcb604675b..09c6939642c0 100644
--- a/src/pages/workspace/accounting/ClaimOfferPage.tsx
+++ b/src/pages/workspace/accounting/ClaimOfferPage.tsx
@@ -26,7 +26,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type {PolicyFeatureName} from '@src/types/onyx/Policy';
-import {AccountingContextProvider, useAccountingContext} from './AccountingContext';
+import {AccountingContextProvider, useAccountingActions} from './AccountingContext';
type IntegrationType = typeof CONST.POLICY.CONNECTIONS.NAME.XERO | typeof CONST.POLICY.RECEIPT_PARTNERS.NAME.UBER;
@@ -48,7 +48,7 @@ function ClaimOfferPage({route, policy}: ClaimOfferPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
- const {startIntegrationFlow} = useAccountingContext();
+ const {startIntegrationFlow} = useAccountingActions();
const expensifyIcons = useMemoizedLazyExpensifyIcons(['TreasureChestGreenWithSparkle'] as const);
const integrations = policy?.receiptPartners;
const {isUberConnected} = useGetReceiptPartnersIntegrationData(policyID);
diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
index e7d9cffe6aa7..f0380c8de295 100644
--- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx
+++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
@@ -56,7 +56,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {ConnectionName} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import {AccountingContextProvider, useAccountingContext} from './AccountingContext';
+import {AccountingContextProvider, useAccountingActions, useAccountingState} from './AccountingContext';
import type {MenuItemData, PolicyAccountingPageProps} from './types';
import {getAccountingIntegrationData, getSynchronizationErrorMessage} from './utils';
@@ -79,7 +79,8 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);
const [datetimeToRelative, setDateTimeToRelative] = useState('');
- const {startIntegrationFlow, popoverAnchorRefs} = useAccountingContext();
+ const {popoverAnchorRefs} = useAccountingState();
+ const {startIntegrationFlow} = useAccountingActions();
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const {isLargeScreenWidth} = useResponsiveLayout();
const route = useRoute();
diff --git a/tests/unit/MentionUserRendererTest.tsx b/tests/unit/MentionUserRendererTest.tsx
index 626345a63026..738bf00be64c 100644
--- a/tests/unit/MentionUserRendererTest.tsx
+++ b/tests/unit/MentionUserRendererTest.tsx
@@ -6,7 +6,7 @@ import Onyx from 'react-native-onyx';
import type {TText} from 'react-native-render-html';
import MentionUserRenderer from '@components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer';
import OnyxListItemProvider from '@components/OnyxListItemProvider';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
+import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext';
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
@@ -95,20 +95,25 @@ jest.mock('@libs/Log', () => ({
function withProvider(children: ReactNode) {
return (
- void) => fn(),
anchor: null,
report: undefined,
isReportArchived: false,
action: undefined,
- checkIfContextMenuActive: () => false,
isDisabled: true,
shouldDisplayContextMenu: false,
}}
>
- {children}
-
+ void) => fn(),
+ checkIfContextMenuActive: () => false,
+ }}
+ >
+ {children}
+
+
);
}
diff --git a/tests/unit/VideoRendererTest.tsx b/tests/unit/VideoRendererTest.tsx
index 7fa62477bd05..6ba2b2516d05 100644
--- a/tests/unit/VideoRendererTest.tsx
+++ b/tests/unit/VideoRendererTest.tsx
@@ -3,7 +3,7 @@ import React from 'react';
import {AttachmentContext} from '@components/AttachmentContext';
import VideoRenderer from '@components/HTMLEngineProvider/HTMLRenderers/VideoRenderer';
import type PressableProps from '@components/Pressable/GenericPressable/types';
-import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
+import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
@@ -29,19 +29,23 @@ jest.mock('@components/VideoPlayerPreview', () => {
onPress={handlePress}
accessibilityRole="button"
accessibilityLabel={fileName}
+ sentryLabel="VideoRendererTest-ShowModalButton"
/>
);
};
});
-const mockShowContextMenuValue = {
+const mockShowContextMenuStateValue = {
anchor: null,
report: undefined,
isReportArchived: false,
action: undefined,
transactionThreadReport: undefined,
- checkIfContextMenuActive: () => {},
isDisabled: true,
+};
+
+const mockShowContextMenuActionsValue = {
+ checkIfContextMenuActive: () => {},
onShowContextMenu: (callback: () => void) => callback(),
};
const mockTNodeAttributes = {
@@ -60,12 +64,14 @@ describe('VideoRenderer', () => {
it('should open the report attachment with isAuthTokenRequired=true', () => {
// Given a VideoRenderer component with a valid attributes
render(
-
-
- {/* @ts-expect-error - Ignoring type errors for testing purposes */}
-
-
- ,
+
+
+
+ {/* @ts-expect-error - Ignoring type errors for testing purposes */}
+
+
+
+ ,
);
// When the user presses the show modal button