From 6dcb9a51f182b76a0cac35ec74e244c529704a80 Mon Sep 17 00:00:00 2001 From: Gerzon Z Date: Wed, 26 May 2021 17:01:06 -0400 Subject: [PATCH] [NEW] Remove member from team (#3117) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Added addTeamMember and removeTeamMember * Minor tweak * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Update SelectListView.js * Update RoomMembersView * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Minor tweaks * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Remove addTeamMember, update removeTeamMember * Update Alert * Minor tweaks * Minor tweaks * Minor tweak * Update showActionSheet on RoomMembersView * Remove team main from query and move code around * Fetch roles * Update RoomMembersView and SelectListView * Updated leaveTeam and handleRemoveFromTeam * Fix validation * Remove unnecessary function * Added confirmationAlert for missing permissions case Co-authored-by: Diego Mello --- app/i18n/locales/en.json | 14 ++- app/lib/methods/getPermissions.js | 1 + app/lib/rocketchat.js | 12 ++ app/views/RoomActionsView/index.js | 22 ++-- app/views/RoomMembersView/index.js | 182 ++++++++++++++++++++++------- app/views/SelectListView.js | 15 +-- 6 files changed, 180 insertions(+), 66 deletions(-) diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 1648d3bf..71403821 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -683,12 +683,9 @@ "No_threads_following": "You are not following any threads", "No_threads_unread": "There are no unread threads", "Messagebox_Send_to_channel": "Send to channel", - "Set_as_leader": "Set as leader", - "Set_as_moderator": "Set as moderator", - "Set_as_owner": "Set as owner", - "Remove_as_leader": "Remove as leader", - "Remove_as_moderator": "Remove as moderator", - "Remove_as_owner": "Remove as owner", + "Leader": "Leader", + "Moderator": "Moderator", + "Owner": "Owner", "Remove_from_room": "Remove from room", "Ignore": "Ignore", "Unignore": "Unignore", @@ -732,9 +729,14 @@ "Leave_Team": "Leave Team", "Select_Team_Channels": "Select the Team's channels you would like to leave.", "Cannot_leave": "Cannot leave", + "Cannot_remove": "Cannot remove", "Last_owner_team_room": "You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.", "last-owner-can-not-be-removed": "Last owner cannot be removed", + "Removing_user_from_this_team": "You are removing {{user}} from this team", + "Remove_User_Team_Channels": "Select the channels you want the user to be removed from.", + "Remove_Member": "Remove Member", "leaving_team": "leaving team", + "removing_team": "removing from team", "member-does-not-exist": "Member does not exist", "Load_More": "Load More", "Load_Newer": "Load Newer", diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index 1f4bcb9c..cc1dd46e 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -22,6 +22,7 @@ const PERMISSIONS = [ 'delete-p', 'edit-message', 'edit-room', + 'edit-team-member', 'edit-team-channel', 'force-delete-message', 'mute-user', diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index c1a7655f..3660b319 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -778,10 +778,22 @@ const RocketChat = { // RC 3.13.0 return this.post('teams.leave', { teamName, rooms }); }, + removeTeamMember({ + teamId, teamName, userId, rooms + }) { + // RC 3.13.0 + return this.post('teams.removeMember', { + teamId, teamName, userId, rooms + }); + }, updateTeamRoom({ roomId, isDefault }) { // RC 3.13.0 return this.post('teams.updateRoom', { roomId, isDefault }); }, + teamListRoomsOfUser({ teamId, userId }) { + // RC 3.13.0 + return this.sdk.get('teams.listRoomsOfUser', { teamId, userId }); + }, joinRoom(roomId, joinCode, type) { // TODO: join code // RC 0.48.0 diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 3256dd0b..89d34ed0 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -5,7 +5,6 @@ import { } from 'react-native'; import { connect } from 'react-redux'; import isEmpty from 'lodash/isEmpty'; -import { Q } from '@nozbe/watermelondb'; import { compareServerVersion, methods } from '../../lib/utils'; import Touch from '../../utils/touch'; @@ -433,14 +432,15 @@ class RoomActionsView extends React.Component { const { navigation } = this.props; try { - const db = database.active; - const subCollection = db.get('subscriptions'); - const teamChannels = await subCollection.query( - Q.where('team_id', room.teamId), - Q.where('team_main', null) - ); + const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id }); - if (teamChannels.length) { + if (result.rooms?.length) { + const teamChannels = result.rooms.map(r => ({ + rid: r._id, + name: r.name, + teamId: r.teamId, + alert: r.isLastOwner + })); navigation.navigate('SelectListView', { title: 'Leave_Team', data: teamChannels, @@ -456,7 +456,11 @@ class RoomActionsView extends React.Component { }); } } catch (e) { - log(e); + showConfirmationAlert({ + message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }), + confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }), + onPress: () => this.handleLeaveTeam() + }); } } diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index 9a47b60f..59d298d9 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -23,9 +23,10 @@ import { withTheme } from '../../theme'; import { themes } from '../../constants/colors'; import { getUserSelector } from '../../selectors/login'; import { withActionSheet } from '../../containers/ActionSheet'; -import { showConfirmationAlert } from '../../utils/info'; +import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import SafeAreaView from '../../containers/SafeAreaView'; import { goRoom } from '../../utils/goRoom'; +import { CustomIcon } from '../../lib/Icons'; const PAGE_SIZE = 25; @@ -34,6 +35,9 @@ const PERMISSION_SET_LEADER = 'set-leader'; const PERMISSION_SET_OWNER = 'set-owner'; const PERMISSION_SET_MODERATOR = 'set-moderator'; const PERMISSION_REMOVE_USER = 'remove-user'; +const PERMISSION_EDIT_TEAM_MEMBER = 'edit-team-member'; +const PERMISION_VIEW_ALL_TEAMS = 'view-all-teams'; +const PERMISSION_VIEW_ALL_TEAM_CHANNELS = 'view-all-team-channels'; class RoomMembersView extends React.Component { static propTypes = { @@ -55,7 +59,10 @@ class RoomMembersView extends React.Component { setLeaderPermission: PropTypes.array, setOwnerPermission: PropTypes.array, setModeratorPermission: PropTypes.array, - removeUserPermission: PropTypes.array + removeUserPermission: PropTypes.array, + editTeamMemberPermission: PropTypes.array, + viewAllTeamChannelsPermission: PropTypes.array, + viewAllTeamsPermission: PropTypes.array } constructor(props) { @@ -94,10 +101,11 @@ class RoomMembersView extends React.Component { const { room } = this.state; const { - muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission + muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission } = this.props; + const result = await RocketChat.hasPermission([ - muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission + muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, ...(room.teamMain ? [editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission] : []) ], room.rid); this.permissions = { @@ -105,7 +113,12 @@ class RoomMembersView extends React.Component { [PERMISSION_SET_LEADER]: result[1], [PERMISSION_SET_OWNER]: result[2], [PERMISSION_SET_MODERATOR]: result[3], - [PERMISSION_REMOVE_USER]: result[4] + [PERMISSION_REMOVE_USER]: result[4], + ...(room.teamMain ? { + [PERMISSION_EDIT_TEAM_MEMBER]: result[5], + [PERMISSION_VIEW_ALL_TEAM_CHANNELS]: result[6], + [PERMISION_VIEW_ALL_TEAMS]: result[7] + } : {}) }; const hasSinglePermission = Object.values(this.permissions).some(p => !!p); @@ -163,9 +176,80 @@ class RoomMembersView extends React.Component { } } + handleRemoveFromTeam = async(selectedUser) => { + try { + const { navigation } = this.props; + const { room } = this.state; + + const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: selectedUser._id }); + + if (result.rooms?.length) { + const teamChannels = result.rooms.map(r => ({ + rid: r._id, + name: r.name, + teamId: r.teamId, + alert: r.isLastOwner + })); + navigation.navigate('SelectListView', { + title: 'Remove_Member', + infoText: 'Remove_User_Team_Channels', + data: teamChannels, + nextAction: selected => this.removeFromTeam(selectedUser, selected), + showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove')) + }); + } else { + showConfirmationAlert({ + message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }), + confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }), + onPress: () => this.removeFromTeam(selectedUser) + }); + } + } catch (e) { + showConfirmationAlert({ + message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }), + confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }), + onPress: () => this.removeFromTeam(selectedUser) + }); + } + } + + removeFromTeam = async(selectedUser, selected) => { + try { + const { members, membersFiltered, room } = this.state; + const { navigation } = this.props; + + const userId = selectedUser._id; + const result = await RocketChat.removeTeamMember({ + teamId: room.teamId, + teamName: room.name, + userId, + ...(selected && { rooms: selected }) + }); + if (result.success) { + const message = I18n.t('User_has_been_removed_from_s', { s: RocketChat.getRoomTitle(room) }); + EventEmitter.emit(LISTENER, { message }); + const newMembers = members.filter(member => member._id !== userId); + const newMembersFiltered = membersFiltered.filter(member => member._id !== userId); + this.setState({ + members: newMembers, + membersFiltered: newMembersFiltered + }); + navigation.navigate('RoomMembersView'); + } + } catch (e) { + log(e); + showErrorAlert( + e.data.error + ? I18n.t(e.data.error) + : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }), + I18n.t('Cannot_remove') + ); + } + } + onPressUser = (selectedUser) => { const { room } = this.state; - const { showActionSheet, user } = this.props; + const { showActionSheet, user, theme } = this.props; const options = [{ icon: 'message', @@ -173,39 +257,6 @@ class RoomMembersView extends React.Component { onPress: () => this.navToDirectMessage(selectedUser) }]; - // Owner - if (this.permissions['set-owner']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); - const isOwner = userRoleResult?.roles.includes('owner'); - options.push({ - icon: 'shield-check', - title: I18n.t(isOwner ? 'Remove_as_owner' : 'Set_as_owner'), - onPress: () => this.handleOwner(selectedUser, !isOwner) - }); - } - - // Leader - if (this.permissions['set-leader']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); - const isLeader = userRoleResult?.roles.includes('leader'); - options.push({ - icon: 'shield-alt', - title: I18n.t(isLeader ? 'Remove_as_leader' : 'Set_as_leader'), - onPress: () => this.handleLeader(selectedUser, !isLeader) - }); - } - - // Moderator - if (this.permissions['set-moderator']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); - const isModerator = userRoleResult?.roles.includes('moderator'); - options.push({ - icon: 'shield', - title: I18n.t(isModerator ? 'Remove_as_moderator' : 'Set_as_moderator'), - onPress: () => this.handleModerator(selectedUser, !isModerator) - }); - } - // Ignore if (selectedUser._id !== user.id) { const { ignored } = room; @@ -236,8 +287,54 @@ class RoomMembersView extends React.Component { }); } + // Owner + if (this.permissions['set-owner']) { + const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const isOwner = userRoleResult?.roles.includes('owner'); + options.push({ + icon: 'shield-check', + title: I18n.t('Owner'), + onPress: () => this.handleOwner(selectedUser, !isOwner), + right: () => + }); + } + + // Leader + if (this.permissions['set-leader']) { + const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const isLeader = userRoleResult?.roles.includes('leader'); + options.push({ + icon: 'shield-alt', + title: I18n.t('Leader'), + onPress: () => this.handleLeader(selectedUser, !isLeader), + right: () => + }); + } + + // Moderator + if (this.permissions['set-moderator']) { + const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const isModerator = userRoleResult?.roles.includes('moderator'); + options.push({ + icon: 'shield', + title: I18n.t('Moderator'), + onPress: () => this.handleModerator(selectedUser, !isModerator), + right: () => + }); + } + + // Remove from team + if (this.permissions['edit-team-member']) { + options.push({ + icon: 'logout', + danger: true, + title: I18n.t('Remove_from_Team'), + onPress: () => this.handleRemoveFromTeam(selectedUser) + }); + } + // Remove from room - if (this.permissions['remove-user']) { + if (this.permissions['remove-user'] && !room.teamMain) { options.push({ icon: 'logout', title: I18n.t('Remove_from_room'), @@ -477,7 +574,10 @@ const mapStateToProps = state => ({ setLeaderPermission: state.permissions[PERMISSION_SET_LEADER], setOwnerPermission: state.permissions[PERMISSION_SET_OWNER], setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR], - removeUserPermission: state.permissions[PERMISSION_REMOVE_USER] + removeUserPermission: state.permissions[PERMISSION_REMOVE_USER], + editTeamMemberPermission: state.permissions[PERMISSION_EDIT_TEAM_MEMBER], + viewAllTeamChannelsPermission: state.permissions[PERMISSION_VIEW_ALL_TEAM_CHANNELS], + viewAllTeamsPermission: state.permissions[PERMISION_VIEW_ALL_TEAMS] }); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); diff --git a/app/views/SelectListView.js b/app/views/SelectListView.js index 9c886da8..bcbb0923 100644 --- a/app/views/SelectListView.js +++ b/app/views/SelectListView.js @@ -14,7 +14,6 @@ import { themes } from '../constants/colors'; import { withTheme } from '../theme'; import SafeAreaView from '../containers/SafeAreaView'; import { animateNextTransition } from '../utils/layoutAnimation'; -import Loading from '../containers/Loading'; const styles = StyleSheet.create({ buttonText: { @@ -41,8 +40,7 @@ class SelectListView extends React.Component { this.showAlert = props.route?.params?.showAlert; this.state = { data, - selected: [], - loading: false + selected: [] }; this.setHeader(); } @@ -96,10 +94,8 @@ class SelectListView extends React.Component { renderItem = ({ item }) => { const { theme } = this.props; - const alert = item.roles.length; - const icon = item.t === 'p' ? 'channel-private' : 'channel-public'; - const checked = this.isChecked(item.rid, item.roles) ? 'check' : null; + const checked = this.isChecked(item.rid) ? 'check' : null; return ( <> @@ -108,8 +104,8 @@ class SelectListView extends React.Component { title={item.name} translateTitle={false} testID={`select-list-view-item-${ item.name }`} - onPress={() => (alert ? this.showAlert() : this.toggleItem(item.rid))} - alert={alert} + onPress={() => (item.alert ? this.showAlert() : this.toggleItem(item.rid))} + alert={item.alert} left={() => } right={() => (checked ? : null)} /> @@ -118,7 +114,7 @@ class SelectListView extends React.Component { } render() { - const { loading, data } = this.state; + const { data } = this.state; const { theme } = this.props; return ( @@ -133,7 +129,6 @@ class SelectListView extends React.Component { contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }} keyboardShouldPersistTaps='always' /> - ); }