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 10 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
266 changes: 109 additions & 157 deletions src/pages/workspace/WorkspaceInviteMessagePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@ 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,141 @@ 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(),
};
}

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()});
if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) {
cdOut marked this conversation as resolved.
Show resolved Hide resolved
Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true);
}

componentWillUnmount() {
if (!this.focusTimeout) {
return;
}
clearTimeout(this.focusTimeout);
}
const saveDraft = (newDraft) => {
Policy.setWorkspaceInviteMessageDraft(props.route.params.policyID, newDraft);
};
cdOut marked this conversation as resolved.
Show resolved Hide resolved

getDefaultWelcomeNote() {
return this.props.translate('workspace.inviteMessage.welcomeNote', {
workspaceName: this.props.policy.name,
const getDefaultWelcomeNote = () =>
translate('workspace.inviteMessage.welcomeNote', {
workspaceName: props.policy.name,
});
}

sendInvitation() {
const welcomeMessage = props.workspaceInviteMessageDraft || getDefaultWelcomeNote();

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, welcomeMessage, 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.themeStyles.flexGrow1, styles.themeStyles.ph5]}
cdOut marked this conversation as resolved.
Show resolved Hide resolved
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.themeStyles.mv2, styles.themeStyles.alignSelfStart]}
>
<View style={[styles.themeStyles.flexRow]}>
<Text style={[styles.themeStyles.mr1, styles.themeStyles.label, styles.themeStyles.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.themeStyles.mv4, styles.themeStyles.justifyContentCenter, styles.themeStyles.alignItemsCenter]}>
<MultipleAvatars
size={CONST.AVATAR_SIZE.LARGE}
icons={OptionsListUtils.getAvatarsForAccountIDs(_.values(props.invitedEmailsToAccountIDsDraft), props.allPersonalDetails, props.invitedEmailsToAccountIDsDraft)}
shouldStackHorizontally
shouldDisplayAvatarsInRows
secondAvatarStyle={[styles.themeStyles.secondAvatarInline]}
/>
</View>
<View style={[styles.themeStyles.mb5]}>
<Text>{translate('workspace.inviteMessage.inviteMessagePrompt')}</Text>
</View>
<View style={[styles.themeStyles.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
inputStyle={[styles.themeStyles.verticalAlignTop]}
containerStyles={[styles.themeStyles.autoGrowHeightMultilineInput]}
value={welcomeMessage}
onChangeText={(text) => {
saveDraft(text);
}}
cdOut marked this conversation as resolved.
Show resolved Hide resolved
ref={(el) => {
updateMultilineInputRange(el);
cdOut marked this conversation as resolved.
Show resolved Hide resolved
inputCallbackRef(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 +220,4 @@ export default compose(
},
}),
withNavigationFocus,
withThemeStyles,
)(WorkspaceInviteMessagePage);
Loading