Skip to content

Commit

Permalink
Merge pull request Expensify#32139 from software-mansion-labs/form-re…
Browse files Browse the repository at this point in the history
…factor/workspace-invite-message-page

[Form Provider Refactor] WorkspaceInviteMessagePage
  • Loading branch information
luacmartins authored Dec 20, 2023
2 parents c09a3fe + f11b5da commit 84d6278
Showing 1 changed file with 119 additions and 157 deletions.
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) {
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);

0 comments on commit 84d6278

Please sign in to comment.