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

[Form Provider Refactor] WorkspaceInviteMessagePage #32139

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 119 additions & 157 deletions src/pages/workspace/WorkspaceInviteMessagePage.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import {isEmpty} from 'lodash';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React from 'react';
import React, {useEffect, useState} from 'react';
import {Keyboard, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import Form from '@components/Form';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import MultipleAvatars from '@components/MultipleAvatars';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withNavigationFocus from '@components/withNavigationFocus';
import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as Localize from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import updateMultilineInputRange from '@libs/updateMultilineInputRange';
import * as Link from '@userActions/Link';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -61,8 +63,6 @@ const propTypes = {
}).isRequired,

...policyPropTypes,
...withLocalizePropTypes,
...withThemeStylesPropTypes,
};

const defaultProps = {
Expand All @@ -71,188 +71,151 @@ const defaultProps = {
invitedEmailsToAccountIDsDraft: {},
};

class WorkspaceInviteMessagePage extends React.Component {
constructor(props) {
super(props);
function WorkspaceInviteMessagePage(props) {
cdOut marked this conversation as resolved.
Show resolved Hide resolved
const styles = useThemeStyles();
const {translate} = useLocalize();

this.sendInvitation = this.sendInvitation.bind(this);
this.validate = this.validate.bind(this);
this.openPrivacyURL = this.openPrivacyURL.bind(this);
this.debouncedSaveDraf = _.debounce((newDraft) => {
Policy.setWorkspaceInviteMessageDraft(this.props.route.params.policyID, newDraft);
}, 2000);
this.state = {
welcomeNote: this.props.workspaceInviteMessageDraft || this.getDefaultWelcomeNote(),
};
}
const [welcomeNote, setWelcomeNote] = useState();

componentDidMount() {
if (_.isEmpty(this.props.invitedEmailsToAccountIDsDraft)) {
Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(this.props.route.params.policyID), true);
return;
}
this.focusWelcomeMessageInput();
}

componentDidUpdate(prevProps) {
if (!prevProps.isFocused && this.props.isFocused) {
this.focusWelcomeMessageInput();
}
const {inputCallbackRef} = useAutoFocusInput();

if (
!(
(prevProps.preferredLocale !== this.props.preferredLocale || prevProps.policy.name !== this.props.policy.name) &&
this.state.welcomeNote === Localize.translate(prevProps.preferredLocale, 'workspace.inviteMessage.welcomeNote', {workspaceName: prevProps.policy.name})
)
) {
return;
}
this.setState({welcomeNote: this.getDefaultWelcomeNote()});
}
const getDefaultWelcomeNote = () =>
props.workspaceInviteMessageDraft ||
translate('workspace.inviteMessage.welcomeNote', {
workspaceName: props.policy.name,
});

componentWillUnmount() {
if (!this.focusTimeout) {
useEffect(() => {
if (!_.isEmpty(props.invitedEmailsToAccountIDsDraft)) {
setWelcomeNote(getDefaultWelcomeNote());
return;
}
clearTimeout(this.focusTimeout);
}
Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

getDefaultWelcomeNote() {
return this.props.translate('workspace.inviteMessage.welcomeNote', {
workspaceName: this.props.policy.name,
});
}
const debouncedSaveDraft = _.debounce((newDraft) => {
Policy.setWorkspaceInviteMessageDraft(props.route.params.policyID, newDraft);
});

sendInvitation() {
const sendInvitation = () => {
Keyboard.dismiss();
Policy.addMembersToWorkspace(this.props.invitedEmailsToAccountIDsDraft, this.state.welcomeNote, this.props.route.params.policyID);
Policy.setWorkspaceInviteMembersDraft(this.props.route.params.policyID, {});
Policy.addMembersToWorkspace(props.invitedEmailsToAccountIDsDraft, welcomeNote, props.route.params.policyID);
Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, {});
SearchInputManager.searchInput = '';
// Pop the invite message page before navigating to the members page.
Navigation.goBack(ROUTES.HOME);
Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(this.props.route.params.policyID));
}
Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(props.route.params.policyID));
};

/**
* Opens privacy url as an external link
* @param {Object} event
*/
openPrivacyURL(event) {
const openPrivacyURL = (event) => {
event.preventDefault();
Link.openExternalLink(CONST.PRIVACY_URL);
}

focusWelcomeMessageInput() {
this.focusTimeout = setTimeout(() => {
this.welcomeMessageInputRef.focus();
// Below condition is needed for web, desktop and mweb only, for native cursor is set at end by default.
if (this.welcomeMessageInputRef.value && this.welcomeMessageInputRef.setSelectionRange) {
const length = this.welcomeMessageInputRef.value.length;
this.welcomeMessageInputRef.setSelectionRange(length, length);
}
}, CONST.ANIMATED_TRANSITION);
}
};

validate() {
const validate = () => {
const errorFields = {};
if (_.isEmpty(this.props.invitedEmailsToAccountIDsDraft)) {
if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) {
errorFields.welcomeMessage = 'workspace.inviteMessage.inviteNoMembersError';
}
return errorFields;
}

render() {
const policyName = lodashGet(this.props.policy, 'name');

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={WorkspaceInviteMessagePage.displayName}
};

const policyName = lodashGet(props.policy, 'name');

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={WorkspaceInviteMessagePage.displayName}
>
<FullPageNotFoundView
shouldShow={_.isEmpty(props.policy) || !PolicyUtils.isPolicyAdmin(props.policy) || PolicyUtils.isPendingDeletePolicy(props.policy)}
subtitleKey={_.isEmpty(props.policy) ? undefined : 'workspace.common.notAuthorized'}
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
>
<FullPageNotFoundView
shouldShow={_.isEmpty(this.props.policy) || !PolicyUtils.isPolicyAdmin(this.props.policy) || PolicyUtils.isPendingDeletePolicy(this.props.policy)}
subtitleKey={_.isEmpty(this.props.policy) ? undefined : 'workspace.common.notAuthorized'}
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
<HeaderWithBackButton
title={translate('workspace.inviteMessage.inviteMessageTitle')}
subtitle={policyName}
shouldShowGetAssistanceButton
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
shouldShowBackButton
onCloseButtonPress={() => Navigation.dismissModal()}
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID))}
/>
<FormProvider
style={[styles.flexGrow1, styles.ph5]}
formID={ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM}
validate={validate}
onSubmit={sendInvitation}
submitButtonText={translate('common.invite')}
enabledWhenOffline
footerContent={
<PressableWithoutFeedback
onPress={openPrivacyURL}
role={CONST.ROLE.LINK}
accessibilityLabel={translate('common.privacy')}
href={CONST.PRIVACY_URL}
style={[styles.mv2, styles.alignSelfStart]}
>
<View style={[styles.flexRow]}>
<Text style={[styles.mr1, styles.label, styles.link]}>{translate('common.privacy')}</Text>
</View>
</PressableWithoutFeedback>
}
>
<HeaderWithBackButton
title={this.props.translate('workspace.inviteMessage.inviteMessageTitle')}
subtitle={policyName}
shouldShowGetAssistanceButton
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
shouldShowBackButton
onCloseButtonPress={() => Navigation.dismissModal()}
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(this.props.route.params.policyID))}
/>

