diff --git a/src/languages/en.ts b/src/languages/en.ts index c186a1fffedf..8ce15d16f4fe 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -811,7 +811,6 @@ export default { title: 'Private notes', personalNoteMessage: 'Keep notes about this chat here. You are the only person who can add, edit or view these notes.', sharedNoteMessage: 'Keep notes about this chat here. Expensify employees and other users on the team.expensify.com domain can view these notes.', - notesUnavailable: 'No notes found for the user', composerLabel: 'Notes', myNote: 'My note', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index a0a30bcf4141..9d0184ffff30 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -806,7 +806,6 @@ export default { title: 'Notas privadas', personalNoteMessage: 'Guarda notas sobre este chat aquí. Usted es la única persona que puede añadir, editar o ver estas notas.', sharedNoteMessage: 'Guarda notas sobre este chat aquí. Los empleados de Expensify y otros usuarios del dominio team.expensify.com pueden ver estas notas.', - notesUnavailable: 'No se han encontrado notas para el usuario', composerLabel: 'Notas', myNote: 'Mi nota', }, diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js index 7c8aec8d12de..78e6929121ae 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.js +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js @@ -7,7 +7,6 @@ import React, {useCallback, useMemo, useRef, useState} from 'react'; import {Keyboard} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -19,8 +18,8 @@ import withLocalize from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import personalDetailsPropType from '@pages/personalDetailsPropType'; import reportPropTypes from '@pages/reportPropTypes'; import styles from '@styles/styles'; @@ -43,23 +42,14 @@ const propTypes = { accountID: PropTypes.string, }), }).isRequired, - - /** Session of currently logged in user */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), }; const defaultProps = { report: {}, - session: { - accountID: null, - }, personalDetailsList: {}, }; -function PrivateNotesEditPage({route, personalDetailsList, session, report}) { +function PrivateNotesEditPage({route, personalDetailsList, report}) { const {translate} = useLocalize(); // We need to edit the note in markdown format, but display it in HTML format @@ -81,8 +71,6 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { [report.reportID], ); - const isCurrentUserNote = Number(session.accountID) === Number(route.params.accountID); - // To focus on the input field when the page loads const privateNotesInput = useRef(null); const focusTimeoutRef = useRef(null); @@ -119,73 +107,62 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { includeSafeAreaPaddingBottom={false} testID={PrivateNotesEditPage.displayName} > - Navigation.goBack(ROUTES.PRIVATE_NOTES_VIEW.getRoute(report.reportID, route.params.accountID))} + shouldShowBackButton + onCloseButtonPress={() => Navigation.dismissModal()} + /> + - Navigation.goBack(ROUTES.PRIVATE_NOTES_VIEW.getRoute(report.reportID, route.params.accountID))} - shouldShowBackButton - onCloseButtonPress={() => Navigation.dismissModal()} - /> - + {translate( + Str.extractEmailDomain(lodashGet(personalDetailsList, [route.params.accountID, 'login'], '')) === CONST.EMAIL.GUIDES_DOMAIN + ? 'privateNotes.sharedNoteMessage' + : 'privateNotes.personalNoteMessage', + )} + + Report.clearPrivateNotesError(report.reportID, route.params.accountID)} + style={[styles.mb3]} > - - {translate( - Str.extractEmailDomain(lodashGet(personalDetailsList, [route.params.accountID, 'login'], '')) === CONST.EMAIL.GUIDES_DOMAIN - ? 'privateNotes.sharedNoteMessage' - : 'privateNotes.personalNoteMessage', - )} - - { + debouncedSavePrivateNote(text); + setPrivateNote(text); }} - onClose={() => Report.clearPrivateNotesError(report.reportID, route.params.accountID)} - style={[styles.mb3]} - > - { - debouncedSavePrivateNote(text); - setPrivateNote(text); - }} - ref={(el) => { - if (!el) { - return; - } - privateNotesInput.current = el; - updateMultilineInputRange(privateNotesInput.current); - }} - /> - - - + ref={(el) => { + if (!el) { + return; + } + privateNotesInput.current = el; + updateMultilineInputRange(privateNotesInput.current); + }} + /> + + ); } @@ -196,13 +173,8 @@ PrivateNotesEditPage.defaultProps = defaultProps; export default compose( withLocalize, + withReportAndPrivateNotesOrNotFound, withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID.toString()}`, - }, - session: { - key: ONYXKEYS.SESSION, - }, personalDetailsList: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.js b/src/pages/PrivateNotes/PrivateNotesListPage.js index ec3905db349e..4d5b348c4b9f 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.js +++ b/src/pages/PrivateNotes/PrivateNotesListPage.js @@ -1,14 +1,11 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useEffect, useMemo} from 'react'; +import React, {useMemo} from 'react'; import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; -import networkPropTypes from '@components/networkPropTypes'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {withNetwork} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -16,12 +13,11 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; +import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import personalDetailsPropType from '@pages/personalDetailsPropType'; import reportPropTypes from '@pages/reportPropTypes'; import styles from '@styles/styles'; -import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -47,8 +43,6 @@ const propTypes = { /** All of the personal details for everyone */ personalDetailsList: PropTypes.objectOf(personalDetailsPropType), - /** Information about the network */ - network: networkPropTypes.isRequired, ...withLocalizePropTypes, }; @@ -60,17 +54,9 @@ const defaultProps = { personalDetailsList: {}, }; -function PrivateNotesListPage({report, personalDetailsList, network, session}) { +function PrivateNotesListPage({report, personalDetailsList, session}) { const {translate} = useLocalize(); - useEffect(() => { - if (network.isOffline && report.isLoadingPrivateNotes) { - return; - } - Report.getReportPrivateNote(report.reportID); - // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add isLoadingPrivateNotes to dependencies - }, [report.reportID, network.isOffline]); - /** * Gets the menu item for each workspace * @@ -124,26 +110,12 @@ function PrivateNotesListPage({report, personalDetailsList, network, session}) { includeSafeAreaPaddingBottom={false} testID={PrivateNotesListPage.displayName} > - - Navigation.dismissModal()} - /> - - {report.isLoadingPrivateNotes && _.isEmpty(lodashGet(report, 'privateNotes', {})) ? ( - - ) : ( - _.map(privateNotes, (item, index) => getMenuItem(item, index)) - )} - - + Navigation.dismissModal()} + /> + {_.map(privateNotes, (item, index) => getMenuItem(item, index))} ); } @@ -154,13 +126,8 @@ PrivateNotesListPage.displayName = 'PrivateNotesListPage'; export default compose( withLocalize, + withReportAndPrivateNotesOrNotFound, withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID.toString()}`, - }, - session: { - key: ONYXKEYS.SESSION, - }, personalDetailsList: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, diff --git a/src/pages/PrivateNotes/PrivateNotesViewPage.js b/src/pages/PrivateNotes/PrivateNotesViewPage.js index bb9d96516437..2b836036448d 100644 --- a/src/pages/PrivateNotes/PrivateNotesViewPage.js +++ b/src/pages/PrivateNotes/PrivateNotesViewPage.js @@ -4,7 +4,6 @@ import React from 'react'; import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -13,7 +12,7 @@ import withLocalize from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; +import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import personalDetailsPropType from '@pages/personalDetailsPropType'; import reportPropTypes from '@pages/reportPropTypes'; import styles from '@styles/styles'; @@ -71,33 +70,28 @@ function PrivateNotesViewPage({route, personalDetailsList, session, report}) { includeSafeAreaPaddingBottom={false} testID={PrivateNotesViewPage.displayName} > - - Navigation.goBack(getFallbackRoute())} - subtitle={isCurrentUserNote ? translate('privateNotes.myNote') : `${lodashGet(personalDetailsList, [route.params.accountID, 'login'], '')} note`} - shouldShowBackButton - onCloseButtonPress={() => Navigation.dismissModal()} - /> - - - isCurrentUserNote && Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, route.params.accountID))} - shouldShowRightIcon={isCurrentUserNote} - numberOfLinesTitle={0} - shouldRenderAsHTML - brickRoadIndicator={!_.isEmpty(lodashGet(report, ['privateNotes', route.params.accountID, 'errors'], '')) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} - disabled={!isCurrentUserNote} - shouldGreyOutWhenDisabled={false} - /> - - - + Navigation.goBack(getFallbackRoute())} + subtitle={isCurrentUserNote ? translate('privateNotes.myNote') : `${lodashGet(personalDetailsList, [route.params.accountID, 'login'], '')} note`} + shouldShowBackButton + onCloseButtonPress={() => Navigation.dismissModal()} + /> + + + isCurrentUserNote && Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, route.params.accountID))} + shouldShowRightIcon={isCurrentUserNote} + numberOfLinesTitle={0} + shouldRenderAsHTML + brickRoadIndicator={!_.isEmpty(lodashGet(report, ['privateNotes', route.params.accountID, 'errors'], '')) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + disabled={!isCurrentUserNote} + shouldGreyOutWhenDisabled={false} + /> + + ); } @@ -108,13 +102,8 @@ PrivateNotesViewPage.defaultProps = defaultProps; export default compose( withLocalize, + withReportAndPrivateNotesOrNotFound, withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID.toString()}`, - }, - session: { - key: ONYXKEYS.SESSION, - }, personalDetailsList: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, diff --git a/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js new file mode 100644 index 000000000000..3982dd5ab542 --- /dev/null +++ b/src/pages/home/report/withReportAndPrivateNotesOrNotFound.js @@ -0,0 +1,130 @@ +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React, {useEffect, useMemo} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import networkPropTypes from '@components/networkPropTypes'; +import {withNetwork} from '@components/OnyxProvider'; +import * as Report from '@libs/actions/Report'; +import compose from '@libs/compose'; +import getComponentDisplayName from '@libs/getComponentDisplayName'; +import * as ReportUtils from '@libs/ReportUtils'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; +import reportPropTypes from '@pages/reportPropTypes'; +import ONYXKEYS from '@src/ONYXKEYS'; +import withReportOrNotFound from './withReportOrNotFound'; + +const propTypes = { + /** The HOC takes an optional ref as a prop and passes it as a ref to the wrapped component. + * That way, if a ref is passed to a component wrapped in the HOC, the ref is a reference to the wrapped component, not the HOC. */ + forwardedRef: PropTypes.func, + + /** The report currently being looked at */ + report: reportPropTypes, + + /** Information about the network */ + network: networkPropTypes.isRequired, + + /** Session of currently logged in user */ + session: PropTypes.shape({ + /** accountID of currently logged in user */ + accountID: PropTypes.number, + }), + + route: PropTypes.shape({ + /** Params from the URL path */ + params: PropTypes.shape({ + /** reportID and accountID passed via route: /r/:reportID/notes/:accountID */ + reportID: PropTypes.string, + accountID: PropTypes.string, + }), + }).isRequired, +}; + +const defaultProps = { + forwardedRef: () => {}, + report: {}, + session: { + accountID: null, + }, +}; + +export default function (WrappedComponent) { + // eslint-disable-next-line rulesdir/no-negated-variables + function WithReportAndPrivateNotesOrNotFound({forwardedRef, ...props}) { + const {route, report, network, session} = props; + const accountID = route.params.accountID; + const isPrivateNotesFetchTriggered = !_.isUndefined(report.isLoadingPrivateNotes); + + useEffect(() => { + // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if network is offline. + if (isPrivateNotesFetchTriggered || network.isOffline) { + return; + } + + Report.getReportPrivateNote(report.reportID); + // eslint-disable-next-line react-hooks/exhaustive-deps -- do not add report.isLoadingPrivateNotes to dependencies + }, [report.reportID, network.isOffline, isPrivateNotesFetchTriggered]); + + const isPrivateNotesEmpty = accountID ? _.isEmpty(lodashGet(report, ['privateNotes', accountID, 'note'], '')) : _.isEmpty(report.privateNotes); + const shouldShowFullScreenLoadingIndicator = !isPrivateNotesFetchTriggered || (isPrivateNotesEmpty && report.isLoadingPrivateNotes); + + // eslint-disable-next-line rulesdir/no-negated-variables + const shouldShowNotFoundPage = useMemo(() => { + // Show not found view if the report is archived, or if the note is not of current user. + if (ReportUtils.isArchivedRoom(report) || (accountID && Number(session.accountID) !== Number(accountID))) { + return true; + } + + // Don't show not found view if the notes are still loading, or if the notes are non-empty. + if (shouldShowFullScreenLoadingIndicator || !isPrivateNotesEmpty) { + return false; + } + + // As notes being empty and not loading is a valid case, show not found view only in offline mode. + return network.isOffline; + }, [report, network.isOffline, accountID, session.accountID, isPrivateNotesEmpty, shouldShowFullScreenLoadingIndicator]); + + if (shouldShowFullScreenLoadingIndicator) { + return ; + } + + if (shouldShowNotFoundPage) { + return ; + } + + return ( + + ); + } + + WithReportAndPrivateNotesOrNotFound.propTypes = propTypes; + WithReportAndPrivateNotesOrNotFound.defaultProps = defaultProps; + WithReportAndPrivateNotesOrNotFound.displayName = `withReportAndPrivateNotesOrNotFound(${getComponentDisplayName(WrappedComponent)})`; + + // eslint-disable-next-line rulesdir/no-negated-variables + const WithReportAndPrivateNotesOrNotFoundWithRef = React.forwardRef((props, ref) => ( + + )); + + WithReportAndPrivateNotesOrNotFoundWithRef.displayName = 'WithReportAndPrivateNotesOrNotFoundWithRef'; + + return compose( + withReportOrNotFound(), + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + }), + withNetwork(), + )(WithReportAndPrivateNotesOrNotFoundWithRef); +} diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx index 81d1376abd37..95997da71a2d 100644 --- a/src/pages/home/report/withReportOrNotFound.tsx +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -36,7 +36,7 @@ export default function ( const isReportIdInRoute = props.route.params.reportID?.length; if (shouldRequireReportID || isReportIdInRoute) { - const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData && (!Object.entries(props.report ?? {}).length || !props.report?.reportID); + const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.report ?? {}).length || !props.report?.reportID); const shouldShowNotFoundPage = !Object.entries(props.report ?? {}).length || !props.report?.reportID || !ReportUtils.canAccessReport(props.report, props.policies, props.betas, {});