From c7569d7951e6653f60e4a03bc62289fa4422fbcc Mon Sep 17 00:00:00 2001 From: Corey Hulen Date: Wed, 6 Jul 2016 13:40:59 -0800 Subject: [PATCH] PLT-2863 adding remove user from team (#3429) * PLT-2863 adding remove user from team * PLT-2863 adding the client side UI * Fixing trailing space * Fixing reported issues * Adding documentatino * Switching to final javascript driver --- actions/global_actions.jsx | 7 ++ actions/websocket_actions.jsx | 17 ++++ components/admin_console/team_users.jsx | 4 + components/admin_console/user_item.jsx | 35 +++++++- components/filtered_user_list.jsx | 15 +++- components/leave_team_modal.jsx | 115 ++++++++++++++++++++++++ components/navbar_dropdown.jsx | 14 +++ components/needs_team.jsx | 2 + components/team_members_dropdown.jsx | 36 +++++++- i18n/en.json | 2 + package.json | 2 +- stores/modal_store.jsx | 1 + stores/team_store.jsx | 10 +++ utils/constants.jsx | 2 + 14 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 components/leave_team_modal.jsx diff --git a/actions/global_actions.jsx b/actions/global_actions.jsx index 4baed20c3cf7..aa51f6f62c5e 100644 --- a/actions/global_actions.jsx +++ b/actions/global_actions.jsx @@ -288,6 +288,13 @@ export function showInviteMemberModal() { }); } +export function showLeaveTeamModal() { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_LEAVE_TEAM_MODAL, + value: true + }); +} + export function showRegisterAppModal() { AppDispatcher.handleViewAction({ type: ActionTypes.TOGGLE_REGISTER_APP_MODAL, diff --git a/actions/websocket_actions.jsx b/actions/websocket_actions.jsx index 17f84638db8e..9d9cf62b727b 100644 --- a/actions/websocket_actions.jsx +++ b/actions/websocket_actions.jsx @@ -135,6 +135,10 @@ function handleMessage(msg) { handleNewUserEvent(); break; + case SocketEvents.LEAVE_TEAM: + handleLeaveTeamEvent(msg); + break; + case SocketEvents.USER_ADDED: handleUserAddedEvent(msg); break; @@ -219,6 +223,19 @@ function handleNewUserEvent() { AsyncClient.getChannelExtraInfo(); } +function handleLeaveTeamEvent(msg) { + if (UserStore.getCurrentId() === msg.user_id) { + TeamStore.removeTeamMember(msg.team_id); + + // if the are on the team begin removed redirect them to the root + if (TeamStore.getCurrentId() === msg.team_id) { + browserHistory.push('/'); + } + } else if (TeamStore.getCurrentId() === msg.team_id) { + GlobalActions.emitProfilesForDmList(); + } +} + function handleDirectAddedEvent(msg) { AsyncClient.getChannel(msg.channel_id); AsyncClient.getDirectProfiles(); diff --git a/components/admin_console/team_users.jsx b/components/admin_console/team_users.jsx index b6bba318200d..3ec3756274f7 100644 --- a/components/admin_console/team_users.jsx +++ b/components/admin_console/team_users.jsx @@ -186,6 +186,10 @@ export default class UserList extends React.Component { var memberList = this.state.users.map((user) => { var teamMember = this.getTeamMemberForUser(user.id); + if (teamMember.delete_at > 0) { + return null; + } + return ( { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + handleMakeActive(e) { e.preventDefault(); Client.updateActive(this.props.user.id, true, @@ -222,6 +236,7 @@ export default class UserItem extends React.Component { ); } + const me = UserStore.getCurrentUser(); const email = user.email; let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin'; let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin'; @@ -299,6 +314,24 @@ export default class UserItem extends React.Component { ); } + let removeFromTeam = null; + if (this.props.user.id !== me.id) { + removeFromTeam = ( +
  • + + + +
  • + ); + } + let makeActive = null; if (showMakeActive) { makeActive = ( @@ -428,7 +461,6 @@ export default class UserItem extends React.Component { passwordReset = null; } - const me = UserStore.getCurrentUser(); let makeDemoteModal = null; if (this.props.user.id === me.id) { const title = ( @@ -511,6 +543,7 @@ export default class UserItem extends React.Component { className='dropdown-menu member-menu' role='menu' > + {removeFromTeam} {makeAdmin} {makeMember} {makeActive} diff --git a/components/filtered_user_list.jsx b/components/filtered_user_list.jsx index b6d8f11f978a..67d038fd99e6 100644 --- a/components/filtered_user_list.jsx +++ b/components/filtered_user_list.jsx @@ -39,17 +39,24 @@ class FilteredUserList extends React.Component { this.state = { filter: '', users: this.filterUsers(props.teamMembers, props.users), - selected: 'team' + selected: 'team', + teamMembers: props.teamMembers }; } - componentWillUpdate(nextProps) { + componentWillReceiveProps(nextProps) { // assume the user list is immutable if (this.props.users !== nextProps.users) { this.setState({ users: this.filterUsers(nextProps.teamMembers, nextProps.users) }); } + + if (this.props.teamMembers !== nextProps.teamMembers) { + this.setState({ + users: this.filterUsers(nextProps.teamMembers, nextProps.users) + }); + } } componentDidMount() { @@ -70,6 +77,10 @@ class FilteredUserList extends React.Component { var filteredUsers = users.filter((user) => { for (const index in teamMembers) { if (teamMembers.hasOwnProperty(index) && teamMembers[index].user_id === user.id) { + if (teamMembers[index].delete_at > 0) { + return false; + } + return true; } } diff --git a/components/leave_team_modal.jsx b/components/leave_team_modal.jsx new file mode 100644 index 000000000000..7263f23d406b --- /dev/null +++ b/components/leave_team_modal.jsx @@ -0,0 +1,115 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import Constants from 'utils/constants.jsx'; +const ActionTypes = Constants.ActionTypes; +import * as GlobalActions from 'actions/global_actions.jsx'; +import ModalStore from 'stores/modal_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + +import {intlShape, injectIntl, FormattedMessage} from 'react-intl'; + +import {Modal} from 'react-bootstrap'; + +import React from 'react'; + +class LeaveTeamModal extends React.Component { + constructor(props) { + super(props); + + this.handleToggle = this.handleToggle.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleHide = this.handleHide.bind(this); + + this.state = { + show: false + }; + } + + componentDidMount() { + ModalStore.addModalListener(ActionTypes.TOGGLE_LEAVE_TEAM_MODAL, this.handleToggle); + } + + componentWillUnmount() { + ModalStore.removeModalListener(ActionTypes.TOGGLE_LEAVE_TEAM_MODAL, this.handleToggle); + } + + handleToggle(value) { + this.setState({ + show: value + }); + } + + handleSubmit() { + GlobalActions.emitLeaveTeam(); + + this.setState({ + show: false + }); + } + + handleHide() { + this.setState({ + show: false + }); + } + + render() { + var currentUser = UserStore.getCurrentUser(); + + if (currentUser != null) { + return ( + + + + + + + + + + + + + + + ); + } + + return null; + } +} + +LeaveTeamModal.propTypes = { + intl: intlShape.isRequired +}; + +export default injectIntl(LeaveTeamModal); diff --git a/components/navbar_dropdown.jsx b/components/navbar_dropdown.jsx index ab228dcb34fa..c660bc16481c 100644 --- a/components/navbar_dropdown.jsx +++ b/components/navbar_dropdown.jsx @@ -236,6 +236,20 @@ export default class NavbarDropdown extends React.Component { ); } + teams.push( +
  • + + + +
  • + ); + if (this.state.teamMembers && this.state.teamMembers.length > 1) { teams.push(
  • + diff --git a/components/team_members_dropdown.jsx b/components/team_members_dropdown.jsx index 2b40da9cf1fb..43449635d9d0 100644 --- a/components/team_members_dropdown.jsx +++ b/components/team_members_dropdown.jsx @@ -19,6 +19,7 @@ export default class TeamMembersDropdown extends React.Component { super(props); this.handleMakeMember = this.handleMakeMember.bind(this); + this.handleRemoveFromTeam = this.handleRemoveFromTeam.bind(this); this.handleMakeActive = this.handleMakeActive.bind(this); this.handleMakeNotActive = this.handleMakeNotActive.bind(this); this.handleMakeAdmin = this.handleMakeAdmin.bind(this); @@ -52,6 +53,19 @@ export default class TeamMembersDropdown extends React.Component { ); } } + handleRemoveFromTeam() { + Client.removeUserFromTeam( + '', + this.props.user.id, + () => { + AsyncClient.getTeamMembers(TeamStore.getCurrentId()); + AsyncClient.getProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } handleMakeActive() { Client.updateActive(this.props.user.id, true, () => { @@ -171,6 +185,7 @@ export default class TeamMembersDropdown extends React.Component { ); } + const me = UserStore.getCurrentUser(); let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin'; let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin'; let showMakeActive = false; @@ -225,6 +240,24 @@ export default class TeamMembersDropdown extends React.Component { ); } + let removeFromTeam = null; + if (this.props.user.id !== me.id) { + removeFromTeam = ( +
  • + + + +
  • + ); + } + let makeActive = null; if (showMakeActive) { // makeActive = ( @@ -260,7 +293,7 @@ export default class TeamMembersDropdown extends React.Component { // // ); } - const me = UserStore.getCurrentUser(); + let makeDemoteModal = null; if (this.props.user.id === me.id) { const title = ( @@ -321,6 +354,7 @@ export default class TeamMembersDropdown extends React.Component { className='dropdown-menu member-menu' role='menu' > + {removeFromTeam} {makeAdmin} {makeMember} {makeActive} diff --git a/i18n/en.json b/i18n/en.json index 945c9c4a988a..322c9ccad0e5 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1202,6 +1202,7 @@ "navbar_dropdown.accountSettings": "Account Settings", "navbar_dropdown.console": "System Console", "navbar_dropdown.create": "Create a New Team", + "navbar_dropdown.leave": "Leave Team", "navbar_dropdown.emoji": "Custom Emoji", "navbar_dropdown.help": "Help", "navbar_dropdown.integrations": "Integrations", @@ -1414,6 +1415,7 @@ "team_members_dropdown.makeAdmin": "Make Team Admin", "team_members_dropdown.makeInactive": "Make Inactive", "team_members_dropdown.makeMember": "Make Member", + "team_members_dropdown.leave_team": "Remove From Team", "team_members_dropdown.member": "Member", "team_members_dropdown.systemAdmin": "System Admin", "team_members_dropdown.teamAdmin": "Team Admin", diff --git a/package.json b/package.json index b3066542e43f..468325e7dbcc 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "keymirror": "0.1.1", "marked": "mattermost/marked#12d2be4cdf54d4ec95fead934e18840b6a2c1a7b", "match-at": "0.1.0", - "mattermost": "mattermost/mattermost-javascript#18527e6c4a9aea69aa7845a62d9618b357faa4e7", + "mattermost": "mattermost/mattermost-javascript#c72a75ca4ac135e2d476fc048ef7adc450e6739f", "object-assign": "4.1.0", "perfect-scrollbar": "0.6.11", "react": "15.0.2", diff --git a/stores/modal_store.jsx b/stores/modal_store.jsx index 0595daaf9465..0209f3993e21 100644 --- a/stores/modal_store.jsx +++ b/stores/modal_store.jsx @@ -33,6 +33,7 @@ class ModalStoreClass extends EventEmitter { switch (type) { case ActionTypes.TOGGLE_IMPORT_THEME_MODAL: case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL: + case ActionTypes.TOGGLE_LEAVE_TEAM_MODAL: case ActionTypes.TOGGLE_DELETE_POST_MODAL: case ActionTypes.TOGGLE_GET_POST_LINK_MODAL: case ActionTypes.TOGGLE_GET_TEAM_INVITE_LINK_MODAL: diff --git a/stores/team_store.jsx b/stores/team_store.jsx index c35c467ae6cc..f4383589afdc 100644 --- a/stores/team_store.jsx +++ b/stores/team_store.jsx @@ -139,6 +139,16 @@ class TeamStoreClass extends EventEmitter { this.team_members.push(member); } + removeTeamMember(teamId) { + for (var index in this.team_members) { + if (this.team_members.hasOwnProperty(index)) { + if (this.team_members[index].team_id === teamId) { + Reflect.deleteProperty(this.team_members, index); + } + } + } + } + getTeamMembers() { return this.team_members; } diff --git a/utils/constants.jsx b/utils/constants.jsx index efae8a05078d..f0cea9e52b72 100644 --- a/utils/constants.jsx +++ b/utils/constants.jsx @@ -113,6 +113,7 @@ export default { TOGGLE_IMPORT_THEME_MODAL: null, TOGGLE_INVITE_MEMBER_MODAL: null, + TOGGLE_LEAVE_TEAM_MODAL: null, TOGGLE_DELETE_POST_MODAL: null, TOGGLE_GET_POST_LINK_MODAL: null, TOGGLE_GET_TEAM_INVITE_LINK_MODAL: null, @@ -160,6 +161,7 @@ export default { CHANNEL_VIEWED: 'channel_viewed', DIRECT_ADDED: 'direct_added', NEW_USER: 'new_user', + LEAVE_TEAM: 'leave_team', USER_ADDED: 'user_added', USER_REMOVED: 'user_removed', TYPING: 'typing',