<Form
style={[this.props.themeStyles.flexGrow1, this.props.themeStyles.ph5]}
formID={ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM}
validate={this.validate}
onSubmit={this.sendInvitation}
submitButtonText={this.props.translate('common.invite')}
enabledWhenOffline
footerContent={
<PressableWithoutFeedback
onPress={this.openPrivacyURL}
role={CONST.ROLE.LINK}
accessibilityLabel={this.props.translate('common.privacy')}
href={CONST.PRIVACY_URL}
style={[this.props.themeStyles.mv2, this.props.themeStyles.alignSelfStart]}
>
<View style={[this.props.themeStyles.flexRow]}>
<Text style={[this.props.themeStyles.mr1, this.props.themeStyles.label, this.props.themeStyles.link]}>{this.props.translate('common.privacy')}</Text>
</View>
</PressableWithoutFeedback>
}
>
<View style={[this.props.themeStyles.mv4, this.props.themeStyles.justifyContentCenter, this.props.themeStyles.alignItemsCenter]}>
<MultipleAvatars
size={CONST.AVATAR_SIZE.LARGE}
icons={OptionsListUtils.getAvatarsForAccountIDs(
_.values(this.props.invitedEmailsToAccountIDsDraft),
this.props.allPersonalDetails,
this.props.invitedEmailsToAccountIDsDraft,
)}
shouldStackHorizontally
shouldDisplayAvatarsInRows
secondAvatarStyle={[this.props.themeStyles.secondAvatarInline]}
/>
</View>
<View style={[this.props.themeStyles.mb5]}>
<Text>{this.props.translate('workspace.inviteMessage.inviteMessagePrompt')}</Text>
</View>
<View style={[this.props.themeStyles.mb3]}>
<TextInput
ref={(el) => (this.welcomeMessageInputRef = el)}
role={CONST.ROLE.PRESENTATION}
inputID="welcomeMessage"
label={this.props.translate('workspace.inviteMessage.personalMessagePrompt')}
accessibilityLabel={this.props.translate('workspace.inviteMessage.personalMessagePrompt')}
autoCompleteType="off"
autoCorrect={false}
autoGrowHeight
containerStyles={[this.props.themeStyles.autoGrowHeightMultilineInput]}
defaultValue={this.state.welcomeNote}
value={this.state.welcomeNote}
onChangeText={(text) => {
this.debouncedSaveDraf(text);
this.setState({welcomeNote: text});
}}
/>
</View>
</Form>
</FullPageNotFoundView>
</ScreenWrapper>
);
}
<View style={[styles.mv4, styles.justifyContentCenter, styles.alignItemsCenter]}>
<MultipleAvatars
size={CONST.AVATAR_SIZE.LARGE}
icons={OptionsListUtils.getAvatarsForAccountIDs(_.values(props.invitedEmailsToAccountIDsDraft), props.allPersonalDetails, props.invitedEmailsToAccountIDsDraft)}
shouldStackHorizontally
shouldDisplayAvatarsInRows
secondAvatarStyle={[styles.secondAvatarInline]}
/>
</View>
<View style={[styles.mb5]}>
<Text>{translate('workspace.inviteMessage.inviteMessagePrompt')}</Text>
</View>
<View style={[styles.mb3]}>
<InputWrapper
InputComponent={TextInput}
role={CONST.ROLE.PRESENTATION}
inputID="welcomeMessage"
label={translate('workspace.inviteMessage.personalMessagePrompt')}
accessibilityLabel={translate('workspace.inviteMessage.personalMessagePrompt')}
autoCompleteType="off"
autoCorrect={false}
autoGrowHeight
containerStyles={[styles.autoGrowHeightMultilineInput]}
defaultValue={getDefaultWelcomeNote()}
value={welcomeNote}
onChangeText={(text) => {
setWelcomeNote(text);
debouncedSaveDraft(text);
}}
ref={(el) => {
if (!el) {
return;
}
inputCallbackRef(el);
updateMultilineInputRange(el);
}}
/>
</View>
</FormProvider>
</FullPageNotFoundView>
</ScreenWrapper>
);
}

WorkspaceInviteMessagePage.propTypes = propTypes;
WorkspaceInviteMessagePage.defaultProps = defaultProps;
WorkspaceInviteMessagePage.displayName = 'WorkspaceInviteMessagePage';

export default compose(
withLocalize,
withPolicyAndFullscreenLoading,
withOnyx({
allPersonalDetails: {
Expand All @@ -267,5 +230,4 @@ export default compose(
},
}),
withNavigationFocus,
withThemeStyles,
)(WorkspaceInviteMessagePage);
Loading