Skip to content

Commit

Permalink
Merge pull request #34656 from Pujan92/fix/31998
Browse files Browse the repository at this point in the history
[TS migration] Migrate 'PrivateNotes' page to TypeScript
  • Loading branch information
marcaaron authored Jan 25, 2024
2 parents bb2e009 + a002cb8 commit 0d0b0a8
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 335 deletions.
4 changes: 0 additions & 4 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,6 @@ const ROUTES = {
route: 'r/:reportID/assignee',
getRoute: (reportID: string) => `r/${reportID}/assignee` as const,
},
PRIVATE_NOTES_VIEW: {
route: 'r/:reportID/notes/:accountID',
getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}` as const,
},
PRIVATE_NOTES_LIST: {
route: 'r/:reportID/notes',
getRoute: (reportID: string) => `r/${reportID}/notes` as const,
Expand Down
1 change: 0 additions & 1 deletion src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ const SCREENS = {
},

PRIVATE_NOTES: {
VIEW: 'PrivateNotes_View',
LIST: 'PrivateNotes_List',
EDIT: 'PrivateNotes_Edit',
},
Expand Down
1 change: 0 additions & 1 deletion src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,6 @@ const EditRequestStackNavigator = createModalStackNavigator<EditRequestNavigator
});

const PrivateNotesModalStackNavigator = createModalStackNavigator<PrivateNotesNavigatorParamList>({
[SCREENS.PRIVATE_NOTES.VIEW]: () => require('../../../pages/PrivateNotes/PrivateNotesViewPage').default as React.ComponentType,
[SCREENS.PRIVATE_NOTES.LIST]: () => require('../../../pages/PrivateNotes/PrivateNotesListPage').default as React.ComponentType,
[SCREENS.PRIVATE_NOTES.EDIT]: () => require('../../../pages/PrivateNotes/PrivateNotesEditPage').default as React.ComponentType,
});
Expand Down
1 change: 0 additions & 1 deletion src/libs/Navigation/linkingConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ const linkingConfig: LinkingOptions<RootStackParamList> = {
},
[SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: {
screens: {
[SCREENS.PRIVATE_NOTES.VIEW]: ROUTES.PRIVATE_NOTES_VIEW.route,
[SCREENS.PRIVATE_NOTES.LIST]: ROUTES.PRIVATE_NOTES_LIST.route,
[SCREENS.PRIVATE_NOTES.EDIT]: ROUTES.PRIVATE_NOTES_EDIT.route,
},
Expand Down
9 changes: 1 addition & 8 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,14 +333,7 @@ type ProcessMoneyRequestHoldNavigatorParamList = {
};

type PrivateNotesNavigatorParamList = {
[SCREENS.PRIVATE_NOTES.VIEW]: {
reportID: string;
accountID: string;
};
[SCREENS.PRIVATE_NOTES.LIST]: {
reportID: string;
accountID: string;
};
[SCREENS.PRIVATE_NOTES.LIST]: undefined;
[SCREENS.PRIVATE_NOTES.EDIT]: {
reportID: string;
accountID: string;
Expand Down
2 changes: 1 addition & 1 deletion src/libs/updateMultilineInputRange/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {TextInput} from 'react-native';

type UpdateMultilineInputRange = (input: HTMLInputElement | TextInput, shouldAutoFocus?: boolean) => void;
type UpdateMultilineInputRange = (input: HTMLInputElement | TextInput | null, shouldAutoFocus?: boolean) => void;

export default UpdateMultilineInputRange;
Original file line number Diff line number Diff line change
@@ -1,81 +1,71 @@
import {useFocusEffect} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import lodashDebounce from 'lodash/debounce';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {Keyboard} from 'react-native';
import type {TextInput as TextInputRN} from 'react-native';
import type {OnyxCollection} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import withLocalize from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types';
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 * as Report from '@userActions/Report';
import * as ReportActions from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {PersonalDetails, Report} from '@src/types/onyx';
import type {Note} from '@src/types/onyx/Report';

const propTypes = {
type PrivateNotesEditPageOnyxProps = {
/** All of the personal details for everyone */
personalDetailsList: PropTypes.objectOf(personalDetailsPropType),

/** The report currently being looked at */
report: reportPropTypes,
route: PropTypes.shape({
/** Params from the URL path */
params: PropTypes.shape({
/** reportID and accountID passed via route: /r/:reportID/notes */
reportID: PropTypes.string,
accountID: PropTypes.string,
}),
}).isRequired,
personalDetailsList: OnyxCollection<PersonalDetails>;
};

const defaultProps = {
report: {},
personalDetailsList: {},
};
type PrivateNotesEditPageProps = PrivateNotesEditPageOnyxProps &
StackScreenProps<PrivateNotesNavigatorParamList, typeof SCREENS.PRIVATE_NOTES.EDIT> & {
/** The report currently being looked at */
report: Report;
};

function PrivateNotesEditPage({route, personalDetailsList, report}) {
function PrivateNotesEditPage({route, personalDetailsList, report}: PrivateNotesEditPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

// We need to edit the note in markdown format, but display it in HTML format
const parser = new ExpensiMark();
const [privateNote, setPrivateNote] = useState(
() => Report.getDraftPrivateNote(report.reportID).trim() || parser.htmlToMarkdown(lodashGet(report, ['privateNotes', route.params.accountID, 'note'], '')).trim(),
() => ReportActions.getDraftPrivateNote(report.reportID).trim() || parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(),
);

/**
* Save the draft of the private note. This debounced so that we're not ceaselessly saving your edit. Saving the draft
* allows one to navigate somewhere else and come back to the private note and still have it in edit mode.
* @param {String} newDraft
*/
const debouncedSavePrivateNote = useMemo(
() =>
_.debounce((text) => {
Report.savePrivateNotesDraft(report.reportID, text);
lodashDebounce((text: string) => {
ReportActions.savePrivateNotesDraft(report.reportID, text);
}, 1000),
[report.reportID],
);

// To focus on the input field when the page loads
const privateNotesInput = useRef(null);
const focusTimeoutRef = useRef(null);
const privateNotesInput = useRef<HTMLInputElement | TextInputRN | null>(null);
const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);

useFocusEffect(
useCallback(() => {
Expand All @@ -94,18 +84,18 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) {
);

const savePrivateNote = () => {
const originalNote = lodashGet(report, ['privateNotes', route.params.accountID, 'note'], '');
const originalNote = report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '';
let editedNote = '';
if (privateNote.trim() !== originalNote.trim()) {
editedNote = Report.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim());
Report.updatePrivateNotes(report.reportID, route.params.accountID, editedNote);
editedNote = ReportActions.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim());
ReportActions.updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote);
}

// We want to delete saved private note draft after saving the note
debouncedSavePrivateNote('');

Keyboard.dismiss();
if (!_.some({...report.privateNotes, [route.params.accountID]: {note: editedNote}}, (item) => item.note)) {
if (!Object.values<Note>({...report.privateNotes, [route.params.accountID]: {note: editedNote}}).some((item) => item.note)) {
ReportUtils.navigateToDetailsPage(report);
} else {
Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
Expand All @@ -120,10 +110,11 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) {
>
<HeaderWithBackButton
title={translate('privateNotes.title')}
onBackButtonPress={() => Navigation.goBack(ROUTES.PRIVATE_NOTES_VIEW.getRoute(report.reportID, route.params.accountID))}
onBackButtonPress={() => Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID))}
shouldShowBackButton
onCloseButtonPress={() => Navigation.dismissModal()}
/>
{/* @ts-expect-error TODO: Remove this once FormProvider (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript. */}
<FormProvider
formID={ONYXKEYS.FORMS.PRIVATE_NOTES_FORM}
onSubmit={savePrivateNote}
Expand All @@ -133,19 +124,20 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) {
>
<Text style={[styles.mb5]}>
{translate(
Str.extractEmailDomain(lodashGet(personalDetailsList, [route.params.accountID, 'login'], '')) === CONST.EMAIL.GUIDES_DOMAIN
Str.extractEmailDomain(personalDetailsList?.[route.params.accountID]?.login ?? '') === CONST.EMAIL.GUIDES_DOMAIN
? 'privateNotes.sharedNoteMessage'
: 'privateNotes.personalNoteMessage',
)}
</Text>
<OfflineWithFeedback
errors={{
...lodashGet(report, ['privateNotes', route.params.accountID, 'errors'], ''),
...(report?.privateNotes?.[Number(route.params.accountID)]?.errors ?? ''),
}}
onClose={() => Report.clearPrivateNotesError(report.reportID, route.params.accountID)}
onClose={() => ReportActions.clearPrivateNotesError(report.reportID, Number(route.params.accountID))}
style={[styles.mb3]}
>
<InputWrapper
// @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
InputComponent={TextInput}
role={CONST.ROLE.PRESENTATION}
inputID="privateNotes"
Expand All @@ -158,7 +150,7 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) {
containerStyles={[styles.autoGrowHeightMultilineInput]}
defaultValue={privateNote}
value={privateNote}
onChangeText={(text) => {
onChangeText={(text: string) => {
debouncedSavePrivateNote(text);
setPrivateNote(text);
}}
Expand All @@ -177,15 +169,11 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) {
}

PrivateNotesEditPage.displayName = 'PrivateNotesEditPage';
PrivateNotesEditPage.propTypes = propTypes;
PrivateNotesEditPage.defaultProps = defaultProps;

export default compose(
withLocalize,
withReportAndPrivateNotesOrNotFound('privateNotes.title'),
withOnyx({
export default withReportAndPrivateNotesOrNotFound('privateNotes.title')(
withOnyx<PrivateNotesEditPageProps, PrivateNotesEditPageOnyxProps>({
personalDetailsList: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
}),
)(PrivateNotesEditPage);
})(PrivateNotesEditPage),
);
Loading

0 comments on commit 0d0b0a8

Please sign in to comment.