From 78314c7d4d1417fd42ab48cbe41d360f80915453 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Sat, 30 Jan 2016 18:10:04 -0300 Subject: PLT-7: Refactoring frontend (chunk 6) - User settings --- web/react/components/access_history_modal.jsx | 311 +++++++++++++++++---- web/react/components/activity_log_modal.jsx | 99 ++++++- web/react/components/confirm_modal.jsx | 6 +- web/react/components/register_app_modal.jsx | 157 +++++++++-- web/react/components/setting_item_max.jsx | 12 +- web/react/components/setting_item_min.jsx | 7 +- web/react/components/setting_picture.jsx | 32 ++- .../user_settings/custom_theme_chooser.jsx | 104 ++++++- .../user_settings/import_theme_modal.jsx | 44 ++- .../user_settings/manage_incoming_hooks.jsx | 49 +++- .../components/user_settings/manage_languages.jsx | 14 +- .../user_settings/manage_outgoing_hooks.jsx | 131 +++++++-- .../user_settings/user_settings_advanced.jsx | 101 ++++++- .../user_settings/user_settings_appearance.jsx | 40 ++- .../user_settings/user_settings_developer.jsx | 42 ++- .../user_settings/user_settings_display.jsx | 140 ++++++++-- .../user_settings/user_settings_general.jsx | 262 ++++++++++++++--- .../user_settings/user_settings_integrations.jsx | 51 +++- .../user_settings/user_settings_modal.jsx | 84 +++++- .../user_settings/user_settings_notifications.jsx | 226 ++++++++++++--- .../user_settings/user_settings_security.jsx | 153 ++++++++-- web/static/i18n/en.json | 266 +++++++++++++++++- web/static/i18n/es.json | 266 +++++++++++++++++- 23 files changed, 2266 insertions(+), 331 deletions(-) (limited to 'web') diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index 85c28ca5c..6319b5681 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -8,7 +8,188 @@ import * as AsyncClient from '../utils/async_client.jsx'; import LoadingScreen from './loading_screen.jsx'; import * as Utils from '../utils/utils.jsx'; -export default class AccessHistoryModal extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + sessionRevoked: { + id: 'access_history.sessionRevoked', + defaultMessage: 'The session with id {sessionId} was revoked' + }, + channelCreated: { + id: 'access_history.channelCreated', + defaultMessage: 'Created the {channelName} channel/group' + }, + establishedDM: { + id: 'access_history.establishedDM', + defaultMessage: 'Established a direct message channel with {username}' + }, + nameUpdated: { + id: 'access_history.nameUpdated', + defaultMessage: 'Updated the {channelName} channel/group name' + }, + headerUpdated: { + id: 'access_history.headerUpdated', + defaultMessage: 'Updated the {channelName} channel/group header' + }, + channelDeleted: { + id: 'access_history.channelDeleted', + defaultMessage: 'Deleted the channel/group with the URL {url}' + }, + userAdded: { + id: 'access_history.userAdded', + defaultMessage: 'Added {username} to the {channelName} channel/group' + }, + userRemoved: { + id: 'access_history.userRemoved', + defaultMessage: 'Removed {username} to the {channelName} channel/group' + }, + attemptedRegisterApp: { + id: 'access_history.attemptedRegisterApp', + defaultMessage: 'Attempted to register a new OAuth Application with ID {id}' + }, + attemptedAllowOAuthAccess: { + id: 'access_history.attemptedAllowOAuthAccess', + defaultMessage: 'Attempted to allow a new OAuth service access' + }, + successfullOAuthAccess: { + id: 'access_history.successfullOAuthAccess', + defaultMessage: 'Successfully gave a new OAuth service access' + }, + failedOAuthAccess: { + id: 'access_history.failedOAuthAccess', + defaultMessage: 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback' + }, + attemptedOAuthToken: { + id: 'access_history.attemptedOAuthToken', + defaultMessage: 'Attempted to get an OAuth access token' + }, + successfullOAuthToken: { + id: 'access_history.successfullOAuthToken', + defaultMessage: 'Successfully added a new OAuth service' + }, + oauthTokenFailed: { + id: 'access_history.oauthTokenFailed', + defaultMessage: 'Failed to get an OAuth access token - {token}' + }, + attemptedLogin: { + id: 'access_history.attemptedLogin', + defaultMessage: 'Attempted to login' + }, + successfullLogin: { + id: 'access_history.successfullLogin', + defaultMessage: 'Successfully logged in' + }, + failedLogin: { + id: 'access_history.failedLogin', + defaultMessage: 'FAILED login attempt' + }, + updatePicture: { + id: 'access_history.updatePicture', + defaultMessage: 'Updated your profile picture' + }, + updateGeneral: { + id: 'access_history.updateGeneral', + defaultMessage: 'Updated the general settings of your account' + }, + attemptedPassword: { + id: 'access_history.attemptedPassword', + defaultMessage: 'Attempted to change password' + }, + successfullPassword: { + id: 'access_history.successfullPassword', + defaultMessage: 'Successfully changed password' + }, + failedPassword: { + id: 'access_history.failedPassword', + defaultMessage: 'Failed to change password - tried to update user password who was logged in through oauth' + }, + updatedRol: { + id: 'access_history.updatedRol', + defaultMessage: 'Updated user role(s) to ' + }, + member: { + id: 'access_history.member', + defaultMessage: 'member' + }, + accountActive: { + id: 'access_history.accountActive', + defaultMessage: 'Account made active' + }, + accountInactive: { + id: 'access_history.accountInactive', + defaultMessage: 'Account made inactive' + }, + by: { + id: 'access_history.by', + defaultMessage: ' by {username}' + }, + byAdmin: { + id: 'access_history.byAdmin', + defaultMessage: ' by an admin' + }, + sentEmail: { + id: 'access_history.sentEmail', + defaultMessage: 'Sent an email to {email} to reset your password' + }, + attemptedReset: { + id: 'access_history.attemptedReset', + defaultMessage: 'Attempted to reset password' + }, + successfullReset: { + id: 'access_history.successfullReset', + defaultMessage: 'Successfully reset password' + }, + updateGlobalNotifications: { + id: 'access_history.updateGlobalNotifications', + defaultMessage: 'Updated your global notification settings' + }, + attemptedWebhookCreate: { + id: 'access_history.attemptedWebhookCreate', + defaultMessage: 'Attempted to create a webhook' + }, + succcessfullWebhookCreate: { + id: 'access_history.successfullWebhookCreate', + defaultMessage: 'Successfully created a webhook' + }, + failedWebhookCreate: { + id: 'access_history.failedWebhookCreate', + defaultMessage: 'Failed to create a webhook - bad channel permissions' + }, + attemptedWebhookDelete: { + id: 'access_history.attemptedWebhookDelete', + defaultMessage: 'Attempted to delete a webhook' + }, + successfullWebhookDelete: { + id: 'access_history.successfullWebhookDelete', + defaultMessage: 'Successfully deleted a webhook' + }, + failedWebhookDelete: { + id: 'access_history.failedWebhookDelete', + defaultMessage: 'Failed to delete a webhook - inappropriate conditions' + }, + logout: { + id: 'access_history.logout', + defaultMessage: 'Logged out of your account' + }, + verified: { + id: 'access_history.verified', + defaultMessage: 'Sucessfully verified your email address' + }, + revokedAll: { + id: 'access_history.revokedAll', + defaultMessage: 'Revoked all current sessions for the team' + }, + loginAttempt: { + id: 'access_history.loginAttempt', + defaultMessage: ' (Login attempt)' + }, + loginFailure: { + id: 'access_history.loginFailure', + defaultMessage: ' (Login failure)' + } +}); + +class AccessHistoryModal extends React.Component { constructor(props) { super(props); @@ -70,11 +251,12 @@ export default class AccessHistoryModal extends React.Component { this.setState({moreInfo: newMoreInfo}); } handleRevokedSession(sessionId) { - return 'The session with id ' + sessionId + ' was revoked'; + return this.props.intl.formatMessage(holders.sessionRevoked, {sessionId: sessionId}); } formatAuditInfo(currentAudit) { const currentActionURL = currentAudit.action.replace(/\/api\/v[1-9]/, ''); + const {formatMessage} = this.props.intl; let currentAuditDesc = ''; if (currentActionURL.indexOf('/channels') === 0) { @@ -96,17 +278,17 @@ export default class AccessHistoryModal extends React.Component { switch (currentActionURL) { case '/channels/create': - currentAuditDesc = 'Created the ' + channelName + ' channel/group'; + currentAuditDesc = formatMessage(holders.channelCreated, {channelName: channelName}); break; case '/channels/create_direct': - currentAuditDesc = 'Established a direct message channel with ' + Utils.getDirectTeammate(channelObj.id).username; + currentAuditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username}); break; case '/channels/update': - currentAuditDesc = 'Updated the ' + channelName + ' channel/group name'; + currentAuditDesc = formatMessage(holders.nameUpdated, {channelName: channelName}); break; case '/channels/update_desc': // support the old path case '/channels/update_header': - currentAuditDesc = 'Updated the ' + channelName + ' channel/group header'; + currentAuditDesc = formatMessage(holders.headerUpdated, {channelName: channelName}); break; default: { let userIdField = []; @@ -123,11 +305,11 @@ export default class AccessHistoryModal extends React.Component { } if (/\/channels\/[A-Za-z0-9]+\/delete/.test(currentActionURL)) { - currentAuditDesc = 'Deleted the channel/group with the URL ' + channelURL; + currentAuditDesc = formatMessage(holders.channelDeleted, {url: channelURL}); } else if (/\/channels\/[A-Za-z0-9]+\/add/.test(currentActionURL)) { - currentAuditDesc = 'Added ' + username + ' to the ' + channelName + ' channel/group'; + currentAuditDesc = formatMessage(holders.userAdded, {username: username, channelName: channelName}); } else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(currentActionURL)) { - currentAuditDesc = 'Removed ' + username + ' from the ' + channelName + ' channel/group'; + currentAuditDesc = formatMessage(holders.userRemoved, {username: username, channelName: channelName}); } break; @@ -141,31 +323,31 @@ export default class AccessHistoryModal extends React.Component { const clientIdField = oauthInfo[0].split('='); if (clientIdField[0] === 'client_id') { - currentAuditDesc = 'Attempted to register a new OAuth Application with ID ' + clientIdField[1]; + currentAuditDesc = formatMessage(holders.attemptedRegisterApp, {id: clientIdField[1]}); } break; } case '/oauth/allow': if (oauthInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to allow a new OAuth service access'; + currentAuditDesc = formatMessage(holders.attemptedAllowOAuthAccess); } else if (oauthInfo[0] === 'success') { - currentAuditDesc = 'Successfully gave a new OAuth service access'; + currentAuditDesc = formatMessage(holders.successfullOAuthAccess); } else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') { - currentAuditDesc = 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'; + currentAuditDesc = formatMessage(holders.failedOAuthAccess); } break; case '/oauth/access_token': if (oauthInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to get an OAuth access token'; + currentAuditDesc = formatMessage(holders.attemptedOAuthToken); } else if (oauthInfo[0] === 'success') { - currentAuditDesc = 'Successfully added a new OAuth service'; + currentAuditDesc = formatMessage(holders.successfullOAuthToken); } else { const oauthTokenFailure = oauthInfo[0].split('-'); if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) { - currentAuditDesc = 'Failed to get an OAuth access token - ' + oauthTokenFailure[1].trim(); + currentAuditDesc = formatMessage(oauthTokenFailure, {token: oauthTokenFailure[1].trim()}); } } @@ -179,11 +361,11 @@ export default class AccessHistoryModal extends React.Component { switch (currentActionURL) { case '/users/login': if (userInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to login'; + currentAuditDesc = formatMessage(holders.attemptedLogin); } else if (userInfo[0] === 'success') { - currentAuditDesc = 'Successfully logged in'; + currentAuditDesc = formatMessage(holders.successfullLogin); } else if (userInfo[0]) { - currentAuditDesc = 'FAILED login attempt'; + currentAuditDesc = formatMessage(holders.failedLogin); } break; @@ -191,29 +373,29 @@ export default class AccessHistoryModal extends React.Component { currentAuditDesc = this.handleRevokedSession(userInfo[0].split('=')[1]); break; case '/users/newimage': - currentAuditDesc = 'Updated your profile picture'; + currentAuditDesc = formatMessage(holders.updatePicture); break; case '/users/update': - currentAuditDesc = 'Updated the general settings of your account'; + currentAuditDesc = formatMessage(holders.updateGeneral); break; case '/users/newpassword': if (userInfo[0] === 'attempted') { - currentAuditDesc = 'Attempted to change password'; + currentAuditDesc = formatMessage(holders.attemptedPassword); } else if (userInfo[0] === 'completed') { - currentAuditDesc = 'Successfully changed password'; + currentAuditDesc = formatMessage(holders.successfullPassword); } else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') { - currentAuditDesc = 'Failed to change password - tried to update user password who was logged in through oauth'; + currentAuditDesc = formatMessage(holders.failedPassword); } break; case '/users/update_roles': { const userRoles = userInfo[0].split('=')[1]; - currentAuditDesc = 'Updated user role(s) to '; + currentAuditDesc = formatMessage(holders.updatedRol); if (userRoles.trim()) { currentAuditDesc += userRoles; } else { - currentAuditDesc += 'member'; + currentAuditDesc += formatMessage(holders.member); } break; @@ -225,9 +407,9 @@ export default class AccessHistoryModal extends React.Component { /* Either describes account activation/deactivation or a revoked session as part of an account deactivation */ if (updateType === 'active') { if (updateField === 'true') { - currentAuditDesc = 'Account made active'; + currentAuditDesc = formatMessage(holders.accountActive); } else if (updateField === 'false') { - currentAuditDesc = 'Account made inactive'; + currentAuditDesc = formatMessage(holders.accountInactive); } const actingUserInfo = userInfo[1].split('='); @@ -235,9 +417,9 @@ export default class AccessHistoryModal extends React.Component { const actingUser = UserStore.getProfile(actingUserInfo[1]); const currentUser = UserStore.getCurrentUser(); if (currentUser && actingUser && (Utils.isAdmin(currentUser.roles) || Utils.isSystemAdmin(currentUser.roles))) { - currentAuditDesc += ' by ' + actingUser.username; + currentAuditDesc += formatMessage(holders.by, {username: actingUser.username}); } else if (currentUser && actingUser) { - currentAuditDesc += ' by an admin'; + currentAuditDesc += formatMessage(holders.byAdmin); } } } else if (updateType === 'session_id') { @@ -247,18 +429,18 @@ export default class AccessHistoryModal extends React.Component { break; } case '/users/send_password_reset': - currentAuditDesc = 'Sent an email to ' + userInfo[0].split('=')[1] + ' to reset your password'; + currentAuditDesc = formatMessage(holders.sentEmail, {email: userInfo[0].split('=')[1]}); break; case '/users/reset_password': if (userInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to reset password'; + currentAuditDesc = formatMessage(holders.attemptedReset); } else if (userInfo[0] === 'success') { - currentAuditDesc = 'Successfully reset password'; + currentAuditDesc = formatMessage(holders.successfullReset); } break; case '/users/update_notify': - currentAuditDesc = 'Updated your global notification settings'; + currentAuditDesc = formatMessage(holders.updateGlobalNotifications); break; default: break; @@ -269,21 +451,21 @@ export default class AccessHistoryModal extends React.Component { switch (currentActionURL) { case '/hooks/incoming/create': if (webhookInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to create a webhook'; + currentAuditDesc = formatMessage(holders.attemptedWebhookCreate); } else if (webhookInfo[0] === 'success') { - currentAuditDesc = 'Successfully created a webhook'; + currentAuditDesc = formatMessage(holders.succcessfullWebhookCreate); } else if (webhookInfo[0] === 'fail - bad channel permissions') { - currentAuditDesc = 'Failed to create a webhook - bad channel permissions'; + currentAuditDesc = formatMessage(holders.failedWebhookCreate); } break; case '/hooks/incoming/delete': if (webhookInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to delete a webhook'; + currentAuditDesc = formatMessage(holders.attemptedWebhookDelete); } else if (webhookInfo[0] === 'success') { - currentAuditDesc = 'Successfully deleted a webhook'; + currentAuditDesc = formatMessage(holders.successfullWebhookDelete); } else if (webhookInfo[0] === 'fail - inappropriate conditions') { - currentAuditDesc = 'Failed to delete a webhook - inappropriate conditions'; + currentAuditDesc = formatMessage(holders.failedWebhookDelete); } break; @@ -293,10 +475,10 @@ export default class AccessHistoryModal extends React.Component { } else { switch (currentActionURL) { case '/logout': - currentAuditDesc = 'Logged out of your account'; + currentAuditDesc = formatMessage(holders.logout); break; case '/verify_email': - currentAuditDesc = 'Sucessfully verified your email address'; + currentAuditDesc = formatMessage(holders.verified); break; default: break; @@ -307,7 +489,7 @@ export default class AccessHistoryModal extends React.Component { if (!currentAuditDesc) { /* Currently not called anywhere */ if (currentAudit.extra_info.indexOf('revoked_all=') >= 0) { - currentAuditDesc = 'Revoked all current sessions for the team'; + currentAuditDesc = formatMessage(holders.revokedAll); } else { let currentActionDesc = ''; if (currentActionURL && currentActionURL.lastIndexOf('/') !== -1) { @@ -328,12 +510,14 @@ export default class AccessHistoryModal extends React.Component { } const currentDate = new Date(currentAudit.create_at); - const currentAuditInfo = currentDate.toDateString() + ' - ' + currentDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc; + const currentAuditInfo = currentDate.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' + + currentDate.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc; return currentAuditInfo; } render() { var accessList = []; + const {formatMessage} = this.props.intl; for (var i = 0; i < this.state.audits.length; i++) { const currentAudit = this.state.audits[i]; const currentAuditInfo = this.formatAuditInfo(currentAudit); @@ -344,7 +528,10 @@ export default class AccessHistoryModal extends React.Component { className='theme' onClick={this.handleMoreInfo.bind(this, i)} > - {'More info'} + ); @@ -354,17 +541,33 @@ export default class AccessHistoryModal extends React.Component { if (currentAudit.action.search('/users/login') >= 0) { if (currentAudit.extra_info === 'attempt') { - currentAudit.session_id += ' (Login attempt)'; + currentAudit.session_id += formatMessage(holders.loginAttempt); } else { - currentAudit.session_id += ' (Login failure)'; + currentAudit.session_id += formatMessage(holders.loginFailure); } } } moreInfo = (
-
{'IP: ' + currentAudit.ip_address}
-
{'Session ID: ' + currentAudit.session_id}
+
+ +
+
+ +
); } @@ -404,7 +607,12 @@ export default class AccessHistoryModal extends React.Component { bsSize='large' > - {'Access History'} + + + {content} @@ -415,6 +623,9 @@ export default class AccessHistoryModal extends React.Component { } AccessHistoryModal.propTypes = { + intl: intlShape.isRequired, show: React.PropTypes.bool.isRequired, onHide: React.PropTypes.func.isRequired }; + +export default injectIntl(AccessHistoryModal); \ No newline at end of file diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 2c42f5971..f8a2af571 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -8,6 +8,8 @@ const Modal = ReactBootstrap.Modal; import LoadingScreen from './loading_screen.jsx'; import * as Utils from '../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class ActivityLogModal extends React.Component { constructor(props) { super(props); @@ -102,16 +104,31 @@ export default class ActivityLogModal extends React.Component { devicePicture = 'fa fa-windows'; } else if (currentSession.device_id && currentSession.device_id.indexOf('apple:') === 0) { devicePicture = 'fa fa-apple'; - devicePlatform = 'iPhone Native App'; + devicePlatform = ( + + ); } else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) { - devicePlatform = 'Android Native App'; + devicePlatform = ( + + ); devicePicture = 'fa fa-android'; } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') { devicePicture = 'fa fa-apple'; } else if (currentSession.props.platform === 'Linux') { if (currentSession.props.os.indexOf('Android') >= 0) { - devicePlatform = 'Android'; + devicePlatform = ( + + ); devicePicture = 'fa fa-android'; } else { devicePicture = 'fa fa-linux'; @@ -122,10 +139,43 @@ export default class ActivityLogModal extends React.Component { if (this.state.moreInfo[i]) { moreInfo = (
-
{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}
-
{`OS: ${currentSession.props.os}`}
-
{`Browser: ${currentSession.props.browser}`}
-
{`Session ID: ${currentSession.id}`}
+
+ +
+
+ +
+
+ +
+
+ +
); } else { @@ -135,7 +185,10 @@ export default class ActivityLogModal extends React.Component { href='#' onClick={this.handleMoreInfo.bind(this, i)} > - More info + ); } @@ -148,7 +201,16 @@ export default class ActivityLogModal extends React.Component {
{devicePlatform}
-
{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}
+
+ +
{moreInfo}
@@ -157,7 +219,10 @@ export default class ActivityLogModal extends React.Component { onClick={this.submitRevoke.bind(this, currentSession.id)} className='btn btn-primary' > - Logout + @@ -178,10 +243,20 @@ export default class ActivityLogModal extends React.Component { bsSize='large' > - {'Active Sessions'} + + + -

{'Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the \'Logout\' button below to end a session.'}

+

+ +

{content}
diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx index cdef1c1ea..987649f38 100644 --- a/web/react/components/confirm_modal.jsx +++ b/web/react/components/confirm_modal.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {FormattedMessage} from 'mm-intl'; const Modal = ReactBootstrap.Modal; export default class ConfirmModal extends React.Component { @@ -33,7 +34,10 @@ export default class ConfirmModal extends React.Component { className='btn btn-default' onClick={this.props.onCancel} > - {'Cancel'} +