From a6102e27d48d00fcc733c4d16754961903a239e0 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Sun, 31 Jan 2016 22:03:30 -0300 Subject: PLT-7: Refactoring frontend (chunk 8) - Sidebar and related components - Small Tweak to demotion and add msg for terminal cmd --- web/react/components/about_build_modal.jsx | 65 ++++++-- web/react/components/admin_console/user_item.jsx | 13 +- web/react/components/change_url_modal.jsx | 54 ++++++- web/react/components/get_link_modal.jsx | 22 ++- .../components/get_team_invite_link_modal.jsx | 27 +++- web/react/components/invite_member_modal.jsx | 126 ++++++++++++--- web/react/components/member_list_team_item.jsx | 50 +++++- web/react/components/more_channels.jsx | 45 +++++- web/react/components/more_direct_channels.jsx | 71 +++++++-- web/react/components/navbar_dropdown.jsx | 68 ++++++-- web/react/components/new_channel_flow.jsx | 66 ++++++-- web/react/components/new_channel_modal.jsx | 118 +++++++++++--- web/react/components/setting_upload.jsx | 21 ++- web/react/components/sidebar.jsx | 109 +++++++++---- web/react/components/sidebar_header.jsx | 23 ++- web/react/components/team_export_tab.jsx | 44 +++++- web/react/components/team_general_tab.jsx | 176 +++++++++++++++++---- web/react/components/team_import_tab.jsx | 67 ++++++-- web/react/components/team_members_modal.jsx | 15 +- web/react/components/team_settings_modal.jsx | 34 +++- web/react/components/unread_channel_indicator.jsx | 2 +- web/static/i18n/en.json | 156 +++++++++++++++++- web/static/i18n/es.json | 159 ++++++++++++++++++- 23 files changed, 1307 insertions(+), 224 deletions(-) (limited to 'web') diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx index f70027498..fe48bb48e 100644 --- a/web/react/components/about_build_modal.jsx +++ b/web/react/components/about_build_modal.jsx @@ -3,6 +3,8 @@ var Modal = ReactBootstrap.Modal; +import {FormattedMessage} from 'mm-intl'; + export default class AboutBuildModal extends React.Component { constructor(props) { super(props); @@ -17,13 +19,28 @@ export default class AboutBuildModal extends React.Component { const config = global.window.mm_config; const license = global.window.mm_license; - let title = 'Team Edition'; + let title = ( + + ); let licensee; if (config.BuildEnterpriseReady === 'true' && license.IsLicensed === 'true') { - title = 'Enterprise Edition'; + title = ( + + ); licensee = (
-
{'Licensed by:'}
+
+ +
{license.Company}
); @@ -35,25 +52,50 @@ export default class AboutBuildModal extends React.Component { onHide={this.doHide} > - {'About Mattermost'} + + + -

{`Mattermost ${title}`}

+

{'Mattermost'} {title}

{licensee}
-
{'Version:'}
+
+ +
{config.Version}
-
{'Build Number:'}
+
+ +
{config.BuildNumber}
-
{'Build Date:'}
+
+ +
{config.BuildDate}
-
{'Build Hash:'}
+
+ +
{config.BuildHash}
@@ -63,7 +105,10 @@ export default class AboutBuildModal extends React.Component { className='btn btn-default' onClick={this.doHide} > - {'Close'} + diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 5ab429dd5..02b01b090 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -9,7 +9,7 @@ import TeamStore from '../../stores/team_store.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; -var messages = defineMessages({ +var holders = defineMessages({ confirmDemoteRoleTitle: { id: 'admin.user_item.confirmDemoteRoleTitle', defaultMessage: 'Confirm demotion from System Admin role' @@ -21,6 +21,10 @@ var messages = defineMessages({ confirmDemoteDescription: { id: 'admin.user_item.confirmDemoteDescription', defaultMessage: 'If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.' + }, + confirmDemotionCmd: { + id: 'admin.user_item.confirmDemotionCmd', + defaultMessage: 'platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"' } }); @@ -332,14 +336,15 @@ export default class UserItem extends React.Component { ); } const me = UserStore.getCurrentUser(); + const {formatMessage} = this.props.intl; let makeDemoteModal = null; if (this.props.user.id === me.id) { makeDemoteModal = ( diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx index bbe93f58d..49d1b86b4 100644 --- a/web/react/components/change_url_modal.jsx +++ b/web/react/components/change_url_modal.jsx @@ -4,6 +4,8 @@ var Modal = ReactBootstrap.Modal; import * as Utils from '../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class ChangeUrlModal extends React.Component { constructor(props) { super(props); @@ -39,21 +41,58 @@ export default class ChangeUrlModal extends React.Component { getURLError(url) { let error = []; //eslint-disable-line prefer-const if (url.length < 2) { - error.push({'Must be longer than two characters'}
); + error.push( + + +
+
+ ); } if (url.charAt(0) === '-' || url.charAt(0) === '_') { - error.push({'Must start with a letter or number'}
); + error.push( + + +
+
+ ); } if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) { - error.push({'Must end with a letter or number'}
); + error.push( + + +
+
); } if (url.indexOf('__') > -1) { - error.push({'Can not contain two underscores in a row.'}
); + error.push( + + +
+
); } // In case of error we don't detect if (error.length === 0) { - error.push({'Invalid URL'}
); + error.push( + + +
+
); } return error; } @@ -137,7 +176,10 @@ export default class ChangeUrlModal extends React.Component { className='btn btn-default' onClick={this.doCancel} > - {'Close'} + ); } var copyLinkConfirm = null; if (this.state.copiedLink) { - copyLinkConfirm =

{' Link copied to clipboard.'}

; + copyLinkConfirm = ( +

+ + +

+ ); } return ( @@ -92,7 +105,10 @@ export default class GetLinkModal extends React.Component { className='btn btn-default' onClick={this.onHide} > - {'Close'} + {copyLink} {copyLinkConfirm} diff --git a/web/react/components/get_team_invite_link_modal.jsx b/web/react/components/get_team_invite_link_modal.jsx index a926c4451..883871267 100644 --- a/web/react/components/get_team_invite_link_modal.jsx +++ b/web/react/components/get_team_invite_link_modal.jsx @@ -6,7 +6,20 @@ import GetLinkModal from './get_link_modal.jsx'; import ModalStore from '../stores/modal_store.jsx'; import TeamStore from '../stores/team_store.jsx'; -export default class GetTeamInviteLinkModal extends React.Component { +import {intlShape, injectIntl, defineMessages} from 'mm-intl'; + +const holders = defineMessages({ + title: { + id: 'get_team_invite_link_modal.title', + defaultMessage: 'Team Invite Link' + }, + help: { + id: 'get_team_invite_link_modal.help', + defaultMessage: 'Send teammates the link below for them to sign-up to this team site.' + } +}); + +class GetTeamInviteLinkModal extends React.Component { constructor(props) { super(props); @@ -32,14 +45,22 @@ export default class GetTeamInviteLinkModal extends React.Component { } render() { + const {formatMessage} = this.props.intl; + return ( this.setState({show: false})} - title='Team Invite Link' - helpText='Send teammates the link below for them to sign-up to this team site.' + title={formatMessage(holders.title)} + helpText={formatMessage(holders.help)} link={TeamStore.getCurrentInviteLink()} /> ); } } + +GetTeamInviteLinkModal.propTypes = { + intl: intlShape.isRequired +}; + +export default injectIntl(GetTeamInviteLinkModal); \ No newline at end of file diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 7e1627555..f2a0a7565 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -12,9 +12,38 @@ import ChannelStore from '../stores/channel_store.jsx'; import TeamStore from '../stores/team_store.jsx'; import ConfirmModal from './confirm_modal.jsx'; +import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + const Modal = ReactBootstrap.Modal; -export default class InviteMemberModal extends React.Component { +const holders = defineMessages({ + emailError: { + id: 'invite_member.emailError', + defaultMessage: 'Please enter a valid email address' + }, + firstname: { + id: 'invite_member.firstname', + defaultMessage: 'First name' + }, + lastname: { + id: 'invite_member.lastname', + defaultMessage: 'Last name' + }, + modalTitle: { + id: 'invite_member.modalTitle', + defaultMessage: 'Discard Invitations?' + }, + modalMessage: { + id: 'invite_member.modalMessage', + defaultMessage: 'You have unsent invitations, are you sure you want to discard them?' + }, + modalButton: { + id: 'invite_member.modalButton', + defaultMessage: 'Yes, Discard' + } +}); + +class InviteMemberModal extends React.Component { constructor(props) { super(props); @@ -72,7 +101,7 @@ export default class InviteMemberModal extends React.Component { var invite = {}; invite.email = ReactDOM.findDOMNode(this.refs['email' + index]).value.trim(); if (!invite.email || !utils.isEmail(invite.email)) { - emailErrors[index] = 'Please enter a valid email address'; + emailErrors[index] = this.props.intl.formatMessage(holders.emailError); valid = false; } else { emailErrors[index] = ''; @@ -103,7 +132,7 @@ export default class InviteMemberModal extends React.Component { this.setState({isSendingEmails: false}); }, (err) => { - if (err.message === 'This person is already on your team') { + if (err.id === 'api.team.invite_members.already.app_error') { emailErrors[err.detailed_error] = err.message; this.setState({emailErrors: emailErrors}); } else { @@ -199,6 +228,7 @@ export default class InviteMemberModal extends React.Component { render() { var currentUser = UserStore.getCurrentUser(); + const {formatMessage} = this.props.intl; if (currentUser != null) { var inviteSections = []; @@ -252,7 +282,7 @@ export default class InviteMemberModal extends React.Component { type='text' className='form-control' ref={'first_name' + index} - placeholder='First name' + placeholder={formatMessage(holders.firstname)} maxLength='64' disabled={!this.state.emailEnabled || !this.state.userCreationEnabled} spellCheck='false' @@ -266,7 +296,7 @@ export default class InviteMemberModal extends React.Component { type='text' className='form-control' ref={'last_name' + index} - placeholder='Last name' + placeholder={formatMessage(holders.lastname)} maxLength='64' disabled={!this.state.emailEnabled || !this.state.userCreationEnabled} spellCheck='false' @@ -318,20 +348,48 @@ export default class InviteMemberModal extends React.Component { type='button' className='btn btn-default' onClick={this.addInviteFields} - >{'Add another'} + > + +

- {'People invited automatically join the '}{defaultChannelName}{' channel.'} + + + ); - var sendButtonLabel = 'Send Invitation'; + var sendButtonLabel = ( + + ); if (this.state.isSendingEmails) { sendButtonLabel = ( - {' Sending'} + + + ); } else if (this.state.inviteIds.length > 1) { - sendButtonLabel = 'Send Invitations'; + sendButtonLabel = ( + + ); } sendButton = ( @@ -352,27 +410,46 @@ export default class InviteMemberModal extends React.Component { href='#' onClick={this.showGetTeamInviteLinkModal} > - {'Team Invite Link'} + ); teamInviteLink = (

- {'You can also invite people using the '}{link}{'.'} +

); } content = (
-

{'Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.'}

+

+ +

{teamInviteLink}
); } else { content = (
-

{'User creation has been disabled for your team. Please ask your team administrator for details.'}

+

+ +

); } @@ -387,7 +464,12 @@ export default class InviteMemberModal extends React.Component { backdrop={this.state.isSendingEmails ? 'static' : true} > - {'Invite New Member'} + + +
@@ -402,15 +484,18 @@ export default class InviteMemberModal extends React.Component { onClick={this.handleHide.bind(this, true)} disabled={this.state.isSendingEmails} > - {'Cancel'} + {sendButton} this.setState({showConfirmModal: false})} @@ -424,4 +509,7 @@ export default class InviteMemberModal extends React.Component { } InviteMemberModal.propTypes = { + intl: intlShape.isRequired }; + +export default injectIntl(InviteMemberModal); \ No newline at end of file diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx index 7967c410d..6e1006911 100644 --- a/web/react/components/member_list_team_item.jsx +++ b/web/react/components/member_list_team_item.jsx @@ -6,6 +6,8 @@ import * as Client from '../utils/client.jsx'; import * as AsyncClient from '../utils/async_client.jsx'; import * as Utils from '../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class MemberListTeamItem extends React.Component { constructor(props) { super(props); @@ -78,14 +80,29 @@ export default class MemberListTeamItem extends React.Component { } const user = this.props.user; - let currentRoles = 'Member'; + let currentRoles = ( + + ); const timestamp = UserStore.getCurrentUser().update_at; if (user.roles.length > 0) { if (Utils.isSystemAdmin(user.roles)) { - currentRoles = 'System Admin'; + currentRoles = ( + + ); } else if (Utils.isAdmin(user.roles)) { - currentRoles = 'Team Admin'; + currentRoles = ( + + ); } else { currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); } @@ -98,7 +115,12 @@ export default class MemberListTeamItem extends React.Component { let showMakeNotActive = user.roles !== 'system_admin'; if (user.delete_at > 0) { - currentRoles = 'Inactive'; + currentRoles = ( + + ); showMakeMember = false; showMakeAdmin = false; showMakeActive = true; @@ -114,7 +136,10 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeAdmin} > - {'Make Team Admin'} + ); @@ -129,7 +154,10 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeMember} > - {'Make Member'} + ); @@ -144,7 +172,10 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeActive} > - {'Make Active'} + ); @@ -159,7 +190,10 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeNotActive} > - {'Make Inactive'} + ); diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index 29512b9b7..d12ea4703 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -8,6 +8,8 @@ import ChannelStore from '../stores/channel_store.jsx'; import LoadingScreen from './loading_screen.jsx'; import NewChannelFlow from './new_channel_flow.jsx'; +import {FormattedMessage} from 'mm-intl'; + function getStateFromStores() { return { channels: ChannelStore.getMoreAll(), @@ -100,7 +102,10 @@ export default class MoreChannels extends React.Component { onClick={self.handleJoin.bind(self, channel, index)} className='btn btn-primary' > - Join + ); } @@ -123,8 +128,18 @@ export default class MoreChannels extends React.Component { } else { moreChannels = (
-

No more channels to join

-

Click 'Create New Channel' to make a new one

+

+ +

+

+ +

); } @@ -148,15 +163,28 @@ export default class MoreChannels extends React.Component { data-dismiss='modal' > - {'Close'} + + + -

{'More Channels'}

+

+ +

- {'Close'} + diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index 3661b19e6..f8a6884d0 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -5,7 +5,20 @@ const Modal = ReactBootstrap.Modal; import UserStore from '../stores/user_store.jsx'; import * as Utils from '../utils/utils.jsx'; -export default class MoreDirectChannels extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + member: { + id: 'more_direct_channels.member', + defaultMessage: 'Member' + }, + search: { + id: 'more_direct_channels.search', + defaultMessage: 'Search members' + } +}); + +class MoreDirectChannels extends React.Component { constructor(props) { super(props); @@ -148,7 +161,10 @@ export default class MoreDirectChannels extends React.Component { className='btn btn-primary btn-message' onClick={this.handleShowDirectChannel.bind(this, user)} > - {'Message'} + ); } @@ -180,6 +196,7 @@ export default class MoreDirectChannels extends React.Component { } render() { + const {formatMessage} = this.props.intl; if (!this.props.show) { return null; } @@ -199,19 +216,44 @@ export default class MoreDirectChannels extends React.Component { const userEntries = users.map(this.createRowForUser); if (userEntries.length === 0) { - userEntries.push({'No users found :('}); + userEntries.push( + + + ); } - let memberString = 'Member'; + let memberString = formatMessage(holders.member); if (users.length !== 1) { memberString += 's'; } let count; if (users.length === this.state.users.length) { - count = `${users.length} ${memberString}`; + count = ( + + ); } else { - count = `${users.length} ${memberString} of ${this.state.users.length} Total`; + count = ( + + ); } return ( @@ -221,7 +263,12 @@ export default class MoreDirectChannels extends React.Component { onHide={this.handleHide} > - {'Direct Messages'} + + +
@@ -229,7 +276,7 @@ export default class MoreDirectChannels extends React.Component {
@@ -254,7 +301,10 @@ export default class MoreDirectChannels extends React.Component { className='btn btn-default' onClick={this.handleHide} > - {'Close'} + @@ -263,6 +313,9 @@ export default class MoreDirectChannels extends React.Component { } MoreDirectChannels.propTypes = { + intl: intlShape.isRequired, show: React.PropTypes.bool.isRequired, onModalDismissed: React.PropTypes.func }; + +export default injectIntl(MoreDirectChannels); \ No newline at end of file diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index d4ec5a5f5..e9df03c33 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -14,6 +14,8 @@ import UserSettingsModal from './user_settings/user_settings_modal.jsx'; import Constants from '../utils/constants.jsx'; +import {FormattedMessage} from 'mm-intl'; + function getStateFromStores() { const teams = []; const teamsObject = UserStore.getTeams(); @@ -97,7 +99,10 @@ export default class NavbarDropdown extends React.Component { href='#' onClick={EventHelpers.showInviteMemberModal} > - {'Invite New Member'} + ); @@ -109,7 +114,10 @@ export default class NavbarDropdown extends React.Component { href='#' onClick={EventHelpers.showGetTeamInviteLinkModal} > - {'Get Team Invite Link'} + ); @@ -120,7 +128,10 @@ export default class NavbarDropdown extends React.Component { manageLink = (
  • - {'Manage Members'} +
  • ); @@ -134,7 +145,10 @@ export default class NavbarDropdown extends React.Component { data-toggle='modal' data-target='#team_settings' > - {'Team Settings'} + ); @@ -146,7 +160,10 @@ export default class NavbarDropdown extends React.Component { - {'System Console'} + ); @@ -165,7 +182,16 @@ export default class NavbarDropdown extends React.Component { this.state.teams.forEach((team) => { if (team.name !== this.props.teamName) { - teams.push(
  • {'Switch to ' + team.display_name}
  • ); + teams.push( +
  • + +
  • ); } }); } @@ -178,7 +204,10 @@ export default class NavbarDropdown extends React.Component { target='_blank' href={Utils.getWindowLocationOrigin() + '/signup_team'} > - {'Create a New Team'} + ); @@ -192,7 +221,10 @@ export default class NavbarDropdown extends React.Component { target='_blank' href={global.window.mm_config.HelpLink} > - {'Help'} + ); @@ -206,7 +238,10 @@ export default class NavbarDropdown extends React.Component { target='_blank' href={global.window.mm_config.ReportAProblemLink} > - {'Report a Problem'} + ); @@ -239,7 +274,10 @@ export default class NavbarDropdown extends React.Component { href='#' onClick={() => this.setState({showUserSettingsModal: true})} > - {'Account Settings'} + {inviteLink} @@ -249,7 +287,10 @@ export default class NavbarDropdown extends React.Component { href='#' onClick={this.handleLogoutClick} > - {'Logout'} + {adminDivider} @@ -265,7 +306,10 @@ export default class NavbarDropdown extends React.Component { href='#' onClick={this.handleAboutModal} > - {'About Mattermost'} + { - if (err.message === 'Name must be 2 or more lowercase alphanumeric characters') { + if (err.id === 'model.channel.is_valid.2_or_more.app_error') { this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE}); } - if (err.message === 'A channel with that handle already exists') { - this.setState({serverError: 'A channel with that URL already exists'}); + if (err.id === 'store.sql_channel.update.exists.app_error') { + this.setState({serverError: formatMessage(messages.alreadyExist)}); return; } this.setState({serverError: err.message}); @@ -130,27 +167,29 @@ export default class NewChannelFlow extends React.Component { let changeURLSubmitButtonText = ''; let channelTerm = ''; + const {formatMessage} = this.props.intl; + // Only listen to flow state if we are being shown if (this.props.show) { switch (this.state.flowState) { case SHOW_NEW_CHANNEL: if (this.state.channelType === 'O') { showChannelModal = true; - channelTerm = 'Channel'; + channelTerm = formatMessage(messages.channel); } else { showGroupModal = true; - channelTerm = 'Group'; + channelTerm = formatMessage(messages.group); } break; case SHOW_EDIT_URL: showChangeURLModal = true; - changeURLTitle = 'Change ' + channelTerm + ' URL'; - changeURLSubmitButtonText = 'Change ' + channelTerm + ' URL'; + changeURLTitle = formatMessage(messages.change, {term: channelTerm}); + changeURLSubmitButtonText = formatMessage(messages.change, {term: channelTerm}); break; case SHOW_EDIT_URL_THEN_COMPLETE: showChangeURLModal = true; - changeURLTitle = 'Set ' + channelTerm + ' URL'; - changeURLSubmitButtonText = 'Create ' + channelTerm; + changeURLTitle = formatMessage(messages.set, {term: channelTerm}); + changeURLSubmitButtonText = formatMessage(messages.create, {term: channelTerm}); break; } } @@ -181,7 +220,7 @@ export default class NewChannelFlow extends React.Component { {this.state.displayNameError}

    ; + displayNameError = ( +

    + + {this.state.displayNameError} +

    + ); displayNameClass += ' has-error'; } @@ -63,29 +81,51 @@ export default class NewChannelModal extends React.Component { var channelSwitchText = ''; switch (this.props.channelType) { case 'P': - channelTerm = 'Group'; + channelTerm = ( + + ); channelSwitchText = (
    - {'Create a new private group with restricted membership. '} + - {'Create a public channel'} +
    ); break; case 'O': - channelTerm = 'Channel'; + channelTerm = ( + + ); channelSwitchText = (
    - {'Create a new public channel anyone can join. '} + - {'Create a private group'} +
    ); @@ -102,7 +142,13 @@ export default class NewChannelModal extends React.Component { onHide={this.props.onModalDismissed} > - {'New ' + channelTerm} + + + {channelTerm} +
    - +
    - {'Edit'} + {')'}

    @@ -141,22 +195,38 @@ export default class NewChannelModal extends React.Component {
    - - + +