summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/admin_console/team_analytics.jsx184
-rw-r--r--web/react/components/edit_post_modal.jsx21
-rw-r--r--web/react/components/popover_list_members.jsx32
-rw-r--r--web/react/components/post_list.jsx47
-rw-r--r--web/react/utils/constants.jsx2
5 files changed, 190 insertions, 96 deletions
diff --git a/web/react/components/admin_console/team_analytics.jsx b/web/react/components/admin_console/team_analytics.jsx
index a945a551c..0c9d1f61b 100644
--- a/web/react/components/admin_console/team_analytics.jsx
+++ b/web/react/components/admin_console/team_analytics.jsx
@@ -210,69 +210,89 @@ export default class TeamAnalytics extends React.Component {
}
var totalCount = (
- <div className='total-count text-center'>
- <div>{'Total Users'}</div>
- <div>{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}</div>
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'Total Users'}<i className='fa fa-users'/></div>
+ <div className='content'>{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}</div>
+ </div>
</div>
);
var openChannelCount = (
- <div className='total-count text-center'>
- <div>{'Public Groups'}</div>
- <div>{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}</div>
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'Public Groups'}<i className='fa fa-unlock-alt'/></div>
+ <div className='content'>{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}</div>
+ </div>
</div>
);
var openPrivateCount = (
- <div className='total-count text-center'>
- <div>{'Private Groups'}</div>
- <div>{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}</div>
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'Private Groups'}<i className='fa fa-lock'/></div>
+ <div className='content'>{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}</div>
+ </div>
</div>
);
var postCount = (
- <div className='total-count text-center'>
- <div>{'Total Posts'}</div>
- <div>{this.state.post_count == null ? 'Loading...' : this.state.post_count}</div>
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'Total Posts'}<i className='fa fa-comment'/></div>
+ <div className='content'>{this.state.post_count == null ? 'Loading...' : this.state.post_count}</div>
+ </div>
</div>
);
var postCountsByDay = (
- <div className='total-count-by-day'>
- <div>{'Total Posts'}</div>
- <div>{'Loading...'}</div>
+ <div className='col-sm-12'>
+ <div className='total-count by-day'>
+ <div className='title'>{'Total Posts'}</div>
+ <div className='content'>{'Loading...'}</div>
+ </div>
</div>
);
if (this.state.post_counts_day != null) {
postCountsByDay = (
- <div className='total-count-by-day'>
- <div>{'Total Posts'}</div>
- <LineChart
- data={this.state.post_counts_day}
- width='740'
- height='225'
- />
+ <div className='col-sm-12'>
+ <div className='total-count by-day'>
+ <div className='title'>{'Total Posts'}</div>
+ <div className='content'>
+ <LineChart
+ data={this.state.post_counts_day}
+ width='740'
+ height='225'
+ />
+ </div>
+ </div>
</div>
);
}
var usersWithPostsByDay = (
- <div className='total-count-by-day'>
- <div>{'Total Posts'}</div>
- <div>{'Loading...'}</div>
+ <div className='col-sm-12'>
+ <div className='total-count by-day'>
+ <div className='title'>{'Total Posts'}</div>
+ <div>{'Loading...'}</div>
+ </div>
</div>
);
if (this.state.user_counts_with_posts_day != null) {
usersWithPostsByDay = (
- <div className='total-count-by-day'>
- <div>{'Active Users With Posts'}</div>
- <LineChart
- data={this.state.user_counts_with_posts_day}
- width='740'
- height='225'
- />
+ <div className='col-sm-12'>
+ <div className='total-count by-day'>
+ <div className='title'>{'Active Users With Posts'}</div>
+ <div className='content'>
+ <LineChart
+ data={this.state.user_counts_with_posts_day}
+ width='740'
+ height='225'
+ />
+ </div>
+ </div>
</div>
);
}
@@ -286,22 +306,26 @@ export default class TeamAnalytics extends React.Component {
if (this.state.recent_active_users != null) {
recentActiveUser = (
- <div className='recent-active-users'>
- <div>{'Recent Active Users'}</div>
- <table width='90%'>
- <tbody>
- {
- this.state.recent_active_users.map((user) => {
- return (
- <tr key={user.id}>
- <td className='recent-active-users-td'>{user.email}</td>
- <td className='recent-active-users-td'>{Utils.displayDateTime(user.last_activity_at)}</td>
- </tr>
- );
- })
- }
- </tbody>
- </table>
+ <div className='col-sm-6'>
+ <div className='total-count recent-active-users'>
+ <div className='title'>{'Recent Active Users'}</div>
+ <div className='content'>
+ <table>
+ <tbody>
+ {
+ this.state.recent_active_users.map((user) => {
+ return (
+ <tr key={user.id}>
+ <td>{user.email}</td>
+ <td>{Utils.displayDateTime(user.last_activity_at)}</td>
+ </tr>
+ );
+ })
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
</div>
);
}
@@ -315,38 +339,50 @@ export default class TeamAnalytics extends React.Component {
if (this.state.newly_created_users != null) {
newUsers = (
- <div className='recent-active-users'>
- <div>{'Newly Created Users'}</div>
- <table width='90%'>
- <tbody>
- {
- this.state.newly_created_users.map((user) => {
- return (
- <tr key={user.id}>
- <td className='recent-active-users-td'>{user.email}</td>
- <td className='recent-active-users-td'>{Utils.displayDateTime(user.create_at)}</td>
- </tr>
- );
- })
- }
- </tbody>
- </table>
+ <div className='col-sm-6'>
+ <div className='total-count recent-active-users'>
+ <div className='title'>{'Newly Created Users'}</div>
+ <div className='content'>
+ <table>
+ <tbody>
+ {
+ this.state.newly_created_users.map((user) => {
+ return (
+ <tr key={user.id}>
+ <td>{user.email}</td>
+ <td>{Utils.displayDateTime(user.create_at)}</td>
+ </tr>
+ );
+ })
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
</div>
);
}
return (
- <div className='wrapper--fixed'>
- <h2>{'Statistics for ' + this.props.team.name}</h2>
+ <div className='wrapper--fixed team_statistics'>
+ <h3>{'Statistics for ' + this.props.team.name}</h3>
{serverError}
- {totalCount}
- {postCount}
- {openChannelCount}
- {openPrivateCount}
- {postCountsByDay}
- {usersWithPostsByDay}
- {recentActiveUser}
- {newUsers}
+ <div className='row'>
+ {totalCount}
+ {postCount}
+ {openChannelCount}
+ {openPrivateCount}
+ </div>
+ <div className='row'>
+ {postCountsByDay}
+ </div>
+ <div className='row'>
+ {usersWithPostsByDay}
+ </div>
+ <div className='row'>
+ {recentActiveUser}
+ {newUsers}
+ </div>
</div>
);
}
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index e5bede026..2abb3f151 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -6,6 +6,10 @@ var AsyncClient = require('../utils/async_client.jsx');
var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
var PostStore = require('../stores/post_store.jsx');
+var PreferenceStore = require('../stores/preference_store.jsx');
+
+var Constants = require('../utils/constants.jsx');
+var KeyCodes = Constants.KeyCodes;
export default class EditPostModal extends React.Component {
constructor() {
@@ -16,6 +20,8 @@ export default class EditPostModal extends React.Component {
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: ''};
}
@@ -51,7 +57,7 @@ export default class EditPostModal extends React.Component {
this.setState({editText: editMessage});
}
handleEditKeyPress(e) {
- if (e.which === 13 && !e.shiftKey && !e.altKey) {
+ if (this.state.ctrlSend === 'false' && e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
e.preventDefault();
ReactDOM.findDOMNode(this.refs.editbox).blur();
this.handleEdit(e);
@@ -72,6 +78,16 @@ export default class EditPostModal extends React.Component {
$(ReactDOM.findDOMNode(this.refs.modal)).modal('show');
}
+ handleKeyDown(e) {
+ if (this.state.ctrlSend === 'true' && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
+ this.handleEdit(e);
+ }
+ }
+ onPreferenceChange() {
+ this.setState({
+ ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
+ });
+ }
componentDidMount() {
var self = this;
@@ -101,9 +117,11 @@ export default class EditPostModal extends React.Component {
});
PostStore.addEditPostListener(this.handleEditPostEvent);
+ PreferenceStore.addChangeListener(this.onPreferenceChange);
}
componentWillUnmount() {
PostStore.removeEditPostListener(this.handleEditPostEvent);
+ PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
render() {
var error = (<div className='form-group'><br /></div>);
@@ -138,6 +156,7 @@ export default class EditPostModal extends React.Component {
<Textbox
onUserInput={this.handleEditInput}
onKeyPress={this.handleEditKeyPress}
+ onKeyDown={this.handleKeyDown}
messageText={this.state.editText}
createMessage='Edit the post...'
id='edit_textbox'
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index 9cffa2400..f3c0fa0b4 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -42,6 +42,10 @@ export default class PopoverListMembers extends React.Component {
};
}
+ componentDidUpdate() {
+ $(ReactDOM.findDOMNode(this.refs.memebersPopover)).find('.popover-content').perfectScrollbar();
+ }
+
handleShowDirectChannel(teammate, e) {
e.preventDefault();
@@ -106,27 +110,27 @@ export default class PopoverListMembers extends React.Component {
let button = '';
if (currentUserId !== m.id && ch.type !== 'D') {
button = (
- <button
- type='button'
- className='btn btn-primary btn-message'
+ <a
+ href='#'
+ className='btn-message'
onClick={(e) => this.handleShowDirectChannel(m, e)}
>
{'Message'}
- </button>
+ </a>
);
}
if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
popoverHtml.push(
<div
- className='text--nowrap'
+ className='text-nowrap'
key={'popover-member-' + i}
>
<img
- className='profile-img pull-left'
- width='38'
- height='38'
+ className='profile-img rounded pull-left'
+ width='26px'
+ height='26px'
src={`/api/v1/users/${m.id}/image?time=${m.update_at}&${Utils.getSessionIndex()}`}
/>
<div className='pull-left'>
@@ -135,14 +139,9 @@ export default class PopoverListMembers extends React.Component {
>
{m.username}
</div>
- <div
- className='more-description'
- >
- {details}
- </div>
</div>
<div
- className='pull-right profile-action'
+ className='pull-right'
>
{button}
</div>
@@ -182,12 +181,11 @@ export default class PopoverListMembers extends React.Component {
placement='bottom'
>
<Popover
+ ref='memebersPopover'
title='Members'
id='member-list-popover'
>
- <div>
- {popoverHtml}
- </div>
+ {popoverHtml}
</Popover>
</Overlay>
</div>
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 0d69e56bf..b9741bac4 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -9,6 +9,7 @@ const LoadingScreen = require('./loading_screen.jsx');
const PostStore = require('../stores/post_store.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
const UserStore = require('../stores/user_store.jsx');
+const TeamStore = require('../stores/team_store.jsx');
const SocketStore = require('../stores/socket_store.jsx');
const PreferenceStore = require('../stores/preference_store.jsx');
@@ -386,17 +387,55 @@ export default class PostList extends React.Component {
}
}
createDefaultIntroMessage(channel) {
+ const team = TeamStore.getCurrent();
+ let inviteModalLink;
+ if (team.type === Constants.INVITE_TEAM) {
+ inviteModalLink = (
+ <a
+ className='intro-links'
+ href='#'
+ data-toggle='modal'
+ data-target='#invite_member'
+ >
+ <i className='fa fa-user-plus'></i>{'Invite others to this team'}
+ </a>
+ );
+ } else {
+ inviteModalLink = (
+ <a
+ className='intro-links'
+ href='#'
+ data-toggle='modal'
+ data-target='#get_link'
+ data-title='Team Invite'
+ data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + team.id}
+ >
+ <i className='fa fa-user-plus'></i>{'Invite others to this team'}
+ </a>
+ );
+ }
+
return (
<div className='channel-intro'>
<h4 className='channel-intro__title'>{'Beginning of ' + channel.display_name}</h4>
<p className='channel-intro__content'>
- {'Welcome to ' + channel.display_name + '!'}
+ <strong>{'Welcome to ' + channel.display_name + '!'}</strong>
<br/><br/>
{'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'}
- <br/><br/>
- {'To create a new channel or join an existing one, go to the Left Sidebar under “Channels” and click “More…”.'}
- <br/>
</p>
+ {inviteModalLink}
+ <a
+ className='intro-links'
+ href='#'
+ data-toggle='modal'
+ data-target='#edit_channel'
+ data-header={channel.header}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ <i className='fa fa-pencil'></i>{'Set a header'}
+ </a>
+ <br/>
</div>
);
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 69e3b007d..43d81d322 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -127,6 +127,8 @@ module.exports = {
MAX_DMS: 20,
DM_CHANNEL: 'D',
OPEN_CHANNEL: 'O',
+ INVITE_TEAM: 'I',
+ OPEN_TEAM: 'O',
MAX_POST_LEN: 4000,
EMOJI_SIZE: 16,
ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",