Merge pull request #1211 from mattermost/PLT-340

PLT-340 allow team directory and open invites
Этот коммит содержится в:
Joram Wilander
2015-10-30 10:57:25 -04:00
родитель c2cd58a33f bedf85a122
Коммит 256be4c14d
28 изменённых файлов: 783 добавлений и 147 удалений

Просмотреть файл

@@ -32,6 +32,7 @@ export default class TeamSettings extends React.Component {
config.TeamSettings.EnableTeamCreation = ReactDOM.findDOMNode(this.refs.EnableTeamCreation).checked;
config.TeamSettings.EnableUserCreation = ReactDOM.findDOMNode(this.refs.EnableUserCreation).checked;
config.TeamSettings.RestrictTeamNames = ReactDOM.findDOMNode(this.refs.RestrictTeamNames).checked;
config.TeamSettings.EnableTeamListing = ReactDOM.findDOMNode(this.refs.EnableTeamListing).checked;
var MaxUsersPerTeam = 50;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) {
@@ -242,6 +243,39 @@ export default class TeamSettings extends React.Component {
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableTeamListing'
>
{'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}
/>
{'true'}
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableTeamListing'
value='false'
defaultChecked={!this.props.config.TeamSettings.EnableTeamListing}
onChange={this.handleChange}
/>
{'false'}
</label>
<p className='help-text'>{'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>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}

Просмотреть файл

@@ -4,6 +4,7 @@
var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var ConfirmModal = require('./confirm_modal.jsx');
export default class InviteMemberModal extends React.Component {
@@ -292,7 +293,7 @@ export default class InviteMemberModal extends React.Component {
} else {
var teamInviteLink = null;
if (currentUser && this.props.teamType === 'O') {
var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id;
var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id;
var link =
(
<a

Просмотреть файл

@@ -101,7 +101,7 @@ export default class Login extends React.Component {
href={'/' + teamName + '/login/gitlab'}
>
<span className='icon' />
<span>with GitLab</span>
<span>{'with GitLab'}</span>
</a>
);
}
@@ -154,7 +154,7 @@ export default class Login extends React.Component {
type='submit'
className='btn btn-primary'
>
Sign in
{'Sign in'}
</button>
</div>
</div>
@@ -166,7 +166,7 @@ export default class Login extends React.Component {
<div>
{loginMessage}
<div className='or__container'>
<span>or</span>
<span>{'or'}</span>
</div>
</div>
);
@@ -176,16 +176,48 @@ export default class Login extends React.Component {
if (emailSignup) {
forgotPassword = (
<div className='form-group'>
<a href={'/' + teamName + '/reset_password'}>I forgot my password</a>
<a href={'/' + teamName + '/reset_password'}>{'I forgot my password'}</a>
</div>
);
}
let userSignUp = null;
if (this.props.inviteId) {
userSignUp = (
<div>
<span>{'Do not have an account? '}
<a
href={'/signup_user_complete/?id=' + this.props.inviteId}
className='signup-team-login'
>
{'Create one now'}
</a>
</span>
</div>
);
}
let teamSignUp = null;
if (global.window.mm_config.EnableTeamCreation === 'true') {
teamSignUp = (
<div className='margin--extra'>
<span>{'Want to create your own team? '}
<a
href='/'
className='signup-team-login'
>
{'Sign up now'}
</a>
</span>
</div>
);
}
return (
<div className='signup-team__container'>
<h5 className='margin--less'>Sign in to:</h5>
<h5 className='margin--less'>{'Sign in to:'}</h5>
<h2 className='signup-team__name'>{teamDisplayName}</h2>
<h2 className='signup-team__subdomain'>on {global.window.mm_config.SiteName}</h2>
<h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2>
<form onSubmit={this.handleSubmit}>
{verifiedBox}
<div className={'form-group' + errorClass}>
@@ -193,20 +225,12 @@ export default class Login extends React.Component {
</div>
{loginMessage}
{emailSignup}
{userSignUp}
<div className='form-group margin--extra form-group--small'>
<span><a href='/find_team'>{'Find other teams'}</a></span>
</div>
{forgotPassword}
<div className='margin--extra'>
<span>{'Want to create your own team? '}
<a
href='/'
className='signup-team-login'
>
Sign up now
</a>
</span>
</div>
{teamSignUp}
</form>
</div>
);
@@ -219,5 +243,6 @@ Login.defaultProps = {
};
Login.propTypes = {
teamName: React.PropTypes.string,
teamDisplayName: React.PropTypes.string
teamDisplayName: React.PropTypes.string,
inviteId: React.PropTypes.string
};

Просмотреть файл

@@ -112,7 +112,7 @@ export default class NavbarDropdown extends React.Component {
data-toggle='modal'
data-target='#get_link'
data-title='Team Invite'
data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}
data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id}
>
{'Get Team Invite Link'}
</a>

Просмотреть файл

@@ -2,6 +2,7 @@
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx');
@@ -51,7 +52,7 @@ export default class SidebarRightMenu extends React.Component {
data-toggle='modal'
data-target='#get_link'
data-title='Team Invite'
data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}
data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id}
><i className='glyphicon glyphicon-link'></i>Get Team Invite Link</a>
</li>
);

Просмотреть файл

@@ -12,6 +12,11 @@ export default class TeamSignUp extends React.Component {
this.updatePage = this.updatePage.bind(this);
if (global.window.mm_config.EnableTeamListing === 'true') {
this.state = {page: 'team_listing'};
return;
}
var count = 0;
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
@@ -36,6 +41,38 @@ export default class TeamSignUp extends React.Component {
}
render() {
if (this.state.page === 'team_listing') {
return (
<div>
<h3>{'Choose a Team'}</h3>
<div className='signup-team-all'>
{
this.props.teams.map((team) => {
return (
<div
key={'team_' + team.name}
className='signup-team-dir'
>
<a
href={'/' + team.name}
>
<div className='signup-team-dir__group'>
<span className='signup-team-dir__name'>{team.display_name}</span>
<span
className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
aria-hidden='true'
/>
</div>
</a>
</div>
);
})
}
</div>
</div>
);
}
if (this.state.page === 'choose') {
return (
<ChoosePage
@@ -51,3 +88,8 @@ export default class TeamSignUp extends React.Component {
}
}
}
TeamSignUp.propTypes = {
teams: React.PropTypes.array
};

Просмотреть файл

@@ -6,29 +6,112 @@ const SettingItemMax = require('./setting_item_max.jsx');
const Client = require('../utils/client.jsx');
const Utils = require('../utils/utils.jsx');
const TeamStore = require('../stores/team_store.jsx');
export default class GeneralTab extends React.Component {
constructor(props) {
super(props);
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.onUpdateSection = this.onUpdateSection.bind(this);
this.onUpdateNameSection = this.onUpdateNameSection.bind(this);
this.updateName = this.updateName.bind(this);
this.onUpdateInviteIdSection = this.onUpdateInviteIdSection.bind(this);
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 = {name: this.props.teamDisplayName, serverError: '', clientError: ''};
this.state = {
name: props.team.display_name,
invite_id: props.team.invite_id,
allow_open_invite: props.team.allow_open_invite,
allow_team_listing: props.team.allow_team_listing,
serverError: '',
clientError: ''
};
}
handleGenerateInviteId(e) {
e.preventDefault();
var newId = '';
for (var i = 0; i < 32; i++) {
newId += Math.floor(Math.random() * 16).toString(16);
}
this.setState({invite_id: newId});
}
handleOpenInviteRadio(openInvite) {
this.setState({allow_open_invite: openInvite});
}
handleTeamListingRadio(listing) {
if (global.window.mm_config.EnableTeamListing !== 'true' && listing) {
ReactDOM.findDOMNode(this.refs.teamListingRadioNo).checked = true;
this.setState({clientError: 'Team directory has been disabled. Please ask a system admin to enable it.'});
} else {
this.setState({allow_team_listing: listing});
}
}
handleOpenInviteSubmit(e) {
e.preventDefault();
var state = {serverError: '', clientError: ''};
var data = this.props.team;
data.allow_open_invite = this.state.allow_open_invite;
Client.updateTeam(data,
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
this.props.updateSection('');
},
(err) => {
state.serverError = err.message;
this.setState(state);
}
);
}
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.props.updateSection('');
},
(err) => {
state.serverError = err.message;
this.setState(state);
}
);
}
handleNameSubmit(e) {
e.preventDefault();
let state = {serverError: '', clientError: ''};
var state = {serverError: '', clientError: ''};
let valid = true;
const name = this.state.name.trim();
if (!name) {
state.clientError = 'This field is required';
valid = false;
} else if (name === this.props.teamDisplayName) {
} else if (name === this.props.team.display_name) {
state.clientError = 'Please choose a new name for your team';
valid = false;
} else {
@@ -41,37 +124,76 @@ export default class GeneralTab extends React.Component {
return;
}
let data = {};
data.new_name = name;
Client.updateTeamDisplayName(data,
function nameChangeSuccess() {
var data = this.props.team;
data.display_name = this.state.name;
Client.updateTeam(data,
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
this.props.updateSection('');
$('#team_settings').modal('hide');
window.location.reload();
}.bind(this),
function nameChangeFail(err) {
},
(err) => {
state.serverError = err.message;
this.setState(state);
}.bind(this)
}
);
}
handleInviteIdSubmit(e) {
e.preventDefault();
var state = {serverError: '', clientError: ''};
let valid = true;
const inviteId = this.state.invite_id.trim();
if (inviteId) {
state.clientError = '';
} else {
state.clientError = 'This field is required';
valid = false;
}
this.setState(state);
if (!valid) {
return;
}
var data = this.props.team;
data.invite_id = this.state.invite_id;
Client.updateTeam(data,
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
this.props.updateSection('');
},
(err) => {
state.serverError = err.message;
this.setState(state);
}
);
}
componentWillReceiveProps(newProps) {
if (newProps.team && newProps.teamDisplayName) {
this.setState({name: newProps.teamDisplayName});
}
}
handleClose() {
this.setState({clientError: '', serverError: ''});
this.props.updateSection('');
}
componentDidMount() {
$('#team_settings').on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
$('#team_settings').off('hidden.bs.modal', this.handleClose);
}
onUpdateSection(e) {
onUpdateNameSection(e) {
e.preventDefault();
if (this.props.activeSection === 'name') {
this.props.updateSection('');
@@ -79,10 +201,44 @@ export default class GeneralTab extends React.Component {
this.props.updateSection('name');
}
}
onUpdateInviteIdSection(e) {
e.preventDefault();
if (this.props.activeSection === 'invite_id') {
this.props.updateSection('');
} else {
this.props.updateSection('invite_id');
}
}
onUpdateOpenInviteSection(e) {
e.preventDefault();
if (this.props.activeSection === 'open_invite') {
this.props.updateSection('');
} else {
this.props.updateSection('open_invite');
}
}
onUpdateTeamListingSection(e) {
e.preventDefault();
if (this.props.activeSection === 'team_listing') {
this.props.updateSection('');
} else {
this.props.updateSection('team_listing');
}
}
updateName(e) {
e.preventDefault();
this.setState({name: e.target.value});
}
updateInviteId(e) {
e.preventDefault();
this.setState({invite_id: e.target.value});
}
render() {
let clientError = null;
let serverError = null;
@@ -93,10 +249,180 @@ export default class GeneralTab extends React.Component {
serverError = this.state.serverError;
}
let teamListingSection;
if (this.props.activeSection === 'team_listing') {
const inputs = [
<div key='userTeamListingOptions'>
<div className='radio'>
<label>
<input
name='userTeamListingOptions'
type='radio'
defaultChecked={this.state.allow_team_listing}
onChange={this.handleTeamListingRadio.bind(this, true)}
/>
{'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)}
/>
{'No'}
</label>
<br/>
</div>
<div><br/>{'When allowed the team will appear on the main page as part of team directory.'}</div>
</div>
];
teamListingSection = (
<SettingItemMax
title='Allow in Team Directory'
inputs={inputs}
submit={this.handleTeamListingSubmit}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateTeamListingSection}
/>
);
} else {
let describe = '';
if (this.state.allow_team_listing === true) {
describe = 'Yes';
} else {
describe = 'No';
}
teamListingSection = (
<SettingItemMin
title='Allow in Team Directory'
describe={describe}
updateSection={this.onUpdateTeamListingSection}
/>
);
}
let openInviteSection;
if (this.props.activeSection === 'open_invite') {
const inputs = [
<div key='userOpenInviteOptions'>
<div className='radio'>
<label>
<input
name='userOpenInviteOptions'
type='radio'
defaultChecked={this.state.allow_open_invite}
onChange={this.handleOpenInviteRadio.bind(this, true)}
/>
{'Yes'}
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
name='userOpenInviteOptions'
type='radio'
defaultChecked={!this.state.allow_open_invite}
onChange={this.handleOpenInviteRadio.bind(this, false)}
/>
{'No'}
</label>
<br/>
</div>
<div><br/>{'When allowed the team signup link will be included on the login page and anyone can signup to this team.'}</div>
</div>
];
openInviteSection = (
<SettingItemMax
title='Allow Open Invitations'
inputs={inputs}
submit={this.handleOpenInviteSubmit}
server_error={serverError}
updateSection={this.onUpdateOpenInviteSection}
/>
);
} else {
let describe = '';
if (this.state.allow_open_invite === true) {
describe = 'Yes';
} else {
describe = 'No';
}
openInviteSection = (
<SettingItemMin
title='Allow Open Invitations'
describe={describe}
updateSection={this.onUpdateOpenInviteSection}
/>
);
}
let inviteSection;
if (this.props.activeSection === 'invite_id') {
const inputs = [];
inputs.push(
<div
key='teamInviteSetting'
className='form-group'
>
<label className='col-sm-5 control-label'>{'Invite Code'}</label>
<div className='col-sm-7'>
<input
className='form-control'
type='text'
onChange={this.updateInviteId}
value={this.state.invite_id}
maxLength='32'
/>
</div>
<div><br/>{'When allowing open invites this code is used as part of the signup process. Changing this code will invalidate the previous open signup link.'}</div>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerateInviteId}
>
{'Re-Generate'}
</button>
</div>
</div>
);
inviteSection = (
<SettingItemMax
title={`Invite Code`}
inputs={inputs}
submit={this.handleInviteIdSubmit}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateInviteIdSection}
/>
);
} else {
inviteSection = (
<SettingItemMin
title={`Invite Code`}
describe={`Click 'Edit' to re-generate invite Code.`}
updateSection={this.onUpdateInviteIdSection}
/>
);
}
let nameSection;
if (this.props.activeSection === 'name') {
let inputs = [];
const inputs = [];
let teamNameLabel = 'Team Name';
if (Utils.isMobile()) {
@@ -127,17 +453,17 @@ export default class GeneralTab extends React.Component {
submit={this.handleNameSubmit}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateSection}
updateSection={this.onUpdateNameSection}
/>
);
} else {
let describe = this.state.name;
var describe = this.state.name;
nameSection = (
<SettingItemMin
title={`Team Name`}
describe={describe}
updateSection={this.onUpdateSection}
updateSection={this.onUpdateNameSection}
/>
);
}
@@ -158,16 +484,19 @@ export default class GeneralTab extends React.Component {
ref='title'
>
<i className='modal-back'></i>
General Settings
{'General Settings'}
</h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
<h3 className='tab-header'>General Settings</h3>
<h3 className='tab-header'>{'General Settings'}</h3>
<div className='divider-dark first'/>
{nameSection}
{openInviteSection}
{teamListingSection}
{inviteSection}
<div className='divider-dark'/>
</div>
</div>
@@ -178,6 +507,5 @@ export default class GeneralTab extends React.Component {
GeneralTab.propTypes = {
updateSection: React.PropTypes.func.isRequired,
team: React.PropTypes.object.isRequired,
activeSection: React.PropTypes.string.isRequired,
teamDisplayName: React.PropTypes.string.isRequired
activeSection: React.PropTypes.string.isRequired
};

Просмотреть файл

@@ -37,7 +37,6 @@ export default class TeamSettings extends React.Component {
team={this.state.team}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
teamDisplayName={this.props.teamDisplayName}
/>
</div>
);
@@ -72,12 +71,11 @@ export default class TeamSettings extends React.Component {
TeamSettings.defaultProps = {
activeTab: '',
activeSection: '',
teamDisplayName: ''
activeSection: ''
};
TeamSettings.propTypes = {
activeTab: React.PropTypes.string.isRequired,
activeSection: React.PropTypes.string.isRequired,
updateSection: React.PropTypes.func.isRequired,
teamDisplayName: React.PropTypes.string.isRequired
updateSection: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -82,7 +82,6 @@ export default class TeamSettingsModal extends React.Component {
activeTab={this.state.activeTab}
activeSection={this.state.activeSection}
updateSection={this.updateSection}
teamDisplayName={this.props.teamDisplayName}
/>
</div>
</div>
@@ -95,5 +94,4 @@ export default class TeamSettingsModal extends React.Component {
}
TeamSettingsModal.propTypes = {
teamDisplayName: React.PropTypes.string.isRequired
};