forked from Ivasoft/mattermost-mobile
620 lines
20 KiB
JavaScript
620 lines
20 KiB
JavaScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import React, {PureComponent} from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import {intlShape} from 'react-intl';
|
|
import {Alert, View} from 'react-native';
|
|
import RNFetchBlob from 'rn-fetch-blob';
|
|
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
|
import {DocumentPickerUtil} from 'react-native-document-picker';
|
|
|
|
import {Client4} from 'mattermost-redux/client';
|
|
|
|
import {buildFileUploadData, encodeHeaderURIStringToUTF8} from 'app/utils/file';
|
|
import {emptyFunction} from 'app/utils/general';
|
|
import {preventDoubleTap} from 'app/utils/tap';
|
|
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|
import {t} from 'app/utils/i18n';
|
|
|
|
import TextSetting from 'app/components/widgets/settings/text_setting';
|
|
import Loading from 'app/components/loading';
|
|
import ErrorText from 'app/components/error_text';
|
|
import StatusBar from 'app/components/status_bar/index';
|
|
import ProfilePictureButton from 'app/components/profile_picture_button';
|
|
import ProfilePicture from 'app/components/profile_picture';
|
|
import mattermostBucket from 'app/mattermost_bucket';
|
|
|
|
import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
|
|
|
|
const MAX_SIZE = 20 * 1024 * 1024;
|
|
const holders = {
|
|
firstName: {
|
|
id: t('user.settings.general.firstName'),
|
|
defaultMessage: 'First Name',
|
|
},
|
|
lastName: {
|
|
id: t('user.settings.general.lastName'),
|
|
defaultMessage: 'Last Name',
|
|
},
|
|
username: {
|
|
id: t('user.settings.general.username'),
|
|
defaultMessage: 'Username',
|
|
},
|
|
nickname: {
|
|
id: t('user.settings.general.nickname'),
|
|
defaultMessage: 'Nickname',
|
|
},
|
|
position: {
|
|
id: t('user.settings.general.position'),
|
|
defaultMessage: 'Position',
|
|
},
|
|
email: {
|
|
id: t('user.settings.general.email'),
|
|
defaultMessage: 'Email',
|
|
},
|
|
};
|
|
|
|
export default class EditProfile extends PureComponent {
|
|
static propTypes = {
|
|
actions: PropTypes.shape({
|
|
setProfileImageUri: PropTypes.func.isRequired,
|
|
removeProfileImage: PropTypes.func.isRequired,
|
|
updateUser: PropTypes.func.isRequired,
|
|
}).isRequired,
|
|
config: PropTypes.object.isRequired,
|
|
currentUser: PropTypes.object.isRequired,
|
|
navigator: PropTypes.object.isRequired,
|
|
theme: PropTypes.object.isRequired,
|
|
};
|
|
|
|
static contextTypes = {
|
|
intl: intlShape,
|
|
};
|
|
|
|
leftButton = {
|
|
id: 'close-settings',
|
|
};
|
|
|
|
rightButton = {
|
|
id: 'update-profile',
|
|
disabled: true,
|
|
showAsAction: 'always',
|
|
};
|
|
|
|
constructor(props, context) {
|
|
super(props);
|
|
|
|
const {email, first_name: firstName, last_name: lastName, nickname, position, username} = props.currentUser;
|
|
const buttons = {
|
|
leftButtons: [this.leftButton],
|
|
rightButtons: [this.rightButton],
|
|
};
|
|
|
|
this.leftButton.title = context.intl.formatMessage({id: t('mobile.account.settings.cancel'), defaultMessage: 'Cancel'});
|
|
this.rightButton.title = context.intl.formatMessage({id: t('mobile.account.settings.save'), defaultMessage: 'Save'});
|
|
|
|
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
|
props.navigator.setButtons(buttons);
|
|
|
|
this.state = {
|
|
email,
|
|
firstName,
|
|
lastName,
|
|
nickname,
|
|
position,
|
|
username,
|
|
};
|
|
}
|
|
|
|
canUpdate = (updatedField) => {
|
|
const {currentUser} = this.props;
|
|
const keys = Object.keys(this.state);
|
|
const newState = {...this.state, ...(updatedField || {})};
|
|
Reflect.deleteProperty(newState, 'error');
|
|
Reflect.deleteProperty(newState, 'updating');
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
let userKey;
|
|
switch (key) {
|
|
case 'firstName':
|
|
userKey = 'first_name';
|
|
break;
|
|
case 'lastName':
|
|
userKey = 'last_name';
|
|
break;
|
|
default:
|
|
userKey = key;
|
|
break;
|
|
}
|
|
|
|
if (currentUser[userKey] !== newState[key]) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
close = () => {
|
|
this.props.navigator.dismissModal({
|
|
animationType: 'slide-down',
|
|
});
|
|
};
|
|
|
|
emitCanUpdateAccount = (enabled) => {
|
|
const buttons = {
|
|
rightButtons: [{...this.rightButton, disabled: !enabled}],
|
|
};
|
|
|
|
this.props.navigator.setButtons(buttons);
|
|
};
|
|
|
|
handleRequestError = (error) => {
|
|
this.setState({error, updating: false});
|
|
this.emitCanUpdateAccount(true);
|
|
if (this.scrollView) {
|
|
this.scrollView.props.scrollToPosition(0, 0);
|
|
}
|
|
};
|
|
|
|
submitUser = preventDoubleTap(async () => {
|
|
this.emitCanUpdateAccount(false);
|
|
this.setState({error: null, updating: true});
|
|
|
|
const {
|
|
profileImage,
|
|
profileImageRemove,
|
|
firstName,
|
|
lastName,
|
|
username,
|
|
nickname,
|
|
position,
|
|
email,
|
|
} = this.state;
|
|
const user = {
|
|
first_name: firstName,
|
|
last_name: lastName,
|
|
username,
|
|
nickname,
|
|
position,
|
|
email,
|
|
};
|
|
const {actions, currentUser} = this.props;
|
|
|
|
if (profileImage) {
|
|
actions.setProfileImageUri(profileImage.uri);
|
|
this.uploadProfileImage().catch(this.handleUploadError);
|
|
}
|
|
|
|
if (profileImageRemove) {
|
|
actions.removeProfileImage(currentUser.id);
|
|
}
|
|
|
|
if (this.canUpdate()) {
|
|
const {error} = await actions.updateUser(user);
|
|
if (error) {
|
|
this.handleRequestError(error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.close();
|
|
});
|
|
|
|
handleUploadProfileImage = (images) => {
|
|
const image = images && images.length > 0 && images[0];
|
|
this.setState({profileImage: image});
|
|
this.emitCanUpdateAccount(true);
|
|
};
|
|
|
|
handleRemoveProfileImage = () => {
|
|
this.setState({profileImageRemove: true});
|
|
this.emitCanUpdateAccount(true);
|
|
this.props.navigator.dismissModal({
|
|
animationType: 'none',
|
|
});
|
|
}
|
|
|
|
uploadProfileImage = async () => {
|
|
const {profileImage} = this.state;
|
|
const {currentUser} = this.props;
|
|
const fileData = buildFileUploadData(profileImage);
|
|
|
|
const headers = {
|
|
Authorization: `Bearer ${Client4.getToken()}`,
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'Content-Type': 'multipart/form-data',
|
|
'X-CSRF-Token': Client4.csrf,
|
|
};
|
|
|
|
const fileInfo = {
|
|
name: 'image',
|
|
filename: encodeHeaderURIStringToUTF8(fileData.name),
|
|
data: RNFetchBlob.wrap(profileImage.uri.replace('file://', '')),
|
|
type: fileData.type,
|
|
};
|
|
|
|
const certificate = await mattermostBucket.getPreference('cert');
|
|
const options = {
|
|
timeout: 10000,
|
|
certificate,
|
|
};
|
|
|
|
return RNFetchBlob.config(options).fetch('POST', `${Client4.getUserRoute(currentUser.id)}/image`, headers, [fileInfo]);
|
|
};
|
|
|
|
updateField = (id, name) => {
|
|
const field = {[id]: name};
|
|
this.setState(field, () => {
|
|
this.emitCanUpdateAccount(this.canUpdate(field));
|
|
});
|
|
};
|
|
|
|
onNavigatorEvent = (event) => {
|
|
if (event.type === 'NavBarButtonPress') {
|
|
switch (event.id) {
|
|
case 'update-profile':
|
|
this.submitUser();
|
|
break;
|
|
case 'close-settings':
|
|
this.close();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
onShowFileSizeWarning = (filename) => {
|
|
const {formatMessage} = this.context.intl;
|
|
const fileSizeWarning = formatMessage({
|
|
id: 'file_upload.fileAbove',
|
|
defaultMessage: 'File above {max}MB cannot be uploaded: {filename}',
|
|
}, {
|
|
max: getFormattedFileSize({size: MAX_SIZE}),
|
|
filename,
|
|
});
|
|
|
|
Alert.alert(fileSizeWarning);
|
|
};
|
|
|
|
renderFirstNameSettings = () => {
|
|
const {formatMessage} = this.context.intl;
|
|
const {config, currentUser, theme} = this.props;
|
|
const {firstName} = this.state;
|
|
|
|
const {auth_service: service} = currentUser;
|
|
const disabled = (service === 'ldap' && config.LdapFristNameAttributeSet === 'true') ||
|
|
(service === 'saml' && config.SamlFirstNameAttributeSet === 'true');
|
|
|
|
return (
|
|
<TextSetting
|
|
disabled={disabled}
|
|
id='firstName'
|
|
label={holders.firstName}
|
|
disabledText={formatMessage({
|
|
id: 'user.settings.general.field_handled_externally',
|
|
defaultMessage: 'This field is handled through your login provider. If you want to change it, you need to do so through your login provider.',
|
|
})}
|
|
onChange={this.updateField}
|
|
theme={theme}
|
|
value={firstName}
|
|
/>
|
|
);
|
|
};
|
|
|
|
renderLastNameSettings = () => {
|
|
const {formatMessage} = this.context.intl;
|
|
const {config, currentUser, theme} = this.props;
|
|
const {lastName} = this.state;
|
|
|
|
const {auth_service: service} = currentUser;
|
|
const disabled = (service === 'ldap' && config.LdapLastNameAttributeSet === 'true') ||
|
|
(service === 'saml' && config.SamlLastNameAttributeSet === 'true');
|
|
|
|
return (
|
|
<View>
|
|
<TextSetting
|
|
disabled={disabled}
|
|
id='lastName'
|
|
label={holders.lastName}
|
|
disabledText={formatMessage({
|
|
id: 'user.settings.general.field_handled_externally',
|
|
defaultMessage: 'This field is handled through your login provider. If you want to change it, you need to do so through your login provider.',
|
|
})}
|
|
onChange={this.updateField}
|
|
theme={theme}
|
|
value={lastName}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
renderUsernameSettings = () => {
|
|
const {formatMessage} = this.context.intl;
|
|
const {currentUser, theme} = this.props;
|
|
const {username} = this.state;
|
|
const disabled = currentUser.auth_service !== '';
|
|
|
|
return (
|
|
<TextSetting
|
|
disabled={disabled}
|
|
id='username'
|
|
label={holders.username}
|
|
disabledText={formatMessage({
|
|
id: 'user.settings.general.field_handled_externally',
|
|
defaultMessage: 'This field is handled through your login provider. If you want to change it, you need to do so through your login provider.',
|
|
})}
|
|
maxLength={22}
|
|
onChange={this.updateField}
|
|
theme={theme}
|
|
value={username}
|
|
/>
|
|
);
|
|
};
|
|
|
|
renderEmailSettings = () => {
|
|
const {formatMessage} = this.context.intl;
|
|
const {currentUser, theme} = this.props;
|
|
const {email} = this.state;
|
|
|
|
let helpText;
|
|
|
|
if (currentUser.auth_service === '') {
|
|
helpText = formatMessage({
|
|
id: 'user.settings.general.emailCantUpdate',
|
|
defaultMessage: 'Email must be updated using a web client or desktop application.',
|
|
});
|
|
} else {
|
|
switch (currentUser.auth_service) {
|
|
case 'gitlab':
|
|
helpText = formatMessage({
|
|
id: 'user.settings.general.emailGitlabCantUpdate',
|
|
defaultMessage: 'Login occurs through GitLab. Email cannot be updated. Email address used for notifications is {email}.',
|
|
}, {email});
|
|
break;
|
|
case 'google':
|
|
helpText = formatMessage({
|
|
id: 'user.settings.general.emailGoogleCantUpdate',
|
|
defaultMessage: 'Login occurs through Google Apps. Email cannot be updated. Email address used for notifications is {email}.',
|
|
}, {email});
|
|
break;
|
|
case 'office365':
|
|
helpText = formatMessage({
|
|
id: 'user.settings.general.emailOffice365CantUpdate',
|
|
defaultMessage: 'Login occurs through Office 365. Email cannot be updated. Email address used for notifications is {email}.',
|
|
}, {email});
|
|
break;
|
|
case 'ldap':
|
|
helpText = formatMessage({
|
|
id: 'user.settings.general.emailLdapCantUpdate',
|
|
defaultMessage: 'Login occurs through AD/LDAP. Email cannot be updated. Email address used for notifications is {email}.',
|
|
}, {email});
|
|
break;
|
|
case 'saml':
|
|
helpText = formatMessage({
|
|
id: 'user.settings.general.emailSamlCantUpdate',
|
|
defaultMessage: 'Login occurs through SAML. Email cannot be updated. Email address used for notifications is {email}.',
|
|
}, {email});
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<View>
|
|
<TextSetting
|
|
disabled={true}
|
|
id='email'
|
|
label={holders.email}
|
|
disabledText={helpText}
|
|
onChange={this.updateField}
|
|
theme={theme}
|
|
value={email}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
renderNicknameSettings = () => {
|
|
const {formatMessage} = this.context.intl;
|
|
const {config, currentUser, theme} = this.props;
|
|
const {nickname} = this.state;
|
|
|
|
const {auth_service: service} = currentUser;
|
|
const disabled = (service === 'ldap' && config.LdapNicknameAttributeSet === 'true') ||
|
|
(service === 'saml' && config.SamlNicknameAttributeSet === 'true');
|
|
|
|
return (
|
|
<TextSetting
|
|
disabled={disabled}
|
|
id='nickname'
|
|
label={holders.nickname}
|
|
disabledText={formatMessage({
|
|
id: 'user.settings.general.field_handled_externally',
|
|
defaultMessage: 'This field is handled through your login provider. If you want to change it, you need to do so through your login provider.',
|
|
})}
|
|
maxLength={22}
|
|
onChange={this.updateField}
|
|
theme={theme}
|
|
value={nickname}
|
|
/>
|
|
);
|
|
};
|
|
|
|
renderPositionSettings = () => {
|
|
const {formatMessage} = this.context.intl;
|
|
const {config, currentUser, theme} = this.props;
|
|
const {position} = this.state;
|
|
|
|
const {auth_service: service} = currentUser;
|
|
const disabled = (service === 'ldap' || service === 'saml') && config.PositionAttribute === 'true';
|
|
|
|
return (
|
|
<TextSetting
|
|
disabled={disabled}
|
|
id='position'
|
|
label={holders.position}
|
|
disabledText={formatMessage({
|
|
id: 'user.settings.general.field_handled_externally',
|
|
defaultMessage: 'This field is handled through your login provider. If you want to change it, you need to do so through your login provider.',
|
|
})}
|
|
maxLength={128}
|
|
onChange={this.updateField}
|
|
theme={theme}
|
|
value={position}
|
|
/>
|
|
);
|
|
};
|
|
|
|
scrollViewRef = (ref) => {
|
|
this.scrollView = ref;
|
|
};
|
|
|
|
renderProfilePicture = () => {
|
|
const {
|
|
currentUser,
|
|
theme,
|
|
navigator,
|
|
} = this.props;
|
|
|
|
const {
|
|
profileImage,
|
|
profileImageRemove,
|
|
} = this.state;
|
|
|
|
const style = getStyleSheet(theme);
|
|
const uri = profileImage ? profileImage.uri : null;
|
|
|
|
return (
|
|
<View style={style.top}>
|
|
<ProfilePictureButton
|
|
currentUser={currentUser}
|
|
theme={theme}
|
|
blurTextBox={emptyFunction}
|
|
browseFileTypes={DocumentPickerUtil.images()}
|
|
canTakeVideo={false}
|
|
canBrowseVideoLibrary={false}
|
|
maxFileSize={MAX_SIZE}
|
|
navigator={navigator}
|
|
wrapper={true}
|
|
uploadFiles={this.handleUploadProfileImage}
|
|
removeProfileImage={this.handleRemoveProfileImage}
|
|
onShowFileSizeWarning={this.onShowFileSizeWarning}
|
|
>
|
|
<ProfilePicture
|
|
userId={currentUser.id}
|
|
size={150}
|
|
statusBorderWidth={6}
|
|
statusSize={40}
|
|
edit={true}
|
|
imageUri={uri}
|
|
profileImageRemove={profileImageRemove}
|
|
/>
|
|
</ProfilePictureButton>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const {theme} = this.props;
|
|
|
|
const {
|
|
error,
|
|
updating,
|
|
} = this.state;
|
|
|
|
const style = getStyleSheet(theme);
|
|
|
|
if (updating) {
|
|
return (
|
|
<View style={[style.container, style.flex]}>
|
|
<StatusBar/>
|
|
<Loading/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
let displayError;
|
|
if (error) {
|
|
displayError = (
|
|
<View style={style.errorContainer}>
|
|
<View style={style.errorWrapper}>
|
|
<ErrorText
|
|
error={error}
|
|
textStyle={style.errorText}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={style.flex}>
|
|
<StatusBar/>
|
|
<KeyboardAwareScrollView
|
|
bounces={false}
|
|
innerRef={this.scrollViewRef}
|
|
style={style.container}
|
|
>
|
|
{displayError}
|
|
<View style={[style.scrollView]}>
|
|
{this.renderProfilePicture()}
|
|
{this.renderFirstNameSettings()}
|
|
<View style={style.separator}/>
|
|
{this.renderLastNameSettings()}
|
|
<View style={style.separator}/>
|
|
{this.renderUsernameSettings()}
|
|
<View style={style.separator}/>
|
|
{this.renderEmailSettings()}
|
|
<View style={style.separator}/>
|
|
{this.renderNicknameSettings()}
|
|
<View style={style.separator}/>
|
|
{this.renderPositionSettings()}
|
|
<View style={style.footer}/>
|
|
</View>
|
|
</KeyboardAwareScrollView>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|
return {
|
|
flex: {
|
|
flex: 1,
|
|
},
|
|
container: {
|
|
backgroundColor: theme.centerChannelBg,
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
|
|
paddingTop: 10,
|
|
},
|
|
top: {
|
|
padding: 25,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
errorContainer: {
|
|
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
|
|
width: '100%',
|
|
},
|
|
errorWrapper: {
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
errorText: {
|
|
fontSize: 14,
|
|
marginHorizontal: 15,
|
|
},
|
|
separator: {
|
|
height: 15,
|
|
},
|
|
footer: {
|
|
height: 40,
|
|
width: '100%',
|
|
},
|
|
};
|
|
});
|