From d005e2d27b2c9e8e704dde5227c8513c09e3d20a Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 16 Apr 2024 12:06:34 +0200 Subject: [PATCH 0001/1024] filter options in share log --- .../ShareLogList/BaseShareLogList.tsx | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx index 578efbe5317b..6eaa3bdd7c0e 100644 --- a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx +++ b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx @@ -23,31 +23,38 @@ function BaseShareLogList({onAttachLogToReport}: BaseShareLogListProps) { const betas = useBetas(); const {options, areOptionsInitialized} = useOptionsList(); - const searchOptions = useMemo(() => { + const defaultOptions = useMemo(() => { if (!areOptionsInitialized) { return { recentReports: [], personalDetails: [], - userToInvite: undefined, + userToInvite: null, + currentUserOption: null, + categoryOptions: [], + tagOptions: [], + taxRatesOptions: [], headerMessage: '', }; } - const { - recentReports: localRecentReports, - personalDetails: localPersonalDetails, - userToInvite: localUserToInvite, - } = OptionsListUtils.getShareLogOptions(options, debouncedSearchValue.trim(), betas ?? []); - - const header = OptionsListUtils.getHeaderMessage((localRecentReports?.length || 0) + (localPersonalDetails?.length || 0) !== 0, Boolean(localUserToInvite), debouncedSearchValue); + const shareLogOptions = OptionsListUtils.getShareLogOptions(options, debouncedSearchValue.trim(), betas ?? []); - return { - recentReports: localRecentReports, - personalDetails: localPersonalDetails, - userToInvite: localUserToInvite, - headerMessage: header, - }; + return {...shareLogOptions, headerMessage: ''}; }, [areOptionsInitialized, options, debouncedSearchValue, betas]); + const searchOptions = useMemo(() => { + if (debouncedSearchValue.trim() === '') { + return defaultOptions; + } + const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchValue); + const headerMessage = OptionsListUtils.getHeaderMessage( + (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0, + Boolean(filteredOptions.userToInvite), + debouncedSearchValue.trim(), + ); + + return {...filteredOptions, headerMessage}; + }, [debouncedSearchValue, defaultOptions]); + const sections = useMemo(() => { const sectionsList = []; From 7ce22422688c7a8dc0329f2969fa9fc42589cc02 Mon Sep 17 00:00:00 2001 From: usman-ghani564 Date: Fri, 3 May 2024 19:43:18 +0500 Subject: [PATCH 0002/1024] enable view photo for group chat --- src/pages/ReportAvatar.tsx | 8 ++++---- src/pages/ReportDetailsPage.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/ReportAvatar.tsx b/src/pages/ReportAvatar.tsx index 121b238012bf..ec24ac6b4d46 100644 --- a/src/pages/ReportAvatar.tsx +++ b/src/pages/ReportAvatar.tsx @@ -22,12 +22,12 @@ type ReportAvatarProps = ReportAvatarOnyxProps & StackScreenProps { @@ -35,7 +35,7 @@ function ReportAvatar({report = {} as Report, policies, isLoadingApp = true}: Re }} isWorkspaceAvatar maybeIcon - originalFileName={policy?.originalFileName ?? policyName} + originalFileName={policy?.originalFileName ?? title} shouldShowNotFoundPage={!report?.reportID && !isLoadingApp} isLoading={(!report?.reportID || !policy?.id) && !!isLoadingApp} /> diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index fa939be4e63d..c88aaa45400c 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -237,7 +237,6 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD isUsingDefaultAvatar={!report.avatarUrl} size={CONST.AVATAR_SIZE.XLARGE} avatarStyle={styles.avatarXLarge} - shouldDisableViewPhoto onImageRemoved={() => { // Calling this without a file will remove the avatar Report.updateGroupChatAvatar(report.reportID ?? ''); @@ -249,6 +248,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD errors={report.errorFields?.avatar ?? null} errorRowStyles={styles.mt6} onErrorClose={() => Report.clearAvatarErrors(report.reportID ?? '')} + onViewPhotoPress={() => Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(report.reportID))} /> ) : ( Date: Mon, 22 Apr 2024 13:58:09 +0200 Subject: [PATCH 0003/1024] Revert "[Revert] #36409 "Fix: three not found view"" This reverts commit 0237f277c0d3c363ee1b20115df440f63da0b538. --- src/libs/Navigation/Navigation.ts | 7 + src/libs/Navigation/dismissRHP.ts | 25 ++++ src/pages/workspace/WorkspaceInitialPage.tsx | 14 ++ .../workspace/WorkspaceInviteMessagePage.tsx | 3 + src/pages/workspace/WorkspaceMembersPage.tsx | 121 ++++++++---------- .../workspace/WorkspacePageWithSections.tsx | 14 +- 6 files changed, 116 insertions(+), 68 deletions(-) create mode 100644 src/libs/Navigation/dismissRHP.ts diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 6f5e86f057d8..de45f27022d2 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -14,6 +14,7 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject'; import originalCloseRHPFlow from './closeRHPFlow'; import originalDismissModal from './dismissModal'; import originalDismissModalWithReport from './dismissModalWithReport'; +import originalDismissRHP from './dismissRHP'; import originalGetTopmostReportActionId from './getTopmostReportActionID'; import originalGetTopmostReportId from './getTopmostReportId'; import linkingConfig from './linkingConfig'; @@ -64,6 +65,11 @@ const dismissModal = (reportID?: string, ref = navigationRef) => { // Re-exporting the closeRHPFlow here to fill in default value for navigationRef. The closeRHPFlow isn't defined in this file to avoid cyclic dependencies. const closeRHPFlow = (ref = navigationRef) => originalCloseRHPFlow(ref); +// Re-exporting the dismissRHP here to fill in default value for navigationRef. The dismissRHP isn't defined in this file to avoid cyclic dependencies. +const dismissRHP = (ref = navigationRef) => { + originalDismissRHP(ref); +}; + // Re-exporting the dismissModalWithReport here to fill in default value for navigationRef. The dismissModalWithReport isn't defined in this file to avoid cyclic dependencies. // This method is needed because it allows to dismiss the modal and then open the report. Within this method is checked whether the report belongs to a specific workspace. Sometimes the report we want to check, hasn't been added to the Onyx yet. // Then we can pass the report as a param without getting it from the Onyx. @@ -367,6 +373,7 @@ export default { setShouldPopAllStateOnUP, navigate, setParams, + dismissRHP, dismissModal, dismissModalWithReport, isActiveRoute, diff --git a/src/libs/Navigation/dismissRHP.ts b/src/libs/Navigation/dismissRHP.ts new file mode 100644 index 000000000000..1c497a79600c --- /dev/null +++ b/src/libs/Navigation/dismissRHP.ts @@ -0,0 +1,25 @@ +import type {NavigationContainerRef} from '@react-navigation/native'; +import {StackActions} from '@react-navigation/native'; +import NAVIGATORS from '@src/NAVIGATORS'; +import type {RootStackParamList} from './types'; + +// This function is in a separate file than Navigation.ts to avoid cyclic dependency. + +/** + * Dismisses the RHP modal stack if there is any + * + * @param targetReportID - The reportID to navigate to after dismissing the modal + */ +function dismissRHP(navigationRef: NavigationContainerRef) { + if (!navigationRef.isReady()) { + return; + } + + const state = navigationRef.getState(); + const lastRoute = state.routes.at(-1); + if (lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) { + navigationRef.dispatch({...StackActions.pop(), target: state.key}); + } +} + +export default dismissRHP; diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 1c861d510a85..bea849954631 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -305,6 +305,20 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc PolicyUtils.goBackFromInvalidPolicy(); }, [policy, prevPolicy]); + // We are checking if the user can access the route. + // If user can't access the route, we are dismissing any modals that are open when the NotFound view is shown + const canAccessRoute = activeRoute && menuItems.some((item) => item.routeName === activeRoute); + + useEffect(() => { + if (!shouldShowNotFoundPage && canAccessRoute) { + return; + } + // We are dismissing any modals that are open when the NotFound view is shown + Navigation.isNavigationReady().then(() => { + Navigation.dismissRHP(); + }); + }, [canAccessRoute, policy, shouldShowNotFoundPage]); + const policyAvatar = useMemo(() => { if (!policy) { return {source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 2eaa38b865e6..a5c39cd92fe5 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -97,6 +97,9 @@ function WorkspaceInviteMessagePage({ setWelcomeNote(parser.htmlToMarkdown(getDefaultWelcomeNote())); return; } + if (isEmptyObject(policy)) { + return; + } Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID), true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 0d723961a29e..ff47af2a0dde 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -7,16 +7,13 @@ import {InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import Button from '@components/Button'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/ButtonWithDropdownMenu/types'; import ConfirmModal from '@components/ConfirmModal'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import MessagesRow from '@components/MessagesRow'; -import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import TableListItem from '@components/SelectionList/TableListItem'; import type {ListItem, SelectionListHandle} from '@components/SelectionList/types'; @@ -47,6 +44,7 @@ import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; +import WorkspacePageWithSections from './WorkspacePageWithSections'; type WorkspaceMembersPageOnyxProps = { /** Session info for the currently logged in user. */ @@ -59,7 +57,6 @@ type WorkspaceMembersPageProps = WithPolicyAndFullscreenLoadingProps & WithCurrentUserPersonalDetailsProps & WorkspaceMembersPageOnyxProps & StackScreenProps; - /** * Inverts an object, equivalent of _.invert */ @@ -71,7 +68,7 @@ function invertObject(object: Record): Record { type MemberOption = Omit & {accountID: number}; -function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails, isLoadingReportData = true}: WorkspaceMembersPageProps) { +function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails}: WorkspaceMembersPageProps) { const policyMemberEmailsToAccountIDs = useMemo(() => PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList), [policy?.employeeList]); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -524,71 +521,63 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, }; return ( - - - { - Navigation.goBack(); - }} - shouldShowBackButton={isSmallScreenWidth} - guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS} - > - {!isSmallScreenWidth && getHeaderButtons()} - - {isSmallScreenWidth && {getHeaderButtons()}} - setRemoveMembersConfirmModalVisible(false)} - prompt={translate('workspace.people.removeMembersPrompt')} - confirmText={translate('common.remove')} - cancelText={translate('common.cancel')} - onModalHide={() => { - InteractionManager.runAfterInteractions(() => { - if (!textInputRef.current) { - return; - } - textInputRef.current.focus(); - }); - }} - /> - - toggleUser(item.accountID)} - onSelectAll={() => toggleAllUsers(data)} - onDismissError={dismissError} - showLoadingPlaceholder={isLoading} - showScrollIndicator - shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} - textInputRef={textInputRef} - customListHeader={getCustomListHeader()} - listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} + {() => ( + <> + {isSmallScreenWidth && {getHeaderButtons()}} + setRemoveMembersConfirmModalVisible(false)} + prompt={translate('workspace.people.removeMembersPrompt')} + confirmText={translate('common.remove')} + cancelText={translate('common.cancel')} + onModalHide={() => { + InteractionManager.runAfterInteractions(() => { + if (!textInputRef.current) { + return; + } + textInputRef.current.focus(); + }); + }} /> - - - + + + toggleUser(item.accountID)} + onSelectAll={() => toggleAllUsers(data)} + onDismissError={dismissError} + showLoadingPlaceholder={isLoading} + showScrollIndicator + shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} + textInputRef={textInputRef} + customListHeader={getCustomListHeader()} + listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} + /> + + + )} + ); } diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 58288f213818..b1ecc8124488 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -82,6 +82,12 @@ type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps & * */ icon?: IconAsset; + /** Content to be added to the header */ + headerContent?: ReactNode; + + /** TestID of the component */ + testID?: string; + /** Whether the page is loading, example any other API call in progres */ isLoading?: boolean; }; @@ -112,6 +118,8 @@ function WorkspacePageWithSections({ shouldShowLoading = true, shouldShowOfflineIndicatorInWideScreen = false, shouldShowNonAdmin = false, + headerContent, + testID, shouldShowNotFoundPage = false, isLoading: isPageLoading = false, }: WorkspacePageWithSectionsProps) { @@ -160,7 +168,7 @@ function WorkspacePageWithSections({ includeSafeAreaPaddingBottom={false} shouldEnablePickerAvoiding={false} shouldEnableMaxHeight - testID={WorkspacePageWithSections.displayName} + testID={testID ?? WorkspacePageWithSections.displayName} shouldShowOfflineIndicatorInWideScreen={shouldShowOfflineIndicatorInWideScreen && !shouldShow} > Navigation.goBack(backButtonRoute)} icon={icon ?? undefined} style={styles.headerBarDesktopHeight} - /> + > + {headerContent} + {(isLoading || firstRender.current) && shouldShowLoading && isFocused ? ( ) : ( From a535acb4bf0bc29346097e1ea784a0846cb4337f Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 6 May 2024 10:32:37 +0200 Subject: [PATCH 0004/1024] fix #40509 --- src/pages/workspace/WorkspaceInitialPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index bea849954631..a97b66cf9bc6 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -317,7 +317,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc Navigation.isNavigationReady().then(() => { Navigation.dismissRHP(); }); - }, [canAccessRoute, policy, shouldShowNotFoundPage]); + }, [canAccessRoute, shouldShowNotFoundPage]); const policyAvatar = useMemo(() => { if (!policy) { From adf1ca458ad1a368a84b9f7a4ba2ffcb9c0e3089 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 6 May 2024 14:09:12 +0200 Subject: [PATCH 0005/1024] fix one more issue on android --- .../Navigation/linkingConfig/getAdaptedStateFromPath.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index d89c6c5a1751..67fb816eb272 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -6,6 +6,7 @@ import getTopmostNestedRHPRoute from '@libs/Navigation/getTopmostNestedRHPRoute' import type {BottomTabName, CentralPaneName, FullScreenName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; import {extractPolicyIDFromPath, getPathWithoutPolicyID} from '@libs/PolicyUtils'; import NAVIGATORS from '@src/NAVIGATORS'; +import type {Screen} from '@src/SCREENS'; import SCREENS from '@src/SCREENS'; import CENTRAL_PANE_TO_RHP_MAPPING from './CENTRAL_PANE_TO_RHP_MAPPING'; import config from './config'; @@ -14,7 +15,7 @@ import getMatchingBottomTabRouteForState from './getMatchingBottomTabRouteForSta import getMatchingCentralPaneRouteForState from './getMatchingCentralPaneRouteForState'; import replacePathInNestedState from './replacePathInNestedState'; -const RHP_SCREENS_OPENED_FROM_LHN = [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS] as const; +const RHP_SCREENS_OPENED_FROM_LHN: Screen[] = [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS, SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]; type RHPScreenOpenedFromLHN = (typeof RHP_SCREENS_OPENED_FROM_LHN)[number]; @@ -166,11 +167,6 @@ function getAdaptedState(state: PartialState const featureTrainingModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR); const reportAttachmentsScreen = state.routes.find((route) => route.name === SCREENS.REPORT_ATTACHMENTS); - if (isNarrowLayout) { - metainfo.isFullScreenNavigatorMandatory = false; - metainfo.isCentralPaneAndBottomTabMandatory = false; - } - if (rhpNavigator) { // Routes // - matching bottom tab From b12d3efd331de67e7ca277d1d582b90400ea964a Mon Sep 17 00:00:00 2001 From: usman-ghani564 Date: Mon, 6 May 2024 19:29:59 +0500 Subject: [PATCH 0006/1024] create variable for filename --- src/pages/ReportAvatar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/ReportAvatar.tsx b/src/pages/ReportAvatar.tsx index ec24ac6b4d46..d20396e0a308 100644 --- a/src/pages/ReportAvatar.tsx +++ b/src/pages/ReportAvatar.tsx @@ -24,6 +24,7 @@ function ReportAvatar({report = {} as Report, policies, isLoadingApp = true}: Re const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '0'}`]; const title = policy ? ReportUtils.getPolicyName(report, false, policy) : report?.reportName; const avatarURL = policy ? ReportUtils.getWorkspaceAvatar(report) : report?.avatarUrl; + const fileName = policy?.originalFileName ?? title; return ( From b513239d07e6eed0d65e7be55d8686bbde0bbdf1 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 8 May 2024 11:37:04 +0200 Subject: [PATCH 0007/1024] fix comment --- src/libs/Navigation/dismissRHP.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/Navigation/dismissRHP.ts b/src/libs/Navigation/dismissRHP.ts index 1c497a79600c..4b4260b22f3f 100644 --- a/src/libs/Navigation/dismissRHP.ts +++ b/src/libs/Navigation/dismissRHP.ts @@ -7,8 +7,6 @@ import type {RootStackParamList} from './types'; /** * Dismisses the RHP modal stack if there is any - * - * @param targetReportID - The reportID to navigate to after dismissing the modal */ function dismissRHP(navigationRef: NavigationContainerRef) { if (!navigationRef.isReady()) { From ce12e86a19f7122f51f46fc71c58787efb8f63c6 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 8 May 2024 11:43:45 +0200 Subject: [PATCH 0008/1024] change type --- src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index 67fb816eb272..f82ce95a7dea 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -15,7 +15,7 @@ import getMatchingBottomTabRouteForState from './getMatchingBottomTabRouteForSta import getMatchingCentralPaneRouteForState from './getMatchingCentralPaneRouteForState'; import replacePathInNestedState from './replacePathInNestedState'; -const RHP_SCREENS_OPENED_FROM_LHN: Screen[] = [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS, SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]; +const RHP_SCREENS_OPENED_FROM_LHN = [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS, SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE] satisfies Screen[]; type RHPScreenOpenedFromLHN = (typeof RHP_SCREENS_OPENED_FROM_LHN)[number]; From 871980e56f969729ae839b1aaf473bb9d298a95e Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 9 May 2024 16:43:29 +0700 Subject: [PATCH 0009/1024] fix message after deleting self mention displays green dot in LHN --- src/libs/ReportActionsUtils.ts | 30 ++++++++++++++++++++++++++++++ src/libs/actions/Report.ts | 10 +++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 906a2963baa8..7c27bd446674 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1,4 +1,5 @@ import fastMerge from 'expensify-common/lib/fastMerge'; +import Str from 'expensify-common/lib/str'; import _ from 'lodash'; import lodashFindLast from 'lodash/findLast'; import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; @@ -84,6 +85,7 @@ Onyx.connect({ }); let currentUserAccountID: number | undefined; +let currentEmail = ''; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (value) => { @@ -93,6 +95,7 @@ Onyx.connect({ } currentUserAccountID = value.accountID; + currentEmail = value?.email ?? ''; }, }); @@ -1199,6 +1202,32 @@ function isLinkedTransactionHeld(reportActionID: string, reportID: string): bool return TransactionUtils.isOnHoldByTransactionID(getLinkedTransactionID(reportActionID, reportID) ?? ''); } +function getAccountIDsFromMessage(message: string) { + const regex = //g; + const matches = []; + let match; + while ((match = regex.exec(message)) !== null) { + matches.push(match[1]); + } + return matches; +} + +function getMentionedEmailsFromMessage(message: string) { + const regex = /(.*?)<\/mention-user>/g; + const matches = []; + let match; + while ((match = regex.exec(message)) !== null) { + matches.push(Str.removeSMSDomain(match[1].substring(1))); + } + return matches; +} + +function didMessageMentionCurrentUser(message: string) { + const accountIDsFromMessage = getAccountIDsFromMessage(message); + const emailsFromMessage = getMentionedEmailsFromMessage(message); + return accountIDsFromMessage.includes(String(currentUserAccountID)) || emailsFromMessage.includes(currentEmail); +} + export { extractLinksFromMessageHtml, getDismissedViolationMessageText, @@ -1265,6 +1294,7 @@ export { isActionableJoinRequestPending, isActionableTrackExpense, isLinkedTransactionHeld, + didMessageMentionCurrentUser, }; export type {LastVisibleMessage}; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 3154ae218d72..7ba42e24aaab 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1324,7 +1324,15 @@ function deleteReportComment(reportID: string, reportAction: ReportAction) { lastActorAccountID, }; } - + const report = ReportUtils.getReport(reportID); + const didCommentMentionCurrentUser = ReportActionsUtils.didMessageMentionCurrentUser(reportAction?.message?.[0]?.html ?? ''); + if (didCommentMentionCurrentUser && reportAction.created === report?.lastMentionedTime) { + const reportActionsForReport = allReportActions?.[reportID]; + const latestMentioneReportAction = Object.values(reportActionsForReport ?? {}).find( + (action) => action.reportActionID !== reportAction.reportActionID && ReportActionsUtils.didMessageMentionCurrentUser(action?.message?.[0]?.html ?? ''), + ); + optimisticReport.lastMentionedTime = latestMentioneReportAction?.created ?? null; + } // If the API call fails we must show the original message again, so we revert the message content back to how it was // and and remove the pendingAction so the strike-through clears const failureData: OnyxUpdate[] = [ From 37806923f2682f4ffcf442efd732affb5606a7b7 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 9 May 2024 17:28:15 +0700 Subject: [PATCH 0010/1024] fix lint --- src/libs/ReportActionsUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 7c27bd446674..e01277733415 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1206,6 +1206,7 @@ function getAccountIDsFromMessage(message: string) { const regex = //g; const matches = []; let match; + // eslint-disable-next-line no-cond-assign while ((match = regex.exec(message)) !== null) { matches.push(match[1]); } @@ -1216,6 +1217,7 @@ function getMentionedEmailsFromMessage(message: string) { const regex = /(.*?)<\/mention-user>/g; const matches = []; let match; + // eslint-disable-next-line no-cond-assign while ((match = regex.exec(message)) !== null) { matches.push(Str.removeSMSDomain(match[1].substring(1))); } From faf6155bdc45576bbdfbaa87625572ee48914fac Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 16 May 2024 09:53:48 +0200 Subject: [PATCH 0011/1024] feat: add confirm screen --- src/ROUTES.ts | 8 +-- src/SCREENS.ts | 2 +- .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 5 +- src/pages/TransactionDuplicate/Confirm.tsx | 64 +++++++++++++++++++ 5 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/pages/TransactionDuplicate/Confirm.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a5f35962b67e..fe1e08d208d1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -815,10 +815,10 @@ const ROUTES = { route: 'r/:threadReportID/duplicates/review/billable', getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/billable` as const, }, - // TRANSACTION_DUPLICATE_CONFIRM: { - // route: 'r/:threadReportID/duplicates/review/description', - // getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/description` as const, - // }, + TRANSACTION_DUPLICATE_CONFIRM_PAGE: { + route: 'r/:threadReportID/duplicates/review/description', + getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/description` as const, + }, POLICY_ACCOUNTING_XERO_IMPORT: { route: 'settings/workspaces/:policyID/accounting/xero/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index fae7c11ba21c..7ccef9217635 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -186,7 +186,7 @@ const SCREENS = { TAX_CODE: 'Transaction_Duplicate_Tax_Code', REIMBURSABLE: 'Transaction_Duplicate_Reimburable', BILLABLE: 'Transaction_Duplicate_Billable', - // CONFIRM: 'Transaction_Duplicate_Confirm', + CONFIRM: 'Transaction_Duplicate_Confirm', }, IOU_SEND: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 0448c4ed9647..d50188343b87 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -375,6 +375,7 @@ const TransactionDuplicateStackNavigator = createModalStackNavigator require('../../../../pages/TransactionDuplicate/ReviewTaxCode').default as React.ComponentType, [SCREENS.TRANSACTION_DUPLICATE.BILLABLE]: () => require('../../../../pages/TransactionDuplicate/ReviewBillable').default as React.ComponentType, [SCREENS.TRANSACTION_DUPLICATE.REIMBURSABLE]: () => require('../../../../pages/TransactionDuplicate/ReviewReimbursable').default as React.ComponentType, + [SCREENS.TRANSACTION_DUPLICATE.CONFIRM]: () => require('../../../../pages/TransactionDuplicate/Confirm').default as React.ComponentType, }); const SearchReportModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 9da05b572b0d..03f2cc80f7e5 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -692,7 +692,10 @@ const config: LinkingOptions['config'] = { path: ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.route, exact: true, }, - // [SCREENS.TRANSACTION_DUPLICATE.CONFIRM]: ROUTES.TRANSACTION_DUPLICATE_CONFIRM.route, + [SCREENS.TRANSACTION_DUPLICATE.CONFIRM]: { + path: ROUTES.TRANSACTION_DUPLICATE_CONFIRM_PAGE.route, + exact: true, + }, }, }, [SCREENS.RIGHT_MODAL.SPLIT_DETAILS]: { diff --git a/src/pages/TransactionDuplicate/Confirm.tsx b/src/pages/TransactionDuplicate/Confirm.tsx new file mode 100644 index 000000000000..b503abf45450 --- /dev/null +++ b/src/pages/TransactionDuplicate/Confirm.tsx @@ -0,0 +1,64 @@ +import type {RouteProp} from '@react-navigation/native'; +import {useRoute} from '@react-navigation/native'; +import React from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import Button from '@components/Button'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MoneyRequestView from '@components/ReportActionItem/MoneyRequestView'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; +import variables from '@styles/variables'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type {Transaction} from '@src/types/onyx'; + +function Confirm() { + const styles = useThemeStyles(); + const route = useRoute>(); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); + const [reviewDuplicates] = useOnyx(ONYXKEYS.FORMS.REVIEW_DUPLICATES_FORM); + const [originalTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${reviewDuplicates?.transactionID}`); + const transaction: Transaction = { + ...originalTransaction, + category: reviewDuplicates?.category, + comment: {comment: reviewDuplicates?.description}, + billable: reviewDuplicates?.billable, + reimbursable: reviewDuplicates?.reimbursable, + tag: reviewDuplicates?.tag, + taxCode: reviewDuplicates?.taxCode, + }; + + return ( + + + + + Confirm the details you're keeping + + The duplicate requests you don't keep will be held for the member to delete + + +