Add Team Description to the Team Settings (#4652)

* draft

* Add Team Description to the Team Settings

* add tooltips for team description

* made changes per PM review

* add message when there is no description set in the team

* squash
Этот коммит содержится в:
Carlos Tadeu Panato Junior
2016-12-01 23:23:28 +01:00
коммит произвёл Joram Wilander
родитель 8c18da21f3
Коммит c51afba71a
15 изменённых файлов: 264 добавлений и 12 удалений

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

@@ -38,7 +38,7 @@ export default class TeamSignupDisplayNamePage extends React.Component {
this.setState({nameError: (
<FormattedMessage
id='create_team.display_name.charLength'
defaultMessage='Name must be {min} or more characters up to a maximum of {max}'
defaultMessage='Name must be {min} or more characters up to a maximum of {max}. You can add a longer team description later.'
values={{
min: Constants.MIN_TEAMNAME_LENGTH,
max: Constants.MAX_TEAMNAME_LENGTH

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

@@ -4,6 +4,7 @@
import React from 'react';
import {Link} from 'react-router/es6';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
export default class SelectTeamItem extends React.Component {
static propTypes = {
@@ -35,8 +36,31 @@ export default class SelectTeamItem extends React.Component {
);
}
var descriptionTooltip = '';
var showDescriptionTooltip = '';
if (this.props.team.description) {
descriptionTooltip = (
<Tooltip id='team-description__tooltip'>
{this.props.team.description}
</Tooltip>
);
showDescriptionTooltip = (
<OverlayTrigger
trigger={['hover', 'focus', 'click']}
delayShow={1000}
placement='left'
overlay={descriptionTooltip}
ref='descriptionOverlay'
>
<span className='fa fa-info-circle signup-team__icon'/>
</OverlayTrigger>
);
}
return (
<div className='signup-team-dir'>
{showDescriptionTooltip}
<Link
to={this.props.url}
onClick={this.handleTeamClick}

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

@@ -736,6 +736,7 @@ export default class Sidebar extends React.Component {
<SidebarHeader
teamDisplayName={this.state.currentTeam.display_name}
teamDescription={this.state.currentTeam.description}
teamName={this.state.currentTeam.name}
teamType={this.state.currentTeam.type}
currentUser={this.state.currentUser}

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

@@ -69,6 +69,25 @@ export default class SidebarHeader extends React.Component {
tutorialTip = createMenuTip(this.toggleDropdown);
}
let teamNameWithToolTip = null;
if (this.props.teamDescription === '') {
teamNameWithToolTip = (
<div className='team__name'>{this.props.teamDisplayName}</div>
);
} else {
teamNameWithToolTip = (
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={1000}
placement='bottom'
overlay={<Tooltip id='team-name__tooltip'>{this.props.teamDescription}</Tooltip>}
ref='descriptionOverlay'
>
<div className='team__name'>{this.props.teamDisplayName}</div>
</OverlayTrigger>
);
}
return (
<div className='team__header theme'>
{tutorialTip}
@@ -79,15 +98,7 @@ export default class SidebarHeader extends React.Component {
{profilePicture}
<div className='header__info'>
<div className='user__name'>{'@' + me.username}</div>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={1000}
placement='bottom'
overlay={<Tooltip id='team-name__tooltip'>{this.props.teamDisplayName}</Tooltip>}
ref='descriptionOverlay'
>
<div className='team__name'>{this.props.teamDisplayName}</div>
</OverlayTrigger>
{teamNameWithToolTip}
</div>
</a>
<SidebarHeaderDropdown
@@ -104,10 +115,12 @@ export default class SidebarHeader extends React.Component {
SidebarHeader.defaultProps = {
teamDisplayName: '',
teamDescription: '',
teamType: ''
};
SidebarHeader.propTypes = {
teamDisplayName: React.PropTypes.string,
teamDescription: React.PropTypes.string,
teamName: React.PropTypes.string,
teamType: React.PropTypes.string,
currentUser: React.PropTypes.object

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

@@ -55,6 +55,10 @@ const holders = defineMessages({
teamNameInfo: {
id: 'general_tab.teamNameInfo',
defaultMessage: 'Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
},
teamDescriptionInfo: {
id: 'general_tab.teamDescriptionInfo',
defaultMessage: 'Team description provides additional information to help users select the right team. Maximum of 50 characters.'
}
});
@@ -68,9 +72,12 @@ class GeneralTab extends React.Component {
this.handleNameSubmit = this.handleNameSubmit.bind(this);
this.handleInviteIdSubmit = this.handleInviteIdSubmit.bind(this);
this.handleOpenInviteSubmit = this.handleOpenInviteSubmit.bind(this);
this.handleDescriptionSubmit = this.handleDescriptionSubmit.bind(this);
this.handleClose = this.handleClose.bind(this);
this.onUpdateNameSection = this.onUpdateNameSection.bind(this);
this.updateName = this.updateName.bind(this);
this.updateDescription = this.updateDescription.bind(this);
this.onUpdateDescriptionSection = this.onUpdateDescriptionSection.bind(this);
this.onUpdateInviteIdSection = this.onUpdateInviteIdSection.bind(this);
this.updateInviteId = this.updateInviteId.bind(this);
this.onUpdateOpenInviteSection = this.onUpdateOpenInviteSection.bind(this);
@@ -95,6 +102,7 @@ class GeneralTab extends React.Component {
name: team.display_name,
invite_id: team.invite_id,
allow_open_invite: team.allow_open_invite,
description: team.description,
serverError: '',
clientError: ''
};
@@ -103,6 +111,7 @@ class GeneralTab extends React.Component {
componentWillReceiveProps(nextProps) {
this.setState({
name: nextProps.team.display_name,
description: nextProps.team.description,
invite_id: nextProps.team.invite_id,
allow_open_invite: nextProps.team.allow_open_invite
});
@@ -215,6 +224,40 @@ class GeneralTab extends React.Component {
this.updateSection('');
}
handleDescriptionSubmit(e) {
e.preventDefault();
var state = {serverError: '', clientError: ''};
let valid = true;
const {formatMessage} = this.props.intl;
const description = this.state.description.trim();
if (description === this.props.team.description) {
state.clientError = formatMessage(holders.chooseName);
valid = false;
} else {
state.clientError = '';
}
this.setState(state);
if (!valid) {
return;
}
var data = this.props.team;
data.description = this.state.description;
updateTeam(data,
() => {
this.updateSection('');
},
(err) => {
state.serverError = err.message;
this.setState(state);
}
);
}
componentDidMount() {
$('#team_settings').on('hidden.bs.modal', this.handleClose);
}
@@ -232,6 +275,15 @@ class GeneralTab extends React.Component {
}
}
onUpdateDescriptionSection(e) {
e.preventDefault();
if (this.props.activeSection === 'description') {
this.updateSection('');
} else {
this.updateSection('description');
}
}
onUpdateInviteIdSection(e) {
e.preventDefault();
if (this.props.activeSection === 'invite_id') {
@@ -254,6 +306,10 @@ class GeneralTab extends React.Component {
this.setState({name: e.target.value});
}
updateDescription(e) {
this.setState({description: e.target.value});
}
updateInviteId(e) {
this.setState({invite_id: e.target.value});
}
@@ -457,6 +513,74 @@ class GeneralTab extends React.Component {
);
}
let descriptionSection;
if (this.props.activeSection === 'description') {
const inputs = [];
let teamDescriptionLabel = (
<FormattedMessage
id='general_tab.teamDescription'
defaultMessage='Team Description'
/>
);
if (Utils.isMobile()) {
teamDescriptionLabel = '';
}
inputs.push(
<div
key='teamDescriptionSetting'
className='form-group'
>
<label className='col-sm-5 control-label'>{teamDescriptionLabel}</label>
<div className='col-sm-7'>
<input
className='form-control'
type='text'
maxLength={Constants.MAX_TEAMDESCRIPTION_LENGTH.toString()}
onChange={this.updateDescription}
value={this.state.description}
/>
</div>
</div>
);
const descriptionExtraInfo = <span>{formatMessage(holders.teamDescriptionInfo)}</span>;
descriptionSection = (
<SettingItemMax
title={formatMessage({id: 'general_tab.teamDescription'})}
inputs={inputs}
submit={this.handleDescriptionSubmit}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateDescriptionSection}
extraInfo={descriptionExtraInfo}
/>
);
} else {
let describemsg = '';
if (this.state.description) {
describemsg = this.state.description;
} else {
describemsg = (
<FormattedMessage
id='general_tab.emptyDescription'
defaultMessage="Click 'Edit' to add a team description."
/>
);
}
descriptionSection = (
<SettingItemMin
title={formatMessage({id: 'general_tab.teamDescription'})}
describe={describemsg}
updateSection={this.onUpdateDescriptionSection}
/>
);
}
return (
<div>
<div className='modal-header'>
@@ -496,6 +620,8 @@ class GeneralTab extends React.Component {
<div className='divider-dark first'/>
{nameSection}
<div className='divider-light'/>
{descriptionSection}
<div className='divider-light'/>
{openInviteSection}
<div className='divider-light'/>
{inviteSection}

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

@@ -1145,7 +1145,7 @@
"create_post.write": "Write a message...",
"create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href={TermsOfServiceLink}>Terms of Service</a> and <a href={PrivacyPolicyLink}>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
"create_team.display_name.back": "Back to previous step",
"create_team.display_name.charLength": "Name must be 2 or more characters up to a maximum of 15",
"create_team.display_name.charLength": "Name must be {min} or more characters up to a maximum of {max}. You can add a longer team description later.",
"create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
"create_team.display_name.next": "Next",
"create_team.display_name.required": "This field is required",
@@ -1272,8 +1272,11 @@
"general_tab.required": "This field is required",
"general_tab.teamName": "Team Name",
"general_tab.teamNameInfo": "Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.",
"general_tab.teamDescription": "Team Description",
"general_tab.teamDescriptionInfo": "Team description provides additional information to help users select the right team. Maximum of 50 characters.",
"general_tab.title": "General Settings",
"general_tab.yes": "Yes",
"general_tab.emptyDescription": "Click 'Edit' to add a team description.",
"get_app.alreadyHaveIt": "Already have it?",
"get_app.androidAppName": "Mattermost for Android",
"get_app.androidHeader": "Mattermost works best if you switch to our Android app",

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

@@ -481,7 +481,8 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 90%;
width: calc(100% - 50px);
}
.signup-team__icon {
@@ -497,6 +498,15 @@
right: -2px;
top: 16px;
}
&.fa-info-circle {
float: left;
line-height: 1.5em;
margin-right: .3em;
padding-left: .5em;
font-size: 1.5em;
top: 11px;
}
}
}

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

@@ -237,6 +237,24 @@ describe('Client.Team', function() {
});
});
it('updateTeamDescription', function(done) {
TestHelper.initBasic(() => {
var team = TestHelper.basicTeam();
team.description = 'test_updated';
TestHelper.basicClient().updateTeam(
team,
function(data) {
assert.equal(data.description, 'test_updated');
done();
},
function(err) {
done(new Error(err.message));
}
);
});
});
it('addUserToTeam', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().createUser(

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

@@ -829,6 +829,7 @@ export const Constants = {
DEFAULT_MAX_CHANNELS_PER_TEAM: 2000,
DEFAULT_MAX_NOTIFICATIONS_PER_CHANNEL: 1000,
MAX_TEAMNAME_LENGTH: 15,
MAX_TEAMDESCRIPTION_LENGTH: 50,
MIN_USERNAME_LENGTH: 3,
MAX_USERNAME_LENGTH: 22,
MAX_NICKNAME_LENGTH: 22,