From ad6007b63983e276c05113bfb2ce57adf7d092e4 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 4 Nov 2024 01:10:11 +0500 Subject: [PATCH 001/123] 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 4737f1ae28a64aa5cb3ece2fca022e09cc343202 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 14 Nov 2024 03:02:15 +0500 Subject: [PATCH 005/123] 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 006/123] 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 007/123] 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 008/123] 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 c20a1aaf49baa21b219bc1dc10254be020fc8617 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 14:30:15 +0700 Subject: [PATCH 009/123] 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 010/123] 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 011/123] 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 012/123] 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 013/123] 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 e5ae24d92d44b6bed5a9388c610a6b6dc0c0e6a5 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 11:21:07 +0700 Subject: [PATCH 014/123] 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 015/123] 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 016/123] 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 017/123] 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 018/123] 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 019/123] 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 9672b4a59cb98b80e759af477bdad7d055efd759 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Wed, 27 Nov 2024 13:40:26 +0500 Subject: [PATCH 020/123] 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: (