Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'NewTask' page to TypeScript #36886

Merged
merged 10 commits into from
Mar 12, 2024
2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.HOME_ADDRESS_FORM]: FormTypes.Form;
[ONYXKEYS.FORMS.NEW_ROOM_FORM]: FormTypes.NewRoomForm;
[ONYXKEYS.FORMS.ROOM_SETTINGS_FORM]: FormTypes.Form;
[ONYXKEYS.FORMS.NEW_TASK_FORM]: FormTypes.Form;
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved
[ONYXKEYS.FORMS.NEW_TASK_FORM]: FormTypes.NewTaskForm;
[ONYXKEYS.FORMS.EDIT_TASK_FORM]: FormTypes.Form;
[ONYXKEYS.FORMS.MONEY_REQUEST_DESCRIPTION_FORM]: FormTypes.Form;
[ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM]: FormTypes.Form;
Expand Down
14 changes: 7 additions & 7 deletions src/libs/actions/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function createTaskAndNavigate(
assigneeEmail: string,
assigneeAccountID = 0,
assigneeChatReport: OnyxEntry<OnyxTypes.Report> = null,
policyID = CONST.POLICY.OWNER_EMAIL_FAKE,
policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE,
) {
const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description, policyID);

Expand Down Expand Up @@ -656,8 +656,8 @@ function clearOutTaskInfoAndNavigate(reportID: string) {
/**
* Get the assignee data
*/
function getAssignee(assigneeAccountID: number, personalDetails: OnyxTypes.PersonalDetailsList): Assignee {
const details = personalDetails[assigneeAccountID];
function getAssignee(assigneeAccountID: number, personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>): Assignee {
const details = personalDetails?.[assigneeAccountID];

if (!details) {
return {
Expand All @@ -677,7 +677,7 @@ function getAssignee(assigneeAccountID: number, personalDetails: OnyxTypes.Perso
/**
* Get the share destination data
* */
function getShareDestination(reportID: string, reports: OnyxCollection<OnyxTypes.Report>, personalDetails: OnyxTypes.PersonalDetailsList): ShareDestination {
function getShareDestination(reportID: string, reports: OnyxCollection<OnyxTypes.Report>, personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>): ShareDestination {
const report = reports?.[`report_${reportID}`] ?? null;

const participantAccountIDs = report?.participantAccountIDs ?? [];
Expand All @@ -688,8 +688,8 @@ function getShareDestination(reportID: string, reports: OnyxCollection<OnyxTypes
if (ReportUtils.isChatReport(report) && ReportUtils.isDM(report) && ReportUtils.hasSingleParticipant(report)) {
const participantAccountID = report?.participantAccountIDs?.[0] ?? -1;

const displayName = personalDetails[participantAccountID]?.displayName ?? '';
const login = personalDetails[participantAccountID]?.login ?? '';
const displayName = personalDetails?.[participantAccountID]?.displayName ?? '';
const login = personalDetails?.[participantAccountID]?.login ?? '';
subtitle = LocalePhoneNumber.formatPhoneNumber(login || displayName);
} else {
subtitle = ReportUtils.getChatRoomSubtitle(report) ?? '';
Expand Down Expand Up @@ -930,4 +930,4 @@ export {
canModifyTask,
};

export type {PolicyValue};
export type {PolicyValue, Assignee, ShareDestination};
Original file line number Diff line number Diff line change
@@ -1,66 +1,53 @@
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapperWithRef from '@components/Form/InputWrapper';
import type {FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import TextInput from '@components/TextInput';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import updateMultilineInputRange from '@libs/updateMultilineInputRange';
import * as Task from '@userActions/Task';
import * as TaskActions from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/NewTaskForm';
import type {Task} from '@src/types/onyx';

const propTypes = {
/** Grab the Share description of the Task */
task: PropTypes.shape({
/** Description of the Task */
description: PropTypes.string,
}),

...withLocalizePropTypes,
};

const defaultProps = {
task: {
description: '',
},
type NewTaskDescriptionPageProps = {
/** Grab the Share title of the Task */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB/NIT: In this comment, Share title is kinda irrelevant to this description page. Maybe just Details of the Task or Grab the details of the Task appears better to me.

Or just omit this comment? Does not appear to be useful at all.

Same for the NewTaskDetailsPage and NewTaskTitlePage. I think Share is only applicable to Share somewhere and is not for details and title.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

task: OnyxEntry<Task>;
};
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved

const parser = new ExpensiMark();

function NewTaskDescriptionPage(props) {
function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {inputCallbackRef} = useAutoFocusInput();

const onSubmit = (values) => {
Task.setDescriptionValue(values.taskDescription);
const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.NEW_TASK_FORM>) => {
TaskActions.setDescriptionValue(values.taskDescription);
Navigation.goBack(ROUTES.NEW_TASK);
};

/**
* @param {Object} values - form input values passed by the Form component
* @returns {Boolean}
*/
function validate(values) {
const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.NEW_TASK_FORM>) => {
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved
const errors = {};

if (values.taskDescription.length > CONST.DESCRIPTION_LIMIT) {
ErrorUtils.addErrorMessage(errors, 'taskDescription', ['common.error.characterLimitExceedCounter', {length: values.taskDescription.length, limit: CONST.DESCRIPTION_LIMIT}]);
}

return errors;
}
};

return (
<ScreenWrapper
Expand All @@ -70,33 +57,33 @@ function NewTaskDescriptionPage(props) {
>
<>
<HeaderWithBackButton
title={props.translate('task.description')}
onCloseButtonPress={() => Task.dismissModalAndClearOutTaskInfo()}
title={translate('task.description')}
onCloseButtonPress={() => TaskActions.dismissModalAndClearOutTaskInfo()}
onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)}
/>
<FormProvider
formID={ONYXKEYS.FORMS.NEW_TASK_FORM}
submitButtonText={props.translate('common.next')}
submitButtonText={translate('common.next')}
style={[styles.mh5, styles.flexGrow1]}
validate={(values) => validate(values)}
onSubmit={(values) => onSubmit(values)}
validate={validate}
onSubmit={onSubmit}
enabledWhenOffline
>
<View style={styles.mb5}>
<InputWrapperWithRef
InputComponent={TextInput}
defaultValue={parser.htmlToMarkdown(parser.replace(props.task.description))}
defaultValue={parser.htmlToMarkdown(parser.replace(task?.description ?? ''))}
inputID={INPUT_IDS.TASK_DESCRIPTION}
label={props.translate('newTaskPage.descriptionOptional')}
accessibilityLabel={props.translate('newTaskPage.descriptionOptional')}
label={translate('newTaskPage.descriptionOptional')}
accessibilityLabel={translate('newTaskPage.descriptionOptional')}
role={CONST.ROLE.PRESENTATION}
ref={(el) => {
inputCallbackRef(el);
updateMultilineInputRange(el);
}}
autoGrowHeight
shouldSubmitForm
containerStyles={[styles.autoGrowHeightMultilineInput]}
containerStyles={styles.autoGrowHeightMultilineInput}
/>
</View>
</FormProvider>
Expand All @@ -106,14 +93,9 @@ function NewTaskDescriptionPage(props) {
}

NewTaskDescriptionPage.displayName = 'NewTaskDescriptionPage';
NewTaskDescriptionPage.propTypes = propTypes;
NewTaskDescriptionPage.defaultProps = defaultProps;

export default compose(
withOnyx({
task: {
key: ONYXKEYS.TASK,
},
}),
withLocalize,
)(NewTaskDescriptionPage);
export default withOnyx<NewTaskDescriptionPageProps, NewTaskDescriptionPageProps>({
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved
task: {
key: ONYXKEYS.TASK,
},
})(NewTaskDescriptionPage);
Original file line number Diff line number Diff line change
@@ -1,58 +1,47 @@
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import TextInput from '@components/TextInput';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as Task from '@userActions/Task';
import * as TaskActions from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/NewTaskForm';
import type {Task} from '@src/types/onyx';

const propTypes = {
/** Task title and description data */
task: PropTypes.shape({
title: PropTypes.string,
description: PropTypes.string,
}),

...withLocalizePropTypes,
};

const defaultProps = {
task: {},
type NewTaskDetailsPageProps = {
/** Grab the Share title of the Task */
task: OnyxEntry<Task>;
};
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved

const parser = new ExpensiMark();

function NewTaskDetailsPage(props) {
function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) {
const styles = useThemeStyles();
const [taskTitle, setTaskTitle] = useState(props.task.title);
const [taskDescription, setTaskDescription] = useState(props.task.description || '');
const {translate} = useLocalize();
const [taskTitle, setTaskTitle] = useState<string>(task?.title ?? '');
const [taskDescription, setTaskDescription] = useState<string>(task?.description ?? '');
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved

const {inputCallbackRef} = useAutoFocusInput();

useEffect(() => {
setTaskTitle(props.task.title);
setTaskDescription(parser.htmlToMarkdown(parser.replace(props.task.description || '')));
}, [props.task]);
setTaskTitle(task?.title ?? '');
setTaskDescription(parser.htmlToMarkdown(parser.replace(task?.description ?? '')));
}, [task]);

/**
* @param {Object} values - form input values passed by the Form component
* @returns {Boolean}
*/
function validate(values) {
const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.NEW_TASK_FORM>) => {
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved
const errors = {};

if (!values.taskTitle) {
Expand All @@ -66,14 +55,14 @@ function NewTaskDetailsPage(props) {
}

return errors;
}
};

// On submit, we want to call the assignTask function and wait to validate
// the response
function onSubmit(values) {
Task.setDetailsValue(values.taskTitle, values.taskDescription);
const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.NEW_TASK_FORM>) => {
TaskActions.setDetailsValue(values.taskTitle, values.taskDescription);
Navigation.navigate(ROUTES.NEW_TASK);
}
};

return (
<ScreenWrapper
Expand All @@ -82,45 +71,47 @@ function NewTaskDetailsPage(props) {
testID={NewTaskDetailsPage.displayName}
>
<HeaderWithBackButton
title={props.translate('newTaskPage.assignTask')}
onCloseButtonPress={() => Task.dismissModalAndClearOutTaskInfo()}
title={translate('newTaskPage.assignTask')}
onCloseButtonPress={() => TaskActions.dismissModalAndClearOutTaskInfo()}
shouldShowBackButton
onBackButtonPress={() => Task.dismissModalAndClearOutTaskInfo()}
onBackButtonPress={() => TaskActions.dismissModalAndClearOutTaskInfo()}
/>
<FormProvider
formID={ONYXKEYS.FORMS.NEW_TASK_FORM}
submitButtonText={props.translate('common.next')}
submitButtonText={translate('common.next')}
style={[styles.mh5, styles.flexGrow1]}
validate={(values) => validate(values)}
onSubmit={(values) => onSubmit(values)}
validate={validate}
onSubmit={onSubmit}
enabledWhenOffline
>
<View style={styles.mb5}>
<InputWrapper
InputComponent={TextInput}
ref={inputCallbackRef}
valueType="string"
role={CONST.ROLE.PRESENTATION}
inputID={INPUT_IDS.TASK_TITLE}
label={props.translate('task.title')}
accessibilityLabel={props.translate('task.title')}
label={translate('task.title')}
accessibilityLabel={translate('task.title')}
value={taskTitle}
onValueChange={(value) => setTaskTitle(value)}
onValueChange={setTaskDescription}
autoCorrect={false}
/>
</View>
<View style={styles.mb5}>
<InputWrapper
valueType="string"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was it added? Is it possible to update types instead of passing an extra prop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is needed to add value, since we can have different types with InputWrapper and here we need only the 'string'

InputComponent={TextInput}
role={CONST.ROLE.PRESENTATION}
inputID={INPUT_IDS.TASK_DESCRIPTION}
label={props.translate('newTaskPage.descriptionOptional')}
accessibilityLabel={props.translate('newTaskPage.descriptionOptional')}
label={translate('newTaskPage.descriptionOptional')}
accessibilityLabel={translate('newTaskPage.descriptionOptional')}
autoGrowHeight
shouldSubmitForm
containerStyles={[styles.autoGrowHeightMultilineInput]}
containerStyles={styles.autoGrowHeightMultilineInput}
defaultValue={parser.htmlToMarkdown(parser.replace(taskDescription))}
value={taskDescription}
onValueChange={(value) => setTaskDescription(value)}
onValueChange={setTaskDescription}
/>
</View>
</FormProvider>
Expand All @@ -129,14 +120,9 @@ function NewTaskDetailsPage(props) {
}

NewTaskDetailsPage.displayName = 'NewTaskDetailsPage';
NewTaskDetailsPage.propTypes = propTypes;
NewTaskDetailsPage.defaultProps = defaultProps;

export default compose(
withOnyx({
task: {
key: ONYXKEYS.TASK,
},
}),
withLocalize,
)(NewTaskDetailsPage);
export default withOnyx<NewTaskDetailsPageProps, NewTaskDetailsPageProps>({
pasyukevich marked this conversation as resolved.
Show resolved Hide resolved
task: {
key: ONYXKEYS.TASK,
},
})(NewTaskDetailsPage);
Loading
Loading