diff options
Diffstat (limited to 'webapp/components')
113 files changed, 1425 insertions, 2898 deletions
diff --git a/webapp/components/activity_log_modal.jsx b/webapp/components/activity_log_modal.jsx index f1dd4a26a..d3e5ce66d 100644 --- a/webapp/components/activity_log_modal.jsx +++ b/webapp/components/activity_log_modal.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import UserStore from 'stores/user_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {Modal} from 'react-bootstrap'; import LoadingScreen from './loading_screen.jsx'; diff --git a/webapp/components/admin_console/admin_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx index 729d4b14d..dd56411f4 100644 --- a/webapp/components/admin_console/admin_navbar_dropdown.jsx +++ b/webapp/components/admin_console/admin_navbar_dropdown.jsx @@ -3,27 +3,20 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; -import TeamStore from 'stores/team_store.jsx'; import Constants from 'utils/constants.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; import {Link} from 'react-router'; -function getStateFromStores() { - return {currentTeam: TeamStore.getCurrent()}; -} - import React from 'react'; export default class AdminNavbarDropdown extends React.Component { constructor(props) { super(props); this.blockToggle = false; - - this.state = getStateFromStores(); } componentDidMount() { @@ -64,24 +57,27 @@ export default class AdminNavbarDropdown extends React.Component { > <li> <Link - to={'/' + this.state.currentTeam.name + '/channels/town-square'} + to={'/select_team'} > <FormattedMessage id='admin.nav.switch' defaultMessage='Switch to {display_name}' values={{ - display_name: this.state.currentTeam.display_name + display_name: global.window.mm_config.SiteName }} /> </Link> </li> <li> - <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > <FormattedMessage id='admin.nav.logout' defaultMessage='Logout' /> - </Link> + </a> </li> </ul> </li> diff --git a/webapp/components/admin_console/admin_sidebar_header.jsx b/webapp/components/admin_console/admin_sidebar_header.jsx index 2e6252075..400730030 100644 --- a/webapp/components/admin_console/admin_sidebar_header.jsx +++ b/webapp/components/admin_console/admin_sidebar_header.jsx @@ -4,6 +4,7 @@ import $ from 'jquery'; import AdminNavbarDropdown from './admin_navbar_dropdown.jsx'; import UserStore from 'stores/user_store.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; @@ -41,7 +42,7 @@ export default class SidebarHeader extends React.Component { profilePicture = ( <img className='user__picture' - src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} + src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at} /> ); } diff --git a/webapp/components/admin_console/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports.jsx index 84def2bce..702c1a969 100644 --- a/webapp/components/admin_console/compliance_reports.jsx +++ b/webapp/components/admin_console/compliance_reports.jsx @@ -7,7 +7,7 @@ import * as Utils from '../../utils/utils.jsx'; import AdminStore from '../../stores/admin_store.jsx'; import UserStore from '../../stores/user_store.jsx'; -import * as Client from '../../utils/client.jsx'; +import * as Client from '../../utils/web_client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl'; @@ -153,7 +153,7 @@ export default class ComplianceReports extends React.Component { var download = ''; if (report.status === 'finished') { download = ( - <a href={'/api/v1/admin/download_compliance_report/' + report.id}> + <a href={Client.getAdminRoute() + '/download_compliance_report/' + report.id}> <FormattedMessage id='admin.compliance_table.download' defaultMessage='Download' diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx index 206bb0faa..b127634e8 100644 --- a/webapp/components/admin_console/compliance_settings.jsx +++ b/webapp/components/admin_console/compliance_settings.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import $ from 'jquery'; -import * as Client from '../../utils/client.jsx'; +import * as Client from '../../utils/web_client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import * as Utils from '../../utils/utils.jsx'; diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx index 8df48b206..1fa75ead9 100644 --- a/webapp/components/admin_console/email_settings.jsx +++ b/webapp/components/admin_console/email_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import crypto from 'crypto'; import ConnectionSecurityDropdownSetting from './connection_security_dropdown_setting.jsx'; diff --git a/webapp/components/admin_console/gitlab_settings.jsx b/webapp/components/admin_console/gitlab_settings.jsx index 7fdedde13..747905ac6 100644 --- a/webapp/components/admin_console/gitlab_settings.jsx +++ b/webapp/components/admin_console/gitlab_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/image_settings.jsx b/webapp/components/admin_console/image_settings.jsx index 576ff18fd..023e9af3b 100644 --- a/webapp/components/admin_console/image_settings.jsx +++ b/webapp/components/admin_console/image_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import crypto from 'crypto'; diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx index dd6e4338f..01402a588 100644 --- a/webapp/components/admin_console/ldap_settings.jsx +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; @@ -61,6 +61,7 @@ class LdapSettings extends React.Component { config.LdapSettings.BindPassword = this.refs.BindPassword.value.trim(); config.LdapSettings.FirstNameAttribute = this.refs.FirstNameAttribute.value.trim(); config.LdapSettings.LastNameAttribute = this.refs.LastNameAttribute.value.trim(); + config.LdapSettings.NicknameAttribute = this.refs.NicknameAttribute.value.trim(); config.LdapSettings.EmailAttribute = this.refs.EmailAttribute.value.trim(); config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim(); config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim(); @@ -441,6 +442,35 @@ class LdapSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='NicknameAttribute' + > + <FormattedMessage + id='admin.ldap.nicknameAttrTitle' + defaultMessage='Nickname Attribute:' + /> + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='NicknameAttribute' + ref='NicknameAttribute' + placeholder={Utils.localizeMessage('admin.ldap.nicknameAttrEx', 'Ex "nickname"')} + defaultValue={this.props.config.LdapSettings.NicknameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'> + <FormattedMessage + id='admin.ldap.nicknameAttrDesc' + defaultMessage='(Optional) The attribute in the LDAP server that will be used to populate the nickname of users in Mattermost.' + /> + </p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='EmailAttribute' > <FormattedMessage diff --git a/webapp/components/admin_console/legal_and_support_settings.jsx b/webapp/components/admin_console/legal_and_support_settings.jsx index bbbb3713c..9f72f5fdf 100644 --- a/webapp/components/admin_console/legal_and_support_settings.jsx +++ b/webapp/components/admin_console/legal_and_support_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx index 20e2fc83a..f2c511e44 100644 --- a/webapp/components/admin_console/license_settings.jsx +++ b/webapp/components/admin_console/license_settings.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -54,10 +54,7 @@ class LicenseSettings extends React.Component { $('#upload-button').button('loading'); - const formData = new FormData(); - formData.append('license', file, file.name); - - Client.uploadLicenseFile(formData, + Client.uploadLicenseFile(file, () => { Utils.clearFileInput(element[0]); $('#upload-button').button('reset'); diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx index 5aa3ca1e0..061c2b6e3 100644 --- a/webapp/components/admin_console/log_settings.jsx +++ b/webapp/components/admin_console/log_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/privacy_settings.jsx b/webapp/components/admin_console/privacy_settings.jsx index a312dddca..5045a6d31 100644 --- a/webapp/components/admin_console/privacy_settings.jsx +++ b/webapp/components/admin_console/privacy_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/rate_settings.jsx b/webapp/components/admin_console/rate_settings.jsx index f3fb1742c..de7a40e6b 100644 --- a/webapp/components/admin_console/rate_settings.jsx +++ b/webapp/components/admin_console/rate_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/reset_password_modal.jsx b/webapp/components/admin_console/reset_password_modal.jsx index f80c740e3..5133f3f28 100644 --- a/webapp/components/admin_console/reset_password_modal.jsx +++ b/webapp/components/admin_console/reset_password_modal.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {Modal} from 'react-bootstrap'; @@ -40,12 +40,9 @@ class ResetPasswordModal extends React.Component { this.setState({serverError: null}); - var data = {}; - data.new_password = password; - data.name = this.props.team.name; - data.user_id = this.props.user.id; - - Client.resetPassword(data, + Client.adminResetPassword( + this.props.user.id, + password, () => { this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.password).value); }, @@ -159,4 +156,4 @@ ResetPasswordModal.propTypes = { onModalDismissed: React.PropTypes.func }; -export default injectIntl(ResetPasswordModal);
\ No newline at end of file +export default injectIntl(ResetPasswordModal); diff --git a/webapp/components/admin_console/service_settings.jsx b/webapp/components/admin_console/service_settings.jsx index 2c3f4081c..90b6a39b4 100644 --- a/webapp/components/admin_console/service_settings.jsx +++ b/webapp/components/admin_console/service_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/sql_settings.jsx b/webapp/components/admin_console/sql_settings.jsx index 33bb2cece..f2e005b83 100644 --- a/webapp/components/admin_console/sql_settings.jsx +++ b/webapp/components/admin_console/sql_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import crypto from 'crypto'; diff --git a/webapp/components/admin_console/team_settings.jsx b/webapp/components/admin_console/team_settings.jsx index 6c9828351..bbb7ec3c4 100644 --- a/webapp/components/admin_console/team_settings.jsx +++ b/webapp/components/admin_console/team_settings.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import $ from 'jquery'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -42,6 +42,7 @@ class TeamSettings extends React.Component { this.handleImageSubmit = this.handleImageSubmit.bind(this); this.uploading = false; + this.timestamp = 0; this.state = { saveNeeded: false, @@ -53,7 +54,7 @@ class TeamSettings extends React.Component { componentWillMount() { if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') { - $.get('/api/v1/admin/get_brand_image').done(() => this.setState({brandImageExists: true})); + $.get(Client.getAdminRoute() + '/get_brand_image').done(() => this.setState({brandImageExists: true})); } } @@ -89,6 +90,7 @@ class TeamSettings extends React.Component { if (element.prop('files').length > 0) { this.setState({fileSelected: true, brandImage: element.prop('files')[0]}); } + $('#upload-button').button('reset'); } handleSubmit(e) { @@ -100,8 +102,8 @@ class TeamSettings extends React.Component { config.TeamSettings.RestrictCreationToDomains = this.refs.RestrictCreationToDomains.value.trim(); config.TeamSettings.EnableTeamCreation = this.refs.EnableTeamCreation.checked; config.TeamSettings.EnableUserCreation = this.refs.EnableUserCreation.checked; + config.TeamSettings.EnableOpenServer = this.refs.EnableOpenServer.checked; config.TeamSettings.RestrictTeamNames = this.refs.RestrictTeamNames.checked; - config.TeamSettings.EnableTeamListing = this.refs.EnableTeamListing.checked; if (this.refs.EnableCustomBrand) { config.TeamSettings.EnableCustomBrand = this.refs.EnableCustomBrand.checked; @@ -155,6 +157,7 @@ class TeamSettings extends React.Component { Client.uploadBrandImage(this.state.brandImage, () => { $('#upload-button').button('complete'); + this.timestamp = Utils.getTimestamp(); this.setState({brandImageExists: true, brandImage: null}); this.uploading = false; }, @@ -193,7 +196,7 @@ class TeamSettings extends React.Component { img = ( <img className='brand-img' - src='/api/v1/admin/get_brand_image' + src={Client.getAdminRoute() + '/get_brand_image?t=' + this.timestamp} /> ); } else { @@ -537,6 +540,53 @@ class TeamSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='EnableOpenServer' + > + <FormattedMessage + id='admin.team.openServerTitle' + defaultMessage='Enable Open Server: ' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableOpenServer' + value='true' + ref='EnableOpenServer' + defaultChecked={this.props.config.TeamSettings.EnableOpenServer} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.team.true' + defaultMessage='true' + /> + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableOpenServer' + value='false' + defaultChecked={!this.props.config.TeamSettings.EnableOpenServer} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.team.false' + defaultMessage='false' + /> + </label> + <p className='help-text'> + <FormattedMessage + id='admin.team.openServerDescription' + defaultMessage='When true, anyone can signup for a user account on this server without the need to be invited.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='RestrictCreationToDomains' > <FormattedMessage @@ -610,53 +660,6 @@ class TeamSettings extends React.Component { </div> </div> - <div className='form-group'> - <label - className='control-label col-sm-4' - htmlFor='EnableTeamListing' - > - <FormattedMessage - id='admin.team.dirTitle' - defaultMessage='Enable Team Directory: ' - /> - </label> - <div className='col-sm-8'> - <label className='radio-inline'> - <input - type='radio' - name='EnableTeamListing' - value='true' - ref='EnableTeamListing' - defaultChecked={this.props.config.TeamSettings.EnableTeamListing} - onChange={this.handleChange} - /> - <FormattedMessage - id='admin.team.true' - defaultMessage='true' - /> - </label> - <label className='radio-inline'> - <input - type='radio' - name='EnableTeamListing' - value='false' - defaultChecked={!this.props.config.TeamSettings.EnableTeamListing} - onChange={this.handleChange} - /> - <FormattedMessage - id='admin.team.false' - defaultMessage='false' - /> - </label> - <p className='help-text'> - <FormattedMessage - id='admin.team.dirDesc' - defaultMessage='When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.' - /> - </p> - </div> - </div> - {brand} <div className='form-group'> diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx index 8b37bd237..2b0e6af0a 100644 --- a/webapp/components/admin_console/team_users.jsx +++ b/webapp/components/admin_console/team_users.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import LoadingScreen from '../loading_screen.jsx'; import UserItem from './user_item.jsx'; import ResetPasswordModal from './reset_password_modal.jsx'; diff --git a/webapp/components/admin_console/user_item.jsx b/webapp/components/admin_console/user_item.jsx index 5bd05d063..b8f21d77e 100644 --- a/webapp/components/admin_console/user_item.jsx +++ b/webapp/components/admin_console/user_item.jsx @@ -1,13 +1,13 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import UserStore from 'stores/user_store.jsx'; import ConfirmModal from '../confirm_modal.jsx'; import TeamStore from 'stores/team_store.jsx'; -import {FormattedMessage} from 'react-intl'; +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; import React from 'react'; import {browserHistory} from 'react-router'; @@ -22,6 +22,7 @@ export default class UserItem extends React.Component { this.handleMakeAdmin = this.handleMakeAdmin.bind(this); this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this); this.handleResetPassword = this.handleResetPassword.bind(this); + this.handleResetMfa = this.handleResetMfa.bind(this); this.handleDemote = this.handleDemote.bind(this); this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this); this.handleDemoteCancel = this.handleDemoteCancel.bind(this); @@ -40,12 +41,9 @@ export default class UserItem extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, ''); } else { - const data = { - user_id: this.props.user.id, - new_roles: '' - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + '', () => { this.props.refreshProfiles(); }, @@ -86,12 +84,9 @@ export default class UserItem extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, 'admin'); } else { - const data = { - user_id: this.props.user.id, - new_roles: 'admin' - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + 'admin', () => { this.props.refreshProfiles(); }, @@ -104,12 +99,10 @@ export default class UserItem extends React.Component { handleMakeSystemAdmin(e) { e.preventDefault(); - const data = { - user_id: this.props.user.id, - new_roles: 'system_admin' - }; - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + 'system_admin', () => { this.props.refreshProfiles(); }, @@ -124,6 +117,19 @@ export default class UserItem extends React.Component { this.props.doPasswordReset(this.props.user); } + handleResetMfa(e) { + e.preventDefault(); + + Client.adminResetMfa(this.props.user.id, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + handleDemote(user, role) { this.setState({ serverError: this.state.serverError, @@ -143,12 +149,9 @@ export default class UserItem extends React.Component { } handleDemoteSubmit() { - const data = { - user_id: this.props.user.id, - new_roles: this.state.role - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + this.state.role, () => { this.setState({ serverError: null, @@ -211,10 +214,15 @@ export default class UserItem extends React.Component { const email = user.email; let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin'; - let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; + + //let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; + let showMakeAdmin = false; + let showMakeSystemAdmin = user.roles === '' || user.roles === 'admin'; let showMakeActive = false; let showMakeNotActive = user.roles !== 'system_admin'; + let mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true'; + let showMfaReset = mfaEnabled && user.mfa_active; if (user.delete_at > 0) { currentRoles = ( @@ -319,6 +327,64 @@ export default class UserItem extends React.Component { </li> ); } + + let mfaReset = null; + if (showMfaReset) { + mfaReset = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleResetMfa} + > + <FormattedMessage + id='admin.user_item.resetMfa' + defaultMessage='Remove MFA' + /> + </a> + </li> + ); + } + + let mfaActiveText; + if (mfaEnabled) { + if (user.mfa_active) { + mfaActiveText = ( + <FormattedHTMLMessage + id='admin.user_item.mfaYes' + defaultMessage=', <strong>MFA</strong>: Yes' + /> + ); + } else { + mfaActiveText = ( + <FormattedHTMLMessage + id='admin.user_item.mfaNo' + defaultMessage=', <strong>MFA</strong>: No' + /> + ); + } + } + + let authServiceText; + if (user.auth_service) { + authServiceText = ( + <FormattedHTMLMessage + id='admin.user_item.authServiceNotEmail' + defaultMessage=', <strong>Sign-in Method:</strong> {service}' + values={{ + service: Utils.toTitleCase(user.auth_service) + }} + /> + ); + } else { + authServiceText = ( + <FormattedHTMLMessage + id='admin.user_item.authServiceEmail' + defaultMessage=', <strong>Sign-in Method:</strong> Email' + /> + ); + } + const me = UserStore.getCurrentUser(); let makeDemoteModal = null; if (this.props.user.id === me.id) { @@ -368,13 +434,23 @@ export default class UserItem extends React.Component { <div className='more-modal__row'> <img className='more-modal__image pull-left' - src={`/api/v1/users/${user.id}/image?time=${user.update_at}`} + src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`} height='36' width='36' /> <div className='more-modal__details'> <div className='more-modal__name'>{Utils.getDisplayName(user)}</div> - <div className='more-modal__description'>{email}</div> + <div className='more-modal__description'> + <FormattedHTMLMessage + id='admin.user_item.emailTitle' + defaultMessage='<strong>Email:</strong> {email}' + values={{ + email + }} + /> + {authServiceText} + {mfaActiveText} + </div> </div> <div className='more-modal__actions'> <div className='dropdown member-drop'> @@ -397,6 +473,7 @@ export default class UserItem extends React.Component { {makeActive} {makeNotActive} {makeSystemAdmin} + {mfaReset} <li role='presentation'> <a role='menuitem' diff --git a/webapp/components/authorize.jsx b/webapp/components/authorize.jsx index 01b37f439..b18469568 100644 --- a/webapp/components/authorize.jsx +++ b/webapp/components/authorize.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/backstage/add_command.jsx index 2eb7bdb21..ba9ac4e79 100644 --- a/webapp/components/backstage/add_command.jsx +++ b/webapp/components/backstage/add_command.jsx @@ -89,6 +89,8 @@ export default class AddCommand extends React.Component { /> ) }); + + return; } if (!command.url) { @@ -101,12 +103,14 @@ export default class AddCommand extends React.Component { /> ) }); + + return; } AsyncClient.addCommand( command, () => { - browserHistory.push('/settings/integrations/commands'); + browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'); }, (err) => { this.setState({ @@ -251,7 +255,7 @@ export default class AddCommand extends React.Component { return ( <div className='backstage-content row'> <BackstageHeader> - <Link to={'/settings/integrations/commands'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}> <FormattedMessage id='installed_command.header' defaultMessage='Slash Commands' @@ -482,7 +486,7 @@ export default class AddCommand extends React.Component { <FormError errors={[this.state.serverError, this.state.clientError]}/> <Link className='btn btn-sm' - to={'/settings/integrations/commands'} + to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'} > <FormattedMessage id='add_command.cancel' diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/backstage/add_incoming_webhook.jsx index f68a263be..0f0d49ea7 100644 --- a/webapp/components/backstage/add_incoming_webhook.jsx +++ b/webapp/components/backstage/add_incoming_webhook.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import {browserHistory} from 'react-router'; +import * as Utils from 'utils/utils.jsx'; import BackstageHeader from './backstage_header.jsx'; import ChannelSelect from 'components/channel_select.jsx'; @@ -69,7 +70,7 @@ export default class AddIncomingWebhook extends React.Component { AsyncClient.addIncomingHook( hook, () => { - browserHistory.push('/settings/integrations/incoming_webhooks'); + browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'); }, (err) => { this.setState({ @@ -102,7 +103,7 @@ export default class AddIncomingWebhook extends React.Component { return ( <div className='backstage-content'> <BackstageHeader> - <Link to={'/settings/integrations/incoming_webhooks'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}> <FormattedMessage id='installed_incoming_webhooks.header' defaultMessage='Incoming Webhooks' diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/backstage/add_outgoing_webhook.jsx index ff5e90e07..245df1604 100644 --- a/webapp/components/backstage/add_outgoing_webhook.jsx +++ b/webapp/components/backstage/add_outgoing_webhook.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import {browserHistory} from 'react-router'; +import * as Utils from 'utils/utils.jsx'; import BackstageHeader from './backstage_header.jsx'; import ChannelSelect from 'components/channel_select.jsx'; @@ -109,7 +110,7 @@ export default class AddOutgoingWebhook extends React.Component { AsyncClient.addOutgoingHook( hook, () => { - browserHistory.push('/settings/integrations/outgoing_webhooks'); + browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'); }, (err) => { this.setState({ @@ -154,7 +155,7 @@ export default class AddOutgoingWebhook extends React.Component { return ( <div className='backstage-content'> <BackstageHeader> - <Link to={'/settings/integrations/outgoing_webhooks'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}> <FormattedMessage id='installed_outgoing_webhooks.header' defaultMessage='Outgoing Webhooks' @@ -273,7 +274,7 @@ export default class AddOutgoingWebhook extends React.Component { <FormError errors={[this.state.serverError, this.state.clientError]}/> <Link className='btn btn-sm' - to={'/settings/integrations/outgoing_webhooks'} + to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'} > <FormattedMessage id='add_outgoing_webhook.cancel' diff --git a/webapp/components/backstage/backstage_navbar.jsx b/webapp/components/backstage/backstage_navbar.jsx index d2d2da1ed..8352296b7 100644 --- a/webapp/components/backstage/backstage_navbar.jsx +++ b/webapp/components/backstage/backstage_navbar.jsx @@ -46,7 +46,7 @@ export default class BackstageNavbar extends React.Component { <div className='backstage-navbar row'> <Link className='backstage-navbar__back' - to={`/${this.state.team.display_name}/channels/town-square`} + to={`/${this.state.team.name}/channels/town-square`} > <i className='fa fa-angle-left'/> <span> diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/backstage_sidebar.jsx index eb84709a3..6f8e0b86a 100644 --- a/webapp/components/backstage/backstage_sidebar.jsx +++ b/webapp/components/backstage/backstage_sidebar.jsx @@ -3,6 +3,7 @@ import React from 'react'; +import * as Utils from 'utils/utils.jsx'; import BackstageCategory from './backstage_category.jsx'; import BackstageSection from './backstage_section.jsx'; import {FormattedMessage} from 'react-intl'; @@ -14,7 +15,7 @@ export default class BackstageSidebar extends React.Component { <ul> <BackstageCategory name='integrations' - parentLink={'/settings'} + parentLink={'/' + Utils.getTeamNameFromUrl() + '/settings'} icon='fa-link' title={ <FormattedMessage diff --git a/webapp/components/backstage/installed_commands.jsx b/webapp/components/backstage/installed_commands.jsx index 3527a574b..8b0cd59c8 100644 --- a/webapp/components/backstage/installed_commands.jsx +++ b/webapp/components/backstage/installed_commands.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import IntegrationStore from 'stores/integration_store.jsx'; +import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledCommand from './installed_command.jsx'; @@ -84,7 +85,7 @@ export default class InstalledCommands extends React.Component { defaultMessage='Add Slash Command' /> } - addLink='/settings/integrations/commands/add' + addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands/add'} > {commands} </InstalledIntegrations> diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/backstage/installed_incoming_webhook.jsx index 58d318310..afa6e9958 100644 --- a/webapp/components/backstage/installed_incoming_webhook.jsx +++ b/webapp/components/backstage/installed_incoming_webhook.jsx @@ -90,6 +90,17 @@ export default class InstalledIncomingWebhook extends React.Component { </span> </div> {description} + <div className='item-details__row'> + <span className='item-details__url'> + <FormattedMessage + id='installed_integrations.url' + defaultMessage='URL: {url}' + values={{ + url: Utils.getWindowLocationOrigin() + '/hooks/' + incomingWebhook.id + }} + /> + </span> + </div> <div className='tem-details__row'> <span className='item-details__creation'> <FormattedMessage diff --git a/webapp/components/backstage/installed_incoming_webhooks.jsx b/webapp/components/backstage/installed_incoming_webhooks.jsx index de7154afe..0d6f900d1 100644 --- a/webapp/components/backstage/installed_incoming_webhooks.jsx +++ b/webapp/components/backstage/installed_incoming_webhooks.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import IntegrationStore from 'stores/integration_store.jsx'; +import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledIncomingWebhook from './installed_incoming_webhook.jsx'; @@ -76,7 +77,7 @@ export default class InstalledIncomingWebhooks extends React.Component { defaultMessage='Add Incoming Webhook' /> } - addLink='/settings/integrations/incoming_webhooks/add' + addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks/add'} > {incomingWebhooks} </InstalledIntegrations> diff --git a/webapp/components/backstage/installed_outgoing_webhooks.jsx b/webapp/components/backstage/installed_outgoing_webhooks.jsx index 15d927a41..98992b081 100644 --- a/webapp/components/backstage/installed_outgoing_webhooks.jsx +++ b/webapp/components/backstage/installed_outgoing_webhooks.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import IntegrationStore from 'stores/integration_store.jsx'; +import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx'; @@ -82,7 +83,7 @@ export default class InstalledOutgoingWebhooks extends React.Component { defaultMessage='Add Outgoing Webhook' /> } - addLink='/settings/integrations/outgoing_webhooks/add' + addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks/add'} > {outgoingWebhooks} </InstalledIntegrations> diff --git a/webapp/components/backstage/integrations.jsx b/webapp/components/backstage/integrations.jsx index 71232ea45..fdd75026a 100644 --- a/webapp/components/backstage/integrations.jsx +++ b/webapp/components/backstage/integrations.jsx @@ -5,6 +5,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import IntegrationOption from './integration_option.jsx'; +import * as Utils from 'utils/utils.jsx'; import WebhookIcon from 'images/webhook_icon.jpg'; @@ -29,7 +30,7 @@ export default class Integrations extends React.Component { defaultMessage='Incoming webhooks allow external integrations to send messages' /> } - link={'/settings/integrations/incoming_webhooks'} + link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'} /> ); } @@ -51,7 +52,7 @@ export default class Integrations extends React.Component { defaultMessage='Outgoing webhooks allow external integrations to receive and respond to messages' /> } - link={'/settings/integrations/outgoing_webhooks'} + link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'} /> ); } @@ -73,7 +74,7 @@ export default class Integrations extends React.Component { defaultMessage='Slash commands send events to an external integration' /> } - link={'/settings/integrations/commands'} + link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'} /> ); } diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx index 16d9ea536..c82f59299 100644 --- a/webapp/components/channel_header.jsx +++ b/webapp/components/channel_header.jsx @@ -25,7 +25,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import * as Utils from 'utils/utils.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage} from 'react-intl'; diff --git a/webapp/components/channel_info_modal.jsx b/webapp/components/channel_info_modal.jsx index c7f9f9f79..4c16dda90 100644 --- a/webapp/components/channel_info_modal.jsx +++ b/webapp/components/channel_info_modal.jsx @@ -3,33 +3,26 @@ import * as Utils from 'utils/utils.jsx'; -import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; - +import {FormattedMessage} from 'react-intl'; import {Modal} from 'react-bootstrap'; -const holders = defineMessages({ - notFound: { - id: 'channel_info.notFound', - defaultMessage: 'No Channel Found' - } -}); - import React from 'react'; -class ChannelInfoModal extends React.Component { +export default class ChannelInfoModal extends React.Component { render() { - const {formatMessage} = this.props.intl; let channel = this.props.channel; if (!channel) { + const notFound = Utils.localizeMessage('channel_info.notFound', 'No Channel Found'); + channel = { - display_name: formatMessage(holders.notFound), - name: formatMessage(holders.notFound), - purpose: formatMessage(holders.notFound), - id: formatMessage(holders.notFound) + display_name: notFound, + name: notFound, + purpose: notFound, + id: notFound }; } - const channelURL = Utils.getShortenedTeamURL() + channel.name; + const channelURL = Utils.getTeamURLFromAddressBar() + '/channels/' + channel.name; return ( <Modal @@ -97,10 +90,7 @@ class ChannelInfoModal extends React.Component { } ChannelInfoModal.propTypes = { - intl: intlShape.isRequired, show: React.PropTypes.bool.isRequired, onHide: React.PropTypes.func.isRequired, channel: React.PropTypes.object.isRequired -}; - -export default injectIntl(ChannelInfoModal);
\ No newline at end of file +};
\ No newline at end of file diff --git a/webapp/components/channel_invite_button.jsx b/webapp/components/channel_invite_button.jsx index 1fcd461ea..ed013bb26 100644 --- a/webapp/components/channel_invite_button.jsx +++ b/webapp/components/channel_invite_button.jsx @@ -4,7 +4,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; import SpinnerButton from 'components/spinner_button.jsx'; @@ -37,13 +37,9 @@ export default class ChannelInviteButton extends React.Component { addingUser: true }); - const data = { - user_id: this.props.user.id - }; - Client.addChannelMember( this.props.channel.id, - data, + this.props.user.id, () => { this.setState({ addingUser: false diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx index 67be2ef50..ecea891bb 100644 --- a/webapp/components/channel_members_modal.jsx +++ b/webapp/components/channel_members_modal.jsx @@ -9,7 +9,7 @@ import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; @@ -97,12 +97,9 @@ export default class ChannelMembersModal extends React.Component { handleRemove(user) { const userId = user.id; - const data = {}; - data.user_id = userId; - Client.removeChannelMember( ChannelStore.getCurrentId(), - data, + userId, () => { const memberList = this.state.memberList.slice(); for (let i = 0; i < memberList.length; i++) { diff --git a/webapp/components/channel_notifications_modal.jsx b/webapp/components/channel_notifications_modal.jsx index 564776876..112c07ad0 100644 --- a/webapp/components/channel_notifications_modal.jsx +++ b/webapp/components/channel_notifications_modal.jsx @@ -6,7 +6,7 @@ import {Modal} from 'react-bootstrap'; import SettingItemMin from './setting_item_min.jsx'; import SettingItemMax from './setting_item_max.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import {FormattedMessage} from 'react-intl'; @@ -60,7 +60,7 @@ export default class ChannelNotificationsModal extends React.Component { data.desktop = notifyLevel; //TODO: This should be moved to event_helpers - Client.updateNotifyProps(data, + Client.updateChannelNotifyProps(data, () => { // YUCK var member = ChannelStore.getMember(channelId); @@ -252,7 +252,7 @@ export default class ChannelNotificationsModal extends React.Component { }; //TODO: This should be fixed, moved to event_helpers - Client.updateNotifyProps(data, + Client.updateChannelNotifyProps(data, () => { // Yuck... var member = ChannelStore.getMember(channelId); diff --git a/webapp/components/channel_select.jsx b/webapp/components/channel_select.jsx index 8622d1f57..238cfa1ae 100644 --- a/webapp/components/channel_select.jsx +++ b/webapp/components/channel_select.jsx @@ -54,7 +54,7 @@ export default class ChannelSelect extends React.Component { ]; this.state.channels.forEach((channel) => { - if (channel.type !== Constants.DM_CHANNEL) { + if (channel.type === Constants.OPEN_CHANNEL) { options.push( <option key={channel.id} diff --git a/webapp/components/claim/claim.jsx b/webapp/components/claim/claim.jsx index 5cfb04af3..0197e1677 100644 --- a/webapp/components/claim/claim.jsx +++ b/webapp/components/claim/claim.jsx @@ -1,8 +1,6 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import TeamStore from 'stores/team_store.jsx'; - import React from 'react'; import {FormattedMessage} from 'react-intl'; import {Link} from 'react-router'; @@ -13,40 +11,15 @@ export default class Claim extends React.Component { constructor(props) { super(props); - this.onTeamChange = this.onTeamChange.bind(this); - this.updateStateFromStores = this.updateStateFromStores.bind(this); - this.state = {}; } componentWillMount() { this.setState({ email: this.props.location.query.email, newType: this.props.location.query.new_type, - oldType: this.props.location.query.old_type, - teamName: this.props.params.team, - teamDisplayName: '' - }); - this.updateStateFromStores(); - } - componentDidMount() { - TeamStore.addChangeListener(this.onTeamChange); - } - componentWillUnmount() { - TeamStore.removeChangeListener(this.onTeamChange); - } - updateStateFromStores() { - const team = TeamStore.getByName(this.state.teamName); - let displayName = ''; - if (team) { - displayName = team.display_name; - } - this.setState({ - teamDisplayName: displayName + oldType: this.props.location.query.old_type }); } - onTeamChange() { - this.updateStateFromStores(); - } render() { return ( <div> @@ -66,8 +39,6 @@ export default class Claim extends React.Component { /> <div id='claim'> {React.cloneElement(this.props.children, { - teamName: this.state.teamName, - teamDisplayName: this.state.teamDisplayName, currentType: this.state.oldType, newType: this.state.newType, email: this.state.email @@ -83,7 +54,6 @@ export default class Claim extends React.Component { Claim.defaultProps = { }; Claim.propTypes = { - params: React.PropTypes.object.isRequired, location: React.PropTypes.object.isRequired, children: React.PropTypes.node }; diff --git a/webapp/components/claim/components/email_to_ldap.jsx b/webapp/components/claim/components/email_to_ldap.jsx index 1ceb42a27..fbf26cade 100644 --- a/webapp/components/claim/components/email_to_ldap.jsx +++ b/webapp/components/claim/components/email_to_ldap.jsx @@ -2,12 +2,11 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; export default class EmailToLDAP extends React.Component { constructor(props) { @@ -45,17 +44,14 @@ export default class EmailToLDAP extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.email_password = password; - postData.ldap_id = ldapId; - postData.ldap_password = ldapPassword; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - - Client.emailToLDAP(postData, + Client.emailToLdap( + this.props.email, + password, + ldapId, + ldapPassword, (data) => { if (data.follow_link) { - browserHistory.push(data.follow_link); + window.location.href = data.follow_link; } }, (error) => { @@ -74,6 +70,20 @@ export default class EmailToLDAP extends React.Component { formClass += ' has-error'; } + let loginPlaceholder; + if (global.window.mm_config.LdapLoginFieldName) { + loginPlaceholder = global.window.mm_config.LdapLoginFieldName; + } else { + loginPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapId', 'LDAP ID'); + } + + let passwordPlaceholder; + if (global.window.mm_config.LdapPasswordFieldName) { + passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName; + } else { + passwordPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'LDAP Password'); + } + return ( <div> <h3> @@ -98,9 +108,8 @@ export default class EmailToLDAP extends React.Component { <p> <FormattedMessage id='claim.email_to_ldap.enterPwd' - defaultMessage='Enter the password for your {team} {site} email account' + defaultMessage='Enter the password for your {site} email account' values={{ - team: this.props.teamDisplayName, site: global.window.mm_config.SiteName }} /> @@ -125,10 +134,6 @@ export default class EmailToLDAP extends React.Component { <FormattedMessage id='claim.email_to_ldap.enterLdapPwd' defaultMessage='Enter the ID and password for your LDAP account' - values={{ - team: this.props.teamDisplayName, - site: global.window.mm_config.SiteName - }} /> </p> <div className={formClass}> @@ -138,7 +143,7 @@ export default class EmailToLDAP extends React.Component { name='ldapId' ref='ldapid' autoComplete='off' - placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapId', 'LDAP ID')} + placeholder={loginPlaceholder} spellCheck='false' /> </div> @@ -149,7 +154,7 @@ export default class EmailToLDAP extends React.Component { name='ldapPassword' ref='ldappassword' autoComplete='off' - placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'LDAP Password')} + placeholder={passwordPlaceholder} spellCheck='false' /> </div> @@ -172,7 +177,5 @@ export default class EmailToLDAP extends React.Component { EmailToLDAP.defaultProps = { }; EmailToLDAP.propTypes = { - email: React.PropTypes.string, - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + email: React.PropTypes.string }; diff --git a/webapp/components/claim/components/email_to_oauth.jsx b/webapp/components/claim/components/email_to_oauth.jsx index f4376067a..1fd284bed 100644 --- a/webapp/components/claim/components/email_to_oauth.jsx +++ b/webapp/components/claim/components/email_to_oauth.jsx @@ -2,12 +2,11 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; export default class EmailToOAuth extends React.Component { constructor(props) { @@ -31,16 +30,13 @@ export default class EmailToOAuth extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.password = password; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - postData.service = this.props.newType; - - Client.emailToOAuth(postData, + Client.emailToOAuth( + this.props.email, + password, + this.props.newType, (data) => { if (data.follow_link) { - browserHistory.push(data.follow_link); + window.location.href = data.follow_link; } }, (error) => { @@ -94,9 +90,8 @@ export default class EmailToOAuth extends React.Component { <p> <FormattedMessage id='claim.email_to_oauth.enterPwd' - defaultMessage='Enter the password for your {team} {site} account' + defaultMessage='Enter the password for your {site} account' values={{ - team: this.props.teamDisplayName, site: global.window.mm_config.SiteName }} /> @@ -134,7 +129,5 @@ EmailToOAuth.defaultProps = { }; EmailToOAuth.propTypes = { newType: React.PropTypes.string, - email: React.PropTypes.string, - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + email: React.PropTypes.string }; diff --git a/webapp/components/claim/components/ldap_to_email.jsx b/webapp/components/claim/components/ldap_to_email.jsx index ed8a314bd..a10cefd6f 100644 --- a/webapp/components/claim/components/ldap_to_email.jsx +++ b/webapp/components/claim/components/ldap_to_email.jsx @@ -2,12 +2,11 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; export default class LDAPToEmail extends React.Component { constructor(props) { @@ -45,16 +44,13 @@ export default class LDAPToEmail extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.email_password = password; - postData.ldap_password = ldapPassword; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - - Client.ldapToEmail(postData, + Client.ldapToEmail( + this.props.email, + password, + ldapPassword, (data) => { if (data.follow_link) { - browserHistory.push(data.follow_link); + window.location.href = data.follow_link; } }, (error) => { @@ -73,6 +69,13 @@ export default class LDAPToEmail extends React.Component { formClass += ' has-error'; } + let passwordPlaceholder; + if (global.window.mm_config.LdapPasswordFieldName) { + passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName; + } else { + passwordPlaceholder = Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'LDAP Password'); + } + return ( <div> <h3> @@ -100,9 +103,9 @@ export default class LDAPToEmail extends React.Component { <p> <FormattedMessage id='claim.ldap_to_email.enterLdapPwd' - defaultMessage='Enter your LDAP password for your {team} {site} email account' + defaultMessage='Enter your {ldapPassword} for your {site} email account' values={{ - team: this.props.teamDisplayName, + ldapPassword: passwordPlaceholder, site: global.window.mm_config.SiteName }} /> @@ -113,7 +116,7 @@ export default class LDAPToEmail extends React.Component { className='form-control' name='ldapPassword' ref='ldappassword' - placeholder={Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'LDAP Password')} + placeholder={passwordPlaceholder} spellCheck='false' /> </div> @@ -162,7 +165,5 @@ export default class LDAPToEmail extends React.Component { LDAPToEmail.defaultProps = { }; LDAPToEmail.propTypes = { - email: React.PropTypes.string, - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + email: React.PropTypes.string }; diff --git a/webapp/components/claim/components/oauth_to_email.jsx b/webapp/components/claim/components/oauth_to_email.jsx index 72e0500a9..7fd18aaa6 100644 --- a/webapp/components/claim/components/oauth_to_email.jsx +++ b/webapp/components/claim/components/oauth_to_email.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -38,12 +38,9 @@ export default class OAuthToEmail extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.password = password; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - - Client.oauthToEmail(postData, + Client.oauthToEmail( + this.props.email, + password, (data) => { if (data.follow_link) { browserHistory.push(data.follow_link); @@ -87,10 +84,9 @@ export default class OAuthToEmail extends React.Component { </p> <p> <FormattedMessage - id='claim.oauth_to_email_newPwd' - defaultMessage='Enter a new password for your {team} {site} account' + id='claim.oauth_to_email.enterNewPwd' + defaultMessage='Enter a new password for your {site} account' values={{ - team: this.props.teamDisplayName, site: global.window.mm_config.SiteName }} /> @@ -137,8 +133,6 @@ export default class OAuthToEmail extends React.Component { OAuthToEmail.defaultProps = { }; OAuthToEmail.propTypes = { - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string, currentType: React.PropTypes.string, email: React.PropTypes.string }; diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx index a91c03d58..e8fa59165 100644 --- a/webapp/components/create_comment.jsx +++ b/webapp/components/create_comment.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; @@ -148,7 +148,6 @@ class CreateComment extends React.Component { Client.createPost( post, - ChannelStore.getCurrent(), (data) => { AsyncClient.getPosts(this.props.channelId); diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index 232537d8b..9bbc44f38 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -11,7 +11,7 @@ import TutorialTip from './tutorial/tutorial_tip.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -172,7 +172,7 @@ class CreatePost extends React.Component { GlobalActions.emitUserPostedEvent(post); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); - Client.createPost(post, channel, + Client.createPost(post, (data) => { AsyncClient.getPosts(); diff --git a/webapp/components/signup_team_complete/components/team_signup_display_name_page.jsx b/webapp/components/create_team/components/display_name.jsx index 111fc6835..e33eee1bc 100644 --- a/webapp/components/signup_team_complete/components/team_signup_display_name_page.jsx +++ b/webapp/components/create_team/components/display_name.jsx @@ -3,7 +3,8 @@ import ReactDOM from 'react-dom'; import * as utils from 'utils/utils.jsx'; -import * as client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; +import {Link} from 'react-router'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; @@ -11,11 +12,11 @@ import logoImage from 'images/logo.png'; const holders = defineMessages({ required: { - id: 'team_signup_display_name.required', + id: 'create_team_display_name.required', defaultMessage: 'This field is required' }, charLength: { - id: 'team_signup_display_name.charLength', + id: 'create_team_display_name.charLength', defaultMessage: 'Name must be 4 or more characters up to a maximum of 15' } }); @@ -26,16 +27,11 @@ class TeamSignupDisplayNamePage extends React.Component { constructor(props) { super(props); - this.submitBack = this.submitBack.bind(this); this.submitNext = this.submitNext.bind(this); this.state = {}; } - submitBack(e) { - e.preventDefault(); - this.props.state.wizard = 'welcome'; - this.props.updateParent(this.props.state); - } + submitNext(e) { e.preventDefault(); @@ -54,12 +50,14 @@ class TeamSignupDisplayNamePage extends React.Component { this.props.state.team.name = utils.cleanUpUrlable(displayName); this.props.updateParent(this.props.state); } + handleFocus(e) { e.preventDefault(); e.currentTarget.select(); } + render() { - client.track('signup', 'signup_team_02_name'); + Client.track('signup', 'signup_team_02_name'); var nameError = null; var nameDivClass = 'form-group'; @@ -77,7 +75,7 @@ class TeamSignupDisplayNamePage extends React.Component { /> <h2> <FormattedMessage - id='team_signup_display_name.teamName' + id='create_team_display_name.teamName' defaultMessage='Team Name' /> </h2> @@ -101,7 +99,7 @@ class TeamSignupDisplayNamePage extends React.Component { </div> <div> <FormattedMessage - id='team_signup_display_name.nameHelp' + id='create_team_display_name.nameHelp' defaultMessage='Name your team in any language. Your team name shows in menus and headings.' /> </div> @@ -111,20 +109,17 @@ class TeamSignupDisplayNamePage extends React.Component { onClick={this.submitNext} > <FormattedMessage - id='team_signup_display_name.next' + id='create_team_display_name.next' defaultMessage='Next' /><i className='glyphicon glyphicon-chevron-right'></i> </button> <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > + <Link to='/select_team'> <FormattedMessage - id='team_signup_display_name.back' + id='create_team_display_name.back' defaultMessage='Back to previous step' /> - </a> + </Link> </div> </form> </div> diff --git a/webapp/components/signup_team_complete/components/team_signup_url_page.jsx b/webapp/components/create_team/components/team_url.jsx index b2ab57285..025103141 100644 --- a/webapp/components/signup_team_complete/components/team_signup_url_page.jsx +++ b/webapp/components/create_team/components/team_url.jsx @@ -4,8 +4,11 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; import Constants from 'utils/constants.jsx'; +import {browserHistory} from 'react-router'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -13,30 +16,30 @@ import logoImage from 'images/logo.png'; const holders = defineMessages({ required: { - id: 'team_signup_url.required', + id: 'create_team_url.required', defaultMessage: 'This field is required' }, regex: { - id: 'team_signup_url.regex', + id: 'create_team_url.regex', defaultMessage: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash." }, charLength: { - id: 'team_signup_url.charLength', + id: 'create_team_url.charLength', defaultMessage: 'Name must be 4 or more characters up to a maximum of 15' }, taken: { - id: 'team_signup_url.taken', + id: 'create_team_url.taken', defaultMessage: 'URL is taken or contains a reserved word' }, unavailable: { - id: 'team_signup_url.unavailable', + id: 'create_team_url.unavailable', defaultMessage: 'This URL is unavailable. Please try another.' } }); import React from 'react'; -class TeamSignupUrlPage extends React.Component { +class TeamUrl extends React.Component { constructor(props) { super(props); @@ -48,7 +51,7 @@ class TeamSignupUrlPage extends React.Component { } submitBack(e) { e.preventDefault(); - this.props.state.wizard = 'team_display_name'; + this.props.state.wizard = 'display_name'; this.props.updateParent(this.props.state); } submitNext(e) { @@ -81,25 +84,39 @@ class TeamSignupUrlPage extends React.Component { } } + $('#finish-button').button('loading'); + var teamSignup = JSON.parse(JSON.stringify(this.props.state)); + teamSignup.team.type = 'O'; + teamSignup.team.name = name; + Client.findTeamByName(name, - (data) => { - if (data) { - this.setState({nameError: formatMessage(holders.unavailable)}); - } else { - if (global.window.mm_config.SendEmailNotifications === 'true') { - this.props.state.wizard = 'send_invites'; - } else { - this.props.state.wizard = 'username'; - } - this.props.state.team.type = 'O'; - - this.props.state.team.name = name; - this.props.updateParent(this.props.state); - } - }, - (err) => { - this.setState({nameError: err.message}); - } + (findTeam) => { + if (findTeam) { + this.setState({nameError: formatMessage(holders.unavailable)}); + $('#finish-button').button('reset'); + } else { + Client.createTeam(teamSignup.team, + (team) => { + Client.track('signup', 'signup_team_08_complete'); + $('#sign-up-button').button('reset'); + TeamStore.saveTeam(team); + TeamStore.appendTeamMember({team_id: team.id, user_id: UserStore.getCurrentId(), roles: 'admin'}); + TeamStore.emitChange(); + browserHistory.push('/' + team.name + '/channels/town-square'); + }, + (err) => { + this.setState({nameError: err.message}); + $('#finish-button').button('reset'); + } + ); + + $('#finish-button').button('reset'); + } + }, + (err) => { + this.setState({nameError: err.message}); + $('#finish-button').button('reset'); + } ); } handleFocus(e) { @@ -130,7 +147,7 @@ class TeamSignupUrlPage extends React.Component { /> <h2> <FormattedMessage - id='team_signup_url.teamUrl' + id='create_team_url.teamUrl' defaultMessage='Team URL' /> </h2> @@ -163,13 +180,13 @@ class TeamSignupUrlPage extends React.Component { </div> <p> <FormattedMessage - id='team_signup_url.webAddress' + id='create_team_url.webAddress' defaultMessage='Choose the web address of your new team:' /> </p> <ul className='color--light'> <FormattedHTMLMessage - id='team_signup_url.hint' + id='create_team_url.hint' defaultMessage="<li>Short and memorable is best</li> <li>Use lowercase letters, numbers and dashes</li> <li>Must start with a letter and can't end in a dash</li>" @@ -177,13 +194,14 @@ class TeamSignupUrlPage extends React.Component { </ul> <button type='submit' + id='finish-button' className='btn btn-primary margin--extra' onClick={this.submitNext} > <FormattedMessage - id='team_signup_url.next' - defaultMessage='Next' - /><i className='glyphicon glyphicon-chevron-right'></i> + id='create_team_password.finish' + defaultMessage='Finish' + /> </button> <div className='margin--extra'> <a @@ -191,7 +209,7 @@ class TeamSignupUrlPage extends React.Component { onClick={this.submitBack} > <FormattedMessage - id='team_signup_url.back' + id='create_team_url.back' defaultMessage='Back to previous step' /> </a> @@ -202,10 +220,10 @@ class TeamSignupUrlPage extends React.Component { } } -TeamSignupUrlPage.propTypes = { +TeamUrl.propTypes = { intl: intlShape.isRequired, state: React.PropTypes.object, updateParent: React.PropTypes.func }; -export default injectIntl(TeamSignupUrlPage); +export default injectIntl(TeamUrl); diff --git a/webapp/components/create_team/create_team.jsx b/webapp/components/create_team/create_team.jsx new file mode 100644 index 000000000..8a119a122 --- /dev/null +++ b/webapp/components/create_team/create_team.jsx @@ -0,0 +1,72 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ErrorBar from 'components/error_bar.jsx'; + +import {FormattedMessage} from 'react-intl'; +import {browserHistory, Link} from 'react-router'; + +import React from 'react'; + +export default class CreateTeam extends React.Component { + constructor(props) { + super(props); + + this.submit = this.submit.bind(this); + this.updateParent = this.updateParent.bind(this); + + const state = {}; + state.team = {}; + state.wizard = 'display_name'; + this.state = state; + } + + submit() { + // todo fill in + } + + componentDidMount() { + browserHistory.push('/create_team/display_name'); + } + + updateParent(state) { + this.setState(state); + browserHistory.push('/create_team/' + state.wizard); + } + + render() { + return ( + <div> + <ErrorBar/> + <div className='signup-header'> + <Link to='/select_team'> + <span className='fa fa-chevron-left'/> + <FormattedMessage + id='web.header.back' + /> + </Link> + </div> + <div className='col-sm-12'> + <div className='signup-team__container'> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> + <FormattedMessage + id='web.root.singup_info' + /> + </h4> + <div className='signup__content'> + {React.cloneElement(this.props.children, { + state: this.state, + updateParent: this.updateParent + })} + </div> + </div> + </div> + </div> + ); + } +} + +CreateTeam.propTypes = { + children: React.PropTypes.node +}; diff --git a/webapp/components/delete_channel_modal.jsx b/webapp/components/delete_channel_modal.jsx index 244472a56..222b72c41 100644 --- a/webapp/components/delete_channel_modal.jsx +++ b/webapp/components/delete_channel_modal.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {Modal} from 'react-bootstrap'; import TeamStore from 'stores/team_store.jsx'; import Constants from 'utils/constants.jsx'; diff --git a/webapp/components/delete_post_modal.jsx b/webapp/components/delete_post_modal.jsx index 0dbdc2b43..b2aea6590 100644 --- a/webapp/components/delete_post_modal.jsx +++ b/webapp/components/delete_post_modal.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import PostStore from 'stores/post_store.jsx'; import ModalStore from 'stores/modal_store.jsx'; import {Modal} from 'react-bootstrap'; diff --git a/webapp/components/do_verify_email.jsx b/webapp/components/do_verify_email.jsx index c3be111ed..fe4d61a72 100644 --- a/webapp/components/do_verify_email.jsx +++ b/webapp/components/do_verify_email.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import {FormattedMessage} from 'react-intl'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import LoadingScreen from './loading_screen.jsx'; import {browserHistory, Link} from 'react-router'; @@ -25,14 +25,14 @@ export default class DoVerifyEmail extends React.Component { const email = this.props.location.query.email; Client.verifyEmail( + uid, + hid, () => { browserHistory.push('/' + teamName + '/login?extra=verified&email=' + email); }, (err) => { this.setState({verifyStatus: 'failure', serverError: err.message}); - }, - uid, - hid + } ); } render() { diff --git a/webapp/components/edit_channel_header_modal.jsx b/webapp/components/edit_channel_header_modal.jsx index 35a5fb9dc..6a7cccebb 100644 --- a/webapp/components/edit_channel_header_modal.jsx +++ b/webapp/components/edit_channel_header_modal.jsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import * as Utils from 'utils/utils.jsx'; diff --git a/webapp/components/edit_channel_purpose_modal.jsx b/webapp/components/edit_channel_purpose_modal.jsx index 31cbdd240..a4779d022 100644 --- a/webapp/components/edit_channel_purpose_modal.jsx +++ b/webapp/components/edit_channel_purpose_modal.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; @@ -49,12 +49,9 @@ export default class EditChannelPurposeModal extends React.Component { return; } - const data = { - channel_id: this.props.channel.id, - channel_purpose: ReactDOM.findDOMNode(this.refs.purpose).value.trim() - }; - - Client.updateChannelPurpose(data, + Client.updateChannelPurpose( + this.props.channel.id, + ReactDOM.findDOMNode(this.refs.purpose).value.trim(), () => { AsyncClient.getChannel(this.props.channel.id); diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx index caf9a0ee5..bc67a34f9 100644 --- a/webapp/components/edit_post_modal.jsx +++ b/webapp/components/edit_post_modal.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import Textbox from './textbox.jsx'; @@ -33,20 +33,25 @@ class EditPostModal extends React.Component { this.handleEdit = this.handleEdit.bind(this); this.handleEditInput = this.handleEditInput.bind(this); this.handleEditKeyPress = this.handleEditKeyPress.bind(this); - this.handleUserInput = this.handleUserInput.bind(this); this.handleEditPostEvent = this.handleEditPostEvent.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this); - this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''}; + this.state = {editText: '', originalText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''}; } handleEdit() { var updatedPost = {}; updatedPost.message = this.state.editText.trim(); + if (updatedPost.message === this.state.originalText.trim()) { + // no changes so just close the modal + $('#edit_post').modal('hide'); + return; + } + if (updatedPost.message.length === 0) { var tempState = this.state; - delete tempState.editText; + Reflect.deleteProperty(tempState, 'editText'); BrowserStore.setItem('edit_state_transfer', tempState); $('#edit_post').modal('hide'); GlobalActions.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments); @@ -79,12 +84,10 @@ class EditPostModal extends React.Component { this.handleEdit(e); } } - handleUserInput(e) { - this.setState({editText: e.target.value}); - } handleEditPostEvent(options) { this.setState({ editText: options.message || '', + originalText: options.message || '', title: options.title || '', post_id: options.postId || '', channel_id: options.channelId || '', @@ -108,7 +111,7 @@ class EditPostModal extends React.Component { var self = this; $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { - self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); + self.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); }); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => { @@ -116,7 +119,15 @@ class EditPostModal extends React.Component { if (!button) { return; } - self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refocusid')}); + self.setState({ + editText: $(button).attr('data-message'), + originalText: $(button).attr('data-message'), + title: $(button).attr('data-title'), + channel_id: $(button).attr('data-channelid'), + post_id: $(button).attr('data-postid'), + comments: $(button).attr('data-comments'), + refocusId: $(button).attr('data-refocusid') + }); }); $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', () => { @@ -163,17 +174,17 @@ class EditPostModal extends React.Component { aria-label='Close' onClick={this.handleEditClose} > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'×'}</span> </button> - <h4 className='modal-title'> - <FormattedMessage - id='edit_post.edit' - defaultMessage='Edit {title}' - values={{ - title: this.state.title - }} - /> - </h4> + <h4 className='modal-title'> + <FormattedMessage + id='edit_post.edit' + defaultMessage='Edit {title}' + values={{ + title: this.state.title + }} + /> + </h4> </div> <div className='edit-modal-body modal-body'> <Textbox diff --git a/webapp/components/error_bar.jsx b/webapp/components/error_bar.jsx index 572f96e02..7257ffe94 100644 --- a/webapp/components/error_bar.jsx +++ b/webapp/components/error_bar.jsx @@ -29,8 +29,8 @@ export default class ErrorBar extends React.Component { } componentWillMount() { - if (global.window.mm_config.SendEmailNotifications === 'false') { - ErrorStore.storeLastError({message: Utils.localizeMessage('error_bar.preview_mode', 'Preview Mode: Email notifications have not been configured')}); + if (!ErrorStore.getIgnoreEmailPreview() && global.window.mm_config.SendEmailNotifications === 'false') { + ErrorStore.storeLastError({email_preview: true, message: Utils.localizeMessage('error_bar.preview_mode', 'Preview Mode: Email notifications have not been configured')}); this.onErrorChange(); } } diff --git a/webapp/components/file_attachment.jsx b/webapp/components/file_attachment.jsx index ccd4070aa..4a040a35b 100644 --- a/webapp/components/file_attachment.jsx +++ b/webapp/components/file_attachment.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {intlShape, injectIntl, defineMessages} from 'react-intl'; @@ -100,7 +100,7 @@ class FileAttachment extends React.Component { getFileInfoFromName(name) { var fileInfo = utils.splitFileLocation(name); - fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; + fileInfo.path = utils.getWindowLocationOrigin() + Client.getFilesRoute() + '/get' + fileInfo.path; return fileInfo; } diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx index 8d2ec5ac8..03a8a8128 100644 --- a/webapp/components/file_upload.jsx +++ b/webapp/components/file_upload.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import 'jquery-dragster/jquery.dragster.js'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -49,9 +49,9 @@ class FileUpload extends React.Component { fileUploadSuccess(channelId, data) { this.props.onFileUpload(data.filenames, data.client_ids, channelId); - var requests = this.state.requests; + const requests = JSON.parse(JSON.stringify(this.state.requests)); for (var j = 0; j < data.client_ids.length; j++) { - delete requests[data.client_ids[j]]; + Reflect.deleteProperty(requests, data.client_ids[j]); } this.setState({requests}); } @@ -64,13 +64,13 @@ class FileUpload extends React.Component { // clear any existing errors this.props.onUploadError(null); - var channelId = this.props.channelId || ChannelStore.getCurrentId(); + const channelId = this.props.channelId || ChannelStore.getCurrentId(); - var uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId); - var numUploads = 0; + const uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId); + let numUploads = 0; // keep track of how many files have been too large - var tooLargeFiles = []; + const tooLargeFiles = []; for (let i = 0; i < files.length && numUploads < uploadsRemaining; i++) { if (files[i].size > Constants.MAX_FILE_SIZE) { @@ -81,18 +81,15 @@ class FileUpload extends React.Component { // generate a unique id that can be used by other components to refer back to this upload const clientId = Utils.generateId(); - // prepare data to be uploaded - var formData = new FormData(); - formData.append('channel_id', channelId); - formData.append('files', files[i], files[i].name); - formData.append('client_ids', clientId); - - var request = Client.uploadFile(formData, + const request = Client.uploadFile(files[i], + files[i].name, + channelId, + clientId, this.fileUploadSuccess.bind(this, channelId), this.fileUploadFail.bind(this, clientId) ); - var requests = this.state.requests; + const requests = this.state.requests; requests[clientId] = request; this.setState({requests}); @@ -231,8 +228,6 @@ class FileUpload extends React.Component { // generate a unique id that can be used by other components to refer back to this file upload var clientId = Utils.generateId(); - var formData = new FormData(); - formData.append('channel_id', channelId); var d = new Date(); var hour; if (d.getHours() < 10) { @@ -247,16 +242,17 @@ class FileUpload extends React.Component { min = String(d.getMinutes()); } - var name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext; - formData.append('files', file, name); - formData.append('client_ids', clientId); + const name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext; - var request = Client.uploadFile(formData, + const request = Client.uploadFile(file, + name, + channelId, + clientId, self.fileUploadSuccess.bind(self, channelId), self.fileUploadFail.bind(self, clientId) ); - var requests = self.state.requests; + const requests = self.state.requests; requests[clientId] = request; self.setState({requests}); @@ -280,13 +276,13 @@ class FileUpload extends React.Component { } cancelUpload(clientId) { - var requests = this.state.requests; - var request = requests[clientId]; + const requests = JSON.parse(JSON.stringify(this.state.requests)); + const request = requests[clientId]; if (request) { request.abort(); - delete requests[clientId]; + Reflect.deleteProperty(requests, clientId); this.setState({requests}); } } diff --git a/webapp/components/filtered_user_list.jsx b/webapp/components/filtered_user_list.jsx index bd6c49714..5aa0bd96c 100644 --- a/webapp/components/filtered_user_list.jsx +++ b/webapp/components/filtered_user_list.jsx @@ -113,6 +113,7 @@ class FilteredUserList extends React.Component { > <UserList users={users} + teamMembers={this.props.teamMembers} actions={this.props.actions} actionProps={this.props.actionProps} /> @@ -124,6 +125,7 @@ class FilteredUserList extends React.Component { FilteredUserList.defaultProps = { users: [], + teamMembers: [], actions: [], actionProps: {} }; @@ -131,6 +133,7 @@ FilteredUserList.defaultProps = { FilteredUserList.propTypes = { intl: intlShape.isRequired, users: React.PropTypes.arrayOf(React.PropTypes.object), + teamMembers: React.PropTypes.arrayOf(React.PropTypes.object), actions: React.PropTypes.arrayOf(React.PropTypes.func), actionProps: React.PropTypes.object, style: React.PropTypes.object diff --git a/webapp/components/not_logged_in.jsx b/webapp/components/header_footer_template.jsx index 4beee6259..ce2f59541 100644 --- a/webapp/components/not_logged_in.jsx +++ b/webapp/components/header_footer_template.jsx @@ -9,12 +9,12 @@ import {Link} from 'react-router'; export default class NotLoggedIn extends React.Component { componentDidMount() { - $('body').attr('class', 'sticky'); - $('#root').attr('class', 'container-fluid'); + $('body').addClass('sticky'); + $('#root').addClass('container-fluid'); } componentWillUnmount() { - $('body').attr('class', ''); - $('#root').attr('class', ''); + $('body').removeClass('sticky'); + $('#root').removeClass('container-fluid'); } render() { return ( diff --git a/webapp/components/invite_member_modal.jsx b/webapp/components/invite_member_modal.jsx index 17cec7aad..4ac620f08 100644 --- a/webapp/components/invite_member_modal.jsx +++ b/webapp/components/invite_member_modal.jsx @@ -5,7 +5,7 @@ import ReactDOM from 'react-dom'; import * as utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import ModalStore from 'stores/modal_store.jsx'; import UserStore from 'stores/user_store.jsx'; diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx index 1dcb6b0aa..3941dd12c 100644 --- a/webapp/components/logged_in.jsx +++ b/webapp/components/logged_in.jsx @@ -2,39 +2,17 @@ // See License.txt for license information. import $ from 'jquery'; +import LoadingScreen from 'components/loading_screen.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as GlobalActions from 'action_creators/global_actions.jsx'; import UserStore from 'stores/user_store.jsx'; -import ChannelStore from 'stores/channel_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; -import Constants from 'utils/constants.jsx'; -const TutorialSteps = Constants.TutorialSteps; -const Preferences = Constants.Preferences; -import ErrorBar from 'components/error_bar.jsx'; import * as Websockets from 'action_creators/websocket_actions.jsx'; -import LoadingScreen from 'components/loading_screen.jsx'; +import Constants from 'utils/constants.jsx'; import {browserHistory} from 'react-router'; -import SidebarRight from 'components/sidebar_right.jsx'; -import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; -import Navbar from 'components/navbar.jsx'; - -// Modals -import GetPostLinkModal from 'components/get_post_link_modal.jsx'; -import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx'; -import EditPostModal from 'components/edit_post_modal.jsx'; -import DeletePostModal from 'components/delete_post_modal.jsx'; -import MoreChannelsModal from 'components/more_channels.jsx'; -import TeamSettingsModal from 'components/team_settings_modal.jsx'; -import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx'; -import RegisterAppModal from 'components/register_app_modal.jsx'; -import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx'; -import InviteMemberModal from 'components/invite_member_modal.jsx'; -import SelectTeamModal from 'components/admin_console/select_team_modal.jsx'; - const CLIENT_STATUS_INTERVAL = 30000; const BACKSPACE_CHAR = 8; @@ -45,19 +23,22 @@ export default class LoggedIn extends React.Component { super(params); this.onUserChanged = this.onUserChanged.bind(this); + this.setupUser = this.setupUser.bind(this); this.state = { - user: null, - profiles: null + user: UserStore.getCurrentUser() }; + + if (this.state.user) { + this.setupUser(this.state.user); + } } + isValidState() { - return this.state.user != null && this.state.profiles != null; + return this.state.user != null; } - onUserChanged() { - // Grab the current user - const user = UserStore.getCurrentUser(); + setupUser(user) { // Update segment indentify if (global.window.mm_config.SegmentDeveloperKey != null && global.window.mm_config.SegmentDeveloperKey !== '') { global.window.analytics.identify(user.id, { @@ -65,7 +46,6 @@ export default class LoggedIn extends React.Component { email: user.email, createdAt: user.create_at, username: user.username, - team_id: user.team_id, id: user.id }); } @@ -78,25 +58,19 @@ export default class LoggedIn extends React.Component { Utils.applyTheme(Constants.THEMES.default); } } + } - // Go to tutorial if we are first arrivign - const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); - if (tutorialStep <= TutorialSteps.INTRO_SCREENS) { - browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial'); - } - - // Get profiles - const profiles = UserStore.getProfiles(); + onUserChanged() { + // Grab the current user + const user = UserStore.getCurrentUser(); + this.setupUser(user); this.setState({ - user, - profiles + user }); } - componentWillMount() { - // Emit view action - GlobalActions.viewLoggedIn(); + componentWillMount() { // Listen for user UserStore.addChangeListener(this.onUserChanged); @@ -116,7 +90,7 @@ export default class LoggedIn extends React.Component { } console.log('detected logout from a different tab'); //eslint-disable-line no-console - browserHistory.push('/' + this.props.params.team); + browserHistory.push('/'); } if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) { @@ -170,18 +144,6 @@ export default class LoggedIn extends React.Component { $('body').addClass('ios'); } - // Set up tracking for whether the window is active - window.isActive = true; - $(window).on('focus', () => { - AsyncClient.updateLastViewedAt(); - ChannelStore.resetCounts(ChannelStore.getCurrentId()); - ChannelStore.emitChange(); - window.isActive = true; - }); - $(window).on('blur', () => { - window.isActive = false; - }); - // if preferences have already been stored in local storage do not wait until preference store change is fired and handled in channel.jsx const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT); Utils.applyFont(selectedFont); @@ -193,13 +155,11 @@ export default class LoggedIn extends React.Component { } }); } + componentWillUnmount() { $('#root').attr('class', ''); clearInterval(this.intervalId); - $(window).off('focus'); - $(window).off('blur'); - Websockets.close(); UserStore.removeChangeListener(this.onUserChanged); @@ -211,75 +171,18 @@ export default class LoggedIn extends React.Component { $(window).off('keydown.preventBackspace'); } + render() { if (!this.isValidState()) { return <LoadingScreen/>; } - let content = []; - if (this.props.children) { - content = this.props.children; - } else { - content.push( - this.props.navbar - ); - content.push( - this.props.sidebar - ); - content.push( - <div - key='inner-wrap' - className='inner-wrap channel__wrap' - > - <div className='row header'> - <div id='navbar'> - <Navbar/> - </div> - </div> - <div className='row main'> - {React.cloneElement(this.props.center, { - user: this.state.user, - profiles: this.state.profiles - })} - </div> - </div> - ); - } - return ( - <div className='channel-view'> - <ErrorBar/> - <div className='container-fluid'> - <SidebarRight/> - <SidebarRightMenu/> - {content} - - <GetPostLinkModal/> - <GetTeamInviteLinkModal/> - <InviteMemberModal/> - <ImportThemeModal/> - <TeamSettingsModal/> - <MoreChannelsModal/> - <EditPostModal/> - <DeletePostModal/> - <RemovedFromChannelModal/> - <RegisterAppModal/> - <SelectTeamModal/> - </div> - </div> - ); + return React.cloneElement(this.props.children, { + user: this.state.user + }); } } -LoggedIn.defaultProps = { -}; - LoggedIn.propTypes = { - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]), - navbar: React.PropTypes.element, - sidebar: React.PropTypes.element, - center: React.PropTypes.element, - params: React.PropTypes.object + children: React.PropTypes.object }; diff --git a/webapp/components/login/login.jsx b/webapp/components/login/login.jsx index a3dadbf36..c5955a1f4 100644 --- a/webapp/components/login/login.jsx +++ b/webapp/components/login/login.jsx @@ -5,12 +5,14 @@ import LoginEmail from './components/login_email.jsx'; import LoginUsername from './components/login_username.jsx'; import LoginLdap from './components/login_ldap.jsx'; import LoginMfa from './components/login_mfa.jsx'; +import ErrorBar from 'components/error_bar.jsx'; -import TeamStore from 'stores/team_store.jsx'; +import * as GlobalActions from '../../action_creators/global_actions.jsx'; import UserStore from 'stores/user_store.jsx'; +import Client from 'utils/web_client.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; -import * as Client from 'utils/client.jsx'; + import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -18,40 +20,24 @@ import {FormattedMessage} from 'react-intl'; import {browserHistory, Link} from 'react-router'; import React from 'react'; +import logoImage from 'images/logo.png'; export default class Login extends React.Component { constructor(props) { super(props); - this.getStateFromStores = this.getStateFromStores.bind(this); - this.onTeamChange = this.onTeamChange.bind(this); this.preSubmit = this.preSubmit.bind(this); this.submit = this.submit.bind(this); + this.finishSignin = this.finishSignin.bind(this); - const state = this.getStateFromStores(); - state.doneCheckLogin = false; + const state = {}; + state.showMfa = false; this.state = state; } componentDidMount() { - TeamStore.addChangeListener(this.onTeamChange); - Client.getMeLoggedIn((data) => { - if (data && data.logged_in !== 'false') { - browserHistory.push('/' + this.props.params.team + '/channels/town-square'); - } else { - this.setState({doneCheckLogin: true}); //eslint-disable-line react/no-did-mount-set-state - } - }); - } - componentWillUnmount() { - TeamStore.removeChangeListener(this.onTeamChange); - } - getStateFromStores() { - return { - currentTeam: TeamStore.getByName(this.props.params.team) - }; - } - onTeamChange() { - this.setState(this.getStateFromStores()); + if (UserStore.getCurrentUser()) { + browserHistory.push('/select_team'); + } } preSubmit(method, loginId, password) { if (global.window.mm_config.EnableMultifactorAuthentication !== 'true') { @@ -59,7 +45,7 @@ export default class Login extends React.Component { return; } - Client.checkMfa(method, this.state.currentTeam.name, loginId, + Client.checkMfa(method, loginId, (data) => { if (data.mfa_required === 'true') { this.setState({showMfa: true, method, loginId, password}); @@ -78,27 +64,41 @@ export default class Login extends React.Component { } ); } + finishSignin() { + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); + } + submit(method, loginId, password, token) { this.setState({showMfa: false, serverEmailError: null, serverUsernameError: null, serverLdapError: null}); - const team = this.state.currentTeam.name; - if (method === Constants.EMAIL_SERVICE) { - Client.loginByEmail(team, loginId, password, token, + Client.webLogin( + loginId, + null, + password, + token, () => { UserStore.setLastEmail(loginId); - browserHistory.push('/' + team + '/channels/town-square'); + this.finishSignin(); }, (err) => { if (err.id === 'api.user.login.not_verified.app_error') { - browserHistory.push('/should_verify_email?teamname=' + encodeURIComponent(team) + '&email=' + encodeURIComponent(loginId)); + browserHistory.push('/should_verify_email?&email=' + encodeURIComponent(loginId)); return; } this.setState({serverEmailError: err.message}); } ); } else if (method === Constants.USERNAME_SERVICE) { - Client.loginByUsername(team, loginId, password, token, + Client.webLogin( + null, + loginId, + password, + token, () => { UserStore.setLastUsername(loginId); @@ -106,7 +106,7 @@ export default class Login extends React.Component { if (redirect) { browserHistory.push(decodeURIComponent(redirect)); } else { - browserHistory.push('/' + team + '/channels/town-square'); + this.finishSignin(); } }, (err) => { @@ -120,13 +120,16 @@ export default class Login extends React.Component { } ); } else if (method === Constants.LDAP_SERVICE) { - Client.loginByLdap(team, loginId, password, token, + Client.loginByLdap( + loginId, + password, + token, () => { const redirect = Utils.getUrlParameter('redirect'); if (redirect) { browserHistory.push(decodeURIComponent(redirect)); } else { - browserHistory.push('/' + team + '/channels/town-square'); + this.finishSignin(); } }, (err) => { @@ -144,7 +147,7 @@ export default class Login extends React.Component { return ( <div> <img - src='/api/v1/admin/get_brand_image' + src={Client.getAdminRoute() + '/get_brand_image'} /> <p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/> </div> @@ -153,7 +156,7 @@ export default class Login extends React.Component { return null; } - createLoginOptions(currentTeam) { + createLoginOptions() { const extraParam = Utils.getUrlParameter('extra'); let extraBox = ''; if (extraParam) { @@ -187,10 +190,19 @@ export default class Login extends React.Component { /> </div> ); + } else if (extraParam === Constants.PASSWORD_CHANGE) { + extraBox = ( + <div className='alert alert-success'> + <i className='fa fa-check'/> + <FormattedMessage + id='login.passwordChanged' + defaultMessage=' Password updated successfully' + /> + </div> + ); } } - const teamName = currentTeam.name; const ldapEnabled = global.window.mm_config.EnableLdap === 'true'; const gitlabSigninEnabled = global.window.mm_config.EnableSignUpWithGitLab === 'true'; const googleSigninEnabled = global.window.mm_config.EnableSignUpWithGoogle === 'true'; @@ -200,10 +212,10 @@ export default class Login extends React.Component { const oauthLogins = []; if (gitlabSigninEnabled) { oauthLogins.push( - <Link + <a className='btn btn-custom-login gitlab' key='gitlab' - to={'/api/v1/oauth/gitlab/login?team=' + encodeURIComponent(teamName)} + href={Client.getOAuthRoute() + '/gitlab/login'} > <span className='icon'/> <span> @@ -212,7 +224,7 @@ export default class Login extends React.Component { defaultMessage='with GitLab' /> </span> - </Link> + </a> ); } @@ -221,7 +233,7 @@ export default class Login extends React.Component { <Link className='btn btn-custom-login google' key='google' - to={'/api/v1/oauth/google/login?team=' + encodeURIComponent(teamName)} + to={Client.getOAuthRoute() + '/google/login'} > <span className='icon'/> <span> @@ -238,7 +250,6 @@ export default class Login extends React.Component { if (emailSigninEnabled) { emailLogin = ( <LoginEmail - teamName={teamName} serverError={this.state.serverEmailError} submit={this.preSubmit} /> @@ -263,7 +274,6 @@ export default class Login extends React.Component { if (usernameSigninEnabled) { usernameLogin = ( <LoginUsername - teamName={teamName} serverError={this.state.serverUsernameError} submit={this.preSubmit} /> @@ -288,7 +298,6 @@ export default class Login extends React.Component { if (ldapEnabled) { ldapLogin = ( <LoginLdap - teamName={teamName} serverError={this.state.serverLdapError} submit={this.preSubmit} /> @@ -309,34 +318,31 @@ export default class Login extends React.Component { } } - let userSignUp; - if (currentTeam.allow_open_invite) { - userSignUp = ( - <div> - <span> + const userSignUp = ( + <div> + <span> + <FormattedMessage + id='login.noAccount' + defaultMessage="Don't have an account? " + /> + <Link + to={'/signup_user_complete'} + className='signup-team-login' + > <FormattedMessage - id='login.noAccount' - defaultMessage="Don't have an account? " + id='login.create' + defaultMessage='Create one now' /> - <Link - to={'/signup_user_complete/?id=' + currentTeam.invite_id} - className='signup-team-login' - > - <FormattedMessage - id='login.create' - defaultMessage='Create one now' - /> - </Link> - </span> - </div> - ); - } + </Link> + </span> + </div> + ); let forgotPassword; if (usernameSigninEnabled || emailSigninEnabled) { forgotPassword = ( <div className='form-group'> - <Link to={'/' + teamName + '/reset_password'}> + <Link to={'/reset_password'}> <FormattedMessage id='login.forgot' defaultMessage='I forgot my password' @@ -346,23 +352,6 @@ export default class Login extends React.Component { ); } - let teamSignUp; - if (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp()) { - teamSignUp = ( - <div className='margin--extra'> - <Link - to='/' - className='signup-team-login' - > - <FormattedMessage - id='login.createTeam' - defaultMessage='Create a new team' - /> - </Link> - </div> - ); - } - return ( <div> {extraBox} @@ -372,16 +361,10 @@ export default class Login extends React.Component { {ldapLogin} {userSignUp} {forgotPassword} - {teamSignUp} </div> ); } render() { - const currentTeam = this.state.currentTeam; - if (currentTeam == null || !this.state.doneCheckLogin) { - return <div/>; - } - let content; let customContent; let customClass; @@ -395,7 +378,7 @@ export default class Login extends React.Component { /> ); } else { - content = this.createLoginOptions(currentTeam); + content = this.createLoginOptions(); customContent = this.createCustomLogin(); if (customContent) { customClass = 'branded'; @@ -404,36 +387,23 @@ export default class Login extends React.Component { return ( <div> - <div className='signup-header'> - <Link to='/'> - <span className='fa fa-chevron-left'/> - <FormattedMessage - id='web.header.back' - /> - </Link> - </div> + <ErrorBar/> <div className='col-sm-12'> <div className={'signup-team__container ' + customClass}> <div className='signup__markdown'> {customContent} </div> + <img + className='signup-team-logo' + src={logoImage} + /> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> + <FormattedMessage + id='web.root.singup_info' + /> + </h4> <div className='signup__content'> - <h5 className='margin--less'> - <FormattedMessage - id='login.signTo' - defaultMessage='Sign in to:' - /> - </h5> - <h2 className='signup-team__name'>{currentTeam.display_name}</h2> - <h2 className='signup-team__subdomain'> - <FormattedMessage - id='login.on' - defaultMessage='on {siteName}' - values={{ - siteName: global.window.mm_config.SiteName - }} - /> - </h2> {content} </div> </div> diff --git a/webapp/components/member_list_team.jsx b/webapp/components/member_list_team.jsx index bb5eee496..d0714e942 100644 --- a/webapp/components/member_list_team.jsx +++ b/webapp/components/member_list_team.jsx @@ -4,6 +4,8 @@ import FilteredUserList from './filtered_user_list.jsx'; import TeamMembersDropdown from './team_members_dropdown.jsx'; import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; import React from 'react'; @@ -13,18 +15,23 @@ export default class MemberListTeam extends React.Component { this.getUsers = this.getUsers.bind(this); this.onChange = this.onChange.bind(this); + this.onTeamChange = this.onTeamChange.bind(this); this.state = { - users: this.getUsers() + users: this.getUsers(), + teamMembers: TeamStore.getMembersForTeam() }; } componentDidMount() { UserStore.addChangeListener(this.onChange); + TeamStore.addChangeListener(this.onTeamChange); + AsyncClient.getTeamMembers(TeamStore.getCurrentId()); } componentWillUnmount() { UserStore.removeChangeListener(this.onChange); + TeamStore.removeChangeListener(this.onTeamChange); } getUsers() { @@ -46,11 +53,18 @@ export default class MemberListTeam extends React.Component { }); } + onTeamChange() { + this.setState({ + teamMembers: TeamStore.getMembersForTeam() + }); + } + render() { return ( <FilteredUserList style={this.props.style} users={this.state.users} + teamMembers={this.state.teamMembers} actions={[TeamMembersDropdown]} /> ); diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx index 5ccf9c11e..3ab05341b 100644 --- a/webapp/components/more_channels.jsx +++ b/webapp/components/more_channels.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as client from 'utils/client.jsx'; +import client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import LoadingScreen from './loading_screen.jsx'; diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx index e4e64c12e..919a72d0a 100644 --- a/webapp/components/navbar.jsx +++ b/webapp/components/navbar.jsx @@ -18,7 +18,7 @@ import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import TeamStore from 'stores/team_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; diff --git a/webapp/components/navbar_dropdown.jsx b/webapp/components/navbar_dropdown.jsx index da1ae237e..19b99a14d 100644 --- a/webapp/components/navbar_dropdown.jsx +++ b/webapp/components/navbar_dropdown.jsx @@ -6,6 +6,7 @@ import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; +import TeamStore from 'stores/team_store.jsx'; import AboutBuildModal from './about_build_modal.jsx'; import TeamMembersModal from './team_members_modal.jsx'; import ToggleModalButton from './toggle_modal_button.jsx'; @@ -25,10 +26,13 @@ export default class NavbarDropdown extends React.Component { this.handleAboutModal = this.handleAboutModal.bind(this); this.aboutModalDismissed = this.aboutModalDismissed.bind(this); + this.onTeamChange = this.onTeamChange.bind(this); this.state = { showUserSettingsModal: false, - showAboutModal: false + showAboutModal: false, + teams: TeamStore.getAll(), + teamMembers: TeamStore.getTeamMembers() }; } handleAboutModal() { @@ -37,6 +41,7 @@ export default class NavbarDropdown extends React.Component { aboutModalDismissed() { this.setState({showAboutModal: false}); } + componentDidMount() { $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { $('.sidebar--left .dropdown-menu').scrollTop(0); @@ -45,10 +50,22 @@ export default class NavbarDropdown extends React.Component { this.blockToggle = false; }, 100); }); + + TeamStore.addChangeListener(this.onTeamChange); + } + + onTeamChange() { + this.setState({ + teams: TeamStore.getAll(), + teamMembers: TeamStore.getTeamMembers() + }); } + componentWillUnmount() { $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); + TeamStore.removeChangeListener(this.onTeamChange); } + render() { var teamLink = ''; var inviteLink = ''; @@ -130,7 +147,7 @@ export default class NavbarDropdown extends React.Component { if (isAdmin || window.EnableAdminOnlyIntegrations !== 'true') { integrationsLink = ( <li> - <Link to={'/settings/integrations'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations'}> <FormattedMessage id='navbar_dropdown.integrations' defaultMessage='Integrations' @@ -163,8 +180,7 @@ export default class NavbarDropdown extends React.Component { <li key='newTeam_li'> <Link key='newTeam_a' - target='_blank' - to={Utils.getWindowLocationOrigin() + '/signup_team'} + to='/create_team' > <FormattedMessage id='navbar_dropdown.create' @@ -175,6 +191,34 @@ export default class NavbarDropdown extends React.Component { ); } + if (this.state.teamMembers && this.state.teamMembers.length > 0) { + teams.push( + <li + key='teamDiv' + className='divider' + ></li> + ); + + for (var index in this.state.teamMembers) { + if (this.state.teamMembers.hasOwnProperty(index)) { + var teamMember = this.state.teamMembers[index]; + var team = this.state.teams[teamMember.team_id]; + + if (team.name !== this.props.teamName) { + teams.push( + <li key={'team_' + team.name}> + <Link + to={'/' + team.name + '/channels/town-square'} + > + {team.display_name} + </Link> + </li> + ); + } + } + } + } + let helpLink = null; if (global.window.mm_config.HelpLink) { helpLink = ( @@ -245,12 +289,15 @@ export default class NavbarDropdown extends React.Component { {inviteLink} {teamLink} <li> - <Link to={'/' + this.props.teamName + '/logout'}> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > <FormattedMessage id='navbar_dropdown.logout' defaultMessage='Logout' /> - </Link> + </a> </li> {adminDivider} {teamSettings} diff --git a/webapp/components/needs_team.jsx b/webapp/components/needs_team.jsx index f624b1ebc..59797f086 100644 --- a/webapp/components/needs_team.jsx +++ b/webapp/components/needs_team.jsx @@ -1,22 +1,156 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import React from 'react'; + +import $ from 'jquery'; + +import {browserHistory} from 'react-router'; +import * as Utils from 'utils/utils.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import PreferenceStore from 'stores/preference_store.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; +import Constants from 'utils/constants.jsx'; +const TutorialSteps = Constants.TutorialSteps; +const Preferences = Constants.Preferences; -import React from 'react'; +import ErrorBar from 'components/error_bar.jsx'; +import SidebarRight from 'components/sidebar_right.jsx'; +import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; +import Navbar from 'components/navbar.jsx'; + +// Modals +import GetPostLinkModal from 'components/get_post_link_modal.jsx'; +import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx'; +import EditPostModal from 'components/edit_post_modal.jsx'; +import DeletePostModal from 'components/delete_post_modal.jsx'; +import MoreChannelsModal from 'components/more_channels.jsx'; +import TeamSettingsModal from 'components/team_settings_modal.jsx'; +import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx'; +import RegisterAppModal from 'components/register_app_modal.jsx'; +import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx'; +import InviteMemberModal from 'components/invite_member_modal.jsx'; +import SelectTeamModal from 'components/admin_console/select_team_modal.jsx'; export default class NeedsTeam extends React.Component { + constructor(params) { + super(params); + + this.onChanged = this.onChanged.bind(this); + + this.state = { + profiles: UserStore.getProfiles(), + team: TeamStore.getCurrent() + }; + } + + onChanged() { + this.setState({ + profiles: UserStore.getProfiles(), + team: TeamStore.getCurrent() + }); + } + componentWillMount() { - GlobalActions.loadTeamRequiredPage(); + UserStore.addChangeListener(this.onChanged); + TeamStore.addChangeListener(this.onChanged); + + // Emit view action + GlobalActions.viewLoggedIn(); + + // Go to tutorial if we are first arrivign + const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); + if (tutorialStep <= TutorialSteps.INTRO_SCREENS) { + browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial'); + } + + // Set up tracking for whether the window is active + window.isActive = true; + $(window).on('focus', () => { + AsyncClient.updateLastViewedAt(); + ChannelStore.resetCounts(ChannelStore.getCurrentId()); + ChannelStore.emitChange(); + window.isActive = true; + }); + $(window).on('blur', () => { + window.isActive = false; + }); + } + + componentWillUnmount() { + UserStore.removeChangeListener(this.onChanged); + TeamStore.addChangeListener(this.onChanged); + $(window).off('focus'); + $(window).off('blur'); } + render() { - return this.props.children; + let content = []; + if (this.props.children) { + content = this.props.children; + } else { + content.push( + this.props.navbar + ); + content.push( + this.props.sidebar + ); + content.push( + <div + key='inner-wrap' + className='inner-wrap channel__wrap' + > + <div className='row header'> + <div id='navbar'> + <Navbar/> + </div> + </div> + <div className='row main'> + {React.cloneElement(this.props.center, { + user: this.props.user, + profiles: this.state.profiles, + team: this.state.team + })} + </div> + </div> + ); + } + return ( + <div className='channel-view'> + <ErrorBar/> + <div className='container-fluid'> + <SidebarRight/> + <SidebarRightMenu/> + {content} + + <GetPostLinkModal/> + <GetTeamInviteLinkModal/> + <InviteMemberModal/> + <ImportThemeModal/> + <TeamSettingsModal/> + <MoreChannelsModal/> + <EditPostModal/> + <DeletePostModal/> + <RemovedFromChannelModal/> + <RegisterAppModal/> + <SelectTeamModal/> + </div> + </div> + ); } } -NeedsTeam.defaultProps = { -}; - NeedsTeam.propTypes = { - children: React.PropTypes.object + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]), + navbar: React.PropTypes.element, + sidebar: React.PropTypes.element, + center: React.PropTypes.element, + params: React.PropTypes.object, + user: React.PropTypes.object }; diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx index 82494dac0..7019da4aa 100644 --- a/webapp/components/new_channel_flow.jsx +++ b/webapp/components/new_channel_flow.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import UserStore from 'stores/user_store.jsx'; import NewChannelModal from './new_channel_modal.jsx'; @@ -106,11 +106,7 @@ class NewChannelFlow extends React.Component { (data) => { Client.getChannel( data.id, - (data2, textStatus, xhr) => { - if (xhr.status === 304 || !data2) { - return; - } - + (data2) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_CHANNEL, channel: data2.channel, diff --git a/webapp/components/notify_counts.jsx b/webapp/components/notify_counts.jsx index acc64dfb0..9238c8736 100644 --- a/webapp/components/notify_counts.jsx +++ b/webapp/components/notify_counts.jsx @@ -32,17 +32,22 @@ export default class NotifyCounts extends React.Component { this.onListenerChange = this.onListenerChange.bind(this); this.state = getCountsStateFromStores(); + this.mounted = false; } componentDidMount() { + this.mounted = true; ChannelStore.addChangeListener(this.onListenerChange); } componentWillUnmount() { + this.mounted = false; ChannelStore.removeChangeListener(this.onListenerChange); } onListenerChange() { - var newState = getCountsStateFromStores(); - if (!utils.areObjectsEqual(newState, this.state)) { - this.setState(newState); + if (this.mounted) { + var newState = getCountsStateFromStores(); + if (!utils.areObjectsEqual(newState, this.state)) { + this.setState(newState); + } } } render() { diff --git a/webapp/components/password_reset_form.jsx b/webapp/components/password_reset_form.jsx index 863420902..23b8952cc 100644 --- a/webapp/components/password_reset_form.jsx +++ b/webapp/components/password_reset_form.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -40,16 +40,12 @@ class PasswordResetForm extends React.Component { error: null }); - const data = {}; - data.new_password = password; - data.hash = this.props.location.query.h; - data.data = this.props.location.query.d; - data.name = this.props.params.team; - - Client.resetPassword(data, + Client.resetPassword( + this.props.location.query.code, + password, () => { this.setState({error: null}); - browserHistory.push('/' + this.props.params.team + '/login'); + browserHistory.push('/login?extra=' + Constants.PASSWORD_CHANGE); }, (err) => { this.setState({error: err.message}); diff --git a/webapp/components/password_reset_send_link.jsx b/webapp/components/password_reset_send_link.jsx index e3ab8949e..65d9439bd 100644 --- a/webapp/components/password_reset_send_link.jsx +++ b/webapp/components/password_reset_send_link.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as client from 'utils/client.jsx'; +import client from 'utils/web_client.jsx'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -43,10 +43,8 @@ class PasswordResetSendLink extends React.Component { error: '' }); - var data = {}; - data.email = email; - data.name = this.props.params.team; - client.sendPasswordReset(data, + client.sendPasswordReset( + email, () => { this.setState({ error: null, diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx index 8c3c381af..387c37ab5 100644 --- a/webapp/components/popover_list_members.jsx +++ b/webapp/components/popover_list_members.jsx @@ -7,6 +7,7 @@ import UserStore from 'stores/user_store.jsx'; import {Popover, Overlay} from 'react-bootstrap'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; import {browserHistory} from 'react-router'; @@ -97,7 +98,7 @@ export default class PopoverListMembers extends React.Component { className='more-modal__image' width='26px' height='26px' - src={`/api/v1/users/${m.id}/image?time=${m.update_at}`} + src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.update_at}`} /> <div className='more-modal__details'> <div diff --git a/webapp/components/post.jsx b/webapp/components/post.jsx index 7fabec741..ae3fa9c98 100644 --- a/webapp/components/post.jsx +++ b/webapp/components/post.jsx @@ -10,7 +10,7 @@ import ChannelStore from 'stores/channel_store.jsx'; import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; @@ -48,7 +48,7 @@ export default class Post extends React.Component { e.preventDefault(); var post = this.props.post; - Client.createPost(post, post.channel_id, + Client.createPost(post, (data) => { AsyncClient.getPosts(); diff --git a/webapp/components/register_app_modal.jsx b/webapp/components/register_app_modal.jsx index c632233cf..82a095c75 100644 --- a/webapp/components/register_app_modal.jsx +++ b/webapp/components/register_app_modal.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import ModalStore from 'stores/modal_store.jsx'; import {Modal} from 'react-bootstrap'; diff --git a/webapp/components/rename_channel_modal.jsx b/webapp/components/rename_channel_modal.jsx index 3e47847e7..ed045da91 100644 --- a/webapp/components/rename_channel_modal.jsx +++ b/webapp/components/rename_channel_modal.jsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import Constants from 'utils/constants.jsx'; diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index 53170ee15..709865dc1 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -9,7 +9,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; import FileAttachmentList from './file_attachment_list.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; var ActionTypes = Constants.ActionTypes; import * as TextFormatting from 'utils/text_formatting.jsx'; @@ -43,7 +43,7 @@ class RhsComment extends React.Component { e.preventDefault(); var post = this.props.post; - Client.createPost(post, post.channel_id, + Client.createPost(post, (data) => { AsyncClient.getPosts(post.channel_id); @@ -261,7 +261,7 @@ class RhsComment extends React.Component { <div className='post__content'> <div className='post__img'> <img - src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} + src={Client.getUsersRoute() + '/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' /> diff --git a/webapp/components/rhs_header_post.jsx b/webapp/components/rhs_header_post.jsx index 189ee0acb..493040800 100644 --- a/webapp/components/rhs_header_post.jsx +++ b/webapp/components/rhs_header_post.jsx @@ -3,6 +3,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import Constants from 'utils/constants.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; @@ -21,16 +22,7 @@ export default class RhsHeaderPost extends React.Component { } handleClose(e) { e.preventDefault(); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_SEARCH, - results: null - }); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_POST_SELECTED, - postId: null - }); + GlobalActions.emitCloseRightHandSide(); } handleBack(e) { e.preventDefault(); diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx index 59364d085..0adbc7f04 100644 --- a/webapp/components/root.jsx +++ b/webapp/components/root.jsx @@ -1,10 +1,10 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; +//import $ from 'jquery'; +//import Client from 'utils/web_client.jsx'; + import * as GlobalActions from 'action_creators/global_actions.jsx'; -import * as Client from 'utils/client.jsx'; -import BrowserStore from 'stores/browser_store.jsx'; import LocalizationStore from 'stores/localization_store.jsx'; import {IntlProvider} from 'react-intl'; @@ -14,6 +14,7 @@ import React from 'react'; import FastClick from 'fastclick'; import {browserHistory} from 'react-router'; +import UserStore from 'stores/user_store.jsx'; export default class Root extends React.Component { constructor(props) { @@ -29,15 +30,16 @@ export default class Root extends React.Component { localizationChanged() { this.setState({locale: LocalizationStore.getLocale(), translations: LocalizationStore.getTranslations()}); } + redirectIfNecessary(props) { if (props.location.pathname === '/') { - Client.getMeLoggedIn((data) => { - if (!data || data.logged_in === 'false') { - browserHistory.push('/signup_team'); - } else { - browserHistory.push('/' + data.team_name + '/channels/town-square'); - } - }); + if (UserStore.getNoAccounts()) { + browserHistory.push('/signup_user_complete'); + } else if (UserStore.getCurrentUser()) { + browserHistory.push('/select_team'); + } else { + browserHistory.push('/login'); + } } } componentWillReceiveProps(newProps) { @@ -47,28 +49,6 @@ export default class Root extends React.Component { // Setup localization listener LocalizationStore.addChangeListener(this.localizationChanged); - // Browser store check version - BrowserStore.checkVersion(); - - window.onerror = (msg, url, line, column, stack) => { - var l = {}; - l.level = 'ERROR'; - l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url; - - $.ajax({ - url: '/api/v1/admin/log_client', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(l) - }); - - if (window.mm_config.EnableDeveloper === 'true') { - window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'}); - window.ErrorStore.emitChange(); - } - }; - // Ya.... /*eslint-disable */ if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") { @@ -76,11 +56,7 @@ export default class Root extends React.Component { analytics.load(window.mm_config.SegmentDeveloperKey); analytics.page(); }}(); - } else { - global.window.analytics = {}; - global.window.analytics.page = function(){}; - global.window.analytics.track = function(){}; - } + } /*eslint-enable */ // Fastclick diff --git a/webapp/components/search_bar.jsx b/webapp/components/search_bar.jsx index caaf0f844..1156ac0f1 100644 --- a/webapp/components/search_bar.jsx +++ b/webapp/components/search_bar.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as client from 'utils/client.jsx'; +import client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import SearchStore from 'stores/search_store.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; diff --git a/webapp/components/select_team/select_team.jsx b/webapp/components/select_team/select_team.jsx new file mode 100644 index 000000000..5804a641e --- /dev/null +++ b/webapp/components/select_team/select_team.jsx @@ -0,0 +1,257 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as Utils from 'utils/utils.jsx'; +import ErrorBar from 'components/error_bar.jsx'; +import LoadingScreen from 'components/loading_screen.jsx'; +import Client from 'utils/web_client.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; + +import * as TextFormatting from 'utils/text_formatting.jsx'; + +import {Link} from 'react-router'; + +import {FormattedMessage} from 'react-intl'; + +//import {browserHistory, Link} from 'react-router'; + +import React from 'react'; +import logoImage from 'images/logo.png'; + +export default class Login extends React.Component { + + constructor(props) { + super(props); + this.onTeamChange = this.onTeamChange.bind(this); + + const state = this.getStateFromStores(false); + this.state = state; + } + + componentDidMount() { + TeamStore.addChangeListener(this.onTeamChange); + AsyncClient.getAllTeamListings(); + } + + componentWillUnmount() { + TeamStore.removeChangeListener(this.onTeamChange); + } + + onTeamChange() { + this.setState(this.getStateFromStores(true)); + } + + getStateFromStores(loaded) { + return { + teams: TeamStore.getAll(), + teamMembers: TeamStore.getTeamMembers(), + teamListings: TeamStore.getTeamListings(), + loaded + }; + } + + createCustomLogin() { + if (global.window.mm_license.IsLicensed === 'true' && + global.window.mm_license.CustomBrand === 'true' && + global.window.mm_config.EnableCustomBrand === 'true') { + const text = global.window.mm_config.CustomBrandText || ''; + + return ( + <div> + <img + src={Client.getAdminRoute() + '/get_brand_image'} + /> + <p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/> + </div> + ); + } + + return null; + } + + render() { + var content; + + let customClass; + const customContent = this.createCustomLogin(); + if (customContent) { + customClass = 'branded'; + } + + var teamContents = []; + var isAlreadyMember = new Map(); + + for (var index in this.state.teamMembers) { + if (this.state.teamMembers.hasOwnProperty(index)) { + var teamMember = this.state.teamMembers[index]; + var team = this.state.teams[teamMember.team_id]; + isAlreadyMember[teamMember.team_id] = true; + teamContents.push( + <div + key={'team_' + team.name} + className='signup-team-dir' + > + <Link + to={'/' + team.name + '/channels/town-square'} + > + <span className='signup-team-dir__name'>{team.display_name}</span> + <span + className='glyphicon glyphicon-menu-right right signup-team-dir__arrow' + aria-hidden='true' + /> + </Link> + </div> + ); + } + } + + if (!teamContents || teamContents.length === 0) { + teamContents = ( + <div className='signup-team-dir-err'> + <div> + <FormattedMessage + id='signup_team.no_teams' + defaultMessage='You do not appear to be a member of any team. Please ask your administrator for an invite, join an open team if one exists or possibly create a new team.' + /> + </div> + </div> + ); + } + + content = ( + <div className='signup__content'> + <h4> + <FormattedMessage + id='signup_team.choose' + defaultMessage='Teams you are a member of:' + /> + </h4> + <div className='signup-team-all'> + {teamContents} + </div> + </div> + ); + + var openTeamContents = []; + + for (var id in this.state.teamListings) { + if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) { + var openTeam = this.state.teamListings[id]; + openTeamContents.push( + <div + key={'team_' + openTeam.name} + className='signup-team-dir' + > + <Link + to={`/signup_user_complete/?id=${openTeam.invite_id}`} + > + <span className='signup-team-dir__name'>{openTeam.display_name}</span> + <span + className='glyphicon glyphicon-menu-right right signup-team-dir__arrow' + aria-hidden='true' + /> + </Link> + </div> + ); + } + } + + var openContent; + if (openTeamContents.length > 0) { + openContent = ( + <div className='signup__content'> + <h4> + <FormattedMessage + id='signup_team.join_open' + defaultMessage='Open teams you can join: ' + /> + </h4> + <div className='signup-team-all'> + {openTeamContents} + </div> + </div> + ); + } + + if (!this.state.loaded) { + openContent = <LoadingScreen/>; + } + + var isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles); + + let teamSignUp; + if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp())) { + teamSignUp = ( + <div className='margin--extra'> + <Link + to='/create_team' + className='signup-team-login' + > + <FormattedMessage + id='login.createTeam' + defaultMessage='Create a new team' + /> + </Link> + </div> + ); + } + + let adminConsoleLink; + if (isSystemAdmin) { + adminConsoleLink = ( + <div className='margin--extra'> + <Link + to='/admin_console' + className='signup-team-login' + > + <FormattedMessage + id='navbar_dropdown.console' + defaultMessage='System Console' + /> + </Link> + </div> + ); + } + + return ( + <div> + <ErrorBar/> + <div className='signup-header'> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > + <span className='fa fa-chevron-left'/> + <FormattedMessage + id='navbar_dropdown.logout' + /> + </a> + </div> + <div className='col-sm-12'> + <div className={'signup-team__container ' + customClass}> + <div className='signup__markdown'> + {customContent} + </div> + <img + className='signup-team-logo' + src={logoImage} + /> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> + <FormattedMessage + id='web.root.singup_info' + /> + </h4> + {content} + {openContent} + {teamSignUp} + {adminConsoleLink} + </div> + </div> + </div> + ); + } +} diff --git a/webapp/components/should_verify_email.jsx b/webapp/components/should_verify_email.jsx index 5103452b0..a95101ba1 100644 --- a/webapp/components/should_verify_email.jsx +++ b/webapp/components/should_verify_email.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import {FormattedMessage} from 'react-intl'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import {Link} from 'react-router'; @@ -18,19 +18,19 @@ export default class ShouldVerifyEmail extends React.Component { }; } handleResend() { - const teamName = this.props.location.query.teamname; const email = this.props.location.query.email; this.setState({resendStatus: 'sending'}); - Client.resendVerification(() => { - this.setState({resendStatus: 'success'}); - }, - () => { - this.setState({resendStatus: 'failure'}); - }, - teamName, - email); + Client.resendVerification( + email, + () => { + this.setState({resendStatus: 'success'}); + }, + () => { + this.setState({resendStatus: 'failure'}); + } + ); } render() { let resendConfirm = ''; diff --git a/webapp/components/sidebar_header.jsx b/webapp/components/sidebar_header.jsx index ec3a03d17..143a3458a 100644 --- a/webapp/components/sidebar_header.jsx +++ b/webapp/components/sidebar_header.jsx @@ -7,6 +7,7 @@ import NavbarDropdown from './navbar_dropdown.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; const Preferences = Constants.Preferences; @@ -61,7 +62,7 @@ export default class SidebarHeader extends React.Component { profilePicture = ( <img className='user__picture' - src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} + src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at} /> ); } diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx index c7e6577fc..42bc7ce53 100644 --- a/webapp/components/sidebar_right_menu.jsx +++ b/webapp/components/sidebar_right_menu.jsx @@ -228,13 +228,16 @@ export default class SidebarRightMenu extends React.Component { {manageLink} {consoleLink} <li> - <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > <i className='fa fa-sign-out'></i> <FormattedMessage id='sidebar_right_menu.logout' defaultMessage='Logout' /> - </Link> + </a> </li> <li className='divider'></li> {helpLink} diff --git a/webapp/components/signup_team.jsx b/webapp/components/signup_team.jsx deleted file mode 100644 index 634aa6e68..000000000 --- a/webapp/components/signup_team.jsx +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ChoosePage from './team_signup_choose_auth.jsx'; -import EmailSignUpPage from './team_signup_with_email.jsx'; -import SSOSignupPage from './team_signup_with_sso.jsx'; -import LdapSignUpPage from './team_signup_with_ldap.jsx'; -import Constants from 'utils/constants.jsx'; -import TeamStore from 'stores/team_store.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {FormattedMessage} from 'react-intl'; - -import React from 'react'; -import {Link} from 'react-router'; - -import logoImage from 'images/logo.png'; - -export default class TeamSignUp extends React.Component { - constructor(props) { - super(props); - - this.updatePage = this.updatePage.bind(this); - this.onTeamUpdate = this.onTeamUpdate.bind(this); - - var count = 0; - - if (global.window.mm_config.EnableSignUpWithEmail === 'true') { - count = count + 1; - } - - if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { - count = count + 1; - } - - if (global.window.mm_config.EnableLdap === 'true') { - count = count + 1; - } - - if (count > 1) { - this.state = {page: 'choose'}; - } else if (global.window.mm_config.EnableSignUpWithEmail === 'true') { - this.state = {page: 'email'}; - } else if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { - this.state = {page: 'gitlab'}; - } else if (global.window.mm_config.EnableLdap === 'true') { - this.state = {page: 'ldap'}; - } else { - this.state = {page: 'none'}; - } - } - - updatePage(page) { - this.setState({page}); - } - - componentWillMount() { - if (global.window.mm_config.EnableTeamListing === 'true') { - AsyncClient.getAllTeams(); - this.onTeamUpdate(); - } - } - - componentDidMount() { - TeamStore.addChangeListener(this.onTeamUpdate); - } - - componentWillUnmount() { - TeamStore.removeChangeListener(this.onTeamUpdate); - } - - onTeamUpdate() { - this.setState({ - teams: TeamStore.getAll() - }); - } - - render() { - let teamListing = null; - - if (global.window.mm_config.EnableTeamListing === 'true') { - if (this.state.teams == null) { - teamListing = (<div/>); - } else if (this.state.teams.length === 0) { - if (global.window.mm_config.EnableTeamCreation !== 'true') { - teamListing = ( - <div> - <FormattedMessage - id='signup_team.noTeams' - defaultMessage='There are no teams included in the Team Directory and team creation has been disabled.' - /> - </div> - ); - } - } else { - teamListing = ( - <div> - <h4> - <FormattedMessage - id='signup_team.choose' - defaultMessage='Choose a Team' - /> - </h4> - <div className='signup-team-all'> - { - Object.values(this.state.teams).map((team) => { - if (team.allow_team_listing) { - return ( - <div - key={'team_' + team.name} - className='signup-team-dir' - > - <Link - to={'/' + team.name} - > - <span className='signup-team-dir__name'>{team.display_name}</span> - <span - className='glyphicon glyphicon-menu-right right signup-team-dir__arrow' - aria-hidden='true' - /> - </Link> - </div> - ); - } - return null; - }) - } - </div> - <h4> - <FormattedMessage - id='signup_team.createTeam' - defaultMessage='Or Create a Team' - /> - </h4> - </div> - ); - } - } - - let signupMethod = null; - let goBack = ( - <div className='signup-header'> - <a - href='#' - onClick={ - (e) => { - e.preventDefault(); - this.updatePage('choose'); - } - } - > - <span className='fa fa-chevron-left'/> - <FormattedMessage - id='web.header.back' - /> - </a> - </div> - ); - - if (global.window.mm_config.EnableTeamCreation !== 'true') { - if (teamListing == null) { - signupMethod = ( - <FormattedMessage - id='signup_team.disabled' - defaultMessage='Team creation has been disabled. Please contact an administrator for access.' - /> - ); - } - } else if (this.state.page === 'choose') { - signupMethod = ( - <ChoosePage - updatePage={this.updatePage} - /> - ); - goBack = null; - } else if (this.state.page === 'email') { - signupMethod = ( - <div> - <EmailSignUpPage/> - </div> - ); - } else if (this.state.page === 'ldap') { - return ( - <div> - {teamListing} - <LdapSignUpPage/> - </div> - ); - } else if (this.state.page === 'gitlab') { - signupMethod = ( - <SSOSignupPage service={Constants.GITLAB_SERVICE}/> - ); - } else if (this.state.page === 'google') { - signupMethod = ( - <SSOSignupPage service={Constants.GOOGLE_SERVICE}/> - ); - } else if (this.state.page === 'none') { - signupMethod = ( - <FormattedMessage - id='signup_team.none' - defaultMessage='No team creation method has been enabled. Please contact an administrator for access.' - /> - ); - goBack = null; - } - - return ( - <div> - {goBack} - <div className='col-sm-12'> - <div className='signup-team__container'> - <img - className='signup-team-logo' - src={logoImage} - /> - <h1>{global.window.mm_config.SiteName}</h1> - <h4 className='color--light'> - <FormattedMessage - id='web.root.singup_info' - /> - </h4> - <div id='signup-team'> - {teamListing} - {signupMethod} - </div> - </div> - </div> - </div> - ); - } -} - -TeamSignUp.propTypes = { -}; - diff --git a/webapp/components/signup_team_complete/components/signup_team_complete.jsx b/webapp/components/signup_team_complete/components/signup_team_complete.jsx index 95b41dbde..00fdafe5f 100644 --- a/webapp/components/signup_team_complete/components/signup_team_complete.jsx +++ b/webapp/components/signup_team_complete/components/signup_team_complete.jsx @@ -74,6 +74,7 @@ export default class SignupTeamComplete extends React.Component { SignupTeamComplete.defaultProps = { }; + SignupTeamComplete.propTypes = { location: React.PropTypes.object, children: React.PropTypes.node diff --git a/webapp/components/signup_team_complete/components/team_signup_email_item.jsx b/webapp/components/signup_team_complete/components/team_signup_email_item.jsx deleted file mode 100644 index c3903ca85..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_email_item.jsx +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; - -import {intlShape, injectIntl, defineMessages} from 'react-intl'; - -const holders = defineMessages({ - validEmail: { - id: 'team_signup_email.validEmail', - defaultMessage: 'Please enter a valid email address' - }, - different: { - id: 'team_signup_email.different', - defaultMessage: 'Please use a different email than the one used at signup' - }, - address: { - id: 'team_signup_email.address', - defaultMessage: 'Email Address' - } -}); - -import React from 'react'; - -class TeamSignupEmailItem extends React.Component { - constructor(props) { - super(props); - - this.getValue = this.getValue.bind(this); - this.validate = this.validate.bind(this); - - this.state = {}; - } - getValue() { - return ReactDOM.findDOMNode(this.refs.email).value.trim(); - } - validate(teamEmail) { - const {formatMessage} = this.props.intl; - const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); - - if (!email) { - return true; - } - - if (!Utils.isEmail(email)) { - this.setState({emailError: formatMessage(holders.validEmail)}); - return false; - } else if (email === teamEmail) { - this.setState({emailError: formatMessage(holders.different)}); - return false; - } - - this.setState({emailError: ''}); - return true; - } - render() { - let emailError = null; - let emailDivClass = 'form-group'; - if (this.state.emailError) { - emailError = <label className='control-label'>{this.state.emailError}</label>; - emailDivClass += ' has-error'; - } - - return ( - <div className={emailDivClass}> - <input - autoFocus={this.props.focus} - type='email' - ref='email' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.address)} - defaultValue={this.props.email} - maxLength='128' - spellCheck='false' - /> - {emailError} - </div> - ); - } -} - -TeamSignupEmailItem.propTypes = { - intl: intlShape.isRequired, - focus: React.PropTypes.bool, - email: React.PropTypes.string -}; - -export default injectIntl(TeamSignupEmailItem, {withRef: true}); diff --git a/webapp/components/signup_team_complete/components/team_signup_finished.jsx b/webapp/components/signup_team_complete/components/team_signup_finished.jsx deleted file mode 100644 index 9fbb8473a..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_finished.jsx +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {FormattedMessage} from 'react-intl'; - -import React from 'react'; - -export default class FinishedPage extends React.Component { - render() { - return ( - <FormattedMessage - id='signup_team_complete.completed' - defaultMessage="You've already completed the signup process for this invitation or this invitation has expired." - /> - ); - } -} diff --git a/webapp/components/signup_team_complete/components/team_signup_password_page.jsx b/webapp/components/signup_team_complete/components/team_signup_password_page.jsx deleted file mode 100644 index 7b8b49e0c..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_password_page.jsx +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; -import BrowserStore from 'stores/browser_store.jsx'; -import UserStore from 'stores/user_store.jsx'; -import Constants from 'utils/constants.jsx'; - -import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; - -import logoImage from 'images/logo.png'; - -const holders = defineMessages({ - passwordError: { - id: 'team_signup_password.passwordError', - defaultMessage: 'Please enter at least {chars} characters' - }, - creating: { - id: 'team_signup_password.creating', - defaultMessage: 'Creating team...' - } -}); - -import React from 'react'; - -class TeamSignupPasswordPage extends React.Component { - constructor(props) { - super(props); - - this.submitBack = this.submitBack.bind(this); - this.submitNext = this.submitNext.bind(this); - - this.state = {}; - } - submitBack(e) { - e.preventDefault(); - this.props.state.wizard = 'username'; - this.props.updateParent(this.props.state); - } - submitNext(e) { - e.preventDefault(); - - var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); - if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { - this.setState({passwordError: this.props.intl.formatMessage(holders.passwordError, {chars: Constants.MIN_PASSWORD_LENGTH})}); - return; - } - - this.setState({passwordError: null, serverError: null}); - $('#finish-button').button('loading'); - var teamSignup = JSON.parse(JSON.stringify(this.props.state)); - teamSignup.user.password = password; - teamSignup.user.allow_marketing = true; - delete teamSignup.wizard; - - Client.createTeamFromSignup(teamSignup, - () => { - Client.track('signup', 'signup_team_08_complete'); - - var props = this.props; - - Client.loginByEmail( - teamSignup.team.name, - teamSignup.team.email, - teamSignup.user.password, - '', // No MFA Token - () => { - UserStore.setLastEmail(teamSignup.team.email); - if (this.props.hash > 0) { - BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'})); - } - - $('#sign-up-button').button('reset'); - props.state.wizard = 'finished'; - props.updateParent(props.state, true); - - browserHistory.push('/' + teamSignup.team.name + '/channels/town-square'); - }, - (err) => { - if (err.id === 'api.user.login.not_verified.app_error') { - browserHistory.push('/should_verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name)); - } else { - this.setState({serverError: err.message}); - $('#finish-button').button('reset'); - } - } - ); - }, - (err) => { - this.setState({serverError: err.message}); - $('#finish-button').button('reset'); - } - ); - } - render() { - Client.track('signup', 'signup_team_07_password'); - - var passwordError = null; - var passwordDivStyle = 'form-group'; - if (this.state.passwordError) { - passwordError = <div className='form-group has-error'><label className='control-label'>{this.state.passwordError}</label></div>; - passwordDivStyle = ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - return ( - <div> - <form> - <img - className='signup-team-logo' - src={logoImage} - /> - <h2 className='margin--less'> - <FormattedMessage - id='team_signup_password.yourPassword' - defaultMessage='Your password' - /> - </h2> - <h5 className='color--light'> - <FormattedMessage - id='team_signup_password.selectPassword' - defaultMessage="Select a password that you'll use to login with your email address:" - /> - </h5> - <div className='inner__content margin--extra'> - <h5><strong> - <FormattedMessage - id='team_signup_password.email' - defaultMessage='Email' - /> - </strong></h5> - <div className='block--gray form-group'>{this.props.state.team.email}</div> - <div className={passwordDivStyle}> - <div className='row'> - <div className='col-sm-11'> - <h5><strong> - <FormattedMessage - id='team_signup_password.choosePwd' - defaultMessage='Choose your password' - /> - </strong></h5> - <input - autoFocus={true} - type='password' - ref='password' - className='form-control' - placeholder='' - maxLength='128' - spellCheck='false' - /> - <span className='color--light help-block'> - <FormattedMessage - id='team_signup_password.hint' - defaultMessage='Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.' - values={{ - min: Constants.MIN_PASSWORD_LENGTH, - max: Constants.MAX_PASSWORD_LENGTH - }} - /> - </span> - </div> - </div> - {passwordError} - {serverError} - </div> - </div> - <div className='form-group'> - <button - type='submit' - className='btn btn-primary margin--extra' - id='finish-button' - data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.creating)} - onClick={this.submitNext} - > - <FormattedMessage - id='team_signup_password.finish' - defaultMessage='Finish' - /> - </button> - </div> - <p> - <FormattedHTMLMessage - id='team_signup_password.agreement' - defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." - values={{ - siteName: global.window.mm_config.SiteName - }} - /> - </p> - <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > - <FormattedMessage - id='team_signup_password.back' - defaultMessage='Back to previous step' - /> - </a> - </div> - </form> - </div> - ); - } -} - -TeamSignupPasswordPage.defaultProps = { - state: {}, - hash: '' -}; -TeamSignupPasswordPage.propTypes = { - intl: intlShape.isRequired, - state: React.PropTypes.object, - hash: React.PropTypes.string, - updateParent: React.PropTypes.func -}; - -export default injectIntl(TeamSignupPasswordPage); diff --git a/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx b/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx deleted file mode 100644 index db060b6b9..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -import EmailItem from './team_signup_email_item.jsx'; -import * as Client from 'utils/client.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import logoImage from 'images/logo.png'; - -import React from 'react'; - -export default class TeamSignupSendInvitesPage extends React.Component { - constructor(props) { - super(props); - this.submitBack = this.submitBack.bind(this); - this.submitNext = this.submitNext.bind(this); - this.submitAddInvite = this.submitAddInvite.bind(this); - this.submitSkip = this.submitSkip.bind(this); - this.keySubmit = this.keySubmit.bind(this); - this.state = { - emailEnabled: global.window.mm_config.SendEmailNotifications === 'true' - }; - } - submitBack(e) { - e.preventDefault(); - this.props.state.wizard = 'team_url'; - - this.props.updateParent(this.props.state); - } - submitNext(e) { - e.preventDefault(); - - var valid = true; - - if (this.state.emailEnabled) { - var emails = []; - - for (var i = 0; i < this.props.state.invites.length; i++) { - if (this.refs['email_' + i].getWrappedInstance().validate(this.props.state.team.email)) { - emails.push(this.refs['email_' + i].getWrappedInstance().getValue()); - } else { - valid = false; - } - } - - if (valid) { - this.props.state.invites = emails; - } - } - - if (valid) { - this.props.state.wizard = 'username'; - this.props.updateParent(this.props.state); - } - } - submitAddInvite(e) { - e.preventDefault(); - this.props.state.wizard = 'send_invites'; - if (!this.props.state.invites) { - this.props.state.invites = []; - } - this.props.state.invites.push(''); - this.props.updateParent(this.props.state); - } - submitSkip(e) { - e.preventDefault(); - this.props.state.wizard = 'username'; - this.props.updateParent(this.props.state); - } - keySubmit(e) { - if (e && e.keyCode === 13) { - this.submitNext(e); - } - } - componentDidMount() { - if (!this.state.emailEnabled) { - // Must use keypress not keyup due to event chain of pressing enter - $('body').keypress(this.keySubmit); - } - } - componentWillUnmount() { - if (!this.state.emailEnabled) { - $('body').off('keypress', this.keySubmit); - } - } - render() { - Client.track('signup', 'signup_team_05_send_invites'); - - var content = null; - var bottomContent = null; - - if (this.state.emailEnabled) { - var emails = []; - - for (var i = 0; i < this.props.state.invites.length; i++) { - if (i === 0) { - emails.push( - <EmailItem - focus={true} - key={i} - ref={'email_' + i} - email={this.props.state.invites[i]} - /> - ); - } else { - emails.push( - <EmailItem - focus={false} - key={i} - ref={'email_' + i} - email={this.props.state.invites[i]} - /> - ); - } - } - - content = ( - <div> - {emails} - <div className='form-group text-right'> - <a - href='#' - onClick={this.submitAddInvite} - > - <FormattedMessage - id='team_signup_send_invites.addInvitation' - defaultMessage='Add Invitation' - /> - </a> - </div> - </div> - ); - - bottomContent = ( - <p className='color--light'> - <FormattedHTMLMessage - id='team_signup_send_invites.prefer' - defaultMessage='if you prefer, you can invite team members later<br /> and ' - /> - <a - href='#' - onClick={this.submitSkip} - > - <FormattedMessage - id='team_signup_send_invites.skip' - defaultMessage='skip this step ' - /> - </a> - <FormattedMessage - id='team_signup_send_invites.forNow' - defaultMessage='for now.' - /> - </p> - ); - } else { - content = ( - <div className='form-group color--light'> - <FormattedMessage - id='team_signup_send_invites.disabled' - defaultMessage='Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.' - /> - </div> - ); - } - - return ( - <div> - <form> - <img - className='signup-team-logo' - src={logoImage} - /> - <h2> - <FormattedMessage - id='team_signup_send_invites.title' - defaultMessage='Invite Team Members' - /> - </h2> - {content} - <div className='form-group'> - <button - type='submit' - className='btn-primary btn' - onClick={this.submitNext} - > - <FormattedMessage - id='team_signup_send_invites.next' - defaultMessage='Next' - /><i className='glyphicon glyphicon-chevron-right'/> - </button> - </div> - </form> - {bottomContent} - <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > - <FormattedMessage - id='team_signup_send_invites.back' - defaultMessage='Back to previous step' - /> - </a> - </div> - </div> - ); - } -} - -TeamSignupSendInvitesPage.propTypes = { - state: React.PropTypes.object.isRequired, - updateParent: React.PropTypes.func.isRequired -}; diff --git a/webapp/components/signup_team_complete/components/team_signup_username_page.jsx b/webapp/components/signup_team_complete/components/team_signup_username_page.jsx deleted file mode 100644 index b79c1179f..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_username_page.jsx +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; -import Constants from 'utils/constants.jsx'; - -import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; - -import logoImage from 'images/logo.png'; - -const holders = defineMessages({ - reserved: { - id: 'team_signup_username.reserved', - defaultMessage: 'This username is reserved, please choose a new one.' - }, - invalid: { - id: 'team_signup_username.invalid', - defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\'' - } -}); - -import React from 'react'; - -class TeamSignupUsernamePage extends React.Component { - constructor(props) { - super(props); - - this.submitBack = this.submitBack.bind(this); - this.submitNext = this.submitNext.bind(this); - - this.state = {}; - } - submitBack(e) { - e.preventDefault(); - if (global.window.mm_config.SendEmailNotifications === 'true') { - this.props.state.wizard = 'send_invites'; - } else { - this.props.state.wizard = 'team_url'; - } - - this.props.updateParent(this.props.state); - } - submitNext(e) { - e.preventDefault(); - - const {formatMessage} = this.props.intl; - var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase(); - - var usernameError = Utils.isValidUsername(name); - if (usernameError === 'Cannot use a reserved word as a username.') { //this should be change to some kind of ID - this.setState({nameError: formatMessage(holders.reserved)}); - return; - } else if (usernameError) { - this.setState({nameError: formatMessage(holders.invalid, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})}); - return; - } - - this.props.state.wizard = 'password'; - this.props.state.user.username = name; - this.props.updateParent(this.props.state); - } - render() { - Client.track('signup', 'signup_team_06_username'); - - var nameError = null; - var nameHelpText = ( - <span className='color--light help-block'> - <FormattedMessage - id='team_signup_username.hint' - defaultMessage="Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'" - values={{ - min: Constants.MIN_USERNAME_LENGTH, - max: Constants.MAX_USERNAME_LENGTH - }} - /> - </span> - ); - var nameDivClass = 'form-group'; - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameHelpText = ''; - nameDivClass += ' has-error'; - } - - return ( - <div> - <form> - <img - className='signup-team-logo' - src={logoImage} - /> - <h2 className='margin--less'> - <FormattedMessage - id='team_signup_username.username' - defaultMessage='Your username' - /> - </h2> - <h5 className='color--light'> - <FormattedMessage - id='team_signup_username.memorable' - defaultMessage='Select a memorable username that makes it easy for teammates to identify you:' - /> - </h5> - <div className='inner__content margin--extra'> - <div className={nameDivClass}> - <div className='row'> - <div className='col-sm-11'> - <h5><strong> - <FormattedMessage - id='team_signup_username.chooseUsername' - defaultMessage='Choose your username' - /> - </strong></h5> - <input - autoFocus={true} - type='text' - ref='name' - className='form-control' - placeholder='' - defaultValue={this.props.state.user.username} - maxLength={Constants.MAX_USERNAME_LENGTH} - spellCheck='false' - /> - {nameHelpText} - </div> - </div> - {nameError} - </div> - </div> - <button - type='submit' - className='btn btn-primary margin--extra' - onClick={this.submitNext} - > - <FormattedMessage - id='team_signup_username.next' - defaultMessage='Next' - /> - <i className='glyphicon glyphicon-chevron-right'></i> - </button> - <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > - <FormattedMessage - id='team_signup_username.back' - defaultMessage='Back to previous step' - /> - </a> - </div> - </form> - </div> - ); - } -} - -TeamSignupUsernamePage.defaultProps = { - state: null -}; -TeamSignupUsernamePage.propTypes = { - intl: intlShape.isRequired, - state: React.PropTypes.object, - updateParent: React.PropTypes.func -}; - -export default injectIntl(TeamSignupUsernamePage); diff --git a/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx b/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx deleted file mode 100644 index 15b708128..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; -import BrowserStore from 'stores/browser_store.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import {browserHistory} from 'react-router'; - -import logoImage from 'images/logo.png'; - -const holders = defineMessages({ - storageError: { - id: 'team_signup_welcome.storageError', - defaultMessage: 'This service requires local storage to be enabled. Please enable it or exit private browsing.' - }, - validEmailError: { - id: 'team_signup_welcome.validEmailError', - defaultMessage: 'Please enter a valid email address' - }, - address: { - id: 'team_signup_welcome.address', - defaultMessage: 'Email Address' - } -}); - -import React from 'react'; - -class TeamSignupWelcomePage extends React.Component { - constructor(props) { - super(props); - - this.submitNext = this.submitNext.bind(this); - this.handleDiffEmail = this.handleDiffEmail.bind(this); - this.handleDiffSubmit = this.handleDiffSubmit.bind(this); - this.handleKeyPress = this.handleKeyPress.bind(this); - - this.state = {useDiff: false}; - - document.addEventListener('keyup', this.handleKeyPress, false); - } - submitNext(e) { - if (!BrowserStore.isLocalStorageSupported()) { - this.setState({storageError: this.props.intl.formatMessage(holders.storageError)}); - return; - } - e.preventDefault(); - this.props.state.wizard = 'team_display_name'; - this.props.updateParent(this.props.state); - } - handleDiffEmail(e) { - e.preventDefault(); - this.setState({useDiff: true}); - } - handleDiffSubmit(e) { - e.preventDefault(); - - const {formatMessage} = this.props.intl; - var state = {useDiff: true, serverError: ''}; - - var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); - if (!email || !Utils.isEmail(email)) { - state.emailError = formatMessage(holders.validEmailError); - this.setState(state); - return; - } else if (!BrowserStore.isLocalStorageSupported()) { - state.emailError = formatMessage(holders.storageError); - this.setState(state); - return; - } - state.emailError = ''; - - Client.signupTeam(email, - function success(data) { - if (data.follow_link) { - browserHistory.push(data.follow_link); - } else { - this.props.state.wizard = 'finished'; - this.props.updateParent(this.props.state); - browserHistory.push('/signup_team_confirm/?email=' + encodeURIComponent(email)); - } - }.bind(this), - function error(err) { - let errorMsg = err.message; - - if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) { - errorMsg = formatMessage(holders.validEmailError); - } - - this.setState({emailError: '', serverError: errorMsg}); - }.bind(this) - ); - } - handleKeyPress(event) { - if (event.keyCode === 13) { - if (this.state.useDiff) { - this.handleDiffSubmit(event); - } else { - this.submitNext(event); - } - } - } - componentWillUnmount() { - document.removeEventListener('keyup', this.handleKeyPress, false); - } - render() { - Client.track('signup', 'signup_team_01_welcome'); - - var storageError = null; - if (this.state.storageError) { - storageError = <label className='control-label'>{this.state.storageError}</label>; - } - - var emailError = null; - var emailDivClass = 'form-group'; - if (this.state.emailError) { - emailError = <label className='control-label'>{this.state.emailError}</label>; - emailDivClass += ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = ( - <div className='form-group has-error'> - <label className='control-label'>{this.state.serverError}</label> - </div> - ); - } - - var differentEmailLinkClass = ''; - var emailDivContainerClass = 'hidden'; - if (this.state.useDiff) { - differentEmailLinkClass = 'hidden'; - emailDivContainerClass = ''; - } - - return ( - <div> - <img - className='signup-team-logo' - src={logoImage} - /> - <h3 className='sub-heading'> - <FormattedMessage - id='team_signup_welcome.welcome' - defaultMessage='Welcome to:' - /> - </h3> - <h1 className='margin--top-none'>{global.window.mm_config.SiteName}</h1> - <p className='margin--less'> - <FormattedMessage - id='team_signup_welcome.lets' - defaultMessage="Let's set up your new team" - /> - </p> - <div> - <FormattedMessage - id='team_signup_welcome.confirm' - defaultMessage='Please confirm your email address:' - /> - <br/> - <div className='inner__content'> - <div className='block--gray'>{this.props.state.team.email}</div> - </div> - </div> - <p className='margin--extra color--light'> - <FormattedHTMLMessage - id='team_signup_welcome.admin' - defaultMessage='Your account will administer the new team site. <br /> - You can add other administrators later.' - /> - </p> - <div className='form-group'> - <button - className='btn-primary btn form-group' - type='submit' - onClick={this.submitNext} - > - <i className='glyphicon glyphicon-ok'></i> - <FormattedMessage - id='team_signup_welcome.yes' - defaultMessage='Yes, this address is correct' - /> - </button> - {storageError} - </div> - <hr/> - <div className={emailDivContainerClass}> - <div className={emailDivClass}> - <div className='row'> - <div className='col-sm-9'> - <input - type='email' - ref='email' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.address)} - maxLength='128' - spellCheck='false' - /> - </div> - </div> - {emailError} - </div> - {serverError} - <button - className='btn btn-md btn-primary' - type='button' - onClick={this.handleDiffSubmit} - > - <FormattedMessage - id='team_signup_welcome.instead' - defaultMessage='Use this instead' - /> - </button> - </div> - <a - href='#' - onClick={this.handleDiffEmail} - className={differentEmailLinkClass} - > - <FormattedMessage - id='team_signup_welcome.different' - defaultMessage='Use a different email' - /> - </a> - </div> - ); - } -} - -TeamSignupWelcomePage.defaultProps = { - state: {} -}; -TeamSignupWelcomePage.propTypes = { - intl: intlShape.isRequired, - updateParent: React.PropTypes.func.isRequired, - state: React.PropTypes.object -}; - -export default injectIntl(TeamSignupWelcomePage); diff --git a/webapp/components/signup_team_confirm.jsx b/webapp/components/signup_team_confirm.jsx deleted file mode 100644 index 117a0b068..000000000 --- a/webapp/components/signup_team_confirm.jsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; -import {Link} from 'react-router'; - -import React from 'react'; - -export default class SignupTeamConfirm extends React.Component { - render() { - return ( - <div> - <div className='signup-header'> - <Link to='/'> - <span className='fa fa-chevron-left'/> - <FormattedMessage - id='web.header.back' - /> - </Link> - </div> - <div className='col-sm-12'> - <div classNameName='signup-team__container'> - <h3> - <FormattedMessage - id='signup_team_confirm.title' - defaultMessage='Sign up Complete' - /> - </h3> - <p> - <FormattedHTMLMessage - id='signup_team_confirm.checkEmail' - defaultMessage='Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team' - values={{ - email: this.props.location.query.email - }} - /> - </p> - </div> - </div> - </div> - ); - } -} - -SignupTeamConfirm.defaultProps = { -}; -SignupTeamConfirm.propTypes = { - location: React.PropTypes.object -}; diff --git a/webapp/components/signup_user_complete.jsx b/webapp/components/signup_user_complete.jsx index 9e821289b..666e72e13 100644 --- a/webapp/components/signup_user_complete.jsx +++ b/webapp/components/signup_user_complete.jsx @@ -3,12 +3,13 @@ import LoadingScreen from 'components/loading_screen.jsx'; import LoginLdap from 'components/login/components/login_ldap.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -24,7 +25,6 @@ class SignupUserComplete extends React.Component { super(props); this.handleSubmit = this.handleSubmit.bind(this); - this.inviteInfoRecieved = this.inviteInfoRecieved.bind(this); this.handleLdapSignup = this.handleLdapSignup.bind(this); this.state = { @@ -34,7 +34,10 @@ class SignupUserComplete extends React.Component { email: '', teamDisplayName: '', teamName: '', - teamId: '' + teamId: '', + openServer: false, + loading: true, + inviteId: '' }; } componentWillMount() { @@ -46,19 +49,91 @@ class SignupUserComplete extends React.Component { let teamDisplayName = ''; let teamName = ''; let teamId = ''; + let openServer = false; + let loading = true; + + if ((inviteId && inviteId.length > 0) || (hash && hash.length > 0)) { + // if we are already logged in then attempt to just join the team + if (UserStore.getCurrentUser()) { + loading = true; + Client.addUserToTeamFromInvite( + data, + hash, + inviteId, + () => { + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); + }, + (err) => { + this.setState({ + noOpenServerError: true, + serverError: err.message, + loading: false + }); + } + ); + } else if (hash) { + // If we have a hash in the url then we are attempting to access a private team + const parsedData = JSON.parse(data); + usedBefore = BrowserStore.getGlobalItem(hash); + email = parsedData.email; + teamDisplayName = parsedData.display_name; + teamName = parsedData.name; + teamId = parsedData.id; + loading = false; + } else { + loading = true; + Client.getInviteInfo( + inviteId, + (inviteData) => { + if (!inviteData) { + return; + } - // If we have a hash in the url then we are attempting to access a private team - if (hash) { - const parsedData = JSON.parse(data); - usedBefore = BrowserStore.getGlobalItem(hash); - email = parsedData.email; - teamDisplayName = parsedData.display_name; - teamName = parsedData.name; - teamId = parsedData.id; + this.setState({ + serverError: null, + teamDisplayName: inviteData.display_name, + teamName: inviteData.name, + teamId: inviteData.id, + loading: false + }); + }, + () => { + this.setState({ + noOpenServerError: true, + loading: false, + serverError: + <FormattedMessage + id='signup_user_completed.invalid_invite' + defaultMessage='The invite link was invalid. Please speak with your Administrator to receive an invitation.' + /> + }); + } + ); + + data = ''; + hash = ''; + } + } else if (global.window.mm_config.EnableOpenServer === 'true' || UserStore.getNoAccounts()) { + // If this is the first account then let them create an account anyway. + // The server will verify it's the first account before allowing creation. + // Of if the server is open then we don't care. + openServer = true; + loading = false; } else { - Client.getInviteInfo(this.inviteInfoRecieved, null, inviteId); - data = ''; - hash = ''; + loading = false; + this.setState({ + noOpenServerError: true, + serverError: + <FormattedMessage + id='signup_user_completed.no_open_server' + defaultMessage='This server does not allow open signups. Please speak with your Administrator to receive an invitation.' + />, + loading: false + }); } this.setState({ @@ -68,29 +143,25 @@ class SignupUserComplete extends React.Component { email, teamDisplayName, teamName, - teamId - }); - } - inviteInfoRecieved(data) { - if (!data) { - return; - } - - this.setState({ - teamDisplayName: data.display_name, - teamName: data.name, - teamId: data.id + teamId, + openServer, + inviteId, + loading }); } handleLdapSignup(method, loginId, password, token) { - Client.loginByLdap(this.state.teamName, loginId, password, token, + Client.loginByLdap(loginId, password, token, () => { const redirect = Utils.getUrlParameter('redirect'); if (redirect) { browserHistory.push(decodeURIComponent(redirect)); } else { - browserHistory.push('/' + this.state.teamName + '/channels/town-square'); + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); } }, (err) => { @@ -187,28 +258,34 @@ class SignupUserComplete extends React.Component { }); const user = { - team_id: this.state.teamId, email: providedEmail, username: providedUsername, password: providedPassword, allow_marketing: true }; - Client.createUser(user, this.state.data, this.state.hash, + Client.createUserWithInvite(user, + this.state.data, + this.state.hash, + this.state.inviteId, () => { Client.track('signup', 'signup_user_02_complete'); - - Client.loginByEmail( - this.state.teamName, + Client.login( user.email, + null, user.password, - '', // No MFA Token + '', () => { UserStore.setLastEmail(user.email); if (this.state.hash > 0) { BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true})); } - browserHistory.push('/' + this.state.teamName + '/channels/town-square'); + + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); }, (err) => { if (err.id === 'api.user.login.not_verified.app_error') { @@ -239,9 +316,7 @@ class SignupUserComplete extends React.Component { ); } - // If we haven't got a team id yet we are waiting for - // the client so just show the standard loading screen - if (this.state.teamId === '') { + if (this.state.loading) { return (<LoadingScreen/>); } @@ -349,7 +424,7 @@ class SignupUserComplete extends React.Component { <a className='btn btn-custom-login gitlab' key='gitlab' - href={'/api/v1/oauth/gitlab/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)} + href={Client.getOAuthRoute() + '/gitlab/signup' + window.location.search} > <span className='icon'/> <span> @@ -367,7 +442,7 @@ class SignupUserComplete extends React.Component { <a className='btn btn-custom-login google' key='google' - href={'/api/v1/oauth/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)} + href={Client.getOAuthRoute() + '/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)} > <span className='icon'/> <span> @@ -497,12 +572,33 @@ class SignupUserComplete extends React.Component { ); } + let terms = ( + <p> + <FormattedHTMLMessage + id='create_team.agreement' + defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." + values={{ + siteName: global.window.mm_config.SiteName + }} + /> + </p> + ); + + if (this.state.noOpenServerError) { + signupMessage = null; + ldapSignup = null; + emailSignup = null; + terms = null; + } + return ( <div> <div className='signup-header'> <Link to='/'> - <span classNameNameName='fa fa-chevron-left'/> - <FormattedMessage id='web.header.back'/> + <span className='fa fa-chevron-left'/> + <FormattedMessage + id='web.header.back' + /> </Link> </div> <div className='col-sm-12'> @@ -511,22 +607,12 @@ class SignupUserComplete extends React.Component { className='signup-team-logo' src={logoImage} /> - <h5 className='margin--less'> - <FormattedMessage - id='signup_user_completed.welcome' - defaultMessage='Welcome to:' - /> - </h5> - <h2 className='signup-team__name'>{this.state.teamName}</h2> - <h2 className='signup-team__subdomain'> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> <FormattedMessage - id='signup_user_completed.onSite' - defaultMessage='on {siteName}' - values={{ - siteName: global.window.mm_config.SiteName - }} + id='web.root.singup_info' /> - </h2> + </h4> <h4 className='color--light'> <FormattedMessage id='signup_user_completed.lets' @@ -537,6 +623,7 @@ class SignupUserComplete extends React.Component { {ldapSignup} {emailSignup} {serverError} + {terms} </div> </div> </div> diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx index 90ec6e660..79ac8aaf1 100644 --- a/webapp/components/suggestion/at_mention_provider.jsx +++ b/webapp/components/suggestion/at_mention_provider.jsx @@ -4,6 +4,7 @@ import SuggestionStore from 'stores/suggestion_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; @@ -42,7 +43,7 @@ class AtMentionSuggestion extends React.Component { icon = ( <img className='mention__image' - src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at} + src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at} /> ); } diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx index eeaee68a7..b7234469a 100644 --- a/webapp/components/suggestion/search_user_provider.jsx +++ b/webapp/components/suggestion/search_user_provider.jsx @@ -3,6 +3,7 @@ import SuggestionStore from 'stores/suggestion_store.jsx'; import UserStore from 'stores/user_store.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; @@ -22,7 +23,7 @@ class SearchUserSuggestion extends React.Component { > <img className='profile-img rounded' - src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at} + src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at} /> <i className='fa fa fa-plus-square'></i>{item.username} </div> diff --git a/webapp/components/team_export_tab.jsx b/webapp/components/team_export_tab.jsx index 9bd5785a0..37f886aab 100644 --- a/webapp/components/team_export_tab.jsx +++ b/webapp/components/team_export_tab.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; diff --git a/webapp/components/team_general_tab.jsx b/webapp/components/team_general_tab.jsx index c27e8ca59..1f783fe9f 100644 --- a/webapp/components/team_general_tab.jsx +++ b/webapp/components/team_general_tab.jsx @@ -5,7 +5,7 @@ import $ from 'jquery'; import SettingItemMin from './setting_item_min.jsx'; import SettingItemMax from './setting_item_max.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import TeamStore from 'stores/team_store.jsx'; @@ -42,7 +42,7 @@ const holders = defineMessages({ }, openInviteTitle: { id: 'general_tab.openInviteTitle', - defaultMessage: 'Allow anyone to sign-up from login page' + defaultMessage: 'Allow anyone to join this team' }, codeTitle: { id: 'general_tab.codeTitle', @@ -68,7 +68,6 @@ class GeneralTab extends React.Component { this.handleNameSubmit = this.handleNameSubmit.bind(this); this.handleInviteIdSubmit = this.handleInviteIdSubmit.bind(this); this.handleOpenInviteSubmit = this.handleOpenInviteSubmit.bind(this); - this.handleTeamListingSubmit = this.handleTeamListingSubmit.bind(this); this.handleClose = this.handleClose.bind(this); this.onUpdateNameSection = this.onUpdateNameSection.bind(this); this.updateName = this.updateName.bind(this); @@ -76,8 +75,6 @@ class GeneralTab extends React.Component { this.updateInviteId = this.updateInviteId.bind(this); this.onUpdateOpenInviteSection = this.onUpdateOpenInviteSection.bind(this); this.handleOpenInviteRadio = this.handleOpenInviteRadio.bind(this); - this.onUpdateTeamListingSection = this.onUpdateTeamListingSection.bind(this); - this.handleTeamListingRadio = this.handleTeamListingRadio.bind(this); this.handleGenerateInviteId = this.handleGenerateInviteId.bind(this); this.state = this.setupInitialState(props); @@ -96,12 +93,19 @@ class GeneralTab extends React.Component { name: team.display_name, invite_id: team.invite_id, allow_open_invite: team.allow_open_invite, - allow_team_listing: team.allow_team_listing, serverError: '', clientError: '' }; } + componentWillReceiveProps(nextProps) { + this.setState({ + name: nextProps.team.display_name, + invite_id: nextProps.team.invite_id, + allow_open_invite: nextProps.team.allow_open_invite + }); + } + handleGenerateInviteId(e) { e.preventDefault(); @@ -117,14 +121,6 @@ class GeneralTab extends React.Component { this.setState({allow_open_invite: openInvite}); } - handleTeamListingRadio(listing) { - if (global.window.mm_config.EnableTeamListing !== 'true' && listing) { - this.setState({clientError: this.props.intl.formatMessage(holders.dirDisabled)}); - } else { - this.setState({allow_team_listing: listing}); - } - } - handleOpenInviteSubmit(e) { e.preventDefault(); @@ -145,26 +141,6 @@ class GeneralTab extends React.Component { ); } - handleTeamListingSubmit(e) { - e.preventDefault(); - - var state = {serverError: '', clientError: ''}; - - var data = this.props.team; - data.allow_team_listing = this.state.allow_team_listing; - Client.updateTeam(data, - (team) => { - TeamStore.saveTeam(team); - TeamStore.emitChange(); - this.updateSection(''); - }, - (err) => { - state.serverError = err.message; - this.setState(state); - } - ); - } - handleNameSubmit(e) { e.preventDefault(); @@ -239,12 +215,6 @@ class GeneralTab extends React.Component { ); } - componentWillReceiveProps(newProps) { - if (newProps.team && newProps.teamDisplayName) { - this.setState({name: newProps.teamDisplayName}); - } - } - handleClose() { this.updateSection(''); } @@ -284,15 +254,6 @@ class GeneralTab extends React.Component { } } - onUpdateTeamListingSection(e) { - e.preventDefault(); - if (this.props.activeSection === 'team_listing') { - this.updateSection(''); - } else { - this.updateSection('team_listing'); - } - } - updateName(e) { e.preventDefault(); this.setState({name: e.target.value}); @@ -313,105 +274,8 @@ class GeneralTab extends React.Component { serverError = this.state.serverError; } - const enableTeamListing = global.window.mm_config.EnableTeamListing === 'true'; const {formatMessage} = this.props.intl; - let teamListingSection; - if (this.props.activeSection === 'team_listing') { - const inputs = []; - let submitHandle = null; - - if (enableTeamListing) { - submitHandle = this.handleTeamListingSubmit; - - inputs.push( - <div key='userTeamListingOptions'> - <div className='radio'> - <label> - <input - name='userTeamListingOptions' - type='radio' - defaultChecked={this.state.allow_team_listing} - onChange={this.handleTeamListingRadio.bind(this, true)} - /> - <FormattedMessage - id='general_tab.yes' - defaultMessage='Yes' - /> - </label> - <br/> - </div> - <div className='radio'> - <label> - <input - ref='teamListingRadioNo' - name='userTeamListingOptions' - type='radio' - defaultChecked={!this.state.allow_team_listing} - onChange={this.handleTeamListingRadio.bind(this, false)} - /> - <FormattedMessage - id='general_tab.no' - defaultMessage='No' - /> - </label> - <br/> - </div> - <div> - <br/> - <FormattedMessage - id='general_tab.includeDirDesc' - defaultMessage='Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.' - /> - </div> - </div> - ); - } else { - inputs.push( - <div key='userTeamListingOptions'> - <div> - <br/> - <FormattedMessage - id='general_tab.dirContact' - defaultMessage='Contact your system administrator to turn on the team directory on the system home page.' - /> - </div> - </div> - ); - } - - teamListingSection = ( - <SettingItemMax - title={formatMessage(holders.includeDirTitle)} - inputs={inputs} - submit={submitHandle} - server_error={serverError} - client_error={clientError} - updateSection={this.onUpdateTeamListingSection} - /> - ); - } else { - let describe = ''; - - if (enableTeamListing) { - if (this.state.allow_team_listing === true) { - describe = formatMessage(holders.yes); - } else { - describe = formatMessage(holders.no); - } - } else { - describe = formatMessage(holders.dirOff); - } - - teamListingSection = ( - <SettingItemMin - title={formatMessage(holders.includeDirTitle)} - describe={describe} - updateSection={this.onUpdateTeamListingSection} - /> - ); - } - let openInviteSection; if (this.props.activeSection === 'open_invite') { const inputs = [ @@ -450,7 +314,7 @@ class GeneralTab extends React.Component { <br/> <FormattedMessage id='general_tab.openInviteDesc' - defaultMessage='When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.' + defaultMessage='When allowed, a link to this team will be including on the landing page allowing anyone with an account to join this team.' /> </div> </div> @@ -639,8 +503,6 @@ class GeneralTab extends React.Component { <div className='divider-light'/> {openInviteSection} <div className='divider-light'/> - {teamListingSection} - <div className='divider-light'/> {inviteSection} <div className='divider-dark'/> </div> diff --git a/webapp/components/team_members_dropdown.jsx b/webapp/components/team_members_dropdown.jsx index ad82a2280..251c2ce3b 100644 --- a/webapp/components/team_members_dropdown.jsx +++ b/webapp/components/team_members_dropdown.jsx @@ -3,7 +3,7 @@ import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import ConfirmModal from './confirm_modal.jsx'; @@ -38,11 +38,9 @@ export default class TeamMembersDropdown extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, ''); } else { - const data = { - user_id: this.props.user.id, - new_roles: '' - }; - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + '', () => { AsyncClient.getProfiles(); }, @@ -79,12 +77,9 @@ export default class TeamMembersDropdown extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, 'admin'); } else { - const data = { - user_id: this.props.user.id, - new_roles: 'admin' - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + 'admin', () => { AsyncClient.getProfiles(); }, @@ -94,12 +89,13 @@ export default class TeamMembersDropdown extends React.Component { ); } } - handleDemote(user, role) { + handleDemote(user, role, newRole) { this.setState({ serverError: this.state.serverError, showDemoteModal: true, user, - role + role, + newRole }); } handleDemoteCancel() { @@ -107,16 +103,14 @@ export default class TeamMembersDropdown extends React.Component { serverError: null, showDemoteModal: false, user: null, - role: null + role: null, + newRole: null }); } handleDemoteSubmit() { - const data = { - user_id: this.props.user.id, - new_roles: this.state.role - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + this.state.newRole, () => { const teamUrl = TeamStore.getCurrentTeamUrl(); if (teamUrl) { @@ -140,6 +134,7 @@ export default class TeamMembersDropdown extends React.Component { ); } + const teamMember = this.props.teamMember; const user = this.props.user; let currentRoles = ( <FormattedMessage @@ -168,8 +163,10 @@ export default class TeamMembersDropdown extends React.Component { } } - let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin'; - let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; + let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin'; + + //let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin'; + let showMakeAdmin = false; let showMakeActive = false; let showMakeNotActive = user.roles !== 'system_admin'; @@ -331,5 +328,6 @@ export default class TeamMembersDropdown extends React.Component { } TeamMembersDropdown.propTypes = { - user: React.PropTypes.object.isRequired + user: React.PropTypes.object.isRequired, + teamMember: React.PropTypes.object.isRequired }; diff --git a/webapp/components/team_settings.jsx b/webapp/components/team_settings.jsx index dc303059d..210d1f541 100644 --- a/webapp/components/team_settings.jsx +++ b/webapp/components/team_settings.jsx @@ -25,6 +25,7 @@ export default class TeamSettings extends React.Component { } onChange() { var team = TeamStore.getCurrent(); + if (!Utils.areObjectsEqual(this.state.team, team)) { this.setState({team}); } diff --git a/webapp/components/team_signup_choose_auth.jsx b/webapp/components/team_signup_choose_auth.jsx deleted file mode 100644 index 7a4ce972a..000000000 --- a/webapp/components/team_signup_choose_auth.jsx +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {FormattedMessage} from 'react-intl'; - -import React from 'react'; - -export default class ChooseAuthPage extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } - render() { - var buttons = []; - if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { - buttons.push( - <a - className='btn btn-custom-login gitlab btn-full' - key='gitlab' - href='#' - onClick={ - function clickGit(e) { - e.preventDefault(); - this.props.updatePage('gitlab'); - }.bind(this) - } - > - <span className='icon'/> - <span> - <FormattedMessage - id='choose_auth_page.gitlabCreate' - defaultMessage='Create new team with GitLab Account' - /> - </span> - </a> - ); - } - - if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { - buttons.push( - <a - className='btn btn-custom-login google btn-full' - key='google' - href='#' - onClick={ - (e) => { - e.preventDefault(); - this.props.updatePage('google'); - } - } - > - <span className='icon'/> - <span> - <FormattedMessage - id='choose_auth_page.googleCreate' - defaultMessage='Create new team with Google Apps Account' - /> - </span> - </a> - ); - } - - if (global.window.mm_config.EnableLdap === 'true') { - buttons.push( - <a - className='btn btn-custom-login ldap btn-full' - key='ldap' - href='#' - onClick={ - (e) => { - e.preventDefault(); - this.props.updatePage('ldap'); - } - } - > - <span className='icon'/> - <span> - <FormattedMessage - id='choose_auth_page.ldapCreate' - defaultMessage='Create new team with LDAP Account' - /> - </span> - </a> - ); - } - - if (global.window.mm_config.EnableSignUpWithEmail === 'true') { - buttons.push( - <a - className='btn btn-custom-login email btn-full' - key='email' - href='#' - onClick={ - function clickEmail(e) { - e.preventDefault(); - this.props.updatePage('email'); - }.bind(this) - } - > - <span className='fa fa-envelope'/> - <span> - <FormattedMessage - id='choose_auth_page.emailCreate' - defaultMessage='Create new team with email address' - /> - </span> - </a> - ); - } - - if (buttons.length === 0) { - buttons = ( - <span> - <FormattedMessage - id='choose_auth_page.noSignup' - defaultMessage='No sign-up methods configured, please contact your system administrator.' - /> - </span> - ); - } - - return ( - <div> - {buttons} - </div> - ); - } -} - -ChooseAuthPage.propTypes = { - updatePage: React.PropTypes.func -}; diff --git a/webapp/components/team_signup_with_email.jsx b/webapp/components/team_signup_with_email.jsx deleted file mode 100644 index 90a6e9773..000000000 --- a/webapp/components/team_signup_with_email.jsx +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; - -const holders = defineMessages({ - emailError: { - id: 'email_signup.emailError', - defaultMessage: 'Please enter a valid email address' - }, - address: { - id: 'email_signup.address', - defaultMessage: 'Email Address' - } -}); - -import React from 'react'; - -class EmailSignUpPage extends React.Component { - constructor() { - super(); - - this.handleSubmit = this.handleSubmit.bind(this); - - this.state = {}; - } - handleSubmit(e) { - e.preventDefault(); - const team = {}; - const state = {serverError: null}; - let isValid = true; - - team.email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); - if (!team.email || !Utils.isEmail(team.email)) { - state.emailError = this.props.intl.formatMessage(holders.emailError); - isValid = false; - } else { - state.emailError = null; - } - - if (!isValid) { - this.setState(state); - return; - } - - Client.signupTeam(team.email, - (data) => { - if (data.follow_link) { - browserHistory.push(data.follow_link); - } else { - browserHistory.push(`/signup_team_confirm/?email=${encodeURIComponent(team.email)}`); - } - }, - (err) => { - state.serverError = err.message; - this.setState(state); - } - ); - } - render() { - let serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - let emailError = null; - if (this.state.emailError) { - emailError = <div className='form-group has-error'><label className='control-label'>{this.state.emailError}</label></div>; - } - - return ( - <form - role='form' - onSubmit={this.handleSubmit} - > - <div className='form-group'> - <input - autoFocus={true} - type='email' - ref='email' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.address)} - maxLength='128' - spellCheck='false' - /> - {emailError} - </div> - <div className='form-group'> - <button - className='btn btn-md btn-primary' - type='submit' - > - <FormattedMessage - id='email_signup.createTeam' - defaultMessage='Create Team' - /> - </button> - {serverError} - </div> - </form> - ); - } -} - -EmailSignUpPage.defaultProps = { -}; -EmailSignUpPage.propTypes = { - intl: intlShape.isRequired -}; - -export default injectIntl(EmailSignUpPage); diff --git a/webapp/components/team_signup_with_ldap.jsx b/webapp/components/team_signup_with_ldap.jsx deleted file mode 100644 index 9d812e8ee..000000000 --- a/webapp/components/team_signup_with_ldap.jsx +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import * as utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; - -const holders = defineMessages({ - team_error: { - id: 'ldap_signup.team_error', - defaultMessage: 'Please enter a team name' - }, - length_error: { - id: 'ldap_signup.length_error', - defaultMessage: 'Name must be 3 or more characters up to a maximum of 15' - }, - teamName: { - id: 'ldap_signup.teamName', - defaultMessage: 'Enter name of new team' - }, - badTeam: { - id: 'login_ldap.badTeam', - defaultMessage: 'Bad team name' - }, - idReq: { - id: 'login_ldap.idlReq', - defaultMessage: 'An LDAP ID is required' - }, - pwdReq: { - id: 'login_ldap.pwdReq', - defaultMessage: 'An LDAP password is required' - }, - username: { - id: 'login_ldap.username', - defaultMessage: 'LDAP Username' - }, - pwd: { - id: 'login_ldap.pwd', - defaultMessage: 'LDAP Password' - } -}); - -import React from 'react'; - -class LdapSignUpPage extends React.Component { - constructor(props) { - super(props); - - this.handleSubmit = this.handleSubmit.bind(this); - this.valueChange = this.valueChange.bind(this); - - this.state = {name: '', id: '', password: ''}; - } - - handleSubmit(e) { - e.preventDefault(); - const {formatMessage} = this.props.intl; - var teamSignup = {}; - teamSignup.user = {}; - teamSignup.team = {}; - var state = this.state; - state.serverError = null; - - teamSignup.team.display_name = this.state.name; - - if (!teamSignup.team.display_name) { - state.serverError = formatMessage(holders.team_error); - this.setState(state); - return; - } - - if (teamSignup.team.display_name.length <= 2) { - state.serverError = formatMessage(holders.length_error); - this.setState(state); - return; - } - - const id = this.refs.id.value.trim(); - if (!id) { - state.serverError = formatMessage(holders.idReq); - this.setState(state); - return; - } - - const password = this.refs.password.value.trim(); - if (!password) { - state.serverError = formatMessage(holders.pwdReq); - this.setState(state); - return; - } - - state.serverError = ''; - this.setState(state); - - teamSignup.team.name = utils.cleanUpUrlable(teamSignup.team.display_name); - teamSignup.team.type = 'O'; - - teamSignup.user.username = ReactDOM.findDOMNode(this.refs.id).value.trim(); - teamSignup.user.password = ReactDOM.findDOMNode(this.refs.password).value.trim(); - teamSignup.user.allow_marketing = true; - teamSignup.user.ldap = true; - teamSignup.user.auth_service = 'ldap'; - - $('#ldap-button').button('loading'); - - Client.createTeamWithLdap(teamSignup, - () => { - Client.track('signup', 'signup_team_ldap_complete'); - - Client.loginByLdap(teamSignup.team.name, id, password, - () => { - browserHistory.push('/' + teamSignup.team.name + '/channels/town-square'); - }, - (err) => { - $('#ldap-button').button('reset'); - this.setState({serverError: err.message}); - } - ); - }, - (err) => { - $('#ldap-button').button('reset'); - this.setState({serverError: err.message}); - } - ); - } - - valueChange() { - this.setState({ - name: ReactDOM.findDOMNode(this.refs.teamname).value.trim(), - id: ReactDOM.findDOMNode(this.refs.id).value.trim(), - password: ReactDOM.findDOMNode(this.refs.password).value.trim() - }); - } - - render() { - const {formatMessage} = this.props.intl; - var nameError = null; - var nameDivClass = 'form-group'; - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameDivClass += ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - var disabled = false; - if (this.state.name.length <= 2) { - disabled = true; - } - - if (this.state.id.length <= 1) { - disabled = true; - } - - if (this.state.password.length <= 1) { - disabled = true; - } - - return ( - <form - role='form' - onSubmit={this.handleSubmit} - > - <div className={nameDivClass}> - <input - autoFocus={true} - type='text' - ref='teamname' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.teamName)} - maxLength='128' - onChange={this.valueChange} - spellCheck='false' - /> - {nameError} - </div> - <div className={nameDivClass}> - <input - className='form-control' - ref='id' - placeholder={formatMessage(holders.username)} - spellCheck='false' - onChange={this.valueChange} - /> - </div> - <div className={nameDivClass}> - <input - type='password' - className='form-control' - ref='password' - placeholder={formatMessage(holders.pwd)} - spellCheck='false' - onChange={this.valueChange} - /> - </div> - <div className='form-group'> - <a - className='btn btn-custom-login ldap btn-full' - key='ldap' - id='ldap-button' - href='#' - onClick={this.handleSubmit} - disabled={disabled} - > - <span className='icon'/> - <span> - <FormattedMessage - id='ldap_signup.ldap' - defaultMessage='Create team with LDAP Account' - /> - </span> - </a> - {serverError} - </div> - </form> - ); - } -} - -LdapSignUpPage.propTypes = { - intl: intlShape.isRequired -}; - -export default injectIntl(LdapSignUpPage); diff --git a/webapp/components/team_signup_with_sso.jsx b/webapp/components/team_signup_with_sso.jsx deleted file mode 100644 index 78396eea8..000000000 --- a/webapp/components/team_signup_with_sso.jsx +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -import * as utils from 'utils/utils.jsx'; -import * as client from 'utils/client.jsx'; -import Constants from 'utils/constants.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; - -const holders = defineMessages({ - team_error: { - id: 'sso_signup.team_error', - defaultMessage: 'Please enter a team name' - }, - length_error: { - id: 'sso_signup.length_error', - defaultMessage: 'Name must be 3 or more characters up to a maximum of 15' - }, - teamName: { - id: 'sso_signup.teamName', - defaultMessage: 'Enter name of new team' - } -}); - -import React from 'react'; -import {browserHistory} from 'react-router'; - -class SSOSignUpPage extends React.Component { - constructor(props) { - super(props); - - this.handleSubmit = this.handleSubmit.bind(this); - this.nameChange = this.nameChange.bind(this); - - this.state = {name: ''}; - } - handleSubmit(e) { - e.preventDefault(); - const {formatMessage} = this.props.intl; - var team = {}; - var state = this.state; - state.nameError = null; - state.serverError = null; - - team.display_name = this.state.name; - - if (!team.display_name) { - state.nameError = formatMessage(holders.team_error); - this.setState(state); - return; - } - - if (team.display_name.length <= 2) { - state.nameError = formatMessage(holders.length_error); - this.setState(state); - return; - } - - team.name = utils.cleanUpUrlable(team.display_name); - team.type = 'O'; - - client.createTeamWithSSO(team, - this.props.service, - (data) => { - if (data.follow_link) { - window.location.href = data.follow_link; - } else { - browserHistory.push('/' + team.name + '/channels/town-square'); - } - }, - (err) => { - state.serverError = err.message; - this.setState(state); - } - ); - } - nameChange() { - this.setState({name: ReactDOM.findDOMNode(this.refs.teamname).value.trim()}); - } - render() { - var nameError = null; - var nameDivClass = 'form-group'; - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameDivClass += ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - var disabled = false; - if (this.state.name.length <= 2) { - disabled = true; - } - - var button = null; - - if (this.props.service === Constants.GITLAB_SERVICE) { - button = ( - <a - className='btn btn-custom-login gitlab btn-full' - key='gitlab' - href='#' - onClick={this.handleSubmit} - disabled={disabled} - > - <span className='icon'/> - <span> - <FormattedMessage - id='sso_signup.gitlab' - defaultMessage='Create team with GitLab Account' - /> - </span> - </a> - ); - } else if (this.props.service === Constants.GOOGLE_SERVICE) { - button = ( - <a - className='btn btn-custom-login google btn-full' - key='google' - href='#' - onClick={this.handleSubmit} - disabled={disabled} - > - <span className='icon'/> - <span> - <FormattedMessage - id='sso_signup.google' - defaultMessage='Create team with Google Apps Account' - /> - </span> - </a> - ); - } - - return ( - <form - role='form' - onSubmit={this.handleSubmit} - > - <div className={nameDivClass}> - <input - autoFocus={true} - type='text' - ref='teamname' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.teamName)} - maxLength='128' - onChange={this.nameChange} - spellCheck='false' - /> - {nameError} - </div> - <div className='form-group'> - {button} - {serverError} - </div> - </form> - ); - } -} - -SSOSignUpPage.defaultProps = { - service: '' -}; -SSOSignUpPage.propTypes = { - intl: intlShape.isRequired, - service: React.PropTypes.string -}; - -export default injectIntl(SSOSignUpPage); diff --git a/webapp/components/toggle_modal_button.jsx b/webapp/components/toggle_modal_button.jsx index de225493c..69bdbbda0 100644 --- a/webapp/components/toggle_modal_button.jsx +++ b/webapp/components/toggle_modal_button.jsx @@ -15,7 +15,8 @@ export default class ModalToggleButton extends React.Component { }; } - show() { + show(e) { + e.preventDefault(); this.setState({show: true}); } @@ -29,10 +30,10 @@ export default class ModalToggleButton extends React.Component { // allow callers to provide an onClick which will be called before the modal is shown let clickHandler = this.show; if (onClick) { - clickHandler = () => { + clickHandler = (e) => { onClick(); - this.show(); + this.show(e); }; } diff --git a/webapp/components/user_list.jsx b/webapp/components/user_list.jsx index 3652723be..626cb3cf5 100644 --- a/webapp/components/user_list.jsx +++ b/webapp/components/user_list.jsx @@ -13,10 +13,18 @@ export default class UserList extends React.Component { let content; if (users.length > 0) { content = users.map((user) => { + var teamMember; + for (var index in this.props.teamMembers) { + if (this.props.teamMembers[index].user_id === user.id) { + teamMember = this.props.teamMembers[index]; + } + } + return ( <UserListRow key={user.id} user={user} + teamMember={teamMember} actions={this.props.actions} actionProps={this.props.actionProps} /> @@ -48,12 +56,14 @@ export default class UserList extends React.Component { UserList.defaultProps = { users: [], + teamMembers: [], actions: [], actionProps: {} }; UserList.propTypes = { users: React.PropTypes.arrayOf(React.PropTypes.object), + teamMembers: React.PropTypes.arrayOf(React.PropTypes.object), actions: React.PropTypes.arrayOf(React.PropTypes.func), actionProps: React.PropTypes.object }; diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx index f6fd91688..a7838ec32 100644 --- a/webapp/components/user_list_row.jsx +++ b/webapp/components/user_list_row.jsx @@ -4,9 +4,10 @@ import Constants from 'utils/constants.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; -export default function UserListRow({user, actions, actionProps}) { +export default function UserListRow({user, teamMember, actions, actionProps}) { const nameFormat = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', ''); let name = user.username; @@ -21,6 +22,7 @@ export default function UserListRow({user, actions, actionProps}) { <Action key={index.toString()} user={user} + teamMember={teamMember} {...actionProps} /> ); @@ -35,7 +37,7 @@ export default function UserListRow({user, actions, actionProps}) { className='more-modal__image' width='38' height='38' - src={`/api/v1/users/${user.id}/image?time=${user.update_at}`} + src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`} /> <div className='more-modal__details' @@ -57,12 +59,17 @@ export default function UserListRow({user, actions, actionProps}) { } UserListRow.defaultProps = { + teamMember: { + team_id: '', + roles: '' + }, actions: [], actionProps: {} }; UserListRow.propTypes = { user: React.PropTypes.object.isRequired, + teamMember: React.PropTypes.object.isRequired, actions: React.PropTypes.arrayOf(React.PropTypes.func), actionProps: React.PropTypes.object }; diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx index d83ab7454..04b4f99a4 100644 --- a/webapp/components/user_profile.jsx +++ b/webapp/components/user_profile.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; @@ -28,7 +29,7 @@ export default class UserProfile extends React.Component { if (this.props.user) { name = Utils.displayUsername(this.props.user.id); email = this.props.user.email; - profileImg = '/api/v1/users/' + this.props.user.id + '/image?time=' + this.props.user.update_at; + profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.update_at; } if (this.props.overwriteName) { diff --git a/webapp/components/user_settings/import_theme_modal.jsx b/webapp/components/user_settings/import_theme_modal.jsx index 2fc75ca13..32da296bf 100644 --- a/webapp/components/user_settings/import_theme_modal.jsx +++ b/webapp/components/user_settings/import_theme_modal.jsx @@ -5,7 +5,7 @@ import ReactDOM from 'react-dom'; import ModalStore from 'stores/modal_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {Modal} from 'react-bootstrap'; import AppDispatcher from '../../dispatcher/app_dispatcher.jsx'; diff --git a/webapp/components/user_settings/manage_languages.jsx b/webapp/components/user_settings/manage_languages.jsx index 094eaa127..bbf3a2e40 100644 --- a/webapp/components/user_settings/manage_languages.jsx +++ b/webapp/components/user_settings/manage_languages.jsx @@ -3,7 +3,7 @@ import SettingItemMax from '../setting_item_max.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as I18n from 'i18n/i18n.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx index eddbc1efe..c6a05d1ee 100644 --- a/webapp/components/user_settings/user_settings_general.jsx +++ b/webapp/components/user_settings/user_settings_general.jsx @@ -9,7 +9,7 @@ import SettingPicture from '../setting_picture.jsx'; import UserStore from 'stores/user_store.jsx'; import ErrorStore from 'stores/error_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -225,11 +225,9 @@ class UserSettingsGeneralTab extends React.Component { return; } - var formData = new FormData(); - formData.append('image', picture, picture.name); this.setState({loadingPicture: true}); - Client.uploadProfileImage(formData, + Client.uploadProfileImage(picture, () => { this.submitActive = false; AsyncClient.getMe(); @@ -781,7 +779,7 @@ class UserSettingsGeneralTab extends React.Component { <SettingPicture title={formatMessage(holders.profilePicture)} submit={this.submitPicture} - src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update} + src={Client.getUsersRoute() + '/' + user.id + '/image?time=' + user.last_picture_update} server_error={serverError} client_error={clientError} updateSection={(e) => { diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx index b119c42f9..fa84ce2d6 100644 --- a/webapp/components/user_settings/user_settings_notifications.jsx +++ b/webapp/components/user_settings/user_settings_notifications.jsx @@ -8,7 +8,7 @@ import SettingItemMax from '../setting_item_max.jsx'; import UserStore from 'stores/user_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx index ff5a898a9..f28e34197 100644 --- a/webapp/components/user_settings/user_settings_security.jsx +++ b/webapp/components/user_settings/user_settings_security.jsx @@ -10,7 +10,7 @@ import ToggleModalButton from '../toggle_modal_button.jsx'; import TeamStore from 'stores/team_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -96,12 +96,10 @@ class SecurityTab extends React.Component { return; } - var data = {}; - data.user_id = user.id; - data.current_password = currentPassword; - data.new_password = newPassword; - - Client.updatePassword(data, + Client.updatePassword( + user.id, + currentPassword, + newPassword, () => { this.props.updateSection(''); AsyncClient.getMe(); @@ -120,11 +118,9 @@ class SecurityTab extends React.Component { ); } activateMfa() { - const data = {}; - data.activate = true; - data.token = this.state.mfaToken; - - Client.updateMfa(data, + Client.updateMfa( + this.state.mfaToken, + true, () => { this.props.updateSection(''); AsyncClient.getMe(); @@ -224,7 +220,7 @@ class SecurityTab extends React.Component { <div className='col-sm-7'> <img className='qr-code-img' - src={'/api/v1/users/generate_mfa_qr?time=' + this.props.user.update_at} + src={Client.getUsersRoute() + '/generate_mfa_qr?time=' + this.props.user.update_at} /> </div> <br/> @@ -531,9 +527,9 @@ class SecurityTab extends React.Component { if (global.window.mm_config.EnableSignUpWithEmail === 'true' && user.auth_service !== '') { let link; if (user.auth_service === Constants.LDAP_SERVICE) { - link = '/' + teamName + '/claim/ldap_to_email?email=' + encodeURIComponent(user.email); + link = '/claim/ldap_to_email?email=' + encodeURIComponent(user.email); } else { - link = '/' + teamName + '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service; + link = '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service; } emailOption = ( @@ -558,7 +554,7 @@ class SecurityTab extends React.Component { <div> <Link className='btn btn-primary' - to={'/' + teamName + '/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE} + to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE} > <FormattedMessage id='user.settings.security.switchGitlab' @@ -594,7 +590,7 @@ class SecurityTab extends React.Component { <div> <Link className='btn btn-primary' - to={'/' + teamName + '/claim/email_to_ldap?email=' + encodeURIComponent(user.email)} + to={'/claim/email_to_ldap?email=' + encodeURIComponent(user.email)} > <FormattedMessage id='user.settings.security.switchLdap' @@ -660,6 +656,13 @@ class SecurityTab extends React.Component { defaultMessage='GitLab SSO' /> ); + } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.ldap' + defaultMessage='LDAP' + /> + ); } return ( diff --git a/webapp/components/user_settings/user_settings_theme.jsx b/webapp/components/user_settings/user_settings_theme.jsx index 14991037d..f19538f71 100644 --- a/webapp/components/user_settings/user_settings_theme.jsx +++ b/webapp/components/user_settings/user_settings_theme.jsx @@ -11,7 +11,7 @@ import SettingItemMax from '../setting_item_max.jsx'; import UserStore from 'stores/user_store.jsx'; import AppDispatcher from '../../dispatcher/app_dispatcher.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; diff --git a/webapp/components/view_image.jsx b/webapp/components/view_image.jsx index 3d3107d92..bd4aeaa41 100644 --- a/webapp/components/view_image.jsx +++ b/webapp/components/view_image.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import AudioVideoPreview from './audio_video_preview.jsx'; import Constants from 'utils/constants.jsx'; |