diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index 1f943cdb3..ed215e5f9 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -42,6 +42,9 @@ export interface IRoom { latest?: string; default?: true; featured?: true; + muted?: string[]; + teamId?: string; + ignored?: string; } export enum OmnichannelSourceType { diff --git a/app/definitions/IUser.ts b/app/definitions/IUser.ts index 461b52fb4..474a2105e 100644 --- a/app/definitions/IUser.ts +++ b/app/definitions/IUser.ts @@ -130,6 +130,7 @@ export interface IUser extends IRocketChatRecord, Omit { @@ -523,7 +524,7 @@ export const toggleRoomLeader = ({ isLeader }: { roomId: string; - t: RoomTypes; + t: SubscriptionType; userId: string; isLeader: boolean; }): any => { @@ -546,7 +547,7 @@ export const toggleRoomModerator = ({ isModerator }: { roomId: string; - t: RoomTypes; + t: SubscriptionType; userId: string; isModerator: boolean; }): any => { @@ -562,7 +563,7 @@ export const toggleRoomModerator = ({ return sdk.post(`${roomTypeToApiType(t)}.removeModerator`, { roomId, userId }); }; -export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: RoomTypes; userId: string }): any => +export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: SubscriptionType; userId: string }): any => // RC 0.48.0 // TODO: missing definitions from server // @ts-ignore @@ -619,7 +620,7 @@ export const getSingleMessage = (msgId: string) => // RC 0.47.0 sdk.get('chat.getMessage', { msgId }); -export const getRoomRoles = (roomId: string, type: RoomTypes): any => +export const getRoomRoles = (roomId: string, type: SubscriptionType): any => // RC 0.65.0 // TODO: missing definitions from server // @ts-ignore diff --git a/app/stacks/MasterDetailStack/types.ts b/app/stacks/MasterDetailStack/types.ts index 50240de7d..533b7f381 100644 --- a/app/stacks/MasterDetailStack/types.ts +++ b/app/stacks/MasterDetailStack/types.ts @@ -4,6 +4,7 @@ import { NavigatorScreenParams } from '@react-navigation/core'; import { IAttachment } from '../../definitions/IAttachment'; import { IMessage } from '../../definitions/IMessage'; import { ISubscription, SubscriptionType } from '../../definitions/ISubscription'; +import { TRoomModel } from '../../definitions'; export type MasterDetailChatsStackParamList = { RoomView: { @@ -44,9 +45,9 @@ export type ModalStackParamList = { title: string; infoText: string; nextAction: Function; - showAlert: boolean; - isSearch: boolean; - onSearch: Function; + showAlert: () => void | boolean; + isSearch?: boolean; + onSearch?: Function; isRadio?: boolean; }; RoomInfoEditView: { @@ -54,7 +55,7 @@ export type ModalStackParamList = { }; RoomMembersView: { rid: string; - room: ISubscription; + room: TRoomModel; }; DiscussionsView: { rid: string; diff --git a/app/utils/goRoom.ts b/app/utils/goRoom.ts index 0a49bb215..041bf9383 100644 --- a/app/utils/goRoom.ts +++ b/app/utils/goRoom.ts @@ -1,14 +1,24 @@ import { ChatsStackParamList } from '../stacks/types'; import Navigation from '../lib/Navigation'; import RocketChat from '../lib/rocketchat'; -import { ISubscription, SubscriptionType } from '../definitions/ISubscription'; +import { IVisitor, SubscriptionType } from '../definitions/ISubscription'; + +export interface IGoRoomItem { + search?: boolean; // comes from spotlight + username?: string; + t?: SubscriptionType; + rid: string; + name?: string; + prid?: string; + visitor?: IVisitor; +} const navigate = ({ item, isMasterDetail, ...props }: { - item: IItem; + item: IGoRoomItem; isMasterDetail: boolean; navigationMethod?: () => ChatsStackParamList; }) => { @@ -30,17 +40,12 @@ const navigate = ({ }); }; -interface IItem extends Partial { - search?: boolean; // comes from spotlight - username?: string; -} - export const goRoom = async ({ item, isMasterDetail = false, ...props }: { - item: IItem; + item: IGoRoomItem; isMasterDetail: boolean; navigationMethod?: any; jumpToMessageId?: string; @@ -50,9 +55,8 @@ export const goRoom = async ({ // if user is using the search we need first to join/create room try { const { username } = item; - // @ts-ignore - const result = await RocketChat.createDirectMessage(username); - if (result.success) { + const result = await RocketChat.createDirectMessage(username as string); + if (result.success && result?.room?._id) { return navigate({ item: { rid: result.room._id, diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.tsx similarity index 79% rename from app/views/RoomMembersView/index.js rename to app/views/RoomMembersView/index.tsx index 3f936fba6..bed9cccbb 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.tsx @@ -1,75 +1,82 @@ +import { Q } from '@nozbe/watermelondb'; import React from 'react'; -import PropTypes from 'prop-types'; import { FlatList } from 'react-native'; import { connect } from 'react-redux'; -import { Q } from '@nozbe/watermelondb'; +import { Observable, Subscription } from 'rxjs'; -import * as List from '../../containers/List'; -import UserItem from '../../presentation/UserItem'; -import scrollPersistTaps from '../../utils/scrollPersistTaps'; -import RocketChat from '../../lib/rocketchat'; -import database from '../../lib/database'; -import { LISTENER } from '../../containers/Toast'; -import EventEmitter from '../../utils/events'; -import log from '../../utils/log'; -import I18n from '../../i18n'; -import SearchBox from '../../containers/SearchBox'; -import protectedFunction from '../../lib/methods/helpers/protectedFunction'; -import * as HeaderButton from '../../containers/HeaderButton'; -import StatusBar from '../../containers/StatusBar'; -import ActivityIndicator from '../../containers/ActivityIndicator'; -import { withTheme } from '../../theme'; import { themes } from '../../constants/colors'; -import { getUserSelector } from '../../selectors/login'; import { withActionSheet } from '../../containers/ActionSheet'; -import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; +import ActivityIndicator from '../../containers/ActivityIndicator'; +import * as HeaderButton from '../../containers/HeaderButton'; +import * as List from '../../containers/List'; import SafeAreaView from '../../containers/SafeAreaView'; -import { goRoom } from '../../utils/goRoom'; +import SearchBox from '../../containers/SearchBox'; +import StatusBar from '../../containers/StatusBar'; +import { LISTENER } from '../../containers/Toast'; +import { IApplicationState, IBaseScreen, IUser, SubscriptionType, TRoomModel, TUserModel } from '../../definitions'; +import I18n from '../../i18n'; +import database from '../../lib/database'; import { CustomIcon } from '../../lib/Icons'; +import protectedFunction from '../../lib/methods/helpers/protectedFunction'; +import RocketChat from '../../lib/rocketchat'; +import UserItem from '../../presentation/UserItem'; +import { getUserSelector } from '../../selectors/login'; +import { ModalStackParamList } from '../../stacks/MasterDetailStack/types'; +import { withTheme } from '../../theme'; +import EventEmitter from '../../utils/events'; +import { goRoom, IGoRoomItem } from '../../utils/goRoom'; +import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; +import log from '../../utils/log'; +import scrollPersistTaps from '../../utils/scrollPersistTaps'; import styles from './styles'; const PAGE_SIZE = 25; -const PERMISSION_MUTE_USER = 'mute-user'; -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 = { - navigation: PropTypes.object, - route: PropTypes.object, - rid: PropTypes.string, - members: PropTypes.array, - baseUrl: PropTypes.string, - room: PropTypes.object, - user: PropTypes.shape({ - id: PropTypes.string, - token: PropTypes.string, - roles: PropTypes.array - }), - showActionSheet: PropTypes.func, - theme: PropTypes.string, - isMasterDetail: PropTypes.bool, - useRealName: PropTypes.bool, - muteUserPermission: PropTypes.array, - setLeaderPermission: PropTypes.array, - setOwnerPermission: PropTypes.array, - setModeratorPermission: PropTypes.array, - removeUserPermission: PropTypes.array, - editTeamMemberPermission: PropTypes.array, - viewAllTeamChannelsPermission: PropTypes.array, - viewAllTeamsPermission: PropTypes.array +interface IRoomMembersViewProps extends IBaseScreen { + rid: string; + members: string[]; + baseUrl: string; + room: TRoomModel; + user: { + id: string; + token: string; + roles: string[]; }; + showActionSheet: (params: any) => {}; // TODO: this work? + theme: string; + isMasterDetail: boolean; + useRealName: boolean; + muteUserPermission: string[]; + setLeaderPermission: string[]; + setOwnerPermission: string[]; + setModeratorPermission: string[]; + removeUserPermission: string[]; + editTeamMemberPermission: string[]; + viewAllTeamChannelsPermission: string[]; + viewAllTeamsPermission: string[]; +} - constructor(props) { +interface IRoomMembersViewState { + isLoading: boolean; + allUsers: boolean; + filtering: boolean; + rid: string; + members: TUserModel[]; + membersFiltered: TUserModel[]; + room: TRoomModel; + end: boolean; +} + +class RoomMembersView extends React.Component { + private mounted: boolean; + private permissions: any; // TODO: fix when get props from api + private roomObservable!: Observable; + private subscription!: Subscription; + private roomRoles: any; + + constructor(props: IRoomMembersViewProps) { super(props); this.mounted = false; - this.MUTE_INDEX = 0; this.permissions = {}; const rid = props.route.params?.rid; const room = props.route.params?.room; @@ -80,7 +87,7 @@ class RoomMembersView extends React.Component { rid, members: [], membersFiltered: [], - room: room || {}, + room: room || ({} as TRoomModel), end: false }; if (room && room.observe) { @@ -89,7 +96,7 @@ class RoomMembersView extends React.Component { if (this.mounted) { this.setState({ room: changes }); } else { - this.state.room = changes; + this.setState({ room: changes }); } }); } @@ -101,6 +108,7 @@ class RoomMembersView extends React.Component { this.mounted = true; this.fetchMembers(); + // @ts-ignore - TODO: room param is wrong if (RocketChat.isGroupChat(room)) { return; } @@ -129,16 +137,16 @@ class RoomMembersView extends React.Component { ); this.permissions = { - [PERMISSION_MUTE_USER]: result[0], - [PERMISSION_SET_LEADER]: result[1], - [PERMISSION_SET_OWNER]: result[2], - [PERMISSION_SET_MODERATOR]: result[3], - [PERMISSION_REMOVE_USER]: result[4], + 'mute-user': result[0], + 'set-leader': result[1], + 'set-owner': result[2], + 'set-moderator': result[3], + '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] + 'edit-team-member': result[5], + 'view-all-team-channels': result[6], + 'view-all-teams': result[7] } : {}) }; @@ -169,20 +177,20 @@ class RoomMembersView extends React.Component { }); }; - onSearchChangeText = protectedFunction(text => { + onSearchChangeText = protectedFunction((text: string) => { const { members } = this.state; - let membersFiltered = []; + let membersFiltered: TUserModel[] = []; text = text.trim(); if (members && members.length > 0 && text) { membersFiltered = members.filter( - m => m.username.toLowerCase().match(text.toLowerCase()) || m.name.toLowerCase().match(text.toLowerCase()) + m => m.username.toLowerCase().match(text.toLowerCase()) || m.name?.toLowerCase().match(text.toLowerCase()) ); } this.setState({ filtering: !!text, membersFiltered }); }); - navToDirectMessage = async item => { + navToDirectMessage = async (item: IUser) => { try { const db = database.active; const subsCollection = db.get('subscriptions'); @@ -193,7 +201,7 @@ class RoomMembersView extends React.Component { } else { const result = await RocketChat.createDirectMessage(item.username); if (result.success) { - this.goRoom({ rid: result.room?._id, name: item.username, t: 'd' }); + this.goRoom({ rid: result.room?._id as string, name: item.username, t: SubscriptionType.DIRECT }); } } } catch (e) { @@ -201,15 +209,15 @@ class RoomMembersView extends React.Component { } }; - handleRemoveFromTeam = async selectedUser => { + handleRemoveFromTeam = async (selectedUser: TUserModel) => { try { const { navigation } = this.props; const { room } = this.state; - const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: selectedUser._id }); + const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId as string, userId: selectedUser._id }); if (result.rooms?.length) { - const teamChannels = result.rooms.map(r => ({ + const teamChannels = result.rooms.map((r: any) => ({ rid: r._id, name: r.name, teamId: r.teamId, @@ -219,7 +227,7 @@ class RoomMembersView extends React.Component { title: 'Remove_Member', infoText: 'Remove_User_Team_Channels', data: teamChannels, - nextAction: selected => this.removeFromTeam(selectedUser, selected), + nextAction: (selected: any) => this.removeFromTeam(selectedUser, selected), showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove')) }); } else { @@ -238,7 +246,7 @@ class RoomMembersView extends React.Component { } }; - removeFromTeam = async (selectedUser, selected) => { + removeFromTeam = async (selectedUser: IUser, selected?: any) => { try { const { members, membersFiltered, room } = this.state; const { navigation } = this.props; @@ -258,9 +266,10 @@ class RoomMembersView extends React.Component { members: newMembers, membersFiltered: newMembersFiltered }); + // @ts-ignore - This is just to force a reload navigation.navigate('RoomMembersView'); } - } catch (e) { + } catch (e: any) { log(e); showErrorAlert( e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }), @@ -269,11 +278,11 @@ class RoomMembersView extends React.Component { } }; - onPressUser = selectedUser => { + onPressUser = (selectedUser: TUserModel) => { const { room } = this.state; const { showActionSheet, user, theme } = this.props; - const options = [ + const options: {}[] = [ { icon: 'message', title: I18n.t('Direct_message'), @@ -315,7 +324,7 @@ class RoomMembersView extends React.Component { // Owner if (this.permissions['set-owner']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); const isOwner = userRoleResult?.roles.includes('owner'); options.push({ icon: 'shield-check', @@ -335,7 +344,7 @@ class RoomMembersView extends React.Component { // Leader if (this.permissions['set-leader']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); const isLeader = userRoleResult?.roles.includes('leader'); options.push({ icon: 'shield-alt', @@ -355,7 +364,7 @@ class RoomMembersView extends React.Component { // Moderator if (this.permissions['set-moderator']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); const isModerator = userRoleResult?.roles.includes('moderator'); options.push({ icon: 'shield', @@ -460,9 +469,10 @@ class RoomMembersView extends React.Component { } }; - goRoom = item => { + goRoom = (item: IGoRoomItem) => { const { navigation, isMasterDetail } = this.props; if (isMasterDetail) { + // @ts-ignore navigation.navigate('DrawerNavigator'); } else { navigation.popToTop(); @@ -470,12 +480,12 @@ class RoomMembersView extends React.Component { goRoom({ item, isMasterDetail }); }; - getUserDisplayName = user => { + getUserDisplayName = (user: TUserModel) => { const { useRealName } = this.props; return (useRealName ? user.name : user.username) || user.username; }; - handleMute = async user => { + handleMute = async (user: TUserModel) => { const { rid } = this.state; try { await RocketChat.toggleMuteUserInRoom(rid, user?.username, !user?.muted); @@ -487,7 +497,7 @@ class RoomMembersView extends React.Component { } }; - handleOwner = async (selectedUser, isOwner) => { + handleOwner = async (selectedUser: TUserModel, isOwner: boolean) => { try { const { room } = this.state; await RocketChat.toggleRoomOwner({ @@ -511,7 +521,7 @@ class RoomMembersView extends React.Component { this.fetchRoomMembersRoles(); }; - handleLeader = async (selectedUser, isLeader) => { + handleLeader = async (selectedUser: TUserModel, isLeader: boolean) => { try { const { room } = this.state; await RocketChat.toggleRoomLeader({ @@ -535,7 +545,7 @@ class RoomMembersView extends React.Component { this.fetchRoomMembersRoles(); }; - handleModerator = async (selectedUser, isModerator) => { + handleModerator = async (selectedUser: TUserModel, isModerator: boolean) => { try { const { room } = this.state; await RocketChat.toggleRoomModerator({ @@ -559,7 +569,7 @@ class RoomMembersView extends React.Component { this.fetchRoomMembersRoles(); }; - handleIgnore = async (selectedUser, ignore) => { + handleIgnore = async (selectedUser: TUserModel, ignore: boolean) => { try { const { room } = this.state; await RocketChat.ignoreUser({ @@ -574,7 +584,7 @@ class RoomMembersView extends React.Component { } }; - handleRemoveUserFromRoom = async selectedUser => { + handleRemoveUserFromRoom = async (selectedUser: TUserModel) => { try { const { room, members, membersFiltered } = this.state; const userId = selectedUser._id; @@ -592,17 +602,15 @@ class RoomMembersView extends React.Component { renderSearchBar = () => this.onSearchChangeText(text)} testID='room-members-view-search' />; - renderItem = ({ item }) => { - const { baseUrl, user, theme } = this.props; + renderItem = ({ item }: { item: TUserModel }) => { + const { theme } = this.props; return ( this.onPressUser(item)} - baseUrl={baseUrl} testID={`room-members-view-item-${item.username}`} - user={user} theme={theme} /> ); @@ -638,19 +646,19 @@ class RoomMembersView extends React.Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: IApplicationState) => ({ baseUrl: state.server.server, user: getUserSelector(state), isMasterDetail: state.app.isMasterDetail, useRealName: state.settings.UI_Use_Real_Name, - muteUserPermission: state.permissions[PERMISSION_MUTE_USER], - setLeaderPermission: state.permissions[PERMISSION_SET_LEADER], - setOwnerPermission: state.permissions[PERMISSION_SET_OWNER], - setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR], - 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] + muteUserPermission: state.permissions['mute-user'], + setLeaderPermission: state.permissions['set-leader'], + setOwnerPermission: state.permissions['set-owner'], + setModeratorPermission: state.permissions['set-moderator'], + removeUserPermission: state.permissions['remove-user'], + editTeamMemberPermission: state.permissions['edit-team-member'], + viewAllTeamChannelsPermission: state.permissions['view-all-team-channels'], + viewAllTeamsPermission: state.permissions['view-all-teams'] }); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); diff --git a/app/views/RoomMembersView/styles.js b/app/views/RoomMembersView/styles.ts similarity index 100% rename from app/views/RoomMembersView/styles.js rename to app/views/RoomMembersView/styles.ts