From ad6007b63983e276c05113bfb2ce57adf7d092e4 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 4 Nov 2024 01:10:11 +0500 Subject: [PATCH 001/312] onboarding flow without errors --- src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 8 + src/SCREENS.ts | 2 + .../ValidateCodeForm/BaseValidateCodeForm.tsx | 24 +-- src/hooks/useOnboardingFlow.ts | 10 +- src/languages/en.ts | 10 ++ src/languages/es.ts | 10 ++ src/languages/params.ts | 6 + .../parameters/JoinAccessiblePolicyParams.ts | 5 + ...idateUserAndGetAccessiblePoliciesParams.ts | 5 + src/libs/API/parameters/index.ts | 2 + src/libs/API/types.ts | 5 + .../Navigators/OnboardingModalNavigator.tsx | 10 ++ src/libs/Navigation/NavigationRoot.tsx | 5 +- src/libs/Navigation/linkingConfig/config.ts | 8 + src/libs/Navigation/types.ts | 6 + src/libs/NavigationUtils.ts | 2 + src/libs/actions/Policy/Member.ts | 33 +++- src/libs/actions/User.ts | 32 ++++ src/libs/actions/Welcome/OnboardingFlow.ts | 11 +- .../BaseOnboardingPersonalDetails.tsx | 27 +++- .../BaseOnboardingPrivateDomain.tsx | 94 +++++++++++ .../OnboardingPrivateDomain/index.native.tsx | 17 ++ src/pages/OnboardingPrivateDomain/index.tsx | 26 +++ src/pages/OnboardingPrivateDomain/types.ts | 15 ++ .../BaseOnboardingPurpose.tsx | 23 ++- .../BaseOnboardingWorkspaces.tsx | 149 ++++++++++++++++++ .../OnboardingWorkspaces/index.native.tsx | 17 ++ src/pages/OnboardingWorkspaces/index.tsx | 26 +++ src/pages/OnboardingWorkspaces/types.ts | 15 ++ .../ValidateCodeForm/BaseValidateCodeForm.tsx | 31 ++-- src/pages/signin/ValidateCodeForm/types.ts | 2 + src/types/onyx/JoinablePolicies.ts | 32 ++++ src/types/onyx/index.ts | 2 + 34 files changed, 635 insertions(+), 38 deletions(-) create mode 100644 src/libs/API/parameters/JoinAccessiblePolicyParams.ts create mode 100644 src/libs/API/parameters/ValidateUserAndGetAccessiblePoliciesParams.ts create mode 100644 src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx create mode 100644 src/pages/OnboardingPrivateDomain/index.native.tsx create mode 100644 src/pages/OnboardingPrivateDomain/index.tsx create mode 100644 src/pages/OnboardingPrivateDomain/types.ts create mode 100644 src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx create mode 100644 src/pages/OnboardingWorkspaces/index.native.tsx create mode 100644 src/pages/OnboardingWorkspaces/index.tsx create mode 100644 src/pages/OnboardingWorkspaces/types.ts create mode 100644 src/types/onyx/JoinablePolicies.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7d3d0edef36e..bc2791c8ce32 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -104,6 +104,8 @@ const ONYXKEYS = { /** Store the information of magic code */ VALIDATE_ACTION_CODE: 'validate_action_code', + KEY_JOINABLE_POLICIES: 'keyJoinablePolicies', + /** Information about the current session (authToken, accountID, email, loading, error) */ SESSION: 'session', STASHED_SESSION: 'stashedSession', @@ -907,6 +909,7 @@ type OnyxValuesMapping = { [ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList; [ONYXKEYS.PENDING_CONTACT_ACTION]: OnyxTypes.PendingContactAction; [ONYXKEYS.VALIDATE_ACTION_CODE]: OnyxTypes.ValidateMagicCodeAction; + [ONYXKEYS.KEY_JOINABLE_POLICIES]: OnyxTypes.JoinablePolicies; [ONYXKEYS.SESSION]: OnyxTypes.Session; [ONYXKEYS.USER_METADATA]: OnyxTypes.UserMetadata; [ONYXKEYS.STASHED_SESSION]: OnyxTypes.Session; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 45501bf46374..d3e022a678f8 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1325,6 +1325,10 @@ const ROUTES = { route: 'onboarding/personal-details', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/personal-details`, backTo), }, + ONBOARDING_PRIVATE_DOMAIN: { + route: 'onboarding/private-domain', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/private-domain`, backTo), + }, ONBOARDING_EMPLOYEES: { route: 'onboarding/employees', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/employees`, backTo), @@ -1337,6 +1341,10 @@ const ROUTES = { route: 'onboarding/purpose', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/purpose`, backTo), }, + ONBOARDING_WORKSPACES: { + route: 'onboarding/workspaces', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/workspaces`, backTo), + }, WELCOME_VIDEO_ROOT: 'onboarding/welcome-video', EXPLANATION_MODAL_ROOT: 'onboarding/explanation', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index dea0f028e1a0..75f38569faf7 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -566,8 +566,10 @@ const SCREENS = { ONBOARDING: { PERSONAL_DETAILS: 'Onboarding_Personal_Details', PURPOSE: 'Onboarding_Purpose', + PRIVATE_DOMAIN: 'Onboarding_Private_Domain', EMPLOYEES: 'Onboarding_Employees', ACCOUNTING: 'Onboarding_Accounting', + WORKSPACES: 'Onboarding_Workspaces', }, WELCOME_VIDEO: { diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index cc2a7314f570..a02f3365cf96 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -63,6 +63,9 @@ type ValidateCodeFormProps = { /** Function to clear error of the form */ clearError: () => void; + /** Whether to show the verify button (hidden in private domain onboarding) */ + hideButton?: boolean; + /** Function is called when validate code modal is mounted and on magic code resend */ sendValidateCode: () => void; }; @@ -78,6 +81,7 @@ function BaseValidateCodeForm({ clearError, sendValidateCode, buttonStyles, + hideButton, }: ValidateCodeFormProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -259,15 +263,17 @@ function BaseValidateCodeForm({ onClose={() => clearError()} style={buttonStyles} > - + )} + {/** + 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 && ( + + )} + + ) : ( + + )} + + + + ); + } + const numberOfThreadReplies = action.childVisibleActionCount ?? 0; + + const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); + const oldestFourAccountIDs = + action.childOldestFourAccountIDs + ?.split(',') + .map((accountID) => Number(accountID)) + .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; + const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; + + return ( + <> + {children} + {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( + + !isEmptyObject(item))} /> + + )} + {!ReportActionsUtils.isMessageDeleted(action) && ( + + { + if (Session.isAnonymousUser()) { + hideContextMenu(false); + + InteractionManager.runAfterInteractions(() => { + Session.signOutAndRedirectToSignIn(); + }); + } else { + toggleReaction(emoji, ignoreSkinToneOnCompare); + } + }} + setIsEmojiPickerActive={setIsEmojiPickerActive} + /> + + )} + + {shouldDisplayThreadReplies && ( + + + + )} + + ); + }; + + /** + * Get ReportActionItem with a proper wrapper + * @param hovered whether the ReportActionItem is hovered + * @param isWhisper whether the ReportActionItem is a whisper + * @param hasErrors whether the report action has any errors + * @returns report action item + */ + + const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { + const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); + + if (draftMessage !== undefined) { + return {content}; + } + + if (!displayAsGroup) { + return ( + item === moderationDecision) && + !ReportActionsUtils.isPendingRemove(action) + } + > + {content} + + ); + } + + return {content}; + }; + + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { + const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) + ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID + : '-1'; + + return ( + + ); + } + if (ReportActionsUtils.isChronosOOOListAction(action)) { + return ( + + ); + } + + // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true + // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet + if ( + ReportActionsUtils.isMoneyRequestAction(action) && + !!report?.isWaitingOnBankAccount && + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + !isSendingMoney + ) { + return null; + } + + // If action is actionable whisper and resolved by user, then we don't want to render anything + if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { + return null; + } + + // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. + // This is a temporary solution needed for comment-linking. + // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. + if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { + return null; + } + + const hasErrors = !isEmptyObject(action.errors); + const whisperedTo = ReportActionsUtils.getWhisperedTo(action); + const isMultipleParticipant = whisperedTo.length > 1; + + const iouReportID = + ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID + ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() + : '-1'; + const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); + const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; + const whisperedToPersonalDetails = isWhisper + ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) + : []; + const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); + const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + + return ( + shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onSecondaryInteraction={showPopover} + preventDefaultContextMenu={draftMessage === undefined && !hasErrors} + withoutFocusOnSecondaryInteraction + accessibilityLabel={translate('accessibilityHints.chatMessage')} + accessible + > + + {(hovered) => ( + + {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } + {shouldDisplayContextMenu && ( + + )} + + { + const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; + if (transactionID) { + Transaction.clearError(transactionID); + } + ReportActions.clearAllRelatedReportActionErrors(reportID, action); + }} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + pendingAction={ + draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) + } + shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} + errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} + errorRowStyles={[styles.ml10, styles.mr2]} + needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} + shouldDisableStrikeThrough + > + {isWhisper && ( + + + + + + {translate('reportActionContextMenu.onlyVisible')} +   + + + + )} + {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} + + + + )} + + + + + + ); +} + +export type { PureReportActionItemProps }; +export default memo(ReportActionItem, (prevProps, nextProps) => { + const prevParentReportAction = prevProps.parentReportAction; + const nextParentReportAction = nextProps.parentReportAction; + return ( + prevProps.displayAsGroup === nextProps.displayAsGroup && + prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && + prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && + lodashIsEqual(prevProps.action, nextProps.action) && + lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && + lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && + lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && + prevProps.report?.statusNum === nextProps.report?.statusNum && + prevProps.report?.stateNum === nextProps.report?.stateNum && + prevProps.report?.parentReportID === nextProps.report?.parentReportID && + prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && + // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport + ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && + prevProps.action.actionName === nextProps.action.actionName && + prevProps.report?.reportName === nextProps.report?.reportName && + prevProps.report?.description === nextProps.report?.description && + ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && + prevProps.report?.managerID === nextProps.report?.managerID && + prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && + prevProps.report?.total === nextProps.report?.total && + prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && + prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && + prevProps.linkedReportActionID === nextProps.linkedReportActionID && + lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && + lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && + lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && + lodashIsEqual(prevParentReportAction, nextParentReportAction) + ); +}); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 559d635f73fe..86d84d48bebe 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -82,63 +82,63 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import type { PureReportActionItemProps } from './PureReportActionItem'; +// type ReportActionItemProps = { +// /** Report for this action */ +// report: OnyxEntry; -type ReportActionItemProps = { - /** Report for this action */ - report: OnyxEntry; +// /** The transaction thread report associated with the report for this action, if any */ +// transactionThreadReport?: OnyxEntry; - /** The transaction thread report associated with the report for this action, if any */ - transactionThreadReport?: OnyxEntry; +// /** Array of report actions for the report for this action */ +// // eslint-disable-next-line react/no-unused-prop-types +// reportActions: OnyxTypes.ReportAction[]; - /** Array of report actions for the report for this action */ - // eslint-disable-next-line react/no-unused-prop-types - reportActions: OnyxTypes.ReportAction[]; +// /** Report action belonging to the report's parent */ +// parentReportAction: OnyxEntry; - /** Report action belonging to the report's parent */ - parentReportAction: OnyxEntry; +// /** The transaction thread report's parentReportAction */ +// /** It's used by withOnyx HOC */ +// // eslint-disable-next-line react/no-unused-prop-types +// parentReportActionForTransactionThread?: OnyxEntry; - /** The transaction thread report's parentReportAction */ - /** It's used by withOnyx HOC */ - // eslint-disable-next-line react/no-unused-prop-types - parentReportActionForTransactionThread?: OnyxEntry; +// /** All the data of the action item */ +// action: OnyxTypes.ReportAction; - /** All the data of the action item */ - action: OnyxTypes.ReportAction; +// /** Should the comment have the appearance of being grouped with the previous comment? */ +// displayAsGroup: boolean; - /** Should the comment have the appearance of being grouped with the previous comment? */ - displayAsGroup: boolean; +// /** Is this the most recent IOU Action? */ +// isMostRecentIOUReportAction: boolean; - /** Is this the most recent IOU Action? */ - isMostRecentIOUReportAction: boolean; +// /** Should we display the new marker on top of the comment? */ +// shouldDisplayNewMarker: boolean; - /** Should we display the new marker on top of the comment? */ - shouldDisplayNewMarker: boolean; +// /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ +// shouldShowSubscriptAvatar?: boolean; - /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ - shouldShowSubscriptAvatar?: boolean; +// /** Position index of the report action in the overall report FlatList view */ +// index: number; - /** Position index of the report action in the overall report FlatList view */ - index: number; +// /** Flag to show, hide the thread divider line */ +// shouldHideThreadDividerLine?: boolean; - /** Flag to show, hide the thread divider line */ - shouldHideThreadDividerLine?: boolean; +// linkedReportActionID?: string; - linkedReportActionID?: string; +// /** Callback to be called on onPress */ +// onPress?: () => void; - /** Callback to be called on onPress */ - onPress?: () => void; +// /** If this is the first visible report action */ +// isFirstVisibleReportAction: boolean; - /** If this is the first visible report action */ - isFirstVisibleReportAction: boolean; +// /** IF the thread divider line will be used */ +// shouldUseThreadDividerLine?: boolean; - /** IF the thread divider line will be used */ - shouldUseThreadDividerLine?: boolean; +// hideThreadReplies?: boolean; - hideThreadReplies?: boolean; - - /** Whether context menu should be displayed */ - shouldDisplayContextMenu?: boolean; -}; +// /** Whether context menu should be displayed */ +// shouldDisplayContextMenu?: boolean; +// }; function ReportActionItem({ action, @@ -158,7 +158,7 @@ function ReportActionItem({ hideThreadReplies = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, -}: ReportActionItemProps) { +}: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const blockedFromConcierge = useBlockedFromConcierge(); From 7647b6d386a90740e6a6a7c00560ccee0c985cd7 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 13 Nov 2024 11:23:37 +0100 Subject: [PATCH 007/312] use outputOrientation --- src/pages/iou/request/step/IOURequestStepScan/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index ea99d2cf0151..9f342ed7b0ea 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -662,7 +662,7 @@ function IOURequestStepScan({ zoom={device.neutralZoom} photo cameraTabIndex={1} - orientation="portrait" + outputOrientation="preview" /> From fd6168b77867db70694cf71901f57e60d12a581e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 13 Nov 2024 13:01:10 +0100 Subject: [PATCH 008/312] integrate getReceiptsUploadFolderPath --- src/CONST.ts | 1 + src/libs/getReceiptsUploadFolderPath/index.android.ts | 7 +++++++ src/libs/getReceiptsUploadFolderPath/index.ios.ts | 7 +++++++ src/libs/getReceiptsUploadFolderPath/index.ts | 5 +++++ src/libs/getReceiptsUploadFolderPath/types.ts | 3 +++ 5 files changed, 23 insertions(+) create mode 100644 src/libs/getReceiptsUploadFolderPath/index.android.ts create mode 100644 src/libs/getReceiptsUploadFolderPath/index.ios.ts create mode 100644 src/libs/getReceiptsUploadFolderPath/index.ts create mode 100644 src/libs/getReceiptsUploadFolderPath/types.ts diff --git a/src/CONST.ts b/src/CONST.ts index e95e3d4a5603..657135a238f6 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5845,6 +5845,7 @@ const CONST = { DOWNLOADS_PATH: '/Downloads', DOWNLOADS_TIMEOUT: 5000, NEW_EXPENSIFY_PATH: '/New Expensify', + RECEIPTS_UPLOAD_PATH: '/Receipts-Pending-Upload', ENVIRONMENT_SUFFIX: { DEV: ' Dev', diff --git a/src/libs/getReceiptsUploadFolderPath/index.android.ts b/src/libs/getReceiptsUploadFolderPath/index.android.ts new file mode 100644 index 000000000000..5179afd64134 --- /dev/null +++ b/src/libs/getReceiptsUploadFolderPath/index.android.ts @@ -0,0 +1,7 @@ +import ReactNativeBlobUtil from 'react-native-blob-util'; +import CONST from '@src/CONST'; +import type GetReceiptsUploadFolderPath from './types'; + +const getReceiptsUploadFolderPath: GetReceiptsUploadFolderPath = () => `${ReactNativeBlobUtil.fs.dirs.DownloadDir}${CONST.RECEIPTS_UPLOAD_PATH}`; + +export default getReceiptsUploadFolderPath; diff --git a/src/libs/getReceiptsUploadFolderPath/index.ios.ts b/src/libs/getReceiptsUploadFolderPath/index.ios.ts new file mode 100644 index 000000000000..c787909055a6 --- /dev/null +++ b/src/libs/getReceiptsUploadFolderPath/index.ios.ts @@ -0,0 +1,7 @@ +import ReactNativeBlobUtil from 'react-native-blob-util'; +import CONST from '@src/CONST'; +import type GetReceiptsUploadFolderPath from './types'; + +const getReceiptsUploadFolderPath: GetReceiptsUploadFolderPath = () => `${ReactNativeBlobUtil.fs.dirs.DocumentDir}${CONST.RECEIPTS_UPLOAD_PATH}`; + +export default getReceiptsUploadFolderPath; diff --git a/src/libs/getReceiptsUploadFolderPath/index.ts b/src/libs/getReceiptsUploadFolderPath/index.ts new file mode 100644 index 000000000000..fcec3b2d6711 --- /dev/null +++ b/src/libs/getReceiptsUploadFolderPath/index.ts @@ -0,0 +1,5 @@ +import type GetReceiptsUploadFolderPath from './types'; + +const getReceiptsUploadFolderPath: GetReceiptsUploadFolderPath = () => ''; + +export default getReceiptsUploadFolderPath; diff --git a/src/libs/getReceiptsUploadFolderPath/types.ts b/src/libs/getReceiptsUploadFolderPath/types.ts new file mode 100644 index 000000000000..44c21e513f4d --- /dev/null +++ b/src/libs/getReceiptsUploadFolderPath/types.ts @@ -0,0 +1,3 @@ +type GetReceiptsUploadFolderPath = () => string; + +export default GetReceiptsUploadFolderPath; From be154dbf5ce671dfe0df92536a760fddd4488cc6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 13 Nov 2024 16:07:36 +0100 Subject: [PATCH 009/312] get new flow workable --- .../step/IOURequestStepScan/index.native.tsx | 97 ++++++++++--------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 9f342ed7b0ea..5bf50419bcbc 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -32,6 +32,7 @@ import * as FileUtils from '@libs/fileDownload/FileUtils'; import getPhotoSource from '@libs/fileDownload/getPhotoSource'; import getCurrentPosition from '@libs/getCurrentPosition'; import getPlatform from '@libs/getPlatform'; +import getReceiptsUploadFolderPath from '@libs/getReceiptsUploadFolderPath'; import * as IOUUtils from '@libs/IOUUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; @@ -496,16 +497,16 @@ function IOURequestStepScan({ setDidCapturePhoto(true); console.debug('[dev] Taking photo'); - console.debug('[dev] ReactNativeBlobUtil.fs.dirs.DocumentDir', ReactNativeBlobUtil.fs.dirs.DocumentDir); - const path = `${ReactNativeBlobUtil.fs.dirs.DocumentDir}/Receipts-Pending-Upload`; + const path = getReceiptsUploadFolderPath(); - console.debug('ReactNativeBlobUtil.fs.isDir(path)', ReactNativeBlobUtil.fs.isDir(path)); + console.debug('[dev] path', path); ReactNativeBlobUtil.fs .isDir(path) .then((isDir) => { - console.debug('[dev] ReactNativeBlobUtil.fs.isDir:', isDir); + console.debug('[dev] Checking if directory exists', isDir); + if (isDir) { return; } @@ -521,55 +522,55 @@ function IOURequestStepScan({ }) .catch((error) => { console.error('[dev] Error checking if directory exists:', error); - }); - - camera?.current - ?.takePhoto({ - flash: flash && hasFlash ? 'on' : 'off', - enableShutterSound: !isPlatformMuted, - path, }) - .then((photo: PhotoFile) => { - console.debug('[dev] photo', photo); - return; - - // Store the receipt on the transaction object in Onyx - const source = getPhotoSource(photo.path); - IOU.setMoneyRequestReceipt(transactionID, source, photo.path, !isEditing); - - FileUtils.readFileAsync( - source, - photo.path, - (file) => { - if (isEditing) { - updateScanAndNavigate(file, source); - return; - } - if (shouldSkipConfirmation) { - setFileResize(file); - setFileSource(source); - const gpsRequired = transaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && file; - if (gpsRequired) { - const shouldStartLocationPermissionFlow = IOUUtils.shouldStartLocationPermissionFlow(); - if (shouldStartLocationPermissionFlow) { - setStartLocationPermissionFlow(true); + .then(() => { + camera?.current + ?.takePhoto({ + flash: flash && hasFlash ? 'on' : 'off', + enableShutterSound: !isPlatformMuted, + path, + }) + .then((photo: PhotoFile) => { + console.debug('[dev] photo', photo); + + // Store the receipt on the transaction object in Onyx + const source = getPhotoSource(photo.path); + IOU.setMoneyRequestReceipt(transactionID, source, photo.path, !isEditing); + + FileUtils.readFileAsync( + source, + photo.path, + (file) => { + if (isEditing) { + updateScanAndNavigate(file, source); return; } - } - } - navigateToConfirmationStep(file, source, false); - }, - () => { + if (shouldSkipConfirmation) { + setFileResize(file); + setFileSource(source); + const gpsRequired = transaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && file; + if (gpsRequired) { + const shouldStartLocationPermissionFlow = IOUUtils.shouldStartLocationPermissionFlow(); + if (shouldStartLocationPermissionFlow) { + setStartLocationPermissionFlow(true); + return; + } + } + } + navigateToConfirmationStep(file, source, false); + }, + () => { + setDidCapturePhoto(false); + showCameraAlert(); + Log.warn('[dev] Error reading photo'); + }, + ); + }) + .catch((error: string) => { setDidCapturePhoto(false); showCameraAlert(); - Log.warn('[dev] Error reading photo'); - }, - ); - }) - .catch((error: string) => { - setDidCapturePhoto(false); - showCameraAlert(); - Log.warn('[dev] Error taking photo', error); + Log.warn('[dev] Error taking photo', error); + }); }); }, [ cameraPermissionStatus, From 4737f1ae28a64aa5cb3ece2fca022e09cc343202 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 14 Nov 2024 03:02:15 +0500 Subject: [PATCH 010/312] add onboarding wrapper --- src/components/OnboardingWrapper.tsx | 23 ++++++++++++++++++ src/pages/OnboardingAccounting/index.tsx | 21 +++++++--------- src/pages/OnboardingEmployees/index.tsx | 21 +++++++--------- src/pages/OnboardingPersonalDetails/index.tsx | 22 +++++++---------- src/pages/OnboardingPrivateDomain/index.tsx | 22 +++++++---------- src/pages/OnboardingPurpose/index.tsx | 24 +++++++------------ src/pages/OnboardingWorkspaces/index.tsx | 22 +++++++---------- 7 files changed, 72 insertions(+), 83 deletions(-) create mode 100644 src/components/OnboardingWrapper.tsx diff --git a/src/components/OnboardingWrapper.tsx b/src/components/OnboardingWrapper.tsx new file mode 100644 index 000000000000..1e7db38f0e6a --- /dev/null +++ b/src/components/OnboardingWrapper.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {View} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; +import FocusTrapForScreens from './FocusTrap/FocusTrapForScreen'; + +type OnboardingWrapperProps = { + /** Rendered child component */ + children: React.ReactNode; +}; + +function OnboardingWrapper({children}: OnboardingWrapperProps) { + const styles = useThemeStyles(); + + return ( + + {children} + + ); +} + +OnboardingWrapper.displayName = 'OnboardingWrapper'; + +export default OnboardingWrapper; diff --git a/src/pages/OnboardingAccounting/index.tsx b/src/pages/OnboardingAccounting/index.tsx index 173f82106ad5..18c5190027b9 100644 --- a/src/pages/OnboardingAccounting/index.tsx +++ b/src/pages/OnboardingAccounting/index.tsx @@ -1,22 +1,17 @@ import React from 'react'; -import {View} from 'react-native'; -import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; -import useThemeStyles from '@hooks/useThemeStyles'; +import OnboardingWrapper from '@components/OnboardingWrapper'; import BaseOnboardingAccounting from './BaseOnboardingAccounting'; import type {OnboardingAccountingProps} from './types'; function OnboardingAccounting(props: OnboardingAccountingProps) { - const styles = useThemeStyles(); return ( - - - - - + + + ); } diff --git a/src/pages/OnboardingEmployees/index.tsx b/src/pages/OnboardingEmployees/index.tsx index 88d752dd62b5..f9101d912bbd 100644 --- a/src/pages/OnboardingEmployees/index.tsx +++ b/src/pages/OnboardingEmployees/index.tsx @@ -1,22 +1,17 @@ import React from 'react'; -import {View} from 'react-native'; -import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; -import useThemeStyles from '@hooks/useThemeStyles'; +import OnboardingWrapper from '@components/OnboardingWrapper'; import BaseOnboardingEmployees from './BaseOnboardingEmployees'; import type {OnboardingEmployeesProps} from './types'; function OnboardingEmployees(props: OnboardingEmployeesProps) { - const styles = useThemeStyles(); return ( - - - - - + + + ); } diff --git a/src/pages/OnboardingPersonalDetails/index.tsx b/src/pages/OnboardingPersonalDetails/index.tsx index 4cccf5dc1186..0a4605452330 100644 --- a/src/pages/OnboardingPersonalDetails/index.tsx +++ b/src/pages/OnboardingPersonalDetails/index.tsx @@ -1,23 +1,17 @@ import React from 'react'; -import {View} from 'react-native'; -import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; -import useThemeStyles from '@hooks/useThemeStyles'; +import OnboardingWrapper from '@components/OnboardingWrapper'; import BaseOnboardingPersonalDetails from './BaseOnboardingPersonalDetails'; import type {OnboardingPersonalDetailsProps} from './types'; function OnboardingPersonalDetails({...rest}: OnboardingPersonalDetailsProps) { - const styles = useThemeStyles(); - return ( - - - - - + + + ); } diff --git a/src/pages/OnboardingPrivateDomain/index.tsx b/src/pages/OnboardingPrivateDomain/index.tsx index 720277c681c0..57eca886d28e 100644 --- a/src/pages/OnboardingPrivateDomain/index.tsx +++ b/src/pages/OnboardingPrivateDomain/index.tsx @@ -1,23 +1,17 @@ import React from 'react'; -import {View} from 'react-native'; -import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; -import useThemeStyles from '@hooks/useThemeStyles'; +import OnboardingWrapper from '@components/OnboardingWrapper'; import BaseOnboardingPrivateDomain from './BaseOnboardingPrivateDomain'; import type {OnboardingPrivateDomainProps} from './types'; function OnboardingPrivateDomain({...rest}: OnboardingPrivateDomainProps) { - const styles = useThemeStyles(); - return ( - - - - - + + + ); } diff --git a/src/pages/OnboardingPurpose/index.tsx b/src/pages/OnboardingPurpose/index.tsx index bbe698e0970c..e67a3e43c383 100644 --- a/src/pages/OnboardingPurpose/index.tsx +++ b/src/pages/OnboardingPurpose/index.tsx @@ -1,24 +1,18 @@ import React from 'react'; -import {View} from 'react-native'; -import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; -import useThemeStyles from '@hooks/useThemeStyles'; +import OnboardingWrapper from '@components/OnboardingWrapper'; import BaseOnboardingPurpose from './BaseOnboardingPurpose'; import type {OnboardingPurposeProps} from './types'; function OnboardingPurpose({...rest}: OnboardingPurposeProps) { - const styles = useThemeStyles(); - return ( - - - - - + + + ); } diff --git a/src/pages/OnboardingWorkspaces/index.tsx b/src/pages/OnboardingWorkspaces/index.tsx index 357b78f350f9..8b6436d912e3 100644 --- a/src/pages/OnboardingWorkspaces/index.tsx +++ b/src/pages/OnboardingWorkspaces/index.tsx @@ -1,23 +1,17 @@ import React from 'react'; -import {View} from 'react-native'; -import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; -import useThemeStyles from '@hooks/useThemeStyles'; +import OnboardingWrapper from '@components/OnboardingWrapper'; import BaseOnboardingWorkspaces from './BaseOnboardingWorkspaces'; import type {OnboardingWorkspacesProps} from './types'; function OnboardingWorkspaces({...rest}: OnboardingWorkspacesProps) { - const styles = useThemeStyles(); - return ( - - - - - + + + ); } From 9489eace5667cf484373d58ca5aa201634da0371 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 16:47:05 +0700 Subject: [PATCH 011/312] use PureComponent in the existing ReportActionItem --- .../home/report/PureReportActionItem.tsx | 74 +- src/pages/home/report/ReportActionItem.tsx | 1872 +++++++++-------- 2 files changed, 1024 insertions(+), 922 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 2c4637582d41..f9cf7ba8bfad 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -81,6 +81,7 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; type PureReportActionItemProps = { /** Report for this action */ @@ -138,7 +139,23 @@ type PureReportActionItemProps = { /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; - draftMessage? : OnyxEntry + + + draftMessage? : string + + iouReport?: OnyxTypes.Report; + + emojiReactions?: OnyxTypes.ReportActionReactions; + + userWallet?: OnyxTypes.UserWallet; + + linkedTransactionRouteError?: Errors; + + reportNameValuePairs?: OnyxTypes.ReportNameValuePairs; + + isUserValidated?: boolean; + + parentReport?: OnyxTypes.Report; }; function PureReportActionItem({ @@ -159,6 +176,15 @@ function PureReportActionItem({ hideThreadReplies = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, + + draftMessage, + iouReport, + emojiReactions, + userWallet, + linkedTransactionRouteError, + reportNameValuePairs, + isUserValidated, + parentReport, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -166,23 +192,23 @@ function PureReportActionItem({ const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); - const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { - selector: (draftMessagesForReport) => { - const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; - return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; - }, - }); - const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); - const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); - const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); - const [linkedTransactionRouteError] = useOnyx( - `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - {selector: (transaction) => transaction?.errorFields?.route ?? null}, - ); + // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { + // selector: (draftMessagesForReport) => { + // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; + // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; + // }, + // }); + // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); + // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); + // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + // const [linkedTransactionRouteError] = useOnyx( + // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, + // {selector: (transaction) => transaction?.errorFields?.route ?? null}, + // ); const theme = useTheme(); const styles = useThemeStyles(); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. - const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); + // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); const StyleUtils = useStyleUtils(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); @@ -197,10 +223,10 @@ function PureReportActionItem({ const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); - const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); + // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; const reportScrollManager = useReportScrollManager(); const isActionableWhisper = @@ -1038,7 +1064,8 @@ function PureReportActionItem({ } export type { PureReportActionItemProps }; -export default memo(ReportActionItem, (prevProps, nextProps) => { +// export default PureReportActionItem; +export default memo(PureReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; const nextParentReportAction = nextProps.parentReportAction; return ( @@ -1068,6 +1095,15 @@ export default memo(ReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && - lodashIsEqual(prevParentReportAction, nextParentReportAction) + lodashIsEqual(prevParentReportAction, nextParentReportAction) && + + prevProps.draftMessage === nextProps.draftMessage && + prevProps.iouReport?.reportID === nextProps.iouReport?.reportID && + prevProps.emojiReactions === nextProps.emojiReactions && + prevProps.userWallet === nextProps.userWallet && + prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && + prevProps.isUserValidated === nextProps.isUserValidated && + prevProps.parentReport?.reportID === nextProps.parentReport?.reportID + ); }); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 86d84d48bebe..a9ef46e53f84 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -83,6 +83,7 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; import type { PureReportActionItemProps } from './PureReportActionItem'; +import PureReportActionItem from './PureReportActionItem'; // type ReportActionItemProps = { // /** Report for this action */ // report: OnyxEntry; @@ -143,25 +144,24 @@ import type { PureReportActionItemProps } from './PureReportActionItem'; function ReportActionItem({ action, report, - transactionThreadReport, - linkedReportActionID, - displayAsGroup, - index, - isMostRecentIOUReportAction, - parentReportAction, - shouldDisplayNewMarker, - shouldHideThreadDividerLine = false, - shouldShowSubscriptAvatar = false, - onPress = undefined, - isFirstVisibleReportAction = false, - shouldUseThreadDividerLine = false, - hideThreadReplies = false, - shouldDisplayContextMenu = true, - parentReportActionForTransactionThread, + ...props + // transactionThreadReport, + // linkedReportActionID, + // displayAsGroup, + // index, + // isMostRecentIOUReportAction, + // parentReportAction, + // shouldDisplayNewMarker, + // shouldHideThreadDividerLine = false, + // shouldShowSubscriptAvatar = false, + // onPress = undefined, + // isFirstVisibleReportAction = false, + // shouldUseThreadDividerLine = false, + // hideThreadReplies = false, + // shouldDisplayContextMenu = true, + // parentReportActionForTransactionThread, + // reportActions, }: PureReportActionItemProps) { - const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const blockedFromConcierge = useBlockedFromConcierge(); const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); @@ -176,896 +176,962 @@ function ReportActionItem({ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const [linkedTransactionRouteError] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - {selector: (transaction) => transaction?.errorFields?.route ?? null}, + { selector: (transaction) => transaction?.errorFields?.route ?? null }, ); - const theme = useTheme(); - const styles = useThemeStyles(); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); - const StyleUtils = useStyleUtils(); - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); - const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); - - const [isHidden, setIsHidden] = useState(false); - const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - const reactionListRef = useContext(ReactionListContext); - const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); - const textInputRef = useRef(null); - const popoverAnchorRef = useRef>(null); - const downloadedPreviews = useRef([]); - const prevDraftMessage = usePrevious(draftMessage); - const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + + const [isUserValidated] = useOnyx(ONYXKEYS.USER, { selector: (user) => !!user?.validated }); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); - const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; - const reportScrollManager = useReportScrollManager(); - const isActionableWhisper = - ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); - const originalMessage = ReportActionsUtils.getOriginalMessage(action); - - const highlightedBackgroundColorIfNeeded = useMemo( - () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), - [StyleUtils, isReportActionLinked, theme.messageHighlightBG], - ); - - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); - const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; - const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; - const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); - - // IOUDetails only exists when we are sending money - const isSendingMoney = - ReportActionsUtils.isMoneyRequestAction(action) && - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; - - const updateHiddenState = useCallback( - (isHiddenValue: boolean) => { - setIsHidden(isHiddenValue); - const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; - const isAttachment = ReportUtils.isReportMessageAttachment(message); - if (!isAttachment) { - return; - } - updateHiddenAttachments(action.reportActionID, isHiddenValue); - }, - [action.reportActionID, action.message, updateHiddenAttachments], - ); - - useEffect( - () => () => { - // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, - // we should also hide them when the current component is destroyed - if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { - ReportActionContextMenu.hideContextMenu(); - ReportActionContextMenu.hideDeleteModal(); - } - if (EmojiPickerAction.isActive(action.reportActionID)) { - EmojiPickerAction.hideEmojiPicker(true); - } - if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { - reactionListRef?.current?.hideReactionList(); - } - }, - [action.reportActionID, reactionListRef], - ); - - useEffect(() => { - // We need to hide EmojiPicker when this is a deleted parent action - if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { - return; - } - - EmojiPickerAction.hideEmojiPicker(true); - }, [isDeletedParentAction, action.reportActionID]); - - useEffect(() => { - if (prevDraftMessage !== undefined || draftMessage === undefined) { - return; - } - - focusComposerWithDelay(textInputRef.current)(true); - }, [prevDraftMessage, draftMessage]); - - useEffect(() => { - if (!Permissions.canUseLinkPreviews()) { - return; - } - - const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); - if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - return; - } - - downloadedPreviews.current = urls; - Report.expandURLPreview(reportID, action.reportActionID); - }, [action, reportID]); - - useEffect(() => { - if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { - return; - } - Report.deleteReportActionDraft(reportID, action); - }, [draftMessage, action, reportID]); - - // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator - // Removed messages should not be shown anyway and should not need this flow - const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; - useEffect(() => { - if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { - return; - } - - // Hide reveal message button and show the message if latestDecision is changed to empty - if (!latestDecision) { - setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - setIsHidden(false); - return; - } - - setModerationDecision(latestDecision); - if ( - ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && - !ReportActionsUtils.isPendingRemove(action) - ) { - setIsHidden(true); - return; - } - setIsHidden(false); - }, [latestDecision, action]); - - const toggleContextMenuFromActiveReportAction = useCallback(() => { - setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - }, [action.reportActionID]); - - const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); - const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); - const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); - /** - * Show the ReportActionContextMenu modal popover. - * - * @param [event] - A press event. - */ - const showPopover = useCallback( - (event: GestureResponderEvent | MouseEvent) => { - // Block menu on the message being Edited or if the report action item has errors - if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { - return; - } - - setIsContextMenuActive(true); - const selection = SelectionScraper.getCurrentSelection(); - ReportActionContextMenu.showContextMenu( - CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, - event, - selection, - popoverAnchorRef.current, - reportID, - action.reportActionID, - originalReportID, - draftMessage ?? '', - () => setIsContextMenuActive(true), - toggleContextMenuFromActiveReportAction, - isArchivedRoom, - isChronosReport, - false, - false, - disabledActions, - false, - setIsEmojiPickerActive as () => void, - ); - }, - [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], - ); - - // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. - // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. - useEffect(() => { - if (index !== 0 || !isActionableWhisper) { - return; - } - - if (prevActionResolution !== (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - reportScrollManager.scrollToIndex(index); - } - }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper, hasResolutionInOriginalMessage]); - - const toggleReaction = useCallback( - (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { - Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); - }, - [reportID, action, emojiReactions], - ); - - const contextValue = useMemo( - () => ({ - anchor: popoverAnchorRef.current, - report: {...report, reportID: report?.reportID ?? ''}, - reportNameValuePairs, - action, - transactionThreadReport, - checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, - isDisabled: false, - }), - [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], - ); - - const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); - - const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); - - const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { - return [ - { - text: 'subscription.cardSection.addCardButton', - key: `${action.reportActionID}-actionableAddPaymentCard-submit`, - onPress: () => { - Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); - }, - isMediumSized: true, - isPrimary: true, - }, - ]; - } - - if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { - return []; - } - - if (ReportActionsUtils.isActionableTrackExpense(action)) { - const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; - return [ - { - text: 'actionableMentionTrackExpense.submit', - key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, - onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); - }, - isMediumSized: true, - }, - { - text: 'actionableMentionTrackExpense.categorize', - key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, - onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); - }, - isMediumSized: true, - }, - { - text: 'actionableMentionTrackExpense.share', - key: `${action.reportActionID}-actionableMentionTrackExpense-share`, - onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); - }, - isMediumSized: true, - }, - { - text: 'actionableMentionTrackExpense.nothing', - key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, - onPress: () => { - Report.dismissTrackExpenseActionableWhisper(reportID, action); - }, - isMediumSized: true, - }, - ]; - } - - if (ReportActionsUtils.isActionableJoinRequest(action)) { - return [ - { - text: 'actionableMentionJoinWorkspaceOptions.accept', - key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, - onPress: () => Member.acceptJoinRequest(reportID, action), - isPrimary: true, - }, - { - text: 'actionableMentionJoinWorkspaceOptions.decline', - key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - onPress: () => Member.declineJoinRequest(reportID, action), - }, - ]; - } - - if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { - return [ - { - text: 'common.yes', - key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), - isPrimary: true, - }, - { - text: 'common.no', - key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), - }, - ]; - } - - return [ - { - text: 'actionableMentionWhisperOptions.invite', - key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), - isPrimary: true, - }, - { - text: 'actionableMentionWhisperOptions.nothing', - key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), - }, - ]; - }, [action, isActionableWhisper, reportID]); - - /** - * Get the content of ReportActionItem - * @param hovered whether the ReportActionItem is hovered - * @param isWhisper whether the report action is a whisper - * @param hasErrors whether the report action has any errors - * @returns child component(s) - */ - const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { - let children; - - // Show the MoneyRequestPreview for when expense is present - if ( - ReportActionsUtils.isMoneyRequestAction(action) && - ReportActionsUtils.getOriginalMessage(action) && - // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) - ) { - // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; - children = ( - - ); - } else if (ReportActionsUtils.isTripPreview(action)) { - children = ( - - ); - } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { - children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( - ${translate('parentReportAction.deletedReport')}`} /> - ) : ( - setIsPaymentMethodPopoverActive(true)} - onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} - isWhisper={isWhisper} - /> - ); - } else if (ReportActionsUtils.isTaskAction(action)) { - children = ; - } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { - children = ( - - - - ); - } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { - const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); - const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; - - const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); - children = ( - - <> - {missingPaymentMethod === 'bankAccount' && ( - - )} - {/** - 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 && ( - - )} - - ) : ( - - )} - - - - ); - } - const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - - const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); - const oldestFourAccountIDs = - action.childOldestFourAccountIDs - ?.split(',') - .map((accountID) => Number(accountID)) - .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; - const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; - - return ( - <> - {children} - {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( - - !isEmptyObject(item))} /> - - )} - {!ReportActionsUtils.isMessageDeleted(action) && ( - - { - if (Session.isAnonymousUser()) { - hideContextMenu(false); - - InteractionManager.runAfterInteractions(() => { - Session.signOutAndRedirectToSignIn(); - }); - } else { - toggleReaction(emoji, ignoreSkinToneOnCompare); - } - }} - setIsEmojiPickerActive={setIsEmojiPickerActive} - /> - - )} - - {shouldDisplayThreadReplies && ( - - - - )} - - ); - }; - - /** - * Get ReportActionItem with a proper wrapper - * @param hovered whether the ReportActionItem is hovered - * @param isWhisper whether the ReportActionItem is a whisper - * @param hasErrors whether the report action has any errors - * @returns report action item - */ - - const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { - const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); - - if (draftMessage !== undefined) { - return {content}; - } - - if (!displayAsGroup) { - return ( - item === moderationDecision) && - !ReportActionsUtils.isPendingRemove(action) - } - > - {content} - - ); - } - - return {content}; - }; - - if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) - ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID - : '-1'; - - return ( - - ); - } - if (ReportActionsUtils.isChronosOOOListAction(action)) { - return ( - - ); - } - - // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true - // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet - if ( - ReportActionsUtils.isMoneyRequestAction(action) && - !!report?.isWaitingOnBankAccount && - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - !isSendingMoney - ) { - return null; - } - - // If action is actionable whisper and resolved by user, then we don't want to render anything - if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - return null; - } - - // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. - // This is a temporary solution needed for comment-linking. - // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. - if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { - return null; - } - - const hasErrors = !isEmptyObject(action.errors); - const whisperedTo = ReportActionsUtils.getWhisperedTo(action); - const isMultipleParticipant = whisperedTo.length > 1; - - const iouReportID = - ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID - ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() - : '-1'; - const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); - const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; - const whisperedToPersonalDetails = isWhisper - ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) - : []; - const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); - const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; - - return ( - shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={showPopover} - preventDefaultContextMenu={draftMessage === undefined && !hasErrors} - withoutFocusOnSecondaryInteraction - accessibilityLabel={translate('accessibilityHints.chatMessage')} - accessible - > - - {(hovered) => ( - - {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } - {shouldDisplayContextMenu && ( - - )} - - { - const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; - if (transactionID) { - Transaction.clearError(transactionID); - } - ReportActions.clearAllRelatedReportActionErrors(reportID, action); - }} - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - pendingAction={ - draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) - } - shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} - errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} - errorRowStyles={[styles.ml10, styles.mr2]} - needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} - shouldDisableStrikeThrough - > - {isWhisper && ( - - - - - - {translate('reportActionContextMenu.onlyVisible')} -   - - - - )} - {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} - - - - )} - - - - - - ); + + + return + + + + // const {translate} = useLocalize(); + // const {shouldUseNarrowLayout} = useResponsiveLayout(); + // const blockedFromConcierge = useBlockedFromConcierge(); + // const reportID = report?.reportID ?? ''; + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); + // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { + // selector: (draftMessagesForReport) => { + // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; + // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; + // }, + // }); + // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); + // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); + // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + // const [linkedTransactionRouteError] = useOnyx( + // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, + // {selector: (transaction) => transaction?.errorFields?.route ?? null}, + // ); + // const theme = useTheme(); + // const styles = useThemeStyles(); + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. + // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); + // const StyleUtils = useStyleUtils(); + // const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + // const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + // const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); + // const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); + + // const [isHidden, setIsHidden] = useState(false); + // const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); + // const reactionListRef = useContext(ReactionListContext); + // const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); + // const textInputRef = useRef(null); + // const popoverAnchorRef = useRef>(null); + // const downloadedPreviews = useRef([]); + // const prevDraftMessage = usePrevious(draftMessage); + // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + // // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); + // const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; + // const reportScrollManager = useReportScrollManager(); + // const isActionableWhisper = + // ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); + // const originalMessage = ReportActionsUtils.getOriginalMessage(action); + + // const highlightedBackgroundColorIfNeeded = useMemo( + // () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), + // [StyleUtils, isReportActionLinked, theme.messageHighlightBG], + // ); + + // const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); + // const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; + // const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; + // const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); + + // // IOUDetails only exists when we are sending money + // const isSendingMoney = + // ReportActionsUtils.isMoneyRequestAction(action) && + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + // ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; + + // const updateHiddenState = useCallback( + // (isHiddenValue: boolean) => { + // setIsHidden(isHiddenValue); + // const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; + // const isAttachment = ReportUtils.isReportMessageAttachment(message); + // if (!isAttachment) { + // return; + // } + // updateHiddenAttachments(action.reportActionID, isHiddenValue); + // }, + // [action.reportActionID, action.message, updateHiddenAttachments], + // ); + + // useEffect( + // () => () => { + // // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, + // // we should also hide them when the current component is destroyed + // if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { + // ReportActionContextMenu.hideContextMenu(); + // ReportActionContextMenu.hideDeleteModal(); + // } + // if (EmojiPickerAction.isActive(action.reportActionID)) { + // EmojiPickerAction.hideEmojiPicker(true); + // } + // if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { + // reactionListRef?.current?.hideReactionList(); + // } + // }, + // [action.reportActionID, reactionListRef], + // ); + + // useEffect(() => { + // // We need to hide EmojiPicker when this is a deleted parent action + // if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { + // return; + // } + + // EmojiPickerAction.hideEmojiPicker(true); + // }, [isDeletedParentAction, action.reportActionID]); + + // useEffect(() => { + // if (prevDraftMessage !== undefined || draftMessage === undefined) { + // return; + // } + + // focusComposerWithDelay(textInputRef.current)(true); + // }, [prevDraftMessage, draftMessage]); + + // useEffect(() => { + // if (!Permissions.canUseLinkPreviews()) { + // return; + // } + + // const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); + // if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + // return; + // } + + // downloadedPreviews.current = urls; + // Report.expandURLPreview(reportID, action.reportActionID); + // }, [action, reportID]); + + // useEffect(() => { + // if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { + // return; + // } + // Report.deleteReportActionDraft(reportID, action); + // }, [draftMessage, action, reportID]); + + // // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator + // // Removed messages should not be shown anyway and should not need this flow + // const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; + // useEffect(() => { + // if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { + // return; + // } + + // // Hide reveal message button and show the message if latestDecision is changed to empty + // if (!latestDecision) { + // setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); + // setIsHidden(false); + // return; + // } + + // setModerationDecision(latestDecision); + // if ( + // ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && + // !ReportActionsUtils.isPendingRemove(action) + // ) { + // setIsHidden(true); + // return; + // } + // setIsHidden(false); + // }, [latestDecision, action]); + + // const toggleContextMenuFromActiveReportAction = useCallback(() => { + // setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + // }, [action.reportActionID]); + + // const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); + // const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); + // const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); + // /** + // * Show the ReportActionContextMenu modal popover. + // * + // * @param [event] - A press event. + // */ + // const showPopover = useCallback( + // (event: GestureResponderEvent | MouseEvent) => { + // // Block menu on the message being Edited or if the report action item has errors + // if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { + // return; + // } + + // setIsContextMenuActive(true); + // const selection = SelectionScraper.getCurrentSelection(); + // ReportActionContextMenu.showContextMenu( + // CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, + // event, + // selection, + // popoverAnchorRef.current, + // reportID, + // action.reportActionID, + // originalReportID, + // draftMessage ?? '', + // () => setIsContextMenuActive(true), + // toggleContextMenuFromActiveReportAction, + // isArchivedRoom, + // isChronosReport, + // false, + // false, + // disabledActions, + // false, + // setIsEmojiPickerActive as () => void, + // ); + // }, + // [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], + // ); + + // // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. + // // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. + // useEffect(() => { + // if (index !== 0 || !isActionableWhisper) { + // return; + // } + + // if (prevActionResolution !== (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { + // reportScrollManager.scrollToIndex(index); + // } + // }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper, hasResolutionInOriginalMessage]); + + // const toggleReaction = useCallback( + // (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { + // Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); + // }, + // [reportID, action, emojiReactions], + // ); + + // const contextValue = useMemo( + // () => ({ + // anchor: popoverAnchorRef.current, + // report: {...report, reportID: report?.reportID ?? ''}, + // reportNameValuePairs, + // action, + // transactionThreadReport, + // checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, + // isDisabled: false, + // }), + // [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], + // ); + + // const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); + + // const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); + + // const actionableItemButtons: ActionableItem[] = useMemo(() => { + // if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { + // return [ + // { + // text: 'subscription.cardSection.addCardButton', + // key: `${action.reportActionID}-actionableAddPaymentCard-submit`, + // onPress: () => { + // Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); + // }, + // isMediumSized: true, + // isPrimary: true, + // }, + // ]; + // } + + // if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { + // return []; + // } + + // if (ReportActionsUtils.isActionableTrackExpense(action)) { + // const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; + // return [ + // { + // text: 'actionableMentionTrackExpense.submit', + // key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, + // onPress: () => { + // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); + // }, + // isMediumSized: true, + // }, + // { + // text: 'actionableMentionTrackExpense.categorize', + // key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, + // onPress: () => { + // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); + // }, + // isMediumSized: true, + // }, + // { + // text: 'actionableMentionTrackExpense.share', + // key: `${action.reportActionID}-actionableMentionTrackExpense-share`, + // onPress: () => { + // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); + // }, + // isMediumSized: true, + // }, + // { + // text: 'actionableMentionTrackExpense.nothing', + // key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, + // onPress: () => { + // Report.dismissTrackExpenseActionableWhisper(reportID, action); + // }, + // isMediumSized: true, + // }, + // ]; + // } + + // if (ReportActionsUtils.isActionableJoinRequest(action)) { + // return [ + // { + // text: 'actionableMentionJoinWorkspaceOptions.accept', + // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, + // onPress: () => Member.acceptJoinRequest(reportID, action), + // isPrimary: true, + // }, + // { + // text: 'actionableMentionJoinWorkspaceOptions.decline', + // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, + // onPress: () => Member.declineJoinRequest(reportID, action), + // }, + // ]; + // } + + // if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { + // return [ + // { + // text: 'common.yes', + // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, + // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), + // isPrimary: true, + // }, + // { + // text: 'common.no', + // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, + // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), + // }, + // ]; + // } + + // return [ + // { + // text: 'actionableMentionWhisperOptions.invite', + // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, + // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), + // isPrimary: true, + // }, + // { + // text: 'actionableMentionWhisperOptions.nothing', + // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, + // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), + // }, + // ]; + // }, [action, isActionableWhisper, reportID]); + + // /** + // * Get the content of ReportActionItem + // * @param hovered whether the ReportActionItem is hovered + // * @param isWhisper whether the report action is a whisper + // * @param hasErrors whether the report action has any errors + // * @returns child component(s) + // */ + // const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { + // let children; + + // // Show the MoneyRequestPreview for when expense is present + // if ( + // ReportActionsUtils.isMoneyRequestAction(action) && + // ReportActionsUtils.getOriginalMessage(action) && + // // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message + // (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) + // ) { + // // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID + // const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; + // children = ( + // + // ); + // } else if (ReportActionsUtils.isTripPreview(action)) { + // children = ( + // + // ); + // } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { + // children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( + // ${translate('parentReportAction.deletedReport')}`} /> + // ) : ( + // setIsPaymentMethodPopoverActive(true)} + // onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} + // isWhisper={isWhisper} + // /> + // ); + // } else if (ReportActionsUtils.isTaskAction(action)) { + // children = ; + // } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { + // children = ( + // + // + // + // ); + // } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { + // const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; + // const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); + // const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; + + // const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); + // children = ( + // + // <> + // {missingPaymentMethod === 'bankAccount' && ( + // + // )} + // {/** + // 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 && ( + // + // )} + // + // ) : ( + // + // )} + // + // + // + // ); + // } + // const numberOfThreadReplies = action.childVisibleActionCount ?? 0; + + // const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); + // const oldestFourAccountIDs = + // action.childOldestFourAccountIDs + // ?.split(',') + // .map((accountID) => Number(accountID)) + // .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; + // const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; + + // return ( + // <> + // {children} + // {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( + // + // !isEmptyObject(item))} /> + // + // )} + // {!ReportActionsUtils.isMessageDeleted(action) && ( + // + // { + // if (Session.isAnonymousUser()) { + // hideContextMenu(false); + + // InteractionManager.runAfterInteractions(() => { + // Session.signOutAndRedirectToSignIn(); + // }); + // } else { + // toggleReaction(emoji, ignoreSkinToneOnCompare); + // } + // }} + // setIsEmojiPickerActive={setIsEmojiPickerActive} + // /> + // + // )} + + // {shouldDisplayThreadReplies && ( + // + // + // + // )} + // + // ); + // }; + + // /** + // * Get ReportActionItem with a proper wrapper + // * @param hovered whether the ReportActionItem is hovered + // * @param isWhisper whether the ReportActionItem is a whisper + // * @param hasErrors whether the report action has any errors + // * @returns report action item + // */ + + // const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { + // const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); + + // if (draftMessage !== undefined) { + // return {content}; + // } + + // if (!displayAsGroup) { + // return ( + // item === moderationDecision) && + // !ReportActionsUtils.isPendingRemove(action) + // } + // > + // {content} + // + // ); + // } + + // return {content}; + // }; + + // if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { + // const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) + // ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID + // : '-1'; + + // return ( + // + // ); + // } + // if (ReportActionsUtils.isChronosOOOListAction(action)) { + // return ( + // + // ); + // } + + // // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true + // // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet + // if ( + // ReportActionsUtils.isMoneyRequestAction(action) && + // !!report?.isWaitingOnBankAccount && + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + // !isSendingMoney + // ) { + // return null; + // } + + // // If action is actionable whisper and resolved by user, then we don't want to render anything + // if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { + // return null; + // } + + // // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. + // // This is a temporary solution needed for comment-linking. + // // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. + // if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { + // return null; + // } + + // const hasErrors = !isEmptyObject(action.errors); + // const whisperedTo = ReportActionsUtils.getWhisperedTo(action); + // const isMultipleParticipant = whisperedTo.length > 1; + + // const iouReportID = + // ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID + // ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() + // : '-1'; + // const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); + // const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; + // const whisperedToPersonalDetails = isWhisper + // ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) + // : []; + // const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); + // const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + + // return ( + // shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + // onPressOut={() => ControlSelection.unblock()} + // onSecondaryInteraction={showPopover} + // preventDefaultContextMenu={draftMessage === undefined && !hasErrors} + // withoutFocusOnSecondaryInteraction + // accessibilityLabel={translate('accessibilityHints.chatMessage')} + // accessible + // > + // + // {(hovered) => ( + // + // {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } + // {shouldDisplayContextMenu && ( + // + // )} + // + // { + // const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; + // if (transactionID) { + // Transaction.clearError(transactionID); + // } + // ReportActions.clearAllRelatedReportActionErrors(reportID, action); + // }} + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // pendingAction={ + // draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) + // } + // shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} + // errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} + // errorRowStyles={[styles.ml10, styles.mr2]} + // needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} + // shouldDisableStrikeThrough + // > + // {isWhisper && ( + // + // + // + // + // + // {translate('reportActionContextMenu.onlyVisible')} + //   + // + // + // + // )} + // {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} + // + // + // + // )} + // + // + // + // + // + // ); } -export default memo(ReportActionItem, (prevProps, nextProps) => { - const prevParentReportAction = prevProps.parentReportAction; - const nextParentReportAction = nextProps.parentReportAction; - return ( - prevProps.displayAsGroup === nextProps.displayAsGroup && - prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && - prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && - lodashIsEqual(prevProps.action, nextProps.action) && - lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && - lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && - lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && - prevProps.report?.statusNum === nextProps.report?.statusNum && - prevProps.report?.stateNum === nextProps.report?.stateNum && - prevProps.report?.parentReportID === nextProps.report?.parentReportID && - prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && - // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport - ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && - prevProps.action.actionName === nextProps.action.actionName && - prevProps.report?.reportName === nextProps.report?.reportName && - prevProps.report?.description === nextProps.report?.description && - ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && - prevProps.report?.managerID === nextProps.report?.managerID && - prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && - prevProps.report?.total === nextProps.report?.total && - prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && - prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && - prevProps.linkedReportActionID === nextProps.linkedReportActionID && - lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && - lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && - lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && - lodashIsEqual(prevParentReportAction, nextParentReportAction) - ); -}); +export default ReportActionItem; + +// export default memo(ReportActionItem, (prevProps, nextProps) => { +// const prevParentReportAction = prevProps.parentReportAction; +// const nextParentReportAction = nextProps.parentReportAction; +// return ( +// prevProps.displayAsGroup === nextProps.displayAsGroup && +// prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && +// prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && +// lodashIsEqual(prevProps.action, nextProps.action) && +// lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && +// lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && +// lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && +// prevProps.report?.statusNum === nextProps.report?.statusNum && +// prevProps.report?.stateNum === nextProps.report?.stateNum && +// prevProps.report?.parentReportID === nextProps.report?.parentReportID && +// prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && +// // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport +// ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && +// prevProps.action.actionName === nextProps.action.actionName && +// prevProps.report?.reportName === nextProps.report?.reportName && +// prevProps.report?.description === nextProps.report?.description && +// ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && +// prevProps.report?.managerID === nextProps.report?.managerID && +// prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && +// prevProps.report?.total === nextProps.report?.total && +// prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && +// prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && +// prevProps.linkedReportActionID === nextProps.linkedReportActionID && +// lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && +// lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && +// lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && +// lodashIsEqual(prevParentReportAction, nextParentReportAction) +// ); +// }); From f4fe4b3ecd80605625832abd8291dfeec0778ec2 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 17:05:29 +0700 Subject: [PATCH 012/312] Remove unnecessary code --- .../home/report/PureReportActionItem.tsx | 19 - src/pages/home/report/ReportActionItem.tsx | 927 ------------------ 2 files changed, 946 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index f9cf7ba8bfad..25731504374a 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -192,23 +192,8 @@ function PureReportActionItem({ const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); - // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { - // selector: (draftMessagesForReport) => { - // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; - // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; - // }, - // }); - // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); - // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); - // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); - // const [linkedTransactionRouteError] = useOnyx( - // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - // {selector: (transaction) => transaction?.errorFields?.route ?? null}, - // ); const theme = useTheme(); const styles = useThemeStyles(); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. - // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); const StyleUtils = useStyleUtils(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); @@ -223,10 +208,6 @@ function PureReportActionItem({ const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); - // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); - // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; const reportScrollManager = useReportScrollManager(); const isActionableWhisper = diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a9ef46e53f84..5aadda8e6232 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -192,22 +192,6 @@ function ReportActionItem({ {...props} action={action} report={report} - // transactionThreadReport={transactionThreadReport} - // linkedReportActionID={linkedReportActionID} - // displayAsGroup={displayAsGroup} - // index={index} - // isMostRecentIOUReportAction={isMostRecentIOUReportAction} - // parentReportAction={parentReportAction} - // shouldDisplayNewMarker={shouldDisplayNewMarker} - // shouldHideThreadDividerLine={shouldHideThreadDividerLine} - // shouldShowSubscriptAvatar={shouldShowSubscriptAvatar} - // onPress={onPress} - // isFirstVisibleReportAction={isFirstVisibleReportAction} - // shouldUseThreadDividerLine={shouldUseThreadDividerLine} - // hideThreadReplies={hideThreadReplies} - // shouldDisplayContextMenu={shouldDisplayContextMenu} - // parentReportActionForTransactionThread={parentReportActionForTransactionThread} - // reportActions={reportActions} draftMessage={draftMessage} iouReport={iouReport} @@ -221,917 +205,6 @@ function ReportActionItem({ /> - - - // const {translate} = useLocalize(); - // const {shouldUseNarrowLayout} = useResponsiveLayout(); - // const blockedFromConcierge = useBlockedFromConcierge(); - // const reportID = report?.reportID ?? ''; - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); - // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { - // selector: (draftMessagesForReport) => { - // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; - // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; - // }, - // }); - // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); - // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); - // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); - // const [linkedTransactionRouteError] = useOnyx( - // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - // {selector: (transaction) => transaction?.errorFields?.route ?? null}, - // ); - // const theme = useTheme(); - // const styles = useThemeStyles(); - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. - // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); - // const StyleUtils = useStyleUtils(); - // const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - // const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - // const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); - // const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); - - // const [isHidden, setIsHidden] = useState(false); - // const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - // const reactionListRef = useContext(ReactionListContext); - // const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); - // const textInputRef = useRef(null); - // const popoverAnchorRef = useRef>(null); - // const downloadedPreviews = useRef([]); - // const prevDraftMessage = usePrevious(draftMessage); - // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); - // // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); - // const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; - // const reportScrollManager = useReportScrollManager(); - // const isActionableWhisper = - // ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); - // const originalMessage = ReportActionsUtils.getOriginalMessage(action); - - // const highlightedBackgroundColorIfNeeded = useMemo( - // () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), - // [StyleUtils, isReportActionLinked, theme.messageHighlightBG], - // ); - - // const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); - // const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; - // const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; - // const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); - - // // IOUDetails only exists when we are sending money - // const isSendingMoney = - // ReportActionsUtils.isMoneyRequestAction(action) && - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - // ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; - - // const updateHiddenState = useCallback( - // (isHiddenValue: boolean) => { - // setIsHidden(isHiddenValue); - // const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; - // const isAttachment = ReportUtils.isReportMessageAttachment(message); - // if (!isAttachment) { - // return; - // } - // updateHiddenAttachments(action.reportActionID, isHiddenValue); - // }, - // [action.reportActionID, action.message, updateHiddenAttachments], - // ); - - // useEffect( - // () => () => { - // // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, - // // we should also hide them when the current component is destroyed - // if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { - // ReportActionContextMenu.hideContextMenu(); - // ReportActionContextMenu.hideDeleteModal(); - // } - // if (EmojiPickerAction.isActive(action.reportActionID)) { - // EmojiPickerAction.hideEmojiPicker(true); - // } - // if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { - // reactionListRef?.current?.hideReactionList(); - // } - // }, - // [action.reportActionID, reactionListRef], - // ); - - // useEffect(() => { - // // We need to hide EmojiPicker when this is a deleted parent action - // if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { - // return; - // } - - // EmojiPickerAction.hideEmojiPicker(true); - // }, [isDeletedParentAction, action.reportActionID]); - - // useEffect(() => { - // if (prevDraftMessage !== undefined || draftMessage === undefined) { - // return; - // } - - // focusComposerWithDelay(textInputRef.current)(true); - // }, [prevDraftMessage, draftMessage]); - - // useEffect(() => { - // if (!Permissions.canUseLinkPreviews()) { - // return; - // } - - // const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); - // if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - // return; - // } - - // downloadedPreviews.current = urls; - // Report.expandURLPreview(reportID, action.reportActionID); - // }, [action, reportID]); - - // useEffect(() => { - // if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { - // return; - // } - // Report.deleteReportActionDraft(reportID, action); - // }, [draftMessage, action, reportID]); - - // // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator - // // Removed messages should not be shown anyway and should not need this flow - // const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; - // useEffect(() => { - // if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { - // return; - // } - - // // Hide reveal message button and show the message if latestDecision is changed to empty - // if (!latestDecision) { - // setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - // setIsHidden(false); - // return; - // } - - // setModerationDecision(latestDecision); - // if ( - // ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && - // !ReportActionsUtils.isPendingRemove(action) - // ) { - // setIsHidden(true); - // return; - // } - // setIsHidden(false); - // }, [latestDecision, action]); - - // const toggleContextMenuFromActiveReportAction = useCallback(() => { - // setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - // }, [action.reportActionID]); - - // const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); - // const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); - // const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); - // /** - // * Show the ReportActionContextMenu modal popover. - // * - // * @param [event] - A press event. - // */ - // const showPopover = useCallback( - // (event: GestureResponderEvent | MouseEvent) => { - // // Block menu on the message being Edited or if the report action item has errors - // if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { - // return; - // } - - // setIsContextMenuActive(true); - // const selection = SelectionScraper.getCurrentSelection(); - // ReportActionContextMenu.showContextMenu( - // CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, - // event, - // selection, - // popoverAnchorRef.current, - // reportID, - // action.reportActionID, - // originalReportID, - // draftMessage ?? '', - // () => setIsContextMenuActive(true), - // toggleContextMenuFromActiveReportAction, - // isArchivedRoom, - // isChronosReport, - // false, - // false, - // disabledActions, - // false, - // setIsEmojiPickerActive as () => void, - // ); - // }, - // [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], - // ); - - // // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. - // // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. - // useEffect(() => { - // if (index !== 0 || !isActionableWhisper) { - // return; - // } - - // if (prevActionResolution !== (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - // reportScrollManager.scrollToIndex(index); - // } - // }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper, hasResolutionInOriginalMessage]); - - // const toggleReaction = useCallback( - // (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { - // Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); - // }, - // [reportID, action, emojiReactions], - // ); - - // const contextValue = useMemo( - // () => ({ - // anchor: popoverAnchorRef.current, - // report: {...report, reportID: report?.reportID ?? ''}, - // reportNameValuePairs, - // action, - // transactionThreadReport, - // checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, - // isDisabled: false, - // }), - // [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], - // ); - - // const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); - - // const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); - - // const actionableItemButtons: ActionableItem[] = useMemo(() => { - // if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { - // return [ - // { - // text: 'subscription.cardSection.addCardButton', - // key: `${action.reportActionID}-actionableAddPaymentCard-submit`, - // onPress: () => { - // Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); - // }, - // isMediumSized: true, - // isPrimary: true, - // }, - // ]; - // } - - // if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { - // return []; - // } - - // if (ReportActionsUtils.isActionableTrackExpense(action)) { - // const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; - // return [ - // { - // text: 'actionableMentionTrackExpense.submit', - // key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, - // onPress: () => { - // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); - // }, - // isMediumSized: true, - // }, - // { - // text: 'actionableMentionTrackExpense.categorize', - // key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, - // onPress: () => { - // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); - // }, - // isMediumSized: true, - // }, - // { - // text: 'actionableMentionTrackExpense.share', - // key: `${action.reportActionID}-actionableMentionTrackExpense-share`, - // onPress: () => { - // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); - // }, - // isMediumSized: true, - // }, - // { - // text: 'actionableMentionTrackExpense.nothing', - // key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, - // onPress: () => { - // Report.dismissTrackExpenseActionableWhisper(reportID, action); - // }, - // isMediumSized: true, - // }, - // ]; - // } - - // if (ReportActionsUtils.isActionableJoinRequest(action)) { - // return [ - // { - // text: 'actionableMentionJoinWorkspaceOptions.accept', - // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, - // onPress: () => Member.acceptJoinRequest(reportID, action), - // isPrimary: true, - // }, - // { - // text: 'actionableMentionJoinWorkspaceOptions.decline', - // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - // onPress: () => Member.declineJoinRequest(reportID, action), - // }, - // ]; - // } - - // if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { - // return [ - // { - // text: 'common.yes', - // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), - // isPrimary: true, - // }, - // { - // text: 'common.no', - // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, - // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), - // }, - // ]; - // } - - // return [ - // { - // text: 'actionableMentionWhisperOptions.invite', - // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), - // isPrimary: true, - // }, - // { - // text: 'actionableMentionWhisperOptions.nothing', - // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), - // }, - // ]; - // }, [action, isActionableWhisper, reportID]); - - // /** - // * Get the content of ReportActionItem - // * @param hovered whether the ReportActionItem is hovered - // * @param isWhisper whether the report action is a whisper - // * @param hasErrors whether the report action has any errors - // * @returns child component(s) - // */ - // const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { - // let children; - - // // Show the MoneyRequestPreview for when expense is present - // if ( - // ReportActionsUtils.isMoneyRequestAction(action) && - // ReportActionsUtils.getOriginalMessage(action) && - // // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - // (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) - // ) { - // // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - // const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; - // children = ( - // - // ); - // } else if (ReportActionsUtils.isTripPreview(action)) { - // children = ( - // - // ); - // } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { - // children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( - // ${translate('parentReportAction.deletedReport')}`} /> - // ) : ( - // setIsPaymentMethodPopoverActive(true)} - // onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} - // isWhisper={isWhisper} - // /> - // ); - // } else if (ReportActionsUtils.isTaskAction(action)) { - // children = ; - // } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { - // children = ( - // - // - // - // ); - // } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { - // const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - // const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); - // const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; - - // const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); - // children = ( - // - // <> - // {missingPaymentMethod === 'bankAccount' && ( - // - // )} - // {/** - // 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 && ( - // - // )} - // - // ) : ( - // - // )} - // - // - // - // ); - // } - // const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - - // const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); - // const oldestFourAccountIDs = - // action.childOldestFourAccountIDs - // ?.split(',') - // .map((accountID) => Number(accountID)) - // .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; - // const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; - - // return ( - // <> - // {children} - // {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( - // - // !isEmptyObject(item))} /> - // - // )} - // {!ReportActionsUtils.isMessageDeleted(action) && ( - // - // { - // if (Session.isAnonymousUser()) { - // hideContextMenu(false); - - // InteractionManager.runAfterInteractions(() => { - // Session.signOutAndRedirectToSignIn(); - // }); - // } else { - // toggleReaction(emoji, ignoreSkinToneOnCompare); - // } - // }} - // setIsEmojiPickerActive={setIsEmojiPickerActive} - // /> - // - // )} - - // {shouldDisplayThreadReplies && ( - // - // - // - // )} - // - // ); - // }; - - // /** - // * Get ReportActionItem with a proper wrapper - // * @param hovered whether the ReportActionItem is hovered - // * @param isWhisper whether the ReportActionItem is a whisper - // * @param hasErrors whether the report action has any errors - // * @returns report action item - // */ - - // const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { - // const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); - - // if (draftMessage !== undefined) { - // return {content}; - // } - - // if (!displayAsGroup) { - // return ( - // item === moderationDecision) && - // !ReportActionsUtils.isPendingRemove(action) - // } - // > - // {content} - // - // ); - // } - - // return {content}; - // }; - - // if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - // const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) - // ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID - // : '-1'; - - // return ( - // - // ); - // } - // if (ReportActionsUtils.isChronosOOOListAction(action)) { - // return ( - // - // ); - // } - - // // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true - // // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet - // if ( - // ReportActionsUtils.isMoneyRequestAction(action) && - // !!report?.isWaitingOnBankAccount && - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - // !isSendingMoney - // ) { - // return null; - // } - - // // If action is actionable whisper and resolved by user, then we don't want to render anything - // if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - // return null; - // } - - // // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. - // // This is a temporary solution needed for comment-linking. - // // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. - // if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { - // return null; - // } - - // const hasErrors = !isEmptyObject(action.errors); - // const whisperedTo = ReportActionsUtils.getWhisperedTo(action); - // const isMultipleParticipant = whisperedTo.length > 1; - - // const iouReportID = - // ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID - // ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() - // : '-1'; - // const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); - // const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; - // const whisperedToPersonalDetails = isWhisper - // ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) - // : []; - // const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); - // const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; - - // return ( - // shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - // onPressOut={() => ControlSelection.unblock()} - // onSecondaryInteraction={showPopover} - // preventDefaultContextMenu={draftMessage === undefined && !hasErrors} - // withoutFocusOnSecondaryInteraction - // accessibilityLabel={translate('accessibilityHints.chatMessage')} - // accessible - // > - // - // {(hovered) => ( - // - // {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } - // {shouldDisplayContextMenu && ( - // - // )} - // - // { - // const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; - // if (transactionID) { - // Transaction.clearError(transactionID); - // } - // ReportActions.clearAllRelatedReportActionErrors(reportID, action); - // }} - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // pendingAction={ - // draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) - // } - // shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} - // errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} - // errorRowStyles={[styles.ml10, styles.mr2]} - // needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} - // shouldDisableStrikeThrough - // > - // {isWhisper && ( - // - // - // - // - // - // {translate('reportActionContextMenu.onlyVisible')} - //   - // - // - // - // )} - // {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} - // - // - // - // )} - // - // - // - // - // - // ); } export default ReportActionItem; - -// export default memo(ReportActionItem, (prevProps, nextProps) => { -// const prevParentReportAction = prevProps.parentReportAction; -// const nextParentReportAction = nextProps.parentReportAction; -// return ( -// prevProps.displayAsGroup === nextProps.displayAsGroup && -// prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && -// prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && -// lodashIsEqual(prevProps.action, nextProps.action) && -// lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && -// lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && -// lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && -// prevProps.report?.statusNum === nextProps.report?.statusNum && -// prevProps.report?.stateNum === nextProps.report?.stateNum && -// prevProps.report?.parentReportID === nextProps.report?.parentReportID && -// prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && -// // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport -// ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && -// prevProps.action.actionName === nextProps.action.actionName && -// prevProps.report?.reportName === nextProps.report?.reportName && -// prevProps.report?.description === nextProps.report?.description && -// ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && -// prevProps.report?.managerID === nextProps.report?.managerID && -// prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && -// prevProps.report?.total === nextProps.report?.total && -// prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && -// prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && -// prevProps.linkedReportActionID === nextProps.linkedReportActionID && -// lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && -// lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && -// lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && -// lodashIsEqual(prevParentReportAction, nextParentReportAction) -// ); -// }); From b049c65910ff72029100ac58ca9736b2bf4fdb42 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 21:42:12 +0700 Subject: [PATCH 013/312] Remove unnecessary code --- src/pages/home/report/ReportActionItem.tsx | 198 +++------------------ 1 file changed, 20 insertions(+), 178 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 5aadda8e6232..d29f9a6599ff 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,167 +1,12 @@ -import lodashIsEqual from 'lodash/isEqual'; -import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; -import type {GestureResponderEvent, TextInput} from 'react-native'; -import {InteractionManager, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import type {Emoji} from '@assets/emojis/types'; -import {AttachmentContext} from '@components/AttachmentContext'; -import Button from '@components/Button'; -import DisplayNames from '@components/DisplayNames'; -import Hoverable from '@components/Hoverable'; -import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import InlineSystemMessage from '@components/InlineSystemMessage'; -import KYCWall from '@components/KYCWall'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; -import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; -import RenderHTML from '@components/RenderHTML'; -import type {ActionableItem} from '@components/ReportActionItem/ActionableItemButtons'; -import ActionableItemButtons from '@components/ReportActionItem/ActionableItemButtons'; -import ChronosOOOListActions from '@components/ReportActionItem/ChronosOOOListActions'; -import ExportIntegration from '@components/ReportActionItem/ExportIntegration'; -import IssueCardMessage from '@components/ReportActionItem/IssueCardMessage'; -import MoneyRequestAction from '@components/ReportActionItem/MoneyRequestAction'; -import ReportPreview from '@components/ReportActionItem/ReportPreview'; -import TaskAction from '@components/ReportActionItem/TaskAction'; -import TaskPreview from '@components/ReportActionItem/TaskPreview'; -import TripRoomPreview from '@components/ReportActionItem/TripRoomPreview'; -import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; -import Text from '@components/Text'; -import UnreadActionIndicator from '@components/UnreadActionIndicator'; -import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; -import useReportScrollManager from '@hooks/useReportScrollManager'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import ControlSelection from '@libs/ControlSelection'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import focusComposerWithDelay from '@libs/focusComposerWithDelay'; -import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; -import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import SelectionScraper from '@libs/SelectionScraper'; -import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; -import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; -import {ReactionListContext} from '@pages/home/ReportScreenContext'; -import * as BankAccounts from '@userActions/BankAccounts'; -import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; -import * as Member from '@userActions/Policy/Member'; -import * as Report from '@userActions/Report'; -import * as ReportActions from '@userActions/ReportActions'; -import * as Session from '@userActions/Session'; -import * as Transaction from '@userActions/Transaction'; -import * as User from '@userActions/User'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type * as OnyxTypes from '@src/types/onyx'; -import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions'; -import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; -import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; -import {hideContextMenu} from './ContextMenu/ReportActionContextMenu'; -import LinkPreviewer from './LinkPreviewer'; -import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; -import ReportActionItemContentCreated from './ReportActionItemContentCreated'; -import ReportActionItemDraft from './ReportActionItemDraft'; -import ReportActionItemGrouped from './ReportActionItemGrouped'; -import ReportActionItemMessage from './ReportActionItemMessage'; -import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; -import ReportActionItemSingle from './ReportActionItemSingle'; -import ReportActionItemThread from './ReportActionItemThread'; -import ReportAttachmentsContext from './ReportAttachmentsContext'; -import type { PureReportActionItemProps } from './PureReportActionItem'; +import type {PureReportActionItemProps} from './PureReportActionItem'; import PureReportActionItem from './PureReportActionItem'; -// type ReportActionItemProps = { -// /** Report for this action */ -// report: OnyxEntry; -// /** The transaction thread report associated with the report for this action, if any */ -// transactionThreadReport?: OnyxEntry; - -// /** Array of report actions for the report for this action */ -// // eslint-disable-next-line react/no-unused-prop-types -// reportActions: OnyxTypes.ReportAction[]; - -// /** Report action belonging to the report's parent */ -// parentReportAction: OnyxEntry; - -// /** The transaction thread report's parentReportAction */ -// /** It's used by withOnyx HOC */ -// // eslint-disable-next-line react/no-unused-prop-types -// parentReportActionForTransactionThread?: OnyxEntry; - -// /** All the data of the action item */ -// action: OnyxTypes.ReportAction; - -// /** Should the comment have the appearance of being grouped with the previous comment? */ -// displayAsGroup: boolean; - -// /** Is this the most recent IOU Action? */ -// isMostRecentIOUReportAction: boolean; - -// /** Should we display the new marker on top of the comment? */ -// shouldDisplayNewMarker: boolean; - -// /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ -// shouldShowSubscriptAvatar?: boolean; - -// /** Position index of the report action in the overall report FlatList view */ -// index: number; - -// /** Flag to show, hide the thread divider line */ -// shouldHideThreadDividerLine?: boolean; - -// linkedReportActionID?: string; - -// /** Callback to be called on onPress */ -// onPress?: () => void; - -// /** If this is the first visible report action */ -// isFirstVisibleReportAction: boolean; - -// /** IF the thread divider line will be used */ -// shouldUseThreadDividerLine?: boolean; - -// hideThreadReplies?: boolean; - -// /** Whether context menu should be displayed */ -// shouldDisplayContextMenu?: boolean; -// }; - -function ReportActionItem({ - action, - report, - ...props - // transactionThreadReport, - // linkedReportActionID, - // displayAsGroup, - // index, - // isMostRecentIOUReportAction, - // parentReportAction, - // shouldDisplayNewMarker, - // shouldHideThreadDividerLine = false, - // shouldShowSubscriptAvatar = false, - // onPress = undefined, - // isFirstVisibleReportAction = false, - // shouldUseThreadDividerLine = false, - // hideThreadReplies = false, - // shouldDisplayContextMenu = true, - // parentReportActionForTransactionThread, - // reportActions, -}: PureReportActionItemProps) { +function ReportActionItem({action, report, ...props}: PureReportActionItemProps) { const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); @@ -176,35 +21,32 @@ function ReportActionItem({ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const [linkedTransactionRouteError] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - { selector: (transaction) => transaction?.errorFields?.route ?? null }, + {selector: (transaction) => transaction?.errorFields?.route ?? null}, ); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); - const [isUserValidated] = useOnyx(ONYXKEYS.USER, { selector: (user) => !!user?.validated }); + const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); - - - return + return ( + + ); } export default ReportActionItem; From 153b99ac93a431be72011152cefdd05d2e6c4fd2 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Sat, 16 Nov 2024 03:39:02 +0300 Subject: [PATCH 014/312] add highlight feature for chat --- src/components/Search/index.tsx | 25 +++++-- src/components/SelectionList/ChatListItem.tsx | 12 +++- src/hooks/useSearchHighlightAndScroll.ts | 69 ++++++++++++++----- src/libs/SearchUIUtils.ts | 1 + 4 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index f7ebeb6907fe..ec7ccbe9a923 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -63,7 +63,10 @@ function mapToItemWithSelectionInfo( shouldAnimateInHighlight: boolean, ) { if (SearchUIUtils.isReportActionListItemType(item)) { - return item; + return { + ...item, + shouldAnimateInHighlight, + }; } return SearchUIUtils.isTransactionListItemType(item) @@ -106,6 +109,8 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const previousTransactions = usePrevious(transactions); + const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); + const previousReportActions = usePrevious(reportActions); useEffect(() => { if (!currentSearchResults?.search?.type) { @@ -183,6 +188,8 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo previousTransactions, queryJSON, offset, + reportActions, + previousReportActions, }); // There's a race condition in Onyx which makes it return data from the previous Search, so in addition to checking that the data is loaded @@ -287,15 +294,21 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo const ListItem = SearchUIUtils.getListItem(type, status); const sortedData = SearchUIUtils.getSortedSections(type, status, data, sortBy, sortOrder); + + const isExpense = type === CONST.SEARCH.DATA_TYPES.EXPENSE; const sortedSelectedData = sortedData.map((item) => { - const baseKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; + const baseKey = isExpense + ? `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}` + : `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(item as ReportActionListItemType).reportActionID}`; // Check if the base key matches the newSearchResultKey (TransactionListItemType) const isBaseKeyMatch = baseKey === newSearchResultKey; // Check if any transaction within the transactions array (ReportListItemType) matches the newSearchResultKey - const isAnyTransactionMatch = (item as ReportListItemType)?.transactions?.some((transaction) => { - const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; - return transactionKey === newSearchResultKey; - }); + const isAnyTransactionMatch = + isExpense && + (item as ReportListItemType)?.transactions?.some((transaction) => { + const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; + return transactionKey === newSearchResultKey; + }); // Determine if either the base key or any transaction key matches const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch; diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index a3e04c9088f1..4f32994de59e 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -5,11 +5,13 @@ import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/M import MultipleAvatars from '@components/MultipleAvatars'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import TextWithTooltip from '@components/TextWithTooltip'; +import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import ReportActionItemDate from '@pages/home/report/ReportActionItemDate'; import ReportActionItemFragment from '@pages/home/report/ReportActionItemFragment'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import BaseListItem from './BaseListItem'; import type {ChatListItemProps, ListItem, ReportActionListItemType} from './types'; @@ -56,11 +58,16 @@ function ChatListItem({ const hoveredBackgroundColor = styles.sidebarLinkHover?.backgroundColor ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; const mentionReportContextValue = useMemo(() => ({currentReportID: item?.reportID ?? '-1'}), [item.reportID]); - + const animatedHighlightStyle = useAnimatedHighlightStyle({ + borderRadius: variables.componentBorderRadius, + shouldHighlight: item?.shouldAnimateInHighlight ?? false, + highlightColor: theme.messageHighlightBG, + backgroundColor: theme.highlightBG, + }); return ( ({ keyForList={item.keyForList} onFocus={onFocus} shouldSyncFocus={shouldSyncFocus} + pressableWrapperStyle={[styles.mh5, animatedHighlightStyle]} hoverStyle={item.isSelected && styles.activeComponentBG} > {(hovered) => ( diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts index 95a953139ebe..21437f36104e 100644 --- a/src/hooks/useSearchHighlightAndScroll.ts +++ b/src/hooks/useSearchHighlightAndScroll.ts @@ -3,6 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {SearchQueryJSON} from '@components/Search/types'; import type {ReportActionListItemType, ReportListItemType, SelectionListHandle, TransactionListItemType} from '@components/SelectionList/types'; import * as SearchActions from '@libs/actions/Search'; +import {isReportActionEntry} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchResults, Transaction} from '@src/types/onyx'; @@ -19,27 +20,33 @@ type UseSearchHighlightAndScroll = { /** * Hook used to trigger a search when a new transaction is added and handle highlighting and scrolling. */ -function useSearchHighlightAndScroll({searchResults, transactions, previousTransactions, queryJSON, offset}: UseSearchHighlightAndScroll) { +function useSearchHighlightAndScroll({searchResults, transactions, previousTransactions, reportActions, previousReportActions, queryJSON, offset}: UseSearchHighlightAndScroll) { // Ref to track if the search was triggered by this hook const triggeredByHookRef = useRef(false); const searchTriggeredRef = useRef(false); const previousSearchResults = usePrevious(searchResults?.data); const [newSearchResultKey, setNewSearchResultKey] = useState(null); - const highlightedTransactionIDs = useRef>(new Set()); + const highlightedIDs = useRef>(new Set()); const initializedRef = useRef(false); - + const type = queryJSON.type; + const isExpense = type === CONST.SEARCH.DATA_TYPES.EXPENSE; + const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; // Trigger search when a new transaction is added useEffect(() => { const previousTransactionsLength = previousTransactions && Object.keys(previousTransactions).length; const transactionsLength = transactions && Object.keys(transactions).length; + const reportActionsLength = reportActions && Object.values(reportActions).reduce((sum, curr) => sum + Object.keys(curr).length, 0); + const prevReportActionsLength = previousReportActions && Object.values(previousReportActions).reduce((sum, curr) => sum + Object.keys(curr).length, 0); // Return early if search was already triggered or there's no change in transactions length - if (searchTriggeredRef.current || previousTransactionsLength === transactionsLength) { + if (searchTriggeredRef.current || (isExpense && previousTransactionsLength === transactionsLength) || (isChat && reportActionsLength === prevReportActionsLength)) { return; } + const newTransactionAdded = transactionsLength && typeof previousTransactionsLength === 'number' && transactionsLength > previousTransactionsLength; + const newReportActionAdded = typeof reportActionsLength === 'number' && typeof prevReportActionsLength === 'number' && reportActionsLength > prevReportActionsLength; // Check if a new transaction was added - if (transactionsLength && typeof previousTransactionsLength === 'number' && transactionsLength > previousTransactionsLength) { + if ((isExpense && newTransactionAdded) || (isChat && newReportActionAdded)) { // Set the flag indicating the search is triggered by the hook triggeredByHookRef.current = true; @@ -54,7 +61,7 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans return () => { searchTriggeredRef.current = false; }; - }, [transactions, previousTransactions, queryJSON, offset]); + }, [transactions, previousTransactions, queryJSON, offset, reportActions, previousReportActions, isChat, isExpense]); // Initialize the set with existing transaction IDs only once useEffect(() => { @@ -63,7 +70,7 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans } const existingTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); - highlightedTransactionIDs.current = new Set(existingTransactionIDs); + highlightedIDs.current = new Set(existingTransactionIDs); initializedRef.current = true; }, [searchResults?.data]); @@ -73,22 +80,41 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans return; } - const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults); - const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); + if (isExpense) { + const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults); + const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); - // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted - const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedTransactionIDs.current.has(id)); + // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted + const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedIDs.current.has(id)); - if (!triggeredByHookRef.current || newTransactionIDs.length === 0) { - return; + if (!triggeredByHookRef.current || newTransactionIDs.length === 0) { + return; + } + + const newTransactionID = newTransactionIDs.at(0) ?? ''; + const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`; + + setNewSearchResultKey(newTransactionKey); + highlightedIDs.current.add(newTransactionID); } + if (isChat) { + const previousReportActionIDs = extractReportActionIDsFromSearchResults(previousSearchResults); + const currentReportActionIDs = extractReportActionIDsFromSearchResults(searchResults.data); - const newTransactionID = newTransactionIDs.at(0) ?? ''; - const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`; + // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted + const newReportActionIDs = currentReportActionIDs.filter((id) => !previousReportActionIDs.includes(id) && !highlightedIDs.current.has(id)); - setNewSearchResultKey(newTransactionKey); - highlightedTransactionIDs.current.add(newTransactionID); - }, [searchResults, previousSearchResults]); + if (!triggeredByHookRef.current || newReportActionIDs.length === 0) { + return; + } + + const newReportActionID = newReportActionIDs.at(0) ?? ''; + const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportActionID}`; + + setNewSearchResultKey(newReportActionKey); + highlightedIDs.current.add(newReportActionID); + } + }, [searchResults?.data, previousSearchResults, isExpense, isChat]); // Reset newSearchResultKey after it's been used useEffect(() => { @@ -174,4 +200,11 @@ function extractTransactionIDsFromSearchResults(searchResultsData: Partial): string[] { + return Object.keys(searchResults ?? {}) + .filter(isReportActionEntry) + .map((key) => Object.keys(searchResults[key] ?? {})) + .flat(); +} + export default useSearchHighlightAndScroll; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index a7ce065a6d23..89e2fa39d94b 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -521,4 +521,5 @@ export { getExpenseTypeTranslationKey, getOverflowMenu, isCorrectSearchUserName, + isReportActionEntry, }; From b2b25b7bec5555b5fc98c3d07a97b739f4117216 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 16 Nov 2024 18:47:05 +0700 Subject: [PATCH 015/312] Clean pending action deletePolicyDistanceRates when success --- src/libs/actions/Policy/DistanceRate.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/DistanceRate.ts b/src/libs/actions/Policy/DistanceRate.ts index 64efdaf732c8..786e22ef91d9 100644 --- a/src/libs/actions/Policy/DistanceRate.ts +++ b/src/libs/actions/Policy/DistanceRate.ts @@ -499,7 +499,10 @@ function deletePolicyDistanceRates(policyID: string, customUnit: CustomUnit, rat }; } else { optimisticRates[rateID] = currentRates[rateID]; - successRates[rateID] = currentRates[rateID]; + successRates[rateID] = { + ...currentRates[rateID], + pendingAction: null, + }; } } From d7159314bd7b9f197ebbb77eccab07560beef31c Mon Sep 17 00:00:00 2001 From: NJ-2020 Date: Sun, 17 Nov 2024 15:46:56 -0800 Subject: [PATCH 016/312] fix invitation messageis converted to uni message --- src/libs/actions/Policy/Member.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index 8fb551cdec81..4c3d20021c6a 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -676,7 +676,9 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount employees: JSON.stringify(logins.map((login) => ({email: login}))), ...(optimisticAnnounceChat.announceChatReportID ? {announceChatReportID: optimisticAnnounceChat.announceChatReportID} : {}), ...(optimisticAnnounceChat.announceChatReportActionID ? {announceCreatedReportActionID: optimisticAnnounceChat.announceChatReportActionID} : {}), - welcomeNote: Parser.replace(welcomeNote), + welcomeNote: Parser.replace(welcomeNote, { + shouldEscapeText: false, + }), policyID, }; if (!isEmptyObject(membersChats.reportCreationData)) { From 7fa7e1f00f5602d1ebed96a651ecc02043744b38 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 21 Nov 2024 15:20:16 +0100 Subject: [PATCH 017/312] override session of the user --- src/ONYXKEYS.ts | 4 ++++ src/components/ImportOnyxState/index.tsx | 8 ++++++-- src/components/ImportOnyxState/utils.ts | 2 +- src/libs/actions/App.ts | 19 +++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b4510a2faeed..85b53ea8a7e8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -458,6 +458,9 @@ const ONYXKEYS = { /** The user's Concierge reportID */ CONCIERGE_REPORT_ID: 'conciergeReportID', + /** The user's session that will be preserved when using imported state */ + PRESERVED_USER_SESSION: 'preservedUserSession', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -1029,6 +1032,7 @@ type OnyxValuesMapping = { [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; + [ONYXKEYS.PRESERVED_USER_SESSION]: OnyxTypes.Session; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index 8add2d9172fd..e3c4fc6109f7 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -1,10 +1,11 @@ import React, {useState} from 'react'; -import Onyx from 'react-native-onyx'; +import Onyx, {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; -import {KEYS_TO_PRESERVE, setIsUsingImportedState} from '@libs/actions/App'; +import {KEYS_TO_PRESERVE, setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; @@ -12,6 +13,7 @@ import {cleanAndTransformState} from './utils'; export default function ImportOnyxState({setIsLoading, isLoading}: ImportOnyxStateProps) { const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); + const [session] = useOnyx(ONYXKEYS.SESSION); const handleFileRead = (file: FileObject) => { if (!file.uri) { @@ -27,6 +29,8 @@ export default function ImportOnyxState({setIsLoading, isLoading}: ImportOnyxSta .then((text) => { const fileContent = text; const transformedState = cleanAndTransformState(fileContent); + const currentUserSessionCopy = {...session}; + setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); Onyx.clear(KEYS_TO_PRESERVE).then(() => { Onyx.multiSet(transformedState) diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index a5f24fa80714..94779868384d 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -3,7 +3,7 @@ import type {UnknownRecord} from 'type-fest'; import ONYXKEYS from '@src/ONYXKEYS'; // List of Onyx keys from the .txt file we want to keep for the local override -const keysToOmit = [ONYXKEYS.ACTIVE_CLIENTS, ONYXKEYS.FREQUENTLY_USED_EMOJIS, ONYXKEYS.NETWORK, ONYXKEYS.CREDENTIALS, ONYXKEYS.SESSION, ONYXKEYS.PREFERRED_THEME]; +const keysToOmit = [ONYXKEYS.ACTIVE_CLIENTS, ONYXKEYS.FREQUENTLY_USED_EMOJIS, ONYXKEYS.NETWORK, ONYXKEYS.CREDENTIALS, ONYXKEYS.PREFERRED_THEME]; function isRecord(value: unknown): value is Record { return typeof value === 'object' && !Array.isArray(value) && value !== null; diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index f1f46aee0a93..71697d293b05 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -89,6 +89,14 @@ Onyx.connect({ }, }); +let preservedUserSession: OnyxTypes.Session | undefined; +Onyx.connect({ + key: ONYXKEYS.PRESERVED_USER_SESSION, + callback: (value) => { + preservedUserSession = value; + }, +}); + const KEYS_TO_PRESERVE: OnyxKey[] = [ ONYXKEYS.ACCOUNT, ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, @@ -102,6 +110,7 @@ const KEYS_TO_PRESERVE: OnyxKey[] = [ ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.CREDENTIALS, + ONYXKEYS.PRESERVED_USER_SESSION, ]; Onyx.connect({ @@ -521,6 +530,10 @@ function setIsUsingImportedState(usingImportedState: boolean) { Onyx.set(ONYXKEYS.IS_USING_IMPORTED_STATE, usingImportedState); } +function setPreservedUserSession(session: OnyxTypes.Session) { + Onyx.set(ONYXKEYS.PRESERVED_USER_SESSION, session); +} + function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { // The value of isUsingImportedState will be lost once Onyx is cleared, so we need to store it const isStateImported = isUsingImportedState; @@ -535,6 +548,11 @@ function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { Navigation.navigate(ROUTES.HOME); } + if (preservedUserSession) { + Onyx.set(ONYXKEYS.SESSION, preservedUserSession); + Onyx.set(ONYXKEYS.PRESERVED_USER_SESSION, null); + } + // Requests in a sequential queue should be called even if the Onyx state is reset, so we do not lose any pending data. // However, the OpenApp request must be called before any other request in a queue to ensure data consistency. // To do that, sequential queue is cleared together with other keys, and then it's restored once the OpenApp request is resolved. @@ -571,5 +589,6 @@ export { updateLastRoute, setIsUsingImportedState, clearOnyxAndResetApp, + setPreservedUserSession, KEYS_TO_PRESERVE, }; From 9b517d4bf2bdb162615aceb318049e12c3105fa5 Mon Sep 17 00:00:00 2001 From: Tomasz Lesniakiewicz Date: Thu, 21 Nov 2024 16:14:13 +0100 Subject: [PATCH 018/312] fix: remove duplicates when report is same as policy --- src/components/ParentNavigationSubtitle.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 997106f3e649..9edf73e5f36f 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -61,7 +61,9 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct {reportName} )} - {!!workspaceName && {` ${translate('threads.in')} ${workspaceName}`}} + {workspaceName && workspaceName !== reportName && ( + {` ${translate('threads.in')} ${workspaceName}`} + )} ); From cab51e896dfeb59cdb12c7b0b5c3c81378212a1e Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 21 Nov 2024 16:29:27 +0100 Subject: [PATCH 019/312] remove .defaultProps and related code from the library --- patches/react-native-render-html+6.3.1.patch | 740 +++++++++++++++++++ 1 file changed, 740 insertions(+) diff --git a/patches/react-native-render-html+6.3.1.patch b/patches/react-native-render-html+6.3.1.patch index 6958556d5a96..0d2c2496f568 100644 --- a/patches/react-native-render-html+6.3.1.patch +++ b/patches/react-native-render-html+6.3.1.patch @@ -1,3 +1,339 @@ +diff --git a/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js b/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js +index 9d16738..bbc66a0 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js ++++ b/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js +@@ -3,7 +3,7 @@ + Object.defineProperty(exports, "__esModule", { + value: true + }); +-exports.default = exports.tchildrenRendererDefaultProps = void 0; ++exports.default = void 0; + + var _renderChildren = _interopRequireDefault(require("./renderChildren")); + +@@ -15,15 +15,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de + */ + const TChildrenRenderer = _renderChildren.default.bind(null); + +-const tchildrenRendererDefaultProps = { +- propsForChildren: {} +-}; +-/** +- * @ignore +- */ +- +-exports.tchildrenRendererDefaultProps = tchildrenRendererDefaultProps; +-TChildrenRenderer.defaultProps = tchildrenRendererDefaultProps; + var _default = TChildrenRenderer; + exports.default = _default; + //# sourceMappingURL=TChildrenRenderer.js.map +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js.map b/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js.map +index 3fdd160..50e52b5 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js.map ++++ b/node_modules/react-native-render-html/lib/commonjs/TChildrenRenderer.js.map +@@ -1 +1 @@ +-{"version":3,"sources":["TChildrenRenderer.tsx"],"names":["TChildrenRenderer","renderChildren","bind","tchildrenRendererDefaultProps","propsForChildren","defaultProps"],"mappings":";;;;;;;AAEA;;;;AAEA;AACA;AACA;AACA;AACA,MAAMA,iBAA4D,GAChEC,wBAAeC,IAAf,CAAoB,IAApB,CADF;;AAGO,MAAMC,6BAGZ,GAAG;AACFC,EAAAA,gBAAgB,EAAE;AADhB,CAHG;AAOP;AACA;AACA;;;AACAJ,iBAAiB,CAACK,YAAlB,GAAiCF,6BAAjC;eAEeH,iB","sourcesContent":["import { FunctionComponent } from 'react';\nimport { TChildrenRendererProps } from './shared-types';\nimport renderChildren from './renderChildren';\n\n/**\n * A component to render collections of tnodes.\n * Especially useful when used with {@link useTNodeChildrenProps}.\n */\nconst TChildrenRenderer: FunctionComponent =\n renderChildren.bind(null);\n\nexport const tchildrenRendererDefaultProps: Pick<\n TChildrenRendererProps,\n 'propsForChildren'\n> = {\n propsForChildren: {}\n};\n\n/**\n * @ignore\n */\nTChildrenRenderer.defaultProps = tchildrenRendererDefaultProps;\n\nexport default TChildrenRenderer;\n"]} +\ No newline at end of file ++{"version":3,"sources":["TChildrenRenderer.tsx"],"names":["TChildrenRenderer","renderChildren","bind"],"mappings":";;;;;;;AAEA;;;;AAEA;AACA;AACA;AACA;AACA,MAAMA,iBAA4D,GAChEC,wBAAeC,IAAf,CAAoB,IAApB,CADF;;eAGeF,iB","sourcesContent":["import { FunctionComponent } from 'react';\nimport { TChildrenRendererProps } from './shared-types';\nimport renderChildren from './renderChildren';\n\n/**\n * A component to render collections of tnodes.\n * Especially useful when used with {@link useTNodeChildrenProps}.\n */\nconst TChildrenRenderer: FunctionComponent =\n renderChildren.bind(null);\n\nexport default TChildrenRenderer;\n"]} +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js b/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js +index 50b43ca..5ecf4a4 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js ++++ b/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js +@@ -8,8 +8,6 @@ exports.default = void 0; + + var _SharedPropsProvider = require("./context/SharedPropsProvider"); + +-var _TChildrenRenderer = require("./TChildrenRenderer"); +- + var _renderChildren = _interopRequireDefault(require("./renderChildren")); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +@@ -78,12 +76,7 @@ function TNodeChildrenRenderer(props) { + + return (0, _renderChildren.default)(useTNodeChildrenProps(props)); + } +-/** +- * @ignore +- */ +- + +-TNodeChildrenRenderer.defaultProps = _TChildrenRenderer.tchildrenRendererDefaultProps; + var _default = TNodeChildrenRenderer; + exports.default = _default; + //# sourceMappingURL=TNodeChildrenRenderer.js.map +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js.map b/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js.map +index 2a7c4b6..ba13ece 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js.map ++++ b/node_modules/react-native-render-html/lib/commonjs/TNodeChildrenRenderer.js.map +@@ -1 +1 @@ +-{"version":3,"sources":["TNodeChildrenRenderer.tsx"],"names":["isCollapsible","tnode","type","useTNodeChildrenProps","propsForChildren","disableMarginCollapsing","renderChild","enableExperimentalMarginCollapsing","shouldCollapseChildren","tchildren","children","TNodeChildrenRenderer","props","data","defaultProps","tchildrenRendererDefaultProps"],"mappings":";;;;;;;;AAEA;;AACA;;AAKA;;;;AAEA,SAASA,aAAT,CAAuBC,KAAvB,EAAqC;AACnC,SAAOA,KAAK,CAACC,IAAN,KAAe,OAAf,IAA0BD,KAAK,CAACC,IAAN,KAAe,UAAhD;AACD;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AACO,SAASC,qBAAT,CAA+B;AACpCF,EAAAA,KADoC;AAEpCG,EAAAA,gBAFoC;AAGpCC,EAAAA,uBAAuB,GAAG,KAHU;AAIpCC,EAAAA;AAJoC,CAA/B,EAKgD;AACrD,QAAM;AAAEC,IAAAA;AAAF,MAAyC,0CAA/C;AACA,QAAMC,sBAAsB,GAC1BD,kCAAkC,IAClC,CAACF,uBADD,IAEAL,aAAa,CAACC,KAAD,CAHf;AAIA,SAAO;AACLG,IAAAA,gBADK;AAELC,IAAAA,uBAAuB,EAAE,CAACG,sBAFrB;AAGLC,IAAAA,SAAS,EAAER,KAAK,CAACS,QAHZ;AAILJ,IAAAA;AAJK,GAAP;AAMD;AAED;AACA;AACA;;;AACA,SAASK,qBAAT,CACEC,KADF,EAEgB;AACd,MAAIA,KAAK,CAACX,KAAN,CAAYC,IAAZ,KAAqB,MAAzB,EAAiC;AAC/B;AACA,WAAOU,KAAK,CAACX,KAAN,CAAYY,IAAnB;AACD,GAJa,CAKd;AACA;AACA;;;AACA,SAAO,6BAAeV,qBAAqB,CAACS,KAAD,CAApC,CAAP;AACD;AAED;AACA;AACA;;;AACAD,qBAAqB,CAACG,YAAtB,GAAqCC,gDAArC;eAEeJ,qB","sourcesContent":["import { ReactElement } from 'react';\nimport { TNode } from '@native-html/transient-render-engine';\nimport { useSharedProps } from './context/SharedPropsProvider';\nimport { tchildrenRendererDefaultProps } from './TChildrenRenderer';\nimport {\n TChildrenRendererProps,\n TNodeChildrenRendererProps\n} from './shared-types';\nimport renderChildren from './renderChildren';\n\nfunction isCollapsible(tnode: TNode) {\n return tnode.type === 'block' || tnode.type === 'phrasing';\n}\n\n/**\n * A hook especially useful when one need to tamper with children in a custom\n * renderer. Should be used with {@link TChildrenRenderer}.\n *\n * @example\n * For example, a custom renderer which inserts ads in an article:\n *\n * ```tsx\n * function ArticleRenderer(props) {\n * const { tnode, TDefaultRenderer, ...defaultRendererProps } = props;\n * const tchildrenProps = useTNodeChildrenProps(props);\n * const firstChildrenChunk = tnode.children.slice(0, 2);\n * const secondChildrenChunk = tnode.children.slice(2, 4);\n * const thirdChildrenChunk = tnode.children.slice(4, 5);\n * return (\n * \n * \n * {firstChildrenChunk.length === 2 ? : null}\n * \n * {secondChildrenChunk.length === 2 ? : null}\n * \n * \n * );\n * };\n * ```\n */\nexport function useTNodeChildrenProps({\n tnode,\n propsForChildren,\n disableMarginCollapsing = false,\n renderChild\n}: TNodeChildrenRendererProps): TChildrenRendererProps {\n const { enableExperimentalMarginCollapsing } = useSharedProps();\n const shouldCollapseChildren =\n enableExperimentalMarginCollapsing &&\n !disableMarginCollapsing &&\n isCollapsible(tnode);\n return {\n propsForChildren,\n disableMarginCollapsing: !shouldCollapseChildren,\n tchildren: tnode.children,\n renderChild\n };\n}\n\n/**\n * A component to render all children of a {@link TNode}.\n */\nfunction TNodeChildrenRenderer(\n props: TNodeChildrenRendererProps\n): ReactElement {\n if (props.tnode.type === 'text') {\n // see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544\n return props.tnode.data as unknown as ReactElement;\n }\n // A tnode type will never change. We can safely\n // ignore the non-conditional rule of hooks.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return renderChildren(useTNodeChildrenProps(props));\n}\n\n/**\n * @ignore\n */\nTNodeChildrenRenderer.defaultProps = tchildrenRendererDefaultProps;\n\nexport default TNodeChildrenRenderer;\n"]} +\ No newline at end of file ++{"version":3,"sources":["TNodeChildrenRenderer.tsx"],"names":["isCollapsible","tnode","type","useTNodeChildrenProps","propsForChildren","disableMarginCollapsing","renderChild","enableExperimentalMarginCollapsing","shouldCollapseChildren","tchildren","children","TNodeChildrenRenderer","props","data"],"mappings":";;;;;;;;AAEA;;AAKA;;;;AAEA,SAASA,aAAT,CAAuBC,KAAvB,EAAqC;AACnC,SAAOA,KAAK,CAACC,IAAN,KAAe,OAAf,IAA0BD,KAAK,CAACC,IAAN,KAAe,UAAhD;AACD;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AACO,SAASC,qBAAT,CAA+B;AACpCF,EAAAA,KADoC;AAEpCG,EAAAA,gBAFoC;AAGpCC,EAAAA,uBAAuB,GAAG,KAHU;AAIpCC,EAAAA;AAJoC,CAA/B,EAKgD;AACrD,QAAM;AAAEC,IAAAA;AAAF,MAAyC,0CAA/C;AACA,QAAMC,sBAAsB,GAC1BD,kCAAkC,IAClC,CAACF,uBADD,IAEAL,aAAa,CAACC,KAAD,CAHf;AAIA,SAAO;AACLG,IAAAA,gBADK;AAELC,IAAAA,uBAAuB,EAAE,CAACG,sBAFrB;AAGLC,IAAAA,SAAS,EAAER,KAAK,CAACS,QAHZ;AAILJ,IAAAA;AAJK,GAAP;AAMD;AAED;AACA;AACA;;;AACA,SAASK,qBAAT,CACEC,KADF,EAEgB;AACd,MAAIA,KAAK,CAACX,KAAN,CAAYC,IAAZ,KAAqB,MAAzB,EAAiC;AAC/B;AACA,WAAOU,KAAK,CAACX,KAAN,CAAYY,IAAnB;AACD,GAJa,CAKd;AACA;AACA;;;AACA,SAAO,6BAAeV,qBAAqB,CAACS,KAAD,CAApC,CAAP;AACD;;eAEcD,qB","sourcesContent":["import { ReactElement } from 'react';\nimport { TNode } from '@native-html/transient-render-engine';\nimport { useSharedProps } from './context/SharedPropsProvider';\nimport {\n TChildrenRendererProps,\n TNodeChildrenRendererProps\n} from './shared-types';\nimport renderChildren from './renderChildren';\n\nfunction isCollapsible(tnode: TNode) {\n return tnode.type === 'block' || tnode.type === 'phrasing';\n}\n\n/**\n * A hook especially useful when one need to tamper with children in a custom\n * renderer. Should be used with {@link TChildrenRenderer}.\n *\n * @example\n * For example, a custom renderer which inserts ads in an article:\n *\n * ```tsx\n * function ArticleRenderer(props) {\n * const { tnode, TDefaultRenderer, ...defaultRendererProps } = props;\n * const tchildrenProps = useTNodeChildrenProps(props);\n * const firstChildrenChunk = tnode.children.slice(0, 2);\n * const secondChildrenChunk = tnode.children.slice(2, 4);\n * const thirdChildrenChunk = tnode.children.slice(4, 5);\n * return (\n * \n * \n * {firstChildrenChunk.length === 2 ? : null}\n * \n * {secondChildrenChunk.length === 2 ? : null}\n * \n * \n * );\n * };\n * ```\n */\nexport function useTNodeChildrenProps({\n tnode,\n propsForChildren,\n disableMarginCollapsing = false,\n renderChild\n}: TNodeChildrenRendererProps): TChildrenRendererProps {\n const { enableExperimentalMarginCollapsing } = useSharedProps();\n const shouldCollapseChildren =\n enableExperimentalMarginCollapsing &&\n !disableMarginCollapsing &&\n isCollapsible(tnode);\n return {\n propsForChildren,\n disableMarginCollapsing: !shouldCollapseChildren,\n tchildren: tnode.children,\n renderChild\n };\n}\n\n/**\n * A component to render all children of a {@link TNode}.\n */\nfunction TNodeChildrenRenderer(\n props: TNodeChildrenRendererProps\n): ReactElement {\n if (props.tnode.type === 'text') {\n // see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544\n return props.tnode.data as unknown as ReactElement;\n }\n // A tnode type will never change. We can safely\n // ignore the non-conditional rule of hooks.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return renderChildren(useTNodeChildrenProps(props));\n}\n\nexport default TNodeChildrenRenderer;\n"]} +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js b/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js +index eafc942..e083941 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js ++++ b/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js +@@ -57,7 +57,11 @@ const TNodeRenderer = /*#__PURE__*/(0, _react.memo)(function MemoizedTNodeRender + const sharedProps = (0, _SharedPropsProvider.useSharedProps)(); + const renderRegistry = (0, _RenderRegistryProvider.useRendererRegistry)(); + const TNodeChildrenRenderer = (0, _TChildrenRendererContext.useTNodeChildrenRenderer)(); +- const tnodeProps = { ...props, ++ const tnodeProps = { ++ propsFromParent: { ++ collapsedMarginTop: null ++ }, ++ ...props, + TNodeChildrenRenderer, + sharedProps + }; +@@ -109,13 +113,6 @@ const TNodeRenderer = /*#__PURE__*/(0, _react.memo)(function MemoizedTNodeRender + const renderFn = tnode.type === 'block' || tnode.type === 'document' ? _renderBlockContent.default : _renderTextualContent.default; + return Renderer === null ? renderFn(assembledProps) : /*#__PURE__*/_react.default.createElement(Renderer, assembledProps); + }); +-const defaultProps = { +- propsFromParent: { +- collapsedMarginTop: null +- } +-}; // @ts-expect-error default props must be defined +- +-TNodeRenderer.defaultProps = defaultProps; + var _default = TNodeRenderer; + exports.default = _default; + //# sourceMappingURL=TNodeRenderer.js.map +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js.map b/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js.map +index 204b82a..7721e92 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js.map ++++ b/node_modules/react-native-render-html/lib/commonjs/TNodeRenderer.js.map +@@ -1 +1 @@ +-{"version":3,"sources":["TNodeRenderer.tsx"],"names":["TDefaultBlockRenderer","renderBlockContent","bind","displayName","TDefaultPhrasingRenderer","renderTextualContent","TDefaultTextRenderer","isGhostTNode","tnode","type","data","TNodeRenderer","MemoizedTNodeRenderer","props","sharedProps","renderRegistry","TNodeChildrenRenderer","tnodeProps","renderer","renderEmptyContent","assembledProps","Renderer","InternalTextRenderer","getInternalTextRenderer","tagName","React","createElement","enableExperimentalGhostLinesPrevention","bypassAnonymousTPhrasingNodes","children","length","every","renderFn","defaultProps","propsFromParent","collapsedMarginTop"],"mappings":";;;;;;;AAAA;;AAEA;;AAOA;;AACA;;AACA;;AACA;;AACA;;AACA;;;;;;;;AAIA,MAAMA,qBAA+C,GACnDC,4BAAmBC,IAAnB,CAAwB,IAAxB,CADF;;;AAGAF,qBAAqB,CAACG,WAAtB,GAAoC,uBAApC;;AAEA,MAAMC,wBAAqD,GACzDC,8BAAqBH,IAArB,CAA0B,IAA1B,CADF;;;AAGAE,wBAAwB,CAACD,WAAzB,GAAuC,0BAAvC;;AAEA,MAAMG,oBAA6C,GACjDD,8BAAqBH,IAArB,CAA0B,IAA1B,CADF;;;AAGAI,oBAAoB,CAACH,WAArB,GAAmC,sBAAnC;;AAEA,SAASI,YAAT,CAAsBC,KAAtB,EAAoC;AAClC,SACGA,KAAK,CAACC,IAAN,KAAe,MAAf,KAA0BD,KAAK,CAACE,IAAN,KAAe,EAAf,IAAqBF,KAAK,CAACE,IAAN,KAAe,GAA9D,CAAD,IACAF,KAAK,CAACC,IAAN,KAAe,OAFjB;AAID;AAED;AACA;AACA;;;AACA,MAAME,aAAa,gBAAG,iBAAK,SAASC,qBAAT,CACzBC,KADyB,EAEJ;AACrB,QAAM;AAAEL,IAAAA;AAAF,MAAYK,KAAlB;AACA,QAAMC,WAAW,GAAG,0CAApB;AACA,QAAMC,cAAc,GAAG,kDAAvB;AACA,QAAMC,qBAAqB,GAAG,yDAA9B;AACA,QAAMC,UAAU,GAAG,EACjB,GAAGJ,KADc;AAEjBG,IAAAA,qBAFiB;AAGjBF,IAAAA;AAHiB,GAAnB;AAKA,QAAMI,QAAQ,GACZV,KAAK,CAACC,IAAN,KAAe,OAAf,IAA0BD,KAAK,CAACC,IAAN,KAAe,UAAzC,GACIT,qBADJ,GAEIQ,KAAK,CAACC,IAAN,KAAe,MAAf,GACAH,oBADA,GAEAE,KAAK,CAACC,IAAN,KAAe,UAAf,GACAL,wBADA,GAEAe,2BAPN;AASA,QAAM;AAAEC,IAAAA,cAAF;AAAkBC,IAAAA;AAAlB,MAA+B,sCACnCJ,UADmC,EAEnCC,QAFmC,CAArC;;AAIA,UAAQV,KAAK,CAACC,IAAd;AACE,SAAK,OAAL;AACE,aAAO,iCAAmBW,cAAnB,CAAP;;AACF,SAAK,MAAL;AACE,YAAME,oBAAoB,GAAGP,cAAc,CAACQ,uBAAf,CAC3BV,KAAK,CAACL,KAAN,CAAYgB,OADe,CAA7B;;AAIA,UAAIF,oBAAJ,EAA0B;AACxB,4BAAOG,eAAMC,aAAN,CAAoBJ,oBAApB,EAA0CL,UAA1C,CAAP;AACD,OAPH,CAQE;AACA;AACA;;;AACA,UACEA,UAAU,CAACT,KAAX,CAAiBE,IAAjB,KAA0B,EAA1B,IACAO,UAAU,CAACH,WAAX,CAAuBa,sCAFzB,EAGE;AACA,eAAO,IAAP;AACD;;AACD;;AACF,SAAK,UAAL;AACE;AACA;AACA,UACEV,UAAU,CAACH,WAAX,CAAuBc,6BAAvB,IACAX,UAAU,CAACT,KAAX,CAAiBgB,OAAjB,IAA4B,IAD5B,IAEAP,UAAU,CAACT,KAAX,CAAiBqB,QAAjB,CAA0BC,MAA1B,IAAoC,CAHtC,EAIE;AACA,4BAAOL,eAAMC,aAAN,CAAoBV,qBAApB,EAA2C;AAChDR,UAAAA,KAAK,EAAEK,KAAK,CAACL;AADmC,SAA3C,CAAP;AAGD,OAXH,CAYE;AACA;AACA;;;AACA,UACES,UAAU,CAACH,WAAX,CAAuBa,sCAAvB,IACAV,UAAU,CAACT,KAAX,CAAiBgB,OAAjB,IAA4B,IAD5B,IAEAP,UAAU,CAACT,KAAX,CAAiBqB,QAAjB,CAA0BE,KAA1B,CAAgCxB,YAAhC,CAHF,EAIE;AACA,eAAO,IAAP;AACD;;AACD;AA3CJ;;AA6CA,QAAMyB,QAAQ,GACZxB,KAAK,CAACC,IAAN,KAAe,OAAf,IAA0BD,KAAK,CAACC,IAAN,KAAe,UAAzC,GACIR,2BADJ,GAEII,6BAHN;AAIA,SAAOgB,QAAQ,KAAK,IAAb,GACHW,QAAQ,CAACZ,cAAD,CADL,gBAEHK,eAAMC,aAAN,CAAoBL,QAApB,EAAqCD,cAArC,CAFJ;AAGD,CA7EqB,CAAtB;AA+EA,MAAMa,YAAwE,GAC5E;AACEC,EAAAA,eAAe,EAAE;AACfC,IAAAA,kBAAkB,EAAE;AADL;AADnB,CADF,C,CAOA;;AACAxB,aAAa,CAACsB,YAAd,GAA6BA,YAA7B;eAQetB,a","sourcesContent":["import React, { memo, ReactElement } from 'react';\nimport { TDefaultRenderer, TNodeRendererProps } from './shared-types';\nimport { useSharedProps } from './context/SharedPropsProvider';\nimport {\n TText,\n TBlock,\n TNode,\n TPhrasing\n} from '@native-html/transient-render-engine';\nimport useAssembledCommonProps from './hooks/useAssembledCommonProps';\nimport { useTNodeChildrenRenderer } from './context/TChildrenRendererContext';\nimport renderTextualContent from './renderTextualContent';\nimport { useRendererRegistry } from './context/RenderRegistryProvider';\nimport renderBlockContent from './renderBlockContent';\nimport renderEmptyContent from './renderEmptyContent';\n\nexport type { TNodeRendererProps } from './shared-types';\n\nconst TDefaultBlockRenderer: TDefaultRenderer =\n renderBlockContent.bind(null);\n\nTDefaultBlockRenderer.displayName = 'TDefaultBlockRenderer';\n\nconst TDefaultPhrasingRenderer: TDefaultRenderer =\n renderTextualContent.bind(null);\n\nTDefaultPhrasingRenderer.displayName = 'TDefaultPhrasingRenderer';\n\nconst TDefaultTextRenderer: TDefaultRenderer =\n renderTextualContent.bind(null);\n\nTDefaultTextRenderer.displayName = 'TDefaultTextRenderer';\n\nfunction isGhostTNode(tnode: TNode) {\n return (\n (tnode.type === 'text' && (tnode.data === '' || tnode.data === ' ')) ||\n tnode.type === 'empty'\n );\n}\n\n/**\n * A component to render any {@link TNode}.\n */\nconst TNodeRenderer = memo(function MemoizedTNodeRenderer(\n props: TNodeRendererProps\n): ReactElement | null {\n const { tnode } = props;\n const sharedProps = useSharedProps();\n const renderRegistry = useRendererRegistry();\n const TNodeChildrenRenderer = useTNodeChildrenRenderer();\n const tnodeProps = {\n ...props,\n TNodeChildrenRenderer,\n sharedProps\n };\n const renderer =\n tnode.type === 'block' || tnode.type === 'document'\n ? TDefaultBlockRenderer\n : tnode.type === 'text'\n ? TDefaultTextRenderer\n : tnode.type === 'phrasing'\n ? TDefaultPhrasingRenderer\n : renderEmptyContent;\n\n const { assembledProps, Renderer } = useAssembledCommonProps(\n tnodeProps,\n renderer as any\n );\n switch (tnode.type) {\n case 'empty':\n return renderEmptyContent(assembledProps);\n case 'text':\n const InternalTextRenderer = renderRegistry.getInternalTextRenderer(\n props.tnode.tagName\n );\n\n if (InternalTextRenderer) {\n return React.createElement(InternalTextRenderer, tnodeProps);\n }\n // If ghost line prevention is enabled and the text data is empty, render\n // nothing to avoid React Native painting a 20px height line.\n // See also https://git.io/JErwX\n if (\n tnodeProps.tnode.data === '' &&\n tnodeProps.sharedProps.enableExperimentalGhostLinesPrevention\n ) {\n return null;\n }\n break;\n case 'phrasing':\n // When a TPhrasing node is anonymous and has only one child, its\n // rendering amounts to rendering its only child.\n if (\n tnodeProps.sharedProps.bypassAnonymousTPhrasingNodes &&\n tnodeProps.tnode.tagName == null &&\n tnodeProps.tnode.children.length <= 1\n ) {\n return React.createElement(TNodeChildrenRenderer, {\n tnode: props.tnode\n });\n }\n // If ghost line prevention is enabled and the tnode is an anonymous empty\n // phrasing node, render nothing to avoid React Native painting a 20px\n // height line. See also https://git.io/JErwX\n if (\n tnodeProps.sharedProps.enableExperimentalGhostLinesPrevention &&\n tnodeProps.tnode.tagName == null &&\n tnodeProps.tnode.children.every(isGhostTNode)\n ) {\n return null;\n }\n break;\n }\n const renderFn =\n tnode.type === 'block' || tnode.type === 'document'\n ? renderBlockContent\n : renderTextualContent;\n return Renderer === null\n ? renderFn(assembledProps)\n : React.createElement(Renderer as any, assembledProps);\n});\n\nconst defaultProps: Required, 'propsFromParent'>> =\n {\n propsFromParent: {\n collapsedMarginTop: null\n }\n };\n\n// @ts-expect-error default props must be defined\nTNodeRenderer.defaultProps = defaultProps;\n\nexport {\n TDefaultBlockRenderer,\n TDefaultPhrasingRenderer,\n TDefaultTextRenderer\n};\n\nexport default TNodeRenderer;\n"]} +\ No newline at end of file ++{"version":3,"sources":["TNodeRenderer.tsx"],"names":["TDefaultBlockRenderer","renderBlockContent","bind","displayName","TDefaultPhrasingRenderer","renderTextualContent","TDefaultTextRenderer","isGhostTNode","tnode","type","data","TNodeRenderer","MemoizedTNodeRenderer","props","sharedProps","renderRegistry","TNodeChildrenRenderer","tnodeProps","propsFromParent","collapsedMarginTop","renderer","renderEmptyContent","assembledProps","Renderer","InternalTextRenderer","getInternalTextRenderer","tagName","React","createElement","enableExperimentalGhostLinesPrevention","bypassAnonymousTPhrasingNodes","children","length","every","renderFn"],"mappings":";;;;;;;AAAA;;AAEA;;AAOA;;AACA;;AACA;;AACA;;AACA;;AACA;;;;;;;;AAIA,MAAMA,qBAA+C,GACnDC,4BAAmBC,IAAnB,CAAwB,IAAxB,CADF;;;AAGAF,qBAAqB,CAACG,WAAtB,GAAoC,uBAApC;;AAEA,MAAMC,wBAAqD,GACzDC,8BAAqBH,IAArB,CAA0B,IAA1B,CADF;;;AAGAE,wBAAwB,CAACD,WAAzB,GAAuC,0BAAvC;;AAEA,MAAMG,oBAA6C,GACjDD,8BAAqBH,IAArB,CAA0B,IAA1B,CADF;;;AAGAI,oBAAoB,CAACH,WAArB,GAAmC,sBAAnC;;AAEA,SAASI,YAAT,CAAsBC,KAAtB,EAAoC;AAClC,SACGA,KAAK,CAACC,IAAN,KAAe,MAAf,KAA0BD,KAAK,CAACE,IAAN,KAAe,EAAf,IAAqBF,KAAK,CAACE,IAAN,KAAe,GAA9D,CAAD,IACAF,KAAK,CAACC,IAAN,KAAe,OAFjB;AAID;AAED;AACA;AACA;;;AACA,MAAME,aAAa,gBAAG,iBAAK,SAASC,qBAAT,CACzBC,KADyB,EAEJ;AACrB,QAAM;AAAEL,IAAAA;AAAF,MAAYK,KAAlB;AACA,QAAMC,WAAW,GAAG,0CAApB;AACA,QAAMC,cAAc,GAAG,kDAAvB;AACA,QAAMC,qBAAqB,GAAG,yDAA9B;AACA,QAAMC,UAAU,GAAG;AACjBC,IAAAA,eAAe,EAAE;AAAEC,MAAAA,kBAAkB,EAAE;AAAtB,KADA;AAEjB,OAAGN,KAFc;AAGjBG,IAAAA,qBAHiB;AAIjBF,IAAAA;AAJiB,GAAnB;AAMA,QAAMM,QAAQ,GACZZ,KAAK,CAACC,IAAN,KAAe,OAAf,IAA0BD,KAAK,CAACC,IAAN,KAAe,UAAzC,GACIT,qBADJ,GAEIQ,KAAK,CAACC,IAAN,KAAe,MAAf,GACAH,oBADA,GAEAE,KAAK,CAACC,IAAN,KAAe,UAAf,GACAL,wBADA,GAEAiB,2BAPN;AASA,QAAM;AAAEC,IAAAA,cAAF;AAAkBC,IAAAA;AAAlB,MAA+B,sCACnCN,UADmC,EAEnCG,QAFmC,CAArC;;AAIA,UAAQZ,KAAK,CAACC,IAAd;AACE,SAAK,OAAL;AACE,aAAO,iCAAmBa,cAAnB,CAAP;;AACF,SAAK,MAAL;AACE,YAAME,oBAAoB,GAAGT,cAAc,CAACU,uBAAf,CAC3BZ,KAAK,CAACL,KAAN,CAAYkB,OADe,CAA7B;;AAIA,UAAIF,oBAAJ,EAA0B;AACxB,4BAAOG,eAAMC,aAAN,CAAoBJ,oBAApB,EAA0CP,UAA1C,CAAP;AACD,OAPH,CAQE;AACA;AACA;;;AACA,UACEA,UAAU,CAACT,KAAX,CAAiBE,IAAjB,KAA0B,EAA1B,IACAO,UAAU,CAACH,WAAX,CAAuBe,sCAFzB,EAGE;AACA,eAAO,IAAP;AACD;;AACD;;AACF,SAAK,UAAL;AACE;AACA;AACA,UACEZ,UAAU,CAACH,WAAX,CAAuBgB,6BAAvB,IACAb,UAAU,CAACT,KAAX,CAAiBkB,OAAjB,IAA4B,IAD5B,IAEAT,UAAU,CAACT,KAAX,CAAiBuB,QAAjB,CAA0BC,MAA1B,IAAoC,CAHtC,EAIE;AACA,4BAAOL,eAAMC,aAAN,CAAoBZ,qBAApB,EAA2C;AAChDR,UAAAA,KAAK,EAAEK,KAAK,CAACL;AADmC,SAA3C,CAAP;AAGD,OAXH,CAYE;AACA;AACA;;;AACA,UACES,UAAU,CAACH,WAAX,CAAuBe,sCAAvB,IACAZ,UAAU,CAACT,KAAX,CAAiBkB,OAAjB,IAA4B,IAD5B,IAEAT,UAAU,CAACT,KAAX,CAAiBuB,QAAjB,CAA0BE,KAA1B,CAAgC1B,YAAhC,CAHF,EAIE;AACA,eAAO,IAAP;AACD;;AACD;AA3CJ;;AA6CA,QAAM2B,QAAQ,GACZ1B,KAAK,CAACC,IAAN,KAAe,OAAf,IAA0BD,KAAK,CAACC,IAAN,KAAe,UAAzC,GACIR,2BADJ,GAEII,6BAHN;AAIA,SAAOkB,QAAQ,KAAK,IAAb,GACHW,QAAQ,CAACZ,cAAD,CADL,gBAEHK,eAAMC,aAAN,CAAoBL,QAApB,EAAqCD,cAArC,CAFJ;AAGD,CA9EqB,CAAtB;eAsFeX,a","sourcesContent":["import React, { memo, ReactElement } from 'react';\nimport { TDefaultRenderer, TNodeRendererProps } from './shared-types';\nimport { useSharedProps } from './context/SharedPropsProvider';\nimport {\n TText,\n TBlock,\n TNode,\n TPhrasing\n} from '@native-html/transient-render-engine';\nimport useAssembledCommonProps from './hooks/useAssembledCommonProps';\nimport { useTNodeChildrenRenderer } from './context/TChildrenRendererContext';\nimport renderTextualContent from './renderTextualContent';\nimport { useRendererRegistry } from './context/RenderRegistryProvider';\nimport renderBlockContent from './renderBlockContent';\nimport renderEmptyContent from './renderEmptyContent';\n\nexport type { TNodeRendererProps } from './shared-types';\n\nconst TDefaultBlockRenderer: TDefaultRenderer =\n renderBlockContent.bind(null);\n\nTDefaultBlockRenderer.displayName = 'TDefaultBlockRenderer';\n\nconst TDefaultPhrasingRenderer: TDefaultRenderer =\n renderTextualContent.bind(null);\n\nTDefaultPhrasingRenderer.displayName = 'TDefaultPhrasingRenderer';\n\nconst TDefaultTextRenderer: TDefaultRenderer =\n renderTextualContent.bind(null);\n\nTDefaultTextRenderer.displayName = 'TDefaultTextRenderer';\n\nfunction isGhostTNode(tnode: TNode) {\n return (\n (tnode.type === 'text' && (tnode.data === '' || tnode.data === ' ')) ||\n tnode.type === 'empty'\n );\n}\n\n/**\n * A component to render any {@link TNode}.\n */\nconst TNodeRenderer = memo(function MemoizedTNodeRenderer(\n props: TNodeRendererProps\n): ReactElement | null {\n const { tnode } = props;\n const sharedProps = useSharedProps();\n const renderRegistry = useRendererRegistry();\n const TNodeChildrenRenderer = useTNodeChildrenRenderer();\n const tnodeProps = {\n propsFromParent: { collapsedMarginTop: null },\n ...props,\n TNodeChildrenRenderer,\n sharedProps\n };\n const renderer =\n tnode.type === 'block' || tnode.type === 'document'\n ? TDefaultBlockRenderer\n : tnode.type === 'text'\n ? TDefaultTextRenderer\n : tnode.type === 'phrasing'\n ? TDefaultPhrasingRenderer\n : renderEmptyContent;\n\n const { assembledProps, Renderer } = useAssembledCommonProps(\n tnodeProps,\n renderer as any\n );\n switch (tnode.type) {\n case 'empty':\n return renderEmptyContent(assembledProps);\n case 'text':\n const InternalTextRenderer = renderRegistry.getInternalTextRenderer(\n props.tnode.tagName\n );\n\n if (InternalTextRenderer) {\n return React.createElement(InternalTextRenderer, tnodeProps);\n }\n // If ghost line prevention is enabled and the text data is empty, render\n // nothing to avoid React Native painting a 20px height line.\n // See also https://git.io/JErwX\n if (\n tnodeProps.tnode.data === '' &&\n tnodeProps.sharedProps.enableExperimentalGhostLinesPrevention\n ) {\n return null;\n }\n break;\n case 'phrasing':\n // When a TPhrasing node is anonymous and has only one child, its\n // rendering amounts to rendering its only child.\n if (\n tnodeProps.sharedProps.bypassAnonymousTPhrasingNodes &&\n tnodeProps.tnode.tagName == null &&\n tnodeProps.tnode.children.length <= 1\n ) {\n return React.createElement(TNodeChildrenRenderer, {\n tnode: props.tnode\n });\n }\n // If ghost line prevention is enabled and the tnode is an anonymous empty\n // phrasing node, render nothing to avoid React Native painting a 20px\n // height line. See also https://git.io/JErwX\n if (\n tnodeProps.sharedProps.enableExperimentalGhostLinesPrevention &&\n tnodeProps.tnode.tagName == null &&\n tnodeProps.tnode.children.every(isGhostTNode)\n ) {\n return null;\n }\n break;\n }\n const renderFn =\n tnode.type === 'block' || tnode.type === 'document'\n ? renderBlockContent\n : renderTextualContent;\n return Renderer === null\n ? renderFn(assembledProps)\n : React.createElement(Renderer as any, assembledProps);\n});\n\nexport {\n TDefaultBlockRenderer,\n TDefaultPhrasingRenderer,\n TDefaultTextRenderer\n};\n\nexport default TNodeRenderer;\n"]} +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js b/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js +index 3a700b6..89a4dd4 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js ++++ b/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js +@@ -5,89 +5,16 @@ Object.defineProperty(exports, "__esModule", { + }); + exports.useAmbientTRenderEngine = useAmbientTRenderEngine; + exports.default = TRenderEngineProvider; +-exports.defaultTRenderEngineProviderProps = exports.defaultFallbackFonts = exports.tRenderEngineProviderPropTypes = void 0; + + var _react = _interopRequireDefault(require("react")); + +-var _reactNative = require("react-native"); +- +-var _propTypes = _interopRequireDefault(require("prop-types")); +- + var _useTRenderEngine = _interopRequireDefault(require("./hooks/useTRenderEngine")); + +-var _defaultSystemFonts = _interopRequireDefault(require("./defaultSystemFonts")); +- + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + const defaultTRenderEngine = {}; + + const TRenderEngineContext = /*#__PURE__*/_react.default.createContext(defaultTRenderEngine); +- +-const tRenderEngineProviderPropTypes = { +- customHTMLElementModels: _propTypes.default.object.isRequired, +- enableCSSInlineProcessing: _propTypes.default.bool, +- enableUserAgentStyles: _propTypes.default.bool, +- idsStyles: _propTypes.default.object, +- ignoredDomTags: _propTypes.default.array, +- ignoreDomNode: _propTypes.default.func, +- domVisitors: _propTypes.default.object, +- ignoredStyles: _propTypes.default.array.isRequired, +- allowedStyles: _propTypes.default.array, +- htmlParserOptions: _propTypes.default.object, +- tagsStyles: _propTypes.default.object, +- classesStyles: _propTypes.default.object, +- emSize: _propTypes.default.number.isRequired, +- baseStyle: _propTypes.default.object, +- systemFonts: _propTypes.default.arrayOf(_propTypes.default.string), +- fallbackFonts: _propTypes.default.shape({ +- serif: _propTypes.default.string, +- 'sans-serif': _propTypes.default.string, +- monospace: _propTypes.default.string +- }), +- setMarkersForTNode: _propTypes.default.func, +- dangerouslyDisableHoisting: _propTypes.default.bool, +- dangerouslyDisableWhitespaceCollapsing: _propTypes.default.bool, +- selectDomRoot: _propTypes.default.func +-}; +-/** +- * Default fallback font for special keys such as 'sans-serif', 'monospace', +- * 'serif', based on current platform. +- */ +- +-exports.tRenderEngineProviderPropTypes = tRenderEngineProviderPropTypes; +-const defaultFallbackFonts = { +- 'sans-serif': _reactNative.Platform.select({ +- ios: 'system', +- default: 'sans-serif' +- }), +- monospace: _reactNative.Platform.select({ +- ios: 'Menlo', +- default: 'monospace' +- }), +- serif: _reactNative.Platform.select({ +- ios: 'Times New Roman', +- default: 'serif' +- }) +-}; +-exports.defaultFallbackFonts = defaultFallbackFonts; +-const defaultTRenderEngineProviderProps = { +- htmlParserOptions: { +- decodeEntities: true +- }, +- emSize: 14, +- ignoredDomTags: [], +- ignoredStyles: [], +- baseStyle: { +- fontSize: 14 +- }, +- tagsStyles: {}, +- classesStyles: {}, +- enableUserAgentStyles: true, +- enableCSSInlineProcessing: true, +- customHTMLElementModels: {}, +- fallbackFonts: defaultFallbackFonts, +- systemFonts: _defaultSystemFonts.default +-}; + /** + * Use the ambient transient render engine. + * +@@ -96,7 +23,6 @@ const defaultTRenderEngineProviderProps = { + * @public + */ + +-exports.defaultTRenderEngineProviderProps = defaultTRenderEngineProviderProps; + + function useAmbientTRenderEngine() { + const engine = _react.default.useContext(TRenderEngineContext); +@@ -126,15 +52,4 @@ function TRenderEngineProvider({ + value: engine + }, children); + } +-/** +- * @ignore +- */ +- +- +-TRenderEngineProvider.defaultProps = defaultTRenderEngineProviderProps; +-/** +- * @ignore +- */ +- +-TRenderEngineProvider.propTypes = tRenderEngineProviderPropTypes; + //# sourceMappingURL=TRenderEngineProvider.js.map +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js.map b/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js.map +index cf76758..c21b058 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js.map ++++ b/node_modules/react-native-render-html/lib/commonjs/TRenderEngineProvider.js.map +@@ -1 +1 @@ +-{"version":3,"sources":["TRenderEngineProvider.tsx"],"names":["defaultTRenderEngine","TRenderEngineContext","React","createContext","tRenderEngineProviderPropTypes","customHTMLElementModels","PropTypes","object","isRequired","enableCSSInlineProcessing","bool","enableUserAgentStyles","idsStyles","ignoredDomTags","array","ignoreDomNode","func","domVisitors","ignoredStyles","allowedStyles","htmlParserOptions","tagsStyles","classesStyles","emSize","number","baseStyle","systemFonts","arrayOf","string","fallbackFonts","shape","serif","monospace","setMarkersForTNode","dangerouslyDisableHoisting","dangerouslyDisableWhitespaceCollapsing","selectDomRoot","defaultFallbackFonts","Platform","select","ios","default","defaultTRenderEngineProviderProps","decodeEntities","fontSize","defaultSystemFonts","useAmbientTRenderEngine","engine","useContext","__DEV__","console","error","TRenderEngineProvider","children","config","defaultProps","propTypes"],"mappings":";;;;;;;;;AACA;;AACA;;AACA;;AACA;;AAEA;;;;AAEA,MAAMA,oBAAoB,GAAG,EAA7B;;AAEA,MAAMC,oBAAoB,gBACxBC,eAAMC,aAAN,CAAmCH,oBAAnC,CADF;;AAGO,MAAMI,8BAGZ,GAAG;AACFC,EAAAA,uBAAuB,EAAEC,mBAAUC,MAAV,CAAiBC,UADxC;AAEFC,EAAAA,yBAAyB,EAAEH,mBAAUI,IAFnC;AAGFC,EAAAA,qBAAqB,EAAEL,mBAAUI,IAH/B;AAIFE,EAAAA,SAAS,EAAEN,mBAAUC,MAJnB;AAKFM,EAAAA,cAAc,EAAEP,mBAAUQ,KALxB;AAMFC,EAAAA,aAAa,EAAET,mBAAUU,IANvB;AAOFC,EAAAA,WAAW,EAAEX,mBAAUC,MAPrB;AAQFW,EAAAA,aAAa,EAAEZ,mBAAUQ,KAAV,CAAgBN,UAR7B;AASFW,EAAAA,aAAa,EAAEb,mBAAUQ,KATvB;AAUFM,EAAAA,iBAAiB,EAAEd,mBAAUC,MAV3B;AAWFc,EAAAA,UAAU,EAAEf,mBAAUC,MAXpB;AAYFe,EAAAA,aAAa,EAAEhB,mBAAUC,MAZvB;AAaFgB,EAAAA,MAAM,EAAEjB,mBAAUkB,MAAV,CAAiBhB,UAbvB;AAcFiB,EAAAA,SAAS,EAAEnB,mBAAUC,MAdnB;AAeFmB,EAAAA,WAAW,EAAEpB,mBAAUqB,OAAV,CAAkBrB,mBAAUsB,MAA5B,CAfX;AAgBFC,EAAAA,aAAa,EAAEvB,mBAAUwB,KAAV,CAAgB;AAC7BC,IAAAA,KAAK,EAAEzB,mBAAUsB,MADY;AAE7B,kBAActB,mBAAUsB,MAFK;AAG7BI,IAAAA,SAAS,EAAE1B,mBAAUsB;AAHQ,GAAhB,CAhBb;AAqBFK,EAAAA,kBAAkB,EAAE3B,mBAAUU,IArB5B;AAsBFkB,EAAAA,0BAA0B,EAAE5B,mBAAUI,IAtBpC;AAuBFyB,EAAAA,sCAAsC,EAAE7B,mBAAUI,IAvBhD;AAwBF0B,EAAAA,aAAa,EAAE9B,mBAAUU;AAxBvB,CAHG;AA8BP;AACA;AACA;AACA;;;AACO,MAAMqB,oBAAoB,GAAG;AAClC,gBAAcC,sBAASC,MAAT,CAAgB;AAAEC,IAAAA,GAAG,EAAE,QAAP;AAAiBC,IAAAA,OAAO,EAAE;AAA1B,GAAhB,CADoB;AAElCT,EAAAA,SAAS,EAAEM,sBAASC,MAAT,CAAgB;AAAEC,IAAAA,GAAG,EAAE,OAAP;AAAgBC,IAAAA,OAAO,EAAE;AAAzB,GAAhB,CAFuB;AAGlCV,EAAAA,KAAK,EAAEO,sBAASC,MAAT,CAAgB;AAAEC,IAAAA,GAAG,EAAE,iBAAP;AAA0BC,IAAAA,OAAO,EAAE;AAAnC,GAAhB;AAH2B,CAA7B;;AAMA,MAAMC,iCAAsD,GAAG;AACpEtB,EAAAA,iBAAiB,EAAE;AACjBuB,IAAAA,cAAc,EAAE;AADC,GADiD;AAIpEpB,EAAAA,MAAM,EAAE,EAJ4D;AAKpEV,EAAAA,cAAc,EAAE,EALoD;AAMpEK,EAAAA,aAAa,EAAE,EANqD;AAOpEO,EAAAA,SAAS,EAAE;AAAEmB,IAAAA,QAAQ,EAAE;AAAZ,GAPyD;AAQpEvB,EAAAA,UAAU,EAAE,EARwD;AASpEC,EAAAA,aAAa,EAAE,EATqD;AAUpEX,EAAAA,qBAAqB,EAAE,IAV6C;AAWpEF,EAAAA,yBAAyB,EAAE,IAXyC;AAYpEJ,EAAAA,uBAAuB,EAAE,EAZ2C;AAapEwB,EAAAA,aAAa,EAAEQ,oBAbqD;AAcpEX,EAAAA,WAAW,EAAEmB;AAduD,CAA/D;AAiBP;AACA;AACA;AACA;AACA;AACA;AACA;;;;AACO,SAASC,uBAAT,GAAmC;AACxC,QAAMC,MAAM,GAAG7C,eAAM8C,UAAN,CAAiB/C,oBAAjB,CAAf;;AACA,MACE,OAAOgD,OAAP,KAAmB,SAAnB,IACAA,OADA,IAEAF,MAAM,KAAK/C,oBAHb,EAIE;AACAkD,IAAAA,OAAO,CAACC,KAAR,CAAc,sDAAd;AACD;;AACD,SAAOJ,MAAP;AACD;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AACe,SAASK,qBAAT,CAA+B;AAC5CC,EAAAA,QAD4C;AAE5C,KAAGC;AAFyC,CAA/B,EAG0C;AACvD,QAAMP,MAAM,GAAG,+BAAiBO,MAAjB,CAAf;AACA,sBACE,6BAAC,oBAAD,CAAsB,QAAtB;AAA+B,IAAA,KAAK,EAAEP;AAAtC,KACGM,QADH,CADF;AAKD;AAED;AACA;AACA;;;AACAD,qBAAqB,CAACG,YAAtB,GAAqCb,iCAArC;AAEA;AACA;AACA;;AACAU,qBAAqB,CAACI,SAAtB,GAAkCpD,8BAAlC","sourcesContent":["import TRenderEngine from '@native-html/transient-render-engine';\nimport React, { PropsWithChildren, ReactElement } from 'react';\nimport { Platform } from 'react-native';\nimport PropTypes from 'prop-types';\nimport useTRenderEngine from './hooks/useTRenderEngine';\nimport { TRenderEngineConfig } from './shared-types';\nimport defaultSystemFonts from './defaultSystemFonts';\n\nconst defaultTRenderEngine = {} as any;\n\nconst TRenderEngineContext =\n React.createContext(defaultTRenderEngine);\n\nexport const tRenderEngineProviderPropTypes: Record<\n keyof TRenderEngineConfig,\n any\n> = {\n customHTMLElementModels: PropTypes.object.isRequired,\n enableCSSInlineProcessing: PropTypes.bool,\n enableUserAgentStyles: PropTypes.bool,\n idsStyles: PropTypes.object,\n ignoredDomTags: PropTypes.array,\n ignoreDomNode: PropTypes.func,\n domVisitors: PropTypes.object,\n ignoredStyles: PropTypes.array.isRequired,\n allowedStyles: PropTypes.array,\n htmlParserOptions: PropTypes.object,\n tagsStyles: PropTypes.object,\n classesStyles: PropTypes.object,\n emSize: PropTypes.number.isRequired,\n baseStyle: PropTypes.object,\n systemFonts: PropTypes.arrayOf(PropTypes.string),\n fallbackFonts: PropTypes.shape({\n serif: PropTypes.string,\n 'sans-serif': PropTypes.string,\n monospace: PropTypes.string\n }),\n setMarkersForTNode: PropTypes.func,\n dangerouslyDisableHoisting: PropTypes.bool,\n dangerouslyDisableWhitespaceCollapsing: PropTypes.bool,\n selectDomRoot: PropTypes.func\n};\n\n/**\n * Default fallback font for special keys such as 'sans-serif', 'monospace',\n * 'serif', based on current platform.\n */\nexport const defaultFallbackFonts = {\n 'sans-serif': Platform.select({ ios: 'system', default: 'sans-serif' }),\n monospace: Platform.select({ ios: 'Menlo', default: 'monospace' }),\n serif: Platform.select({ ios: 'Times New Roman', default: 'serif' })\n};\n\nexport const defaultTRenderEngineProviderProps: TRenderEngineConfig = {\n htmlParserOptions: {\n decodeEntities: true\n },\n emSize: 14,\n ignoredDomTags: [],\n ignoredStyles: [],\n baseStyle: { fontSize: 14 },\n tagsStyles: {},\n classesStyles: {},\n enableUserAgentStyles: true,\n enableCSSInlineProcessing: true,\n customHTMLElementModels: {},\n fallbackFonts: defaultFallbackFonts,\n systemFonts: defaultSystemFonts\n};\n\n/**\n * Use the ambient transient render engine.\n *\n * @returns The ambient transient render engine.\n *\n * @public\n */\nexport function useAmbientTRenderEngine() {\n const engine = React.useContext(TRenderEngineContext);\n if (\n typeof __DEV__ === 'boolean' &&\n __DEV__ &&\n engine === defaultTRenderEngine\n ) {\n console.error('TRenderEngineProvider is missing in the render tree.');\n }\n return engine;\n}\n\n/**\n * A react component to share a {@link TRenderEngine} instance across different\n * rendered contents via {@link RenderHTMLSource}. This can significantly enhance\n * performance in applications with potentially dozens or hundreds of distinct\n * rendered snippets such as chat apps.\n *\n * @param props - Pass engine config here.\n */\nexport default function TRenderEngineProvider({\n children,\n ...config\n}: PropsWithChildren): ReactElement {\n const engine = useTRenderEngine(config);\n return (\n \n {children}\n \n );\n}\n\n/**\n * @ignore\n */\nTRenderEngineProvider.defaultProps = defaultTRenderEngineProviderProps;\n\n/**\n * @ignore\n */\nTRenderEngineProvider.propTypes = tRenderEngineProviderPropTypes;\n"]} +\ No newline at end of file ++{"version":3,"sources":["TRenderEngineProvider.tsx"],"names":["defaultTRenderEngine","TRenderEngineContext","React","createContext","useAmbientTRenderEngine","engine","useContext","__DEV__","console","error","TRenderEngineProvider","children","config"],"mappings":";;;;;;;;AACA;;AACA;;;;AAGA,MAAMA,oBAAoB,GAAG,EAA7B;;AAEA,MAAMC,oBAAoB,gBACxBC,eAAMC,aAAN,CAAmCH,oBAAnC,CADF;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;AACO,SAASI,uBAAT,GAAmC;AACxC,QAAMC,MAAM,GAAGH,eAAMI,UAAN,CAAiBL,oBAAjB,CAAf;;AACA,MACE,OAAOM,OAAP,KAAmB,SAAnB,IACAA,OADA,IAEAF,MAAM,KAAKL,oBAHb,EAIE;AACAQ,IAAAA,OAAO,CAACC,KAAR,CAAc,sDAAd;AACD;;AACD,SAAOJ,MAAP;AACD;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AACe,SAASK,qBAAT,CAA+B;AAC5CC,EAAAA,QAD4C;AAE5C,KAAGC;AAFyC,CAA/B,EAG0C;AACvD,QAAMP,MAAM,GAAG,+BAAiBO,MAAjB,CAAf;AACA,sBACE,6BAAC,oBAAD,CAAsB,QAAtB;AAA+B,IAAA,KAAK,EAAEP;AAAtC,KACGM,QADH,CADF;AAKD","sourcesContent":["import TRenderEngine from '@native-html/transient-render-engine';\nimport React, { PropsWithChildren, ReactElement } from 'react';\nimport useTRenderEngine from './hooks/useTRenderEngine';\nimport { TRenderEngineConfig } from './shared-types';\n\nconst defaultTRenderEngine = {} as any;\n\nconst TRenderEngineContext =\n React.createContext(defaultTRenderEngine);\n\n/**\n * Use the ambient transient render engine.\n *\n * @returns The ambient transient render engine.\n *\n * @public\n */\nexport function useAmbientTRenderEngine() {\n const engine = React.useContext(TRenderEngineContext);\n if (\n typeof __DEV__ === 'boolean' &&\n __DEV__ &&\n engine === defaultTRenderEngine\n ) {\n console.error('TRenderEngineProvider is missing in the render tree.');\n }\n return engine;\n}\n\n/**\n * A react component to share a {@link TRenderEngine} instance across different\n * rendered contents via {@link RenderHTMLSource}. This can significantly enhance\n * performance in applications with potentially dozens or hundreds of distinct\n * rendered snippets such as chat apps.\n *\n * @param props - Pass engine config here.\n */\nexport default function TRenderEngineProvider({\n children,\n ...config\n}: PropsWithChildren): ReactElement {\n const engine = useTRenderEngine(config);\n return (\n \n {children}\n \n );\n}\n"]} +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js b/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js +index 1be151a..3a076d4 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js ++++ b/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js +@@ -7,8 +7,6 @@ exports.default = void 0; + + var _react = _interopRequireDefault(require("react")); + +-var _propTypes = _interopRequireDefault(require("prop-types")); +- + var _useIMGElementState = _interopRequireDefault(require("./useIMGElementState")); + + var _IMGElementContentSuccess = _interopRequireDefault(require("./IMGElementContentSuccess")); +@@ -19,15 +17,10 @@ var _IMGElementContentLoading = _interopRequireDefault(require("./IMGElementCont + + var _IMGElementContentError = _interopRequireDefault(require("./IMGElementContentError")); + +-var _defaultInitialImageDimensions = _interopRequireDefault(require("./defaultInitialImageDimensions")); +- + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +-function identity(arg) { +- return arg; +-} + /** + * A component to render images based on an internal loading state. + * +@@ -37,8 +30,6 @@ function identity(arg) { + * {@link IMGElementContentSuccess}, {@link IMGElementContentLoading} + * and {@link IMGElementContentError} for customization. + */ +- +- + function IMGElement(props) { + const state = (0, _useIMGElementState.default)(props); + let content; +@@ -59,43 +50,6 @@ function IMGElement(props) { + }), content); + } + +-const imgDimensionsType = _propTypes.default.shape({ +- width: _propTypes.default.number, +- height: _propTypes.default.number +-}); +- +-const propTypes = { +- source: _propTypes.default.object.isRequired, +- alt: _propTypes.default.string, +- altColor: _propTypes.default.string, +- height: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), +- width: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), +- style: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.array]), +- computeMaxWidth: _propTypes.default.func.isRequired, +- contentWidth: _propTypes.default.number, +- enableExperimentalPercentWidth: _propTypes.default.bool, +- initialDimensions: imgDimensionsType, +- onPress: _propTypes.default.func, +- testID: _propTypes.default.string, +- objectFit: _propTypes.default.string, +- cachedNaturalDimensions: imgDimensionsType, +- containerProps: _propTypes.default.object +-}; +-/** +- * @ignore +- */ +- +-IMGElement.propTypes = propTypes; +-/** +- * @ignore +- */ +- +-IMGElement.defaultProps = { +- enableExperimentalPercentWidth: false, +- computeMaxWidth: identity, +- imagesInitialDimensions: _defaultInitialImageDimensions.default, +- style: {} +-}; + var _default = IMGElement; + exports.default = _default; + //# sourceMappingURL=IMGElement.js.map +\ No newline at end of file +diff --git a/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js.map b/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js.map +index 1e265b3..24ec0d0 100644 +--- a/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js.map ++++ b/node_modules/react-native-render-html/lib/commonjs/elements/IMGElement.js.map +@@ -1 +1 @@ +-{"version":3,"sources":["IMGElement.tsx"],"names":["identity","arg","IMGElement","props","state","content","type","React","createElement","IMGElementContentSuccess","IMGElementContentLoading","IMGElementContentError","testID","containerProps","onPress","containerStyle","imgDimensionsType","PropTypes","shape","width","number","height","propTypes","source","object","isRequired","alt","string","altColor","oneOfType","style","array","computeMaxWidth","func","contentWidth","enableExperimentalPercentWidth","bool","initialDimensions","objectFit","cachedNaturalDimensions","defaultProps","imagesInitialDimensions","defaultImageInitialDimensions"],"mappings":";;;;;;;AAAA;;AACA;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEA;;;;;;AAIA,SAASA,QAAT,CAAkBC,GAAlB,EAA4B;AAC1B,SAAOA,GAAP;AACD;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AACA,SAASC,UAAT,CAAoBC,KAApB,EAA0D;AACxD,QAAMC,KAAK,GAAG,iCAAmBD,KAAnB,CAAd;AACA,MAAIE,OAAJ;;AACA,MAAID,KAAK,CAACE,IAAN,KAAe,SAAnB,EAA8B;AAC5BD,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBC,iCAApB,EAA8CL,KAA9C,CAAV;AACD,GAFD,MAEO,IAAIA,KAAK,CAACE,IAAN,KAAe,SAAnB,EAA8B;AACnCD,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBE,iCAApB,EAA8CN,KAA9C,CAAV;AACD,GAFM,MAEA;AACLC,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBG,+BAApB,EAA4CP,KAA5C,CAAV;AACD;;AACD,sBACE,6BAAC,4BAAD;AACE,IAAA,MAAM,EAAED,KAAK,CAACS;AADhB,KAEMT,KAAK,CAACU,cAFZ;AAGE,IAAA,OAAO,EAAEV,KAAK,CAACW,OAHjB;AAIE,IAAA,KAAK,EAAEV,KAAK,CAACW;AAJf,MAKGV,OALH,CADF;AASD;;AAED,MAAMW,iBAAiB,GAAGC,mBAAUC,KAAV,CAAgB;AACxCC,EAAAA,KAAK,EAAEF,mBAAUG,MADuB;AAExCC,EAAAA,MAAM,EAAEJ,mBAAUG;AAFsB,CAAhB,CAA1B;;AAKA,MAAME,SAA6C,GAAG;AACpDC,EAAAA,MAAM,EAAEN,mBAAUO,MAAV,CAAiBC,UAD2B;AAEpDC,EAAAA,GAAG,EAAET,mBAAUU,MAFqC;AAGpDC,EAAAA,QAAQ,EAAEX,mBAAUU,MAHgC;AAIpDN,EAAAA,MAAM,EAAEJ,mBAAUY,SAAV,CAAoB,CAACZ,mBAAUU,MAAX,EAAmBV,mBAAUG,MAA7B,CAApB,CAJ4C;AAKpDD,EAAAA,KAAK,EAAEF,mBAAUY,SAAV,CAAoB,CAACZ,mBAAUU,MAAX,EAAmBV,mBAAUG,MAA7B,CAApB,CAL6C;AAMpDU,EAAAA,KAAK,EAAEb,mBAAUY,SAAV,CAAoB,CAACZ,mBAAUO,MAAX,EAAmBP,mBAAUc,KAA7B,CAApB,CAN6C;AAOpDC,EAAAA,eAAe,EAAEf,mBAAUgB,IAAV,CAAeR,UAPoB;AAQpDS,EAAAA,YAAY,EAAEjB,mBAAUG,MAR4B;AASpDe,EAAAA,8BAA8B,EAAElB,mBAAUmB,IATU;AAUpDC,EAAAA,iBAAiB,EAAErB,iBAViC;AAWpDF,EAAAA,OAAO,EAAEG,mBAAUgB,IAXiC;AAYpDrB,EAAAA,MAAM,EAAEK,mBAAUU,MAZkC;AAapDW,EAAAA,SAAS,EAAErB,mBAAUU,MAb+B;AAcpDY,EAAAA,uBAAuB,EAAEvB,iBAd2B;AAepDH,EAAAA,cAAc,EAAEI,mBAAUO;AAf0B,CAAtD;AAkBA;AACA;AACA;;AACAtB,UAAU,CAACoB,SAAX,GAAuBA,SAAvB;AAEA;AACA;AACA;;AACApB,UAAU,CAACsC,YAAX,GAA0B;AACxBL,EAAAA,8BAA8B,EAAE,KADR;AAExBH,EAAAA,eAAe,EAAEhC,QAFO;AAGxByC,EAAAA,uBAAuB,EAAEC,sCAHD;AAIxBZ,EAAAA,KAAK,EAAE;AAJiB,CAA1B;eAOe5B,U","sourcesContent":["import React, { ReactElement, ReactNode } from 'react';\nimport PropTypes from 'prop-types';\nimport useIMGElementState from './useIMGElementState';\nimport IMGElementContentSuccess from './IMGElementContentSuccess';\nimport IMGElementContainer from './IMGElementContainer';\nimport IMGElementContentLoading from './IMGElementContentLoading';\nimport IMGElementContentError from './IMGElementContentError';\nimport type { IMGElementProps } from './img-types';\nimport defaultImageInitialDimensions from './defaultInitialImageDimensions';\n\nexport type { IMGElementProps } from './img-types';\n\nfunction identity(arg: any) {\n return arg;\n}\n\n/**\n * A component to render images based on an internal loading state.\n *\n * @remarks This component will attempt to draw a box of paint dimensions\n * before retrieving the physical dimensions of the image to avoid layout\n * shifts. See also {@link useIMGElementState}, {@link IMGElementContainer},\n * {@link IMGElementContentSuccess}, {@link IMGElementContentLoading}\n * and {@link IMGElementContentError} for customization.\n */\nfunction IMGElement(props: IMGElementProps): ReactElement {\n const state = useIMGElementState(props);\n let content: ReactNode;\n if (state.type === 'success') {\n content = React.createElement(IMGElementContentSuccess, state);\n } else if (state.type === 'loading') {\n content = React.createElement(IMGElementContentLoading, state);\n } else {\n content = React.createElement(IMGElementContentError, state);\n }\n return (\n \n {content}\n \n );\n}\n\nconst imgDimensionsType = PropTypes.shape({\n width: PropTypes.number,\n height: PropTypes.number\n});\n\nconst propTypes: Record = {\n source: PropTypes.object.isRequired,\n alt: PropTypes.string,\n altColor: PropTypes.string,\n height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),\n computeMaxWidth: PropTypes.func.isRequired,\n contentWidth: PropTypes.number,\n enableExperimentalPercentWidth: PropTypes.bool,\n initialDimensions: imgDimensionsType,\n onPress: PropTypes.func,\n testID: PropTypes.string,\n objectFit: PropTypes.string,\n cachedNaturalDimensions: imgDimensionsType,\n containerProps: PropTypes.object\n};\n\n/**\n * @ignore\n */\nIMGElement.propTypes = propTypes;\n\n/**\n * @ignore\n */\nIMGElement.defaultProps = {\n enableExperimentalPercentWidth: false,\n computeMaxWidth: identity,\n imagesInitialDimensions: defaultImageInitialDimensions,\n style: {}\n};\n\nexport default IMGElement;\n"]} +\ No newline at end of file ++{"version":3,"sources":["IMGElement.tsx"],"names":["IMGElement","props","state","content","type","React","createElement","IMGElementContentSuccess","IMGElementContentLoading","IMGElementContentError","testID","containerProps","onPress","containerStyle"],"mappings":";;;;;;;AAAA;;AACA;;AACA;;AACA;;AACA;;AACA;;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,UAAT,CAAoBC,KAApB,EAA0D;AACxD,QAAMC,KAAK,GAAG,iCAAmBD,KAAnB,CAAd;AACA,MAAIE,OAAJ;;AACA,MAAID,KAAK,CAACE,IAAN,KAAe,SAAnB,EAA8B;AAC5BD,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBC,iCAApB,EAA8CL,KAA9C,CAAV;AACD,GAFD,MAEO,IAAIA,KAAK,CAACE,IAAN,KAAe,SAAnB,EAA8B;AACnCD,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBE,iCAApB,EAA8CN,KAA9C,CAAV;AACD,GAFM,MAEA;AACLC,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBG,+BAApB,EAA4CP,KAA5C,CAAV;AACD;;AACD,sBACE,6BAAC,4BAAD;AACE,IAAA,MAAM,EAAED,KAAK,CAACS;AADhB,KAEMT,KAAK,CAACU,cAFZ;AAGE,IAAA,OAAO,EAAEV,KAAK,CAACW,OAHjB;AAIE,IAAA,KAAK,EAAEV,KAAK,CAACW;AAJf,MAKGV,OALH,CADF;AASD;;eAEcH,U","sourcesContent":["import React, { ReactElement, ReactNode } from 'react';\nimport useIMGElementState from './useIMGElementState';\nimport IMGElementContentSuccess from './IMGElementContentSuccess';\nimport IMGElementContainer from './IMGElementContainer';\nimport IMGElementContentLoading from './IMGElementContentLoading';\nimport IMGElementContentError from './IMGElementContentError';\nimport type { IMGElementProps } from './img-types';\n\nexport type { IMGElementProps } from './img-types';\n\n/**\n * A component to render images based on an internal loading state.\n *\n * @remarks This component will attempt to draw a box of paint dimensions\n * before retrieving the physical dimensions of the image to avoid layout\n * shifts. See also {@link useIMGElementState}, {@link IMGElementContainer},\n * {@link IMGElementContentSuccess}, {@link IMGElementContentLoading}\n * and {@link IMGElementContentError} for customization.\n */\nfunction IMGElement(props: IMGElementProps): ReactElement {\n const state = useIMGElementState(props);\n let content: ReactNode;\n if (state.type === 'success') {\n content = React.createElement(IMGElementContentSuccess, state);\n } else if (state.type === 'loading') {\n content = React.createElement(IMGElementContentLoading, state);\n } else {\n content = React.createElement(IMGElementContentError, state);\n }\n return (\n \n {content}\n \n );\n}\n\nexport default IMGElement;\n"]} +\ No newline at end of file diff --git a/node_modules/react-native-render-html/react-native-render-html.podspec b/node_modules/react-native-render-html/react-native-render-html.podspec deleted file mode 100644 index 6ac80d8..0000000 @@ -23,3 +359,407 @@ index 6ac80d8..0000000 - - s.dependency 'React-Core' -end +diff --git a/node_modules/react-native-render-html/src/RenderHTMLConfigProvider.tsx b/node_modules/react-native-render-html/src/RenderHTMLConfigProvider.tsx +index 0df5375..925062a 100644 +--- a/node_modules/react-native-render-html/src/RenderHTMLConfigProvider.tsx ++++ b/node_modules/react-native-render-html/src/RenderHTMLConfigProvider.tsx +@@ -1,5 +1,4 @@ + import React, { PropsWithChildren, ReactElement, useMemo } from 'react'; +-import PropTypes from 'prop-types'; + import RenderersPropsProvider from './context/RenderersPropsProvider'; + import SharedPropsProvider from './context/SharedPropsProvider'; + import TChildrenRenderersContext from './context/TChildrenRendererContext'; +@@ -20,29 +19,6 @@ const childrenRendererContext = { + TNodeChildrenRenderer + }; + +-export type RenderHTMLConfigPropTypes = Record; +- +-export const renderHTMLConfigPropTypes: RenderHTMLConfigPropTypes = { +- bypassAnonymousTPhrasingNodes: PropTypes.bool, +- defaultTextProps: PropTypes.object, +- defaultViewProps: PropTypes.object, +- enableExperimentalBRCollapsing: PropTypes.bool, +- enableExperimentalGhostLinesPrevention: PropTypes.bool, +- enableExperimentalMarginCollapsing: PropTypes.bool, +- remoteErrorView: PropTypes.func, +- remoteLoadingView: PropTypes.func, +- debug: PropTypes.bool, +- computeEmbeddedMaxWidth: PropTypes.func, +- renderersProps: PropTypes.object, +- WebView: PropTypes.any, +- GenericPressable: PropTypes.any, +- defaultWebViewProps: PropTypes.object, +- pressableHightlightColor: PropTypes.string, +- customListStyleSpecs: PropTypes.object, +- renderers: PropTypes.object, +- provideEmbeddedHeaders: PropTypes.func +-}; +- + /** + * A component to provide configuration for {@link RenderHTMLSource} + * descendants, to be used in conjunction with {@link TRenderEngineProvider}. +@@ -85,8 +61,3 @@ export default function RenderHTMLConfigProvider( + + ); + } +- +-/** +- * @ignore +- */ +-RenderHTMLConfigProvider.propTypes = renderHTMLConfigPropTypes; +diff --git a/node_modules/react-native-render-html/src/RenderHTMLSource.tsx b/node_modules/react-native-render-html/src/RenderHTMLSource.tsx +index c91da52..fd0e052 100644 +--- a/node_modules/react-native-render-html/src/RenderHTMLSource.tsx ++++ b/node_modules/react-native-render-html/src/RenderHTMLSource.tsx +@@ -1,7 +1,6 @@ + import equals from 'ramda/src/equals'; + import React, { memo, ReactElement, useMemo } from 'react'; + import { Dimensions } from 'react-native'; +-import PropTypes from 'prop-types'; + import ttreeEventsContext from './context/ttreeEventsContext'; + import isUriSource from './helpers/isUriSource'; + import { SourceLoaderProps, TTreeEvents } from './internal-types'; +@@ -25,29 +24,6 @@ export type RenderHTMLSourcePropTypes = Record< + any + >; + +-export const renderSourcePropTypes: RenderHTMLSourcePropTypes = { +- source: PropTypes.oneOfType([ +- PropTypes.shape({ +- html: PropTypes.string.isRequired, +- baseUrl: PropTypes.string +- }), +- PropTypes.shape({ +- dom: PropTypes.object.isRequired, +- baseUrl: PropTypes.string +- }), +- PropTypes.shape({ +- uri: PropTypes.string.isRequired, +- method: PropTypes.string, +- body: PropTypes.any, +- headers: PropTypes.object +- }) +- ]), +- onTTreeChange: PropTypes.func, +- onHTMLLoaded: PropTypes.func, +- onDocumentMetadataLoaded: PropTypes.func, +- contentWidth: PropTypes.number +-}; +- + function isEmptySource(source: undefined | HTMLSource) { + return ( + !source || +@@ -136,9 +112,4 @@ const RenderHTMLSource = memo( + } + ); + +-/** +- * @ignore +- */ +-(RenderHTMLSource as any).propTypes = renderSourcePropTypes; +- + export default RenderHTMLSource; +diff --git a/node_modules/react-native-render-html/src/TChildrenRenderer.tsx b/node_modules/react-native-render-html/src/TChildrenRenderer.tsx +index 618a592..e12888e 100644 +--- a/node_modules/react-native-render-html/src/TChildrenRenderer.tsx ++++ b/node_modules/react-native-render-html/src/TChildrenRenderer.tsx +@@ -9,16 +9,4 @@ import renderChildren from './renderChildren'; + const TChildrenRenderer: FunctionComponent = + renderChildren.bind(null); + +-export const tchildrenRendererDefaultProps: Pick< +- TChildrenRendererProps, +- 'propsForChildren' +-> = { +- propsForChildren: {} +-}; +- +-/** +- * @ignore +- */ +-TChildrenRenderer.defaultProps = tchildrenRendererDefaultProps; +- + export default TChildrenRenderer; +diff --git a/node_modules/react-native-render-html/src/TNodeChildrenRenderer.tsx b/node_modules/react-native-render-html/src/TNodeChildrenRenderer.tsx +index bf5aef6..b820de0 100644 +--- a/node_modules/react-native-render-html/src/TNodeChildrenRenderer.tsx ++++ b/node_modules/react-native-render-html/src/TNodeChildrenRenderer.tsx +@@ -1,7 +1,6 @@ + import { ReactElement } from 'react'; + import { TNode } from '@native-html/transient-render-engine'; + import { useSharedProps } from './context/SharedPropsProvider'; +-import { tchildrenRendererDefaultProps } from './TChildrenRenderer'; + import { + TChildrenRendererProps, + TNodeChildrenRendererProps +@@ -73,9 +72,4 @@ function TNodeChildrenRenderer( + return renderChildren(useTNodeChildrenProps(props)); + } + +-/** +- * @ignore +- */ +-TNodeChildrenRenderer.defaultProps = tchildrenRendererDefaultProps; +- + export default TNodeChildrenRenderer; +diff --git a/node_modules/react-native-render-html/src/TNodeRenderer.tsx b/node_modules/react-native-render-html/src/TNodeRenderer.tsx +index d32140f..0804ba7 100644 +--- a/node_modules/react-native-render-html/src/TNodeRenderer.tsx ++++ b/node_modules/react-native-render-html/src/TNodeRenderer.tsx +@@ -49,6 +49,7 @@ const TNodeRenderer = memo(function MemoizedTNodeRenderer( + const renderRegistry = useRendererRegistry(); + const TNodeChildrenRenderer = useTNodeChildrenRenderer(); + const tnodeProps = { ++ propsFromParent: { collapsedMarginTop: null }, + ...props, + TNodeChildrenRenderer, + sharedProps +@@ -120,16 +121,6 @@ const TNodeRenderer = memo(function MemoizedTNodeRenderer( + : React.createElement(Renderer as any, assembledProps); + }); + +-const defaultProps: Required, 'propsFromParent'>> = +- { +- propsFromParent: { +- collapsedMarginTop: null +- } +- }; +- +-// @ts-expect-error default props must be defined +-TNodeRenderer.defaultProps = defaultProps; +- + export { + TDefaultBlockRenderer, + TDefaultPhrasingRenderer, +diff --git a/node_modules/react-native-render-html/src/TRenderEngineProvider.tsx b/node_modules/react-native-render-html/src/TRenderEngineProvider.tsx +index 95b60df..96604c8 100644 +--- a/node_modules/react-native-render-html/src/TRenderEngineProvider.tsx ++++ b/node_modules/react-native-render-html/src/TRenderEngineProvider.tsx +@@ -1,73 +1,13 @@ + import TRenderEngine from '@native-html/transient-render-engine'; + import React, { PropsWithChildren, ReactElement } from 'react'; +-import { Platform } from 'react-native'; +-import PropTypes from 'prop-types'; + import useTRenderEngine from './hooks/useTRenderEngine'; + import { TRenderEngineConfig } from './shared-types'; +-import defaultSystemFonts from './defaultSystemFonts'; + + const defaultTRenderEngine = {} as any; + + const TRenderEngineContext = + React.createContext(defaultTRenderEngine); + +-export const tRenderEngineProviderPropTypes: Record< +- keyof TRenderEngineConfig, +- any +-> = { +- customHTMLElementModels: PropTypes.object.isRequired, +- enableCSSInlineProcessing: PropTypes.bool, +- enableUserAgentStyles: PropTypes.bool, +- idsStyles: PropTypes.object, +- ignoredDomTags: PropTypes.array, +- ignoreDomNode: PropTypes.func, +- domVisitors: PropTypes.object, +- ignoredStyles: PropTypes.array.isRequired, +- allowedStyles: PropTypes.array, +- htmlParserOptions: PropTypes.object, +- tagsStyles: PropTypes.object, +- classesStyles: PropTypes.object, +- emSize: PropTypes.number.isRequired, +- baseStyle: PropTypes.object, +- systemFonts: PropTypes.arrayOf(PropTypes.string), +- fallbackFonts: PropTypes.shape({ +- serif: PropTypes.string, +- 'sans-serif': PropTypes.string, +- monospace: PropTypes.string +- }), +- setMarkersForTNode: PropTypes.func, +- dangerouslyDisableHoisting: PropTypes.bool, +- dangerouslyDisableWhitespaceCollapsing: PropTypes.bool, +- selectDomRoot: PropTypes.func +-}; +- +-/** +- * Default fallback font for special keys such as 'sans-serif', 'monospace', +- * 'serif', based on current platform. +- */ +-export const defaultFallbackFonts = { +- 'sans-serif': Platform.select({ ios: 'system', default: 'sans-serif' }), +- monospace: Platform.select({ ios: 'Menlo', default: 'monospace' }), +- serif: Platform.select({ ios: 'Times New Roman', default: 'serif' }) +-}; +- +-export const defaultTRenderEngineProviderProps: TRenderEngineConfig = { +- htmlParserOptions: { +- decodeEntities: true +- }, +- emSize: 14, +- ignoredDomTags: [], +- ignoredStyles: [], +- baseStyle: { fontSize: 14 }, +- tagsStyles: {}, +- classesStyles: {}, +- enableUserAgentStyles: true, +- enableCSSInlineProcessing: true, +- customHTMLElementModels: {}, +- fallbackFonts: defaultFallbackFonts, +- systemFonts: defaultSystemFonts +-}; +- + /** + * Use the ambient transient render engine. + * +@@ -106,13 +46,3 @@ export default function TRenderEngineProvider({ + + ); + } +- +-/** +- * @ignore +- */ +-TRenderEngineProvider.defaultProps = defaultTRenderEngineProviderProps; +- +-/** +- * @ignore +- */ +-TRenderEngineProvider.propTypes = tRenderEngineProviderPropTypes; +diff --git a/node_modules/react-native-render-html/src/elements/IMGElement.tsx b/node_modules/react-native-render-html/src/elements/IMGElement.tsx +index 573e7c1..a6fc90b 100644 +--- a/node_modules/react-native-render-html/src/elements/IMGElement.tsx ++++ b/node_modules/react-native-render-html/src/elements/IMGElement.tsx +@@ -1,19 +1,13 @@ + import React, { ReactElement, ReactNode } from 'react'; +-import PropTypes from 'prop-types'; + import useIMGElementState from './useIMGElementState'; + import IMGElementContentSuccess from './IMGElementContentSuccess'; + import IMGElementContainer from './IMGElementContainer'; + import IMGElementContentLoading from './IMGElementContentLoading'; + import IMGElementContentError from './IMGElementContentError'; + import type { IMGElementProps } from './img-types'; +-import defaultImageInitialDimensions from './defaultInitialImageDimensions'; + + export type { IMGElementProps } from './img-types'; + +-function identity(arg: any) { +- return arg; +-} +- + /** + * A component to render images based on an internal loading state. + * +@@ -44,42 +38,4 @@ function IMGElement(props: IMGElementProps): ReactElement { + ); + } + +-const imgDimensionsType = PropTypes.shape({ +- width: PropTypes.number, +- height: PropTypes.number +-}); +- +-const propTypes: Record = { +- source: PropTypes.object.isRequired, +- alt: PropTypes.string, +- altColor: PropTypes.string, +- height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +- width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +- style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), +- computeMaxWidth: PropTypes.func.isRequired, +- contentWidth: PropTypes.number, +- enableExperimentalPercentWidth: PropTypes.bool, +- initialDimensions: imgDimensionsType, +- onPress: PropTypes.func, +- testID: PropTypes.string, +- objectFit: PropTypes.string, +- cachedNaturalDimensions: imgDimensionsType, +- containerProps: PropTypes.object +-}; +- +-/** +- * @ignore +- */ +-IMGElement.propTypes = propTypes; +- +-/** +- * @ignore +- */ +-IMGElement.defaultProps = { +- enableExperimentalPercentWidth: false, +- computeMaxWidth: identity, +- imagesInitialDimensions: defaultImageInitialDimensions, +- style: {} +-}; +- + export default IMGElement; +diff --git a/node_modules/react-native-render-html/src/elements/useIMGElementState.ts b/node_modules/react-native-render-html/src/elements/useIMGElementState.ts +index 6590d21..b603f26 100644 +--- a/node_modules/react-native-render-html/src/elements/useIMGElementState.ts ++++ b/node_modules/react-native-render-html/src/elements/useIMGElementState.ts +@@ -63,6 +63,10 @@ function useImageNaturalDimensions

(props: { + }; + } + ++function identity(arg: any) { ++ return arg; ++} ++ + function useFetchedNaturalDimensions(props: { + cachedNaturalDimensions?: ImageDimensions; + source: ImageURISource; +@@ -116,7 +120,7 @@ export default function useIMGElementState( + altColor, + source, + contentWidth, +- computeMaxWidth, ++ computeMaxWidth = identity, + objectFit, + initialDimensions = defaultImageInitialDimensions, + cachedNaturalDimensions +diff --git a/node_modules/react-native-render-html/src/elements/useImageSpecifiedDimensions.ts b/node_modules/react-native-render-html/src/elements/useImageSpecifiedDimensions.ts +index 5d6271b..710c73f 100644 +--- a/node_modules/react-native-render-html/src/elements/useImageSpecifiedDimensions.ts ++++ b/node_modules/react-native-render-html/src/elements/useImageSpecifiedDimensions.ts +@@ -71,8 +71,7 @@ function deriveSpecifiedDimensionsFromProps({ + export default function useImageSpecifiedDimensions( + props: UseIMGElementStateProps + ) { +- const { contentWidth, enableExperimentalPercentWidth, style, width, height } = +- props; ++ const { contentWidth, enableExperimentalPercentWidth = false, style = {}, width, height } = props + const flatStyle = useMemo(() => StyleSheet.flatten(style) || {}, [style]); + const specifiedDimensions = useMemo( + () => +diff --git a/node_modules/react-native-render-html/src/index.ts b/node_modules/react-native-render-html/src/index.ts +index 8569583..b59ec49 100644 +--- a/node_modules/react-native-render-html/src/index.ts ++++ b/node_modules/react-native-render-html/src/index.ts +@@ -128,7 +128,6 @@ export { + export { default as TNodeRenderer } from './TNodeRenderer'; + export { + default as TRenderEngineProvider, +- defaultFallbackFonts, + useAmbientTRenderEngine + } from './TRenderEngineProvider'; + export { default as RenderHTMLConfigProvider } from './RenderHTMLConfigProvider'; +diff --git a/node_modules/react-native-render-html/src/renderChildren.tsx b/node_modules/react-native-render-html/src/renderChildren.tsx +index a669402..be9ffd6 100644 +--- a/node_modules/react-native-render-html/src/renderChildren.tsx ++++ b/node_modules/react-native-render-html/src/renderChildren.tsx +@@ -4,8 +4,6 @@ import TNodeRenderer from './TNodeRenderer'; + import { TChildrenRendererProps } from './shared-types'; + import collapseTopMarginForChild from './helpers/collapseTopMarginForChild'; + +-const empty = {}; +- + const mapCollapsibleChildren = ( + propsForChildren: TChildrenRendererProps['propsForChildren'], + renderChild: TChildrenRendererProps['renderChild'], +@@ -39,7 +37,7 @@ const mapCollapsibleChildren = ( + + export default function renderChildren({ + tchildren, +- propsForChildren = empty, ++ propsForChildren = {}, + disableMarginCollapsing, + renderChild + }: TChildrenRendererProps): ReactElement { From ee7c1f5a65fae14e2cee8cda31ff631b446b42f7 Mon Sep 17 00:00:00 2001 From: Tomasz Lesniakiewicz Date: Thu, 21 Nov 2024 19:17:07 +0100 Subject: [PATCH 020/312] fix lint --- src/components/ParentNavigationSubtitle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 9edf73e5f36f..c80e8a8caf1a 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -61,7 +61,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct {reportName} )} - {workspaceName && workspaceName !== reportName && ( + {!!workspaceName && workspaceName !== reportName && ( {` ${translate('threads.in')} ${workspaceName}`} )} From c20a1aaf49baa21b219bc1dc10254be020fc8617 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 14:30:15 +0700 Subject: [PATCH 021/312] Resolve conflict --- .../home/report/PureReportActionItem.tsx | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 25731504374a..77628d4e7dcf 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -131,11 +131,15 @@ type PureReportActionItemProps = { /** If this is the first visible report action */ isFirstVisibleReportAction: boolean; + /** + * Is the action a thread's parent reportAction viewed from within the thread report? + * It will be false if we're viewing the same parent report action from the report it belongs to rather than the thread. + */ + isThreadReportParentAction?: boolean; + /** IF the thread divider line will be used */ shouldUseThreadDividerLine?: boolean; - hideThreadReplies?: boolean; - /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; @@ -172,8 +176,8 @@ function PureReportActionItem({ shouldShowSubscriptAvatar = false, onPress = undefined, isFirstVisibleReportAction = false, + isThreadReportParentAction = false, shouldUseThreadDividerLine = false, - hideThreadReplies = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, @@ -364,9 +368,22 @@ function PureReportActionItem({ disabledActions, false, setIsEmojiPickerActive as () => void, + undefined, + isThreadReportParentAction, ); }, - [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], + [ + draftMessage, + action, + reportID, + toggleContextMenuFromActiveReportAction, + originalReportID, + shouldDisplayContextMenu, + disabledActions, + isArchivedRoom, + isChronosReport, + isThreadReportParentAction, + ], ); // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. @@ -792,7 +809,7 @@ function PureReportActionItem({ } const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); + const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(action, isThreadReportParentAction); const oldestFourAccountIDs = action.childOldestFourAccountIDs ?.split(',') @@ -978,6 +995,7 @@ function PureReportActionItem({ displayAsGroup={displayAsGroup} disabledActions={disabledActions} isVisible={hovered && draftMessage === undefined && !hasErrors} + isThreadReportParentAction={isThreadReportParentAction} draftMessage={draftMessage} isChronosReport={isChronosReport} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} @@ -1043,9 +1061,7 @@ function PureReportActionItem({ ); } - export type { PureReportActionItemProps }; -// export default PureReportActionItem; export default memo(PureReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; const nextParentReportAction = nextProps.parentReportAction; @@ -1085,6 +1101,5 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && prevProps.isUserValidated === nextProps.isUserValidated && prevProps.parentReport?.reportID === nextProps.parentReport?.reportID - ); -}); +}); \ No newline at end of file From 1333d4c40f327e07081cadbce7bc812fe85409b2 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 14:51:48 +0700 Subject: [PATCH 022/312] lint --- src/pages/home/report/PureReportActionItem.tsx | 14 +++++--------- src/pages/home/report/ReportActionItem.tsx | 1 + 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 77628d4e7dcf..3439f5fe6c74 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -62,9 +62,9 @@ import * as Session from '@userActions/Session'; import * as Transaction from '@userActions/Transaction'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions'; @@ -81,7 +81,6 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import type {Errors} from '@src/types/onyx/OnyxCommon'; type PureReportActionItemProps = { /** Report for this action */ @@ -143,9 +142,7 @@ type PureReportActionItemProps = { /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; - - - draftMessage? : string + draftMessage?: string; iouReport?: OnyxTypes.Report; @@ -1061,7 +1058,7 @@ function PureReportActionItem({ ); } -export type { PureReportActionItemProps }; +export type {PureReportActionItemProps}; export default memo(PureReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; const nextParentReportAction = nextProps.parentReportAction; @@ -1093,13 +1090,12 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && lodashIsEqual(prevParentReportAction, nextParentReportAction) && - prevProps.draftMessage === nextProps.draftMessage && prevProps.iouReport?.reportID === nextProps.iouReport?.reportID && prevProps.emojiReactions === nextProps.emojiReactions && prevProps.userWallet === nextProps.userWallet && prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && prevProps.isUserValidated === nextProps.isUserValidated && - prevProps.parentReport?.reportID === nextProps.parentReport?.reportID + prevProps.parentReport?.reportID === nextProps.parentReport?.reportID ); -}); \ No newline at end of file +}); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index d29f9a6599ff..a162efc0db26 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -34,6 +34,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) return ( Date: Fri, 22 Nov 2024 15:37:18 +0700 Subject: [PATCH 023/312] Add comment --- src/pages/home/report/PureReportActionItem.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 3439f5fe6c74..12ce82a57804 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -142,20 +142,28 @@ type PureReportActionItemProps = { /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; + /** ReportAction Draftmessage */ draftMessage?: string; + /** The IOU/Expense report we are paying */ iouReport?: OnyxTypes.Report; + /** All the emoji reactions for the report action. */ emojiReactions?: OnyxTypes.ReportActionReactions; + /** User's Expensify Wallet */ userWallet?: OnyxTypes.UserWallet; + /** Linked transaction route error */ linkedTransactionRouteError?: Errors; + /** Optional property for report name-value pairs */ reportNameValuePairs?: OnyxTypes.ReportNameValuePairs; + /** Optional property to indicate if the user is validated */ isUserValidated?: boolean; + /** Parent report */ parentReport?: OnyxTypes.Report; }; @@ -177,7 +185,6 @@ function PureReportActionItem({ shouldUseThreadDividerLine = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, - draftMessage, iouReport, emojiReactions, From 672278e1eb60e153aff29c148a3c38d6ada72ffc Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 16:12:44 +0700 Subject: [PATCH 024/312] extract useBlockedFromConcierge, usePersonalDetails --- src/pages/home/report/PureReportActionItem.tsx | 11 ++++++++--- src/pages/home/report/ReportActionItem.tsx | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 12ce82a57804..fc88cbb0f58d 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -14,7 +14,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import InlineSystemMessage from '@components/InlineSystemMessage'; import KYCWall from '@components/KYCWall'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; import RenderHTML from '@components/RenderHTML'; @@ -165,6 +164,12 @@ type PureReportActionItemProps = { /** Parent report */ parentReport?: OnyxTypes.Report; + + /** Personal details list */ + personalDetails?: OnyxTypes.PersonalDetailsList; + + /** Whether or not the user is blocked from concierge */ + blockedFromConcierge?: OnyxTypes.BlockedFromConcierge; }; function PureReportActionItem({ @@ -193,17 +198,17 @@ function PureReportActionItem({ reportNameValuePairs, isUserValidated, parentReport, + personalDetails, + blockedFromConcierge, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const blockedFromConcierge = useBlockedFromConcierge(); const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a162efc0db26..67742f755bd1 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,7 +1,9 @@ import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; +import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PureReportActionItemProps} from './PureReportActionItem'; import PureReportActionItem from './PureReportActionItem'; @@ -31,6 +33,8 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const blockedFromConcierge = useBlockedFromConcierge(); return ( ); } From 3e3a093f5d2ac44dd4df33a7e155c446054ed716 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 16:25:29 +0700 Subject: [PATCH 025/312] fix type error --- src/pages/home/report/PureReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index fc88cbb0f58d..52f30b429101 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -620,7 +620,7 @@ function PureReportActionItem({ ); } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1] ?? {}); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); From 8758645b14ce54171a6da2d5f946662ffa155866 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Fri, 22 Nov 2024 11:58:48 +0100 Subject: [PATCH 026/312] split the patch into two files --- ...native-render-html+6.3.1+001+initial.patch | 25 +++++++++++++++++++ ...-html+6.3.1+002+fix-console-warning.patch} | 25 ------------------- 2 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 patches/react-native-render-html+6.3.1+001+initial.patch rename patches/{react-native-render-html+6.3.1.patch => react-native-render-html+6.3.1+002+fix-console-warning.patch} (98%) diff --git a/patches/react-native-render-html+6.3.1+001+initial.patch b/patches/react-native-render-html+6.3.1+001+initial.patch new file mode 100644 index 000000000000..a14965af4caf --- /dev/null +++ b/patches/react-native-render-html+6.3.1+001+initial.patch @@ -0,0 +1,25 @@ +diff --git a/node_modules/react-native-render-html/react-native-render-html.podspec b/node_modules/react-native-render-html/react-native-render-html.podspec +deleted file mode 100644 +index 6ac80d8..0000000 +--- a/node_modules/react-native-render-html/react-native-render-html.podspec ++++ /dev/null +@@ -1,19 +0,0 @@ +-require 'json' +- +-package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +- +-Pod::Spec.new do |s| +- s.name = package['name'] +- s.version = package['version'] +- s.summary = package['description'] +- s.license = package['license'] +- +- s.authors = package['author'] +- s.homepage = package['homepage'] +- s.platform = :ios, "9.0" +- +- s.source = { :git => "https://github.com/meliorence/react-native-render-html.git", :tag => "v#{s.version}" } +- s.source_files = "ios/**/*.{h,m}" +- +- s.dependency 'React-Core' +-end \ No newline at end of file diff --git a/patches/react-native-render-html+6.3.1.patch b/patches/react-native-render-html+6.3.1+002+fix-console-warning.patch similarity index 98% rename from patches/react-native-render-html+6.3.1.patch rename to patches/react-native-render-html+6.3.1+002+fix-console-warning.patch index 0d2c2496f568..e7a5cd3b230a 100644 --- a/patches/react-native-render-html+6.3.1.patch +++ b/patches/react-native-render-html+6.3.1+002+fix-console-warning.patch @@ -334,31 +334,6 @@ index 1e265b3..24ec0d0 100644 \ No newline at end of file +{"version":3,"sources":["IMGElement.tsx"],"names":["IMGElement","props","state","content","type","React","createElement","IMGElementContentSuccess","IMGElementContentLoading","IMGElementContentError","testID","containerProps","onPress","containerStyle"],"mappings":";;;;;;;AAAA;;AACA;;AACA;;AACA;;AACA;;AACA;;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,UAAT,CAAoBC,KAApB,EAA0D;AACxD,QAAMC,KAAK,GAAG,iCAAmBD,KAAnB,CAAd;AACA,MAAIE,OAAJ;;AACA,MAAID,KAAK,CAACE,IAAN,KAAe,SAAnB,EAA8B;AAC5BD,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBC,iCAApB,EAA8CL,KAA9C,CAAV;AACD,GAFD,MAEO,IAAIA,KAAK,CAACE,IAAN,KAAe,SAAnB,EAA8B;AACnCD,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBE,iCAApB,EAA8CN,KAA9C,CAAV;AACD,GAFM,MAEA;AACLC,IAAAA,OAAO,gBAAGE,eAAMC,aAAN,CAAoBG,+BAApB,EAA4CP,KAA5C,CAAV;AACD;;AACD,sBACE,6BAAC,4BAAD;AACE,IAAA,MAAM,EAAED,KAAK,CAACS;AADhB,KAEMT,KAAK,CAACU,cAFZ;AAGE,IAAA,OAAO,EAAEV,KAAK,CAACW,OAHjB;AAIE,IAAA,KAAK,EAAEV,KAAK,CAACW;AAJf,MAKGV,OALH,CADF;AASD;;eAEcH,U","sourcesContent":["import React, { ReactElement, ReactNode } from 'react';\nimport useIMGElementState from './useIMGElementState';\nimport IMGElementContentSuccess from './IMGElementContentSuccess';\nimport IMGElementContainer from './IMGElementContainer';\nimport IMGElementContentLoading from './IMGElementContentLoading';\nimport IMGElementContentError from './IMGElementContentError';\nimport type { IMGElementProps } from './img-types';\n\nexport type { IMGElementProps } from './img-types';\n\n/**\n * A component to render images based on an internal loading state.\n *\n * @remarks This component will attempt to draw a box of paint dimensions\n * before retrieving the physical dimensions of the image to avoid layout\n * shifts. See also {@link useIMGElementState}, {@link IMGElementContainer},\n * {@link IMGElementContentSuccess}, {@link IMGElementContentLoading}\n * and {@link IMGElementContentError} for customization.\n */\nfunction IMGElement(props: IMGElementProps): ReactElement {\n const state = useIMGElementState(props);\n let content: ReactNode;\n if (state.type === 'success') {\n content = React.createElement(IMGElementContentSuccess, state);\n } else if (state.type === 'loading') {\n content = React.createElement(IMGElementContentLoading, state);\n } else {\n content = React.createElement(IMGElementContentError, state);\n }\n return (\n \n {content}\n \n );\n}\n\nexport default IMGElement;\n"]} \ No newline at end of file -diff --git a/node_modules/react-native-render-html/react-native-render-html.podspec b/node_modules/react-native-render-html/react-native-render-html.podspec -deleted file mode 100644 -index 6ac80d8..0000000 ---- a/node_modules/react-native-render-html/react-native-render-html.podspec -+++ /dev/null -@@ -1,19 +0,0 @@ --require 'json' -- --package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) -- --Pod::Spec.new do |s| -- s.name = package['name'] -- s.version = package['version'] -- s.summary = package['description'] -- s.license = package['license'] -- -- s.authors = package['author'] -- s.homepage = package['homepage'] -- s.platform = :ios, "9.0" -- -- s.source = { :git => "https://github.com/meliorence/react-native-render-html.git", :tag => "v#{s.version}" } -- s.source_files = "ios/**/*.{h,m}" -- -- s.dependency 'React-Core' --end diff --git a/node_modules/react-native-render-html/src/RenderHTMLConfigProvider.tsx b/node_modules/react-native-render-html/src/RenderHTMLConfigProvider.tsx index 0df5375..925062a 100644 --- a/node_modules/react-native-render-html/src/RenderHTMLConfigProvider.tsx From 4628a5b82f8f1056844587536e2dd70c466f073c Mon Sep 17 00:00:00 2001 From: Anas Up Date: Fri, 22 Nov 2024 06:12:48 -0500 Subject: [PATCH 027/312] wrap scrollToBottomForCurrentUserAction with InteractionManager.runAfterInteractions --- src/pages/home/report/ReportActionsList.tsx | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 57d3c48e3b61..cf0536ca07b5 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -421,19 +421,21 @@ function ReportActionsList({ const scrollToBottomForCurrentUserAction = useCallback( (isFromCurrentUser: boolean) => { - // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where - // they are now in the list. - if (!isFromCurrentUser) { - return; - } - if (!hasNewestReportActionRef.current) { - if (isInNarrowPaneModal) { + InteractionManager.runAfterInteractions(() => { + // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where + // they are now in the list. + if (!isFromCurrentUser) { return; } - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID)); - return; - } - InteractionManager.runAfterInteractions(() => reportScrollManager.scrollToBottom()); + if (!hasNewestReportActionRef.current) { + if (isInNarrowPaneModal) { + return; + } + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID)); + return; + } + reportScrollManager.scrollToBottom(); + }); }, [isInNarrowPaneModal, reportScrollManager, report.reportID], ); From e5ae24d92d44b6bed5a9388c610a6b6dc0c0e6a5 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 11:21:07 +0700 Subject: [PATCH 028/312] Extract other onyx related vars from PureReportActionItem --- src/libs/ReportUtils.ts | 1 + src/libs/actions/ReportActions.ts | 1 + .../home/report/PureReportActionItem.tsx | 133 ++++++++++++++---- src/pages/home/report/ReportActionItem.tsx | 18 +++ 4 files changed, 124 insertions(+), 29 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 952e0c2fe4cc..4e98e84344bd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8759,4 +8759,5 @@ export type { TransactionDetails, PartialReportAction, ParsingDetails, + MissingPaymentMethod, }; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 9338527eaccc..f0a018e344a1 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -98,6 +98,7 @@ function clearAllRelatedReportActionErrors(reportID: string, reportAction: Repor } } +export type {IgnoreDirection}; export { // eslint-disable-next-line import/prefer-default-export clearAllRelatedReportActionErrors, diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 52f30b429101..79195a2477dd 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -3,6 +3,7 @@ import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useSta import type {GestureResponderEvent, TextInput} from 'react-native'; import {InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; @@ -41,26 +42,25 @@ import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; -import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {MissingPaymentMethod} from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; -import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import * as BankAccounts from '@userActions/BankAccounts'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as Member from '@userActions/Policy/Member'; import * as Report from '@userActions/Report'; -import * as ReportActions from '@userActions/ReportActions'; +import type {IgnoreDirection} from '@userActions/ReportActions'; import * as Session from '@userActions/Session'; -import * as Transaction from '@userActions/Transaction'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; +import type {IOUAction} from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; @@ -170,6 +170,58 @@ type PureReportActionItemProps = { /** Whether or not the user is blocked from concierge */ blockedFromConcierge?: OnyxTypes.BlockedFromConcierge; + + /** ID of the original report from which the given reportAction is first created */ + originalReportID?: string; + + deleteReportActionDraft: (reportID: string, action: OnyxTypes.ReportAction) => void; + + isArchivedRoom?: boolean; + + isChronosReport?: boolean; + + toggleEmojiReaction?: ( + reportID: string, + reportAction: OnyxTypes.ReportAction, + reactionObject: Emoji, + existingReactions: OnyxEntry, + paramSkinTone: number | undefined, + ignoreSkinToneOnCompare: boolean | undefined, + ) => void; + + doesUserHavePaymentCardAdded: boolean | undefined; + + createDraftTransactionAndNavigateToParticipantSelector: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; + + resolveActionableReportMentionWhisper: ( + reportId: string, + reportAction: OnyxEntry, + resolution: ValueOf, + ) => void; + + isClosedExpenseReportWithNoExpenses: (report: OnyxEntry) => boolean; + + getIndicatedMissingPaymentMethod: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; + + isReimbursementDeQueuedAction: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; + + getReimbursementDeQueuedActionMessage: ( + reportAction: OnyxEntry>, + reportOrID: OnyxEntry | string, + isLHNPreview?: boolean, + ) => string; + + getForReportAction: (reportID: string | undefined, reportAction: OnyxEntry) => string; + + getTransactionsWithReceipts: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; + + isCurrentUserTheOnlyParticipant: (participantAccountIDs?: number[]) => boolean; + + clearError: (transactionID: string) => void; + + clearAllRelatedReportActionErrors: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; + + dismissTrackExpenseActionableWhisper: (reportID: string, reportAction: OnyxEntry) => void; }; function PureReportActionItem({ @@ -200,12 +252,28 @@ function PureReportActionItem({ parentReport, personalDetails, blockedFromConcierge, + originalReportID = '-1', + deleteReportActionDraft = () => {}, + isArchivedRoom, + isChronosReport, + doesUserHavePaymentCardAdded, + toggleEmojiReaction = () => {}, + createDraftTransactionAndNavigateToParticipantSelector = () => {}, + resolveActionableReportMentionWhisper = () => {}, + isClosedExpenseReportWithNoExpenses, + isCurrentUserTheOnlyParticipant, + getIndicatedMissingPaymentMethod, + isReimbursementDeQueuedAction, + getReimbursementDeQueuedActionMessage, + getForReportAction, + getTransactionsWithReceipts, + clearError = () => {}, + clearAllRelatedReportActionErrors = () => {}, + dismissTrackExpenseActionableWhisper = () => {}, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const reportID = report?.reportID ?? ''; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -309,8 +377,8 @@ function PureReportActionItem({ if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { return; } - Report.deleteReportActionDraft(reportID, action); - }, [draftMessage, action, reportID]); + deleteReportActionDraft(reportID, action); + }, [draftMessage, action, reportID, deleteReportActionDraft]); // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator // Removed messages should not be shown anyway and should not need this flow @@ -342,9 +410,8 @@ function PureReportActionItem({ setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); }, [action.reportActionID]); - const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); - const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); + /** * Show the ReportActionContextMenu modal popover. * @@ -409,9 +476,9 @@ function PureReportActionItem({ const toggleReaction = useCallback( (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { - Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); + toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); }, - [reportID, action, emojiReactions], + [reportID, action, emojiReactions, toggleEmojiReaction], ); const contextValue = useMemo( @@ -432,7 +499,7 @@ function PureReportActionItem({ const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { + if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded && shouldRenderAddPaymentCard()) { return [ { text: 'subscription.cardSection.addCardButton', @@ -457,7 +524,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.submit', key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); + createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); }, isMediumSized: true, }, @@ -465,7 +532,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.categorize', key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); + createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); }, isMediumSized: true, }, @@ -473,7 +540,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.share', key: `${action.reportActionID}-actionableMentionTrackExpense-share`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); + createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); }, isMediumSized: true, }, @@ -481,7 +548,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.nothing', key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, onPress: () => { - Report.dismissTrackExpenseActionableWhisper(reportID, action); + dismissTrackExpenseActionableWhisper(reportID, action); }, isMediumSized: true, }, @@ -509,13 +576,13 @@ function PureReportActionItem({ { text: 'common.yes', key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), + onPress: () => resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), isPrimary: true, }, { text: 'common.no', key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), + onPress: () => resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; } @@ -533,7 +600,15 @@ function PureReportActionItem({ onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; - }, [action, isActionableWhisper, reportID]); + }, [ + action, + isActionableWhisper, + reportID, + createDraftTransactionAndNavigateToParticipantSelector, + dismissTrackExpenseActionableWhisper, + doesUserHavePaymentCardAdded, + resolveActionableReportMentionWhisper, + ]); /** * Get the content of ReportActionItem @@ -584,7 +659,7 @@ function PureReportActionItem({ /> ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { - children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( + children = isClosedExpenseReportWithNoExpenses(iouReport) ? ( ${translate('parentReportAction.deletedReport')}`} /> ) : ( ); - } else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) { - children = ; + } else if (isReimbursementDeQueuedAction(action)) { + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { - children = ; + children = ; } else if ( ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) @@ -965,12 +1040,12 @@ function PureReportActionItem({ ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() : '-1'; - const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); + const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID); const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; const whisperedToPersonalDetails = isWhisper ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) : []; - const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); + const isWhisperOnlyVisibleByUser = isWhisper && isCurrentUserTheOnlyParticipant(whisperedTo); const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( @@ -1021,9 +1096,9 @@ function PureReportActionItem({ onClose={() => { const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; if (transactionID) { - Transaction.clearError(transactionID); + clearError(transactionID); } - ReportActions.clearAllRelatedReportActionErrors(reportID, action); + clearAllRelatedReportActionErrors(reportID, action); }} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing pendingAction={ diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 67742f755bd1..a3597b1712a8 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,8 +1,12 @@ import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; +import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; +import * as Report from '@userActions/Report'; +import * as ReportActions from '@userActions/ReportActions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PureReportActionItemProps} from './PureReportActionItem'; @@ -52,6 +56,20 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) parentReport={parentReport} personalDetails={personalDetails} blockedFromConcierge={blockedFromConcierge} + originalReportID={originalReportID} + deleteReportActionDraft={Report.deleteReportActionDraft} + isArchivedRoom={ReportUtils.isArchivedRoomWithID(originalReportID)} + isChronosReport={ReportUtils.chatIncludesChronosWithID(originalReportID)} + toggleEmojiReaction={Report.toggleEmojiReaction} + doesUserHavePaymentCardAdded={doesUserHavePaymentCardAdded()} + createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector} + resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper} + isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses} + isReimbursementDeQueuedAction={ReportActionsUtils.isReimbursementDeQueuedAction} + getForReportAction={ModifiedExpenseMessage.getForReportAction} + getTransactionsWithReceipts={ReportUtils.getTransactionsWithReceipts} + isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} + clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors} /> ); } From e137acdcf65e886cab73e91af177d5f25386d1c1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 11:35:25 +0700 Subject: [PATCH 029/312] Make all extracted onyx vars optional, fix typecheck error --- .../home/report/PureReportActionItem.tsx | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 79195a2477dd..46802dde2205 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -174,7 +174,7 @@ type PureReportActionItemProps = { /** ID of the original report from which the given reportAction is first created */ originalReportID?: string; - deleteReportActionDraft: (reportID: string, action: OnyxTypes.ReportAction) => void; + deleteReportActionDraft?: (reportID: string, action: OnyxTypes.ReportAction) => void; isArchivedRoom?: boolean; @@ -189,39 +189,39 @@ type PureReportActionItemProps = { ignoreSkinToneOnCompare: boolean | undefined, ) => void; - doesUserHavePaymentCardAdded: boolean | undefined; + doesUserHavePaymentCardAdded?: boolean | undefined; - createDraftTransactionAndNavigateToParticipantSelector: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; + createDraftTransactionAndNavigateToParticipantSelector?: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; - resolveActionableReportMentionWhisper: ( + resolveActionableReportMentionWhisper?: ( reportId: string, reportAction: OnyxEntry, resolution: ValueOf, ) => void; - isClosedExpenseReportWithNoExpenses: (report: OnyxEntry) => boolean; + isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; - getIndicatedMissingPaymentMethod: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; + getIndicatedMissingPaymentMethod?: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; - isReimbursementDeQueuedAction: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; + isReimbursementDeQueuedAction?: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; - getReimbursementDeQueuedActionMessage: ( + getReimbursementDeQueuedActionMessage?: ( reportAction: OnyxEntry>, reportOrID: OnyxEntry | string, isLHNPreview?: boolean, ) => string; - getForReportAction: (reportID: string | undefined, reportAction: OnyxEntry) => string; + getForReportAction?: (reportID: string | undefined, reportAction: OnyxEntry) => string; - getTransactionsWithReceipts: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; + getTransactionsWithReceipts?: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; - isCurrentUserTheOnlyParticipant: (participantAccountIDs?: number[]) => boolean; + isCurrentUserTheOnlyParticipant?: (participantAccountIDs?: number[]) => boolean; - clearError: (transactionID: string) => void; + clearError?: (transactionID: string) => void; - clearAllRelatedReportActionErrors: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; + clearAllRelatedReportActionErrors?: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; - dismissTrackExpenseActionableWhisper: (reportID: string, reportAction: OnyxEntry) => void; + dismissTrackExpenseActionableWhisper?: (reportID: string, reportAction: OnyxEntry) => void; }; function PureReportActionItem({ @@ -260,13 +260,14 @@ function PureReportActionItem({ toggleEmojiReaction = () => {}, createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, - isClosedExpenseReportWithNoExpenses, - isCurrentUserTheOnlyParticipant, - getIndicatedMissingPaymentMethod, - isReimbursementDeQueuedAction, - getReimbursementDeQueuedActionMessage, - getForReportAction, - getTransactionsWithReceipts, + isClosedExpenseReportWithNoExpenses = () => false, + isCurrentUserTheOnlyParticipant = () => false, + getIndicatedMissingPaymentMethod = () => undefined, + isReimbursementDeQueuedAction = (reportAction: OnyxEntry): reportAction is OnyxTypes.ReportAction => + false, + getReimbursementDeQueuedActionMessage = () => '', + getForReportAction = () => '', + getTransactionsWithReceipts = () => [], clearError = () => {}, clearAllRelatedReportActionErrors = () => {}, dismissTrackExpenseActionableWhisper = () => {}, @@ -698,7 +699,7 @@ function PureReportActionItem({ const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1] ?? {}); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; - const missingPaymentMethod = getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); + const missingPaymentMethod = getIndicatedMissingPaymentMethod?.(userWallet, linkedReport?.reportID ?? '-1', action); children = ( Date: Tue, 26 Nov 2024 12:41:44 +0700 Subject: [PATCH 030/312] Add comment for new props --- src/pages/home/report/PureReportActionItem.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 46802dde2205..eda0d7965172 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -174,12 +174,16 @@ type PureReportActionItemProps = { /** ID of the original report from which the given reportAction is first created */ originalReportID?: string; + /** Function to deletes the draft for a comment report action. */ deleteReportActionDraft?: (reportID: string, action: OnyxTypes.ReportAction) => void; + /** Whether the room is archived */ isArchivedRoom?: boolean; + /** Whether the room is a chronos report */ isChronosReport?: boolean; + /** Function to toggle emoji reaction */ toggleEmojiReaction?: ( reportID: string, reportAction: OnyxTypes.ReportAction, @@ -189,38 +193,51 @@ type PureReportActionItemProps = { ignoreSkinToneOnCompare: boolean | undefined, ) => void; + /** Whether the user has a payment card added to its account. */ doesUserHavePaymentCardAdded?: boolean | undefined; + /** Function to create a draft transaction and navigate to participant selector */ createDraftTransactionAndNavigateToParticipantSelector?: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; + /** Function to resolve actionable report mention whisper */ resolveActionableReportMentionWhisper?: ( reportId: string, reportAction: OnyxEntry, resolution: ValueOf, ) => void; + /** Whether the provided report is a closed expense report with no expenses */ isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; + /** What missing payment method does this report action indicate, if any? */ getIndicatedMissingPaymentMethod?: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; + /** Whether the provided report action is a reimbursement de-queued action */ isReimbursementDeQueuedAction?: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; + /** Returns the preview message for `REIMBURSEMENT_DEQUEUED` action */ getReimbursementDeQueuedActionMessage?: ( reportAction: OnyxEntry>, reportOrID: OnyxEntry | string, isLHNPreview?: boolean, ) => string; + /** Get the report action message when expense has been modified. */ getForReportAction?: (reportID: string | undefined, reportAction: OnyxEntry) => string; + /** Gets all transactions on an IOU report with a receipt */ getTransactionsWithReceipts?: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; + /** Whether the current user is the only participant in the report */ isCurrentUserTheOnlyParticipant?: (participantAccountIDs?: number[]) => boolean; + /** Function to clear an error from a transaction */ clearError?: (transactionID: string) => void; + /** Function to clear all errors from a report action */ clearAllRelatedReportActionErrors?: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; + /** Function to dismiss the actionable whisper for tracking expenses */ dismissTrackExpenseActionableWhisper?: (reportID: string, reportAction: OnyxEntry) => void; }; From 0a764e23c4f75c206f5a13dfa5e84ad387368e15 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 12:55:48 +0700 Subject: [PATCH 031/312] complete missing props --- src/pages/home/report/PureReportActionItem.tsx | 2 +- src/pages/home/report/ReportActionItem.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index eda0d7965172..b3cb9aa809bf 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -273,8 +273,8 @@ function PureReportActionItem({ deleteReportActionDraft = () => {}, isArchivedRoom, isChronosReport, - doesUserHavePaymentCardAdded, toggleEmojiReaction = () => {}, + doesUserHavePaymentCardAdded, createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, isClosedExpenseReportWithNoExpenses = () => false, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a3597b1712a8..174a4f544549 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -7,6 +7,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; import * as Report from '@userActions/Report'; import * as ReportActions from '@userActions/ReportActions'; +import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PureReportActionItemProps} from './PureReportActionItem'; @@ -65,11 +66,15 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector} resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper} isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses} + isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} + getIndicatedMissingPaymentMethod={ReportUtils.getIndicatedMissingPaymentMethod} isReimbursementDeQueuedAction={ReportActionsUtils.isReimbursementDeQueuedAction} + getReimbursementDeQueuedActionMessage={ReportUtils.getReimbursementDeQueuedActionMessage} getForReportAction={ModifiedExpenseMessage.getForReportAction} getTransactionsWithReceipts={ReportUtils.getTransactionsWithReceipts} - isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} + clearError={Transaction.clearError} clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors} + dismissTrackExpenseActionableWhisper={Report.dismissTrackExpenseActionableWhisper} /> ); } From cc1cd38e34d04036bb392956c11ee0f863d034f3 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 13:18:09 +0700 Subject: [PATCH 032/312] extract resolveActionableMentionWhisper --- src/pages/home/report/PureReportActionItem.tsx | 13 +++++++++++-- src/pages/home/report/ReportActionItem.tsx | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index b3cb9aa809bf..eddcea1ac021 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -206,6 +206,13 @@ type PureReportActionItemProps = { resolution: ValueOf, ) => void; + /** Function to resolve actionable mention whisper */ + resolveActionableMentionWhisper?: ( + reportId: string, + reportAction: OnyxEntry, + resolution: ValueOf, + ) => void; + /** Whether the provided report is a closed expense report with no expenses */ isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; @@ -277,6 +284,7 @@ function PureReportActionItem({ doesUserHavePaymentCardAdded, createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, + resolveActionableMentionWhisper = () => {}, isClosedExpenseReportWithNoExpenses = () => false, isCurrentUserTheOnlyParticipant = () => false, getIndicatedMissingPaymentMethod = () => undefined, @@ -609,13 +617,13 @@ function PureReportActionItem({ { text: 'actionableMentionWhisperOptions.invite', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), + onPress: () => resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), isPrimary: true, }, { text: 'actionableMentionWhisperOptions.nothing', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), + onPress: () => resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; }, [ @@ -626,6 +634,7 @@ function PureReportActionItem({ dismissTrackExpenseActionableWhisper, doesUserHavePaymentCardAdded, resolveActionableReportMentionWhisper, + resolveActionableMentionWhisper, ]); /** diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 174a4f544549..9b886e344bf4 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -65,6 +65,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) doesUserHavePaymentCardAdded={doesUserHavePaymentCardAdded()} createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector} resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper} + resolveActionableMentionWhisper={Report.resolveActionableMentionWhisper} isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses} isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} getIndicatedMissingPaymentMethod={ReportUtils.getIndicatedMissingPaymentMethod} From d36b949b50a887d9f48544e04d7ab7c544c69b65 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 13:37:11 +0700 Subject: [PATCH 033/312] adjust memo --- src/pages/home/report/PureReportActionItem.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index eddcea1ac021..86fcf84806f8 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -1206,10 +1206,17 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevParentReportAction, nextParentReportAction) && prevProps.draftMessage === nextProps.draftMessage && prevProps.iouReport?.reportID === nextProps.iouReport?.reportID && - prevProps.emojiReactions === nextProps.emojiReactions && - prevProps.userWallet === nextProps.userWallet && - prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && + lodashIsEqual(prevProps.emojiReactions, nextProps.emojiReactions) && + lodashIsEqual(prevProps.userWallet, nextProps.userWallet) && + lodashIsEqual(prevProps.linkedTransactionRouteError, nextProps.linkedTransactionRouteError) && + lodashIsEqual(prevProps.reportNameValuePairs, nextProps.reportNameValuePairs) && prevProps.isUserValidated === nextProps.isUserValidated && - prevProps.parentReport?.reportID === nextProps.parentReport?.reportID + prevProps.parentReport?.reportID === nextProps.parentReport?.reportID && + lodashIsEqual(prevProps.personalDetails, nextProps.personalDetails) && + lodashIsEqual(prevProps.blockedFromConcierge, nextProps.blockedFromConcierge) && + prevProps.originalReportID === nextProps.originalReportID && + prevProps.isArchivedRoom === nextProps.isArchivedRoom && + prevProps.isChronosReport === nextProps.isChronosReport && + prevProps.doesUserHavePaymentCardAdded === nextProps.doesUserHavePaymentCardAdded ); }); From 0c3a705ca56675b6c27f516a1e96fe00fa0de44f Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 26 Nov 2024 23:39:05 +0700 Subject: [PATCH 034/312] fix: Join button appear when open expense on search --- src/pages/home/HeaderView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 969e1a90ec3b..11f77b90cdfe 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -148,6 +148,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); const shouldUseGroupTitle = isGroupChat && (!!report?.reportName || !isMultipleParticipant); const isLoading = !report?.reportID || !title; + const isParentReportLoading = !!report?.parentReportID && !parentReport; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; @@ -291,7 +292,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto {!shouldUseNarrowLayout && isChatUsedForOnboarding && freeTrialButton} {isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && } - {canJoin && !shouldUseNarrowLayout && joinButton} + {!isParentReportLoading && canJoin && !shouldUseNarrowLayout && joinButton} {shouldDisplaySearchRouter && } @@ -312,7 +313,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto )} - {!isLoading && canJoin && shouldUseNarrowLayout && {joinButton}} + {!isParentReportLoading && !isLoading && canJoin && shouldUseNarrowLayout && {joinButton}} {!isLoading && isChatUsedForOnboarding && shouldUseNarrowLayout && {freeTrialButton}} ); From 9672b4a59cb98b80e759af477bdad7d055efd759 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Wed, 27 Nov 2024 13:40:26 +0500 Subject: [PATCH 035/312] fix errors --- src/libs/UserUtils.ts | 12 ++++ src/libs/actions/Onboarding.ts | 22 +++++++ src/libs/actions/Welcome/OnboardingFlow.ts | 1 + .../BaseOnboardingPersonalDetails.tsx | 65 +++++++++++-------- .../BaseOnboardingPrivateDomain.tsx | 13 +++- .../OnboardingPrivateDomain/index.native.tsx | 4 +- src/pages/OnboardingPrivateDomain/index.tsx | 4 +- .../BaseOnboardingWorkspaces.tsx | 64 ++++++++++++------ 8 files changed, 132 insertions(+), 53 deletions(-) create mode 100644 src/libs/actions/Onboarding.ts diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 2fb274a00ed9..7cd11c4fad0e 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -67,6 +67,17 @@ function hasLoginListInfo(loginList: OnyxEntry): boolean { return Object.values(loginList ?? {}).some((login) => session?.email !== login.partnerUserID && !login.validatedDate); } +/** + * Checks if the current user has a validated the primary contact method + */ +function isCurrentUserValidated(loginList: OnyxEntry): boolean { + if (!loginList || !session?.email) { + return false; + } + + return !!loginList?.[session.email]?.validatedDate; +} + /** * Gets the appropriate brick road indicator status for a given loginList. * Error status is higher priority, so we check for that first. @@ -265,5 +276,6 @@ export { hashText, isDefaultAvatar, getContactMethod, + isCurrentUserValidated, }; export type {AvatarSource, LoginListIndicator}; diff --git a/src/libs/actions/Onboarding.ts b/src/libs/actions/Onboarding.ts new file mode 100644 index 000000000000..e9ca20ecd26b --- /dev/null +++ b/src/libs/actions/Onboarding.ts @@ -0,0 +1,22 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; + +/** + * Clear Personal Details draft + */ +function clearPersonalDetailsDraft() { + Onyx.set(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM_DRAFT, null); +} + +/** + * Set the personal details Onyx data + */ +function setPersonalDetails(firstName: string, lastName: string) { + Onyx.merge(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM, {firstName, lastName}); +} + +export { + // eslint-disable-next-line import/prefer-default-export + clearPersonalDetailsDraft, + setPersonalDetails, +}; diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts index 9aa0f07dc59c..35f19ba85e02 100644 --- a/src/libs/actions/Welcome/OnboardingFlow.ts +++ b/src/libs/actions/Welcome/OnboardingFlow.ts @@ -5,6 +5,7 @@ import linkingConfig from '@libs/Navigation/linkingConfig'; import getAdaptedStateFromPath from '@libs/Navigation/linkingConfig/getAdaptedStateFromPath'; import {navigationRef} from '@libs/Navigation/Navigation'; import type {RootStackParamList} from '@libs/Navigation/types'; +import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index b03c28842a21..f10f9edcdf52 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -22,6 +22,7 @@ import * as LoginUtils from '@libs/LoginUtils'; import navigateAfterOnboarding from '@libs/navigateAfterOnboarding'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; +import * as Onboarding from '@userActions/Onboarding'; import * as PersonalDetails from '@userActions/PersonalDetails'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; @@ -37,6 +38,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID); const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); + const [onboardingPersonalDetails] = useOnyx(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM); // We need to use isSmallScreenWidth, see navigateAfterOnboarding function comment // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth @@ -53,18 +55,8 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat Welcome.setOnboardingErrorMessage(''); }, []); - const handleSubmit = useCallback( - (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { - const firstName = values.firstName.trim(); - const lastName = values.lastName.trim(); - - PersonalDetails.setDisplayName(firstName, lastName); - - if (isPrivateDomain && !onboardingPurposeSelected) { - Navigation.navigate(ROUTES.ONBOARDING_PRIVATE_DOMAIN.getRoute(route.params?.backTo)); - return; - } - + const completeOnboarding = useCallback( + (firstName: string, lastName: string) => { if (!onboardingPurposeSelected) { return; } @@ -83,17 +75,36 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat navigateAfterOnboarding(isSmallScreenWidth, shouldUseNarrowLayout, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); }, - [ - onboardingPurposeSelected, - onboardingAdminsChatReportID, - onboardingPolicyID, - route.params?.backTo, - activeWorkspaceID, - canUseDefaultRooms, - isSmallScreenWidth, - shouldUseNarrowLayout, - isPrivateDomain, - ], + [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, route.params?.backTo, activeWorkspaceID, canUseDefaultRooms, isSmallScreenWidth, shouldUseNarrowLayout], + ); + + useEffect(() => { + const skippedPrivateDomainFlow = isPrivateDomain && onboardingPurposeSelected; + + if (!skippedPrivateDomainFlow || !onboardingPersonalDetails?.firstName || !onboardingPersonalDetails?.lastName) { + return; + } + + completeOnboarding(onboardingPersonalDetails.firstName, onboardingPersonalDetails.lastName); + }, [onboardingPersonalDetails, isPrivateDomain, onboardingPurposeSelected, completeOnboarding]); + + const handleSubmit = useCallback( + (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { + const firstName = values.firstName.trim(); + const lastName = values.lastName.trim(); + + PersonalDetails.setDisplayName(firstName, lastName); + Onboarding.clearPersonalDetailsDraft(); + Onboarding.setPersonalDetails(firstName, lastName); + + if (isPrivateDomain && !onboardingPurposeSelected) { + Navigation.navigate(ROUTES.ONBOARDING_PRIVATE_DOMAIN.getRoute(route.params?.backTo)); + return; + } + + completeOnboarding(firstName, lastName); + }, + [isPrivateDomain, onboardingPurposeSelected, route.params?.backTo, completeOnboarding], ); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { @@ -138,7 +149,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat style={[styles.defaultModalContainer, shouldUseNativeStyles && styles.pt8]} > @@ -166,7 +177,8 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat label={translate('common.firstName')} aria-label={translate('common.firstName')} role={CONST.ROLE.PRESENTATION} - defaultValue={currentUserPersonalDetails?.firstName} + // eslint-disable-next-line react/jsx-props-no-spreading + {...(currentUserPersonalDetails?.firstName && {defaultValue: currentUserPersonalDetails.firstName})} shouldSaveDraft maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} spellCheck={false} @@ -180,7 +192,8 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat label={translate('common.lastName')} aria-label={translate('common.lastName')} role={CONST.ROLE.PRESENTATION} - defaultValue={currentUserPersonalDetails?.lastName} + // eslint-disable-next-line react/jsx-props-no-spreading + {...(currentUserPersonalDetails?.lastName && {defaultValue: currentUserPersonalDetails.lastName})} shouldSaveDraft maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} spellCheck={false} diff --git a/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx b/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx index 2609a71f5398..570b6cfa3730 100644 --- a/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx +++ b/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx @@ -13,6 +13,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import AccountUtils from '@libs/AccountUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as UserUtils from '@libs/UserUtils'; import * as User from '@userActions/User'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -25,10 +26,13 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS); const [session] = useOnyx(ONYXKEYS.SESSION); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); const {shouldUseNarrowLayout, onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); + const isValidated = UserUtils.isCurrentUserValidated(loginList); + const isValidateCodeFormSubmitting = AccountUtils.isValidateCodeFormSubmitting(account); const email = session?.email ?? ''; @@ -42,8 +46,13 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard }, [credentials?.login]); useEffect(() => { - sendValidateCode(); - }, [sendValidateCode]); + if (!isValidated) { + sendValidateCode(); + return; + } + + Navigation.navigate(ROUTES.ONBOARDING_WORKSPACES.getRoute()); + }, [sendValidateCode, isValidated]); return ( ); } diff --git a/src/pages/OnboardingPrivateDomain/index.tsx b/src/pages/OnboardingPrivateDomain/index.tsx index 57eca886d28e..e82a8aeeaa9a 100644 --- a/src/pages/OnboardingPrivateDomain/index.tsx +++ b/src/pages/OnboardingPrivateDomain/index.tsx @@ -3,13 +3,13 @@ import OnboardingWrapper from '@components/OnboardingWrapper'; import BaseOnboardingPrivateDomain from './BaseOnboardingPrivateDomain'; import type {OnboardingPrivateDomainProps} from './types'; -function OnboardingPrivateDomain({...rest}: OnboardingPrivateDomainProps) { +function OnboardingPrivateDomain(props: OnboardingPrivateDomainProps) { return ( ); diff --git a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx index a27e799a5d04..4f6cd9299519 100644 --- a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx +++ b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -9,13 +9,16 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import UserListItem from '@components/SelectionList/UserListItem'; import Text from '@components/Text'; +import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import navigateAfterJoinRequest from '@libs/navigateAfterJoinRequest'; +import navigateAfterOnboarding from '@libs/navigateAfterOnboarding'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import * as MemberAction from '@userActions/Policy/Member'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; @@ -28,33 +31,45 @@ function BaseOnboardingWorkspaces({shouldUseNativeStyles, route}: BaseOnboarding const {isOffline} = useNetwork(); const styles = useThemeStyles(); const {translate} = useLocalize(); - // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only + // We need to use isSmallScreenWidth, see navigateAfterOnboarding function comment // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth, onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); - const [selectedPolicyID, setSelectedPolicyID] = useState(); + const {onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const [joinablePolicies] = useOnyx(ONYXKEYS.JOINABLE_POLICIES); const [joinablePoliciesLoading] = useOnyx(ONYXKEYS.JOINABLE_POLICIES_LOADING); - const handleJoinWorkspace = (policyID: string, automaticJoiningEnabled: boolean) => { - Report.completeOnboarding(CONST.ONBOARDING_CHOICES.LOOKING_AROUND, CONST.ONBOARDING_MESSAGES[CONST.ONBOARDING_CHOICES.LOOKING_AROUND], policyID); - Welcome.setOnboardingAdminsChatReportID(); - Welcome.setOnboardingPolicyID(policyID); + const [onboardingPersonalDetails] = useOnyx(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM); - if (automaticJoiningEnabled) { + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); + + const isValidated = UserUtils.isCurrentUserValidated(loginList); + + const {canUseDefaultRooms} = usePermissions(); + const {activeWorkspaceID} = useActiveWorkspace(); + + const handleJoinWorkspace = React.useCallback( + (policyID: string) => { MemberAction.addMemberToPrivateDomainWorkspace(policyID); - navigateAfterJoinRequest(); - } else { - Report.navigateToConciergeChat(); - } - }; + Report.completeOnboarding( + CONST.ONBOARDING_CHOICES.LOOKING_AROUND, + CONST.ONBOARDING_MESSAGES[CONST.ONBOARDING_CHOICES.LOOKING_AROUND], + onboardingPersonalDetails?.firstName ?? '', + onboardingPersonalDetails?.lastName ?? '', + undefined, + policyID, + ); + Welcome.setOnboardingAdminsChatReportID(); + Welcome.setOnboardingPolicyID(policyID); + navigateAfterOnboarding(isSmallScreenWidth, shouldUseNarrowLayout, canUseDefaultRooms, policyID, activeWorkspaceID, route.params?.backTo); + }, + [onboardingPersonalDetails?.firstName, onboardingPersonalDetails?.lastName, isSmallScreenWidth, shouldUseNarrowLayout, canUseDefaultRooms, activeWorkspaceID, route.params?.backTo], + ); const policyIDItems = useMemo(() => { return Object.values(joinablePolicies ?? {}).map((policyInfo) => { return { text: policyInfo.policyName, alternateText: translate('onboarding.workspaceMemberList', {employeeCount: policyInfo.employeeCount, policyOwner: policyInfo.policyOwner}), keyForList: policyInfo.policyID, - isSelected: policyInfo.policyID === selectedPolicyID, rightElement: (