From 1ba1253f51bf6561b5255445d296e58d692d39ea Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Wed, 23 Jun 2021 13:14:48 -0300 Subject: [PATCH 1/8] wip --- .../contextualBar/Info/ConvertToTeamModal.js | 23 --------------- .../Info/RoomInfo/RoomInfoWithData.js | 14 ++++++++-- client/views/teams/ConvertToChannelModal.tsx | 28 +++++++++++++++++++ .../teams/contextualBar/info/TeamsInfo.js | 10 ++++++- .../contextualBar/info/TeamsInfoWithLogic.js | 18 +++++++++++- 5 files changed, 66 insertions(+), 27 deletions(-) delete mode 100644 client/views/room/contextualBar/Info/ConvertToTeamModal.js create mode 100644 client/views/teams/ConvertToChannelModal.tsx diff --git a/client/views/room/contextualBar/Info/ConvertToTeamModal.js b/client/views/room/contextualBar/Info/ConvertToTeamModal.js deleted file mode 100644 index 5bb5d298abc41..0000000000000 --- a/client/views/room/contextualBar/Info/ConvertToTeamModal.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import GenericModal from '../../../../components/GenericModal'; -import { useTranslation } from '../../../../contexts/TranslationContext'; - -const ChannelToTeamModal = ({ onClose, onConfirm }) => { - const t = useTranslation(); - - return ( - - {` ${t('Converting_channel_to_a_team')}`} - - ); -}; - -export default ChannelToTeamModal; diff --git a/client/views/room/contextualBar/Info/RoomInfo/RoomInfoWithData.js b/client/views/room/contextualBar/Info/RoomInfo/RoomInfoWithData.js index e48f125fa4897..eae8779cb0324 100644 --- a/client/views/room/contextualBar/Info/RoomInfo/RoomInfoWithData.js +++ b/client/views/room/contextualBar/Info/RoomInfo/RoomInfoWithData.js @@ -17,7 +17,6 @@ import { useEndpointActionExperimental } from '../../../../../hooks/useEndpointA import WarningModal from '../../../../admin/apps/WarningModal'; import { useTabBarClose } from '../../../providers/ToolboxProvider'; import ChannelToTeamModal from '../ChannelToTeamModal/ChannelToTeamModal'; -import ConvertToTeamModal from '../ConvertToTeamModal'; import RoomInfo from './RoomInfo'; const retentionPolicyMaxAge = { @@ -180,7 +179,18 @@ const RoomInfoWithData = ({ rid, openEditing, onClickBack, onEnterRoom, resetSta } }; - setModal(); + setModal( + + {t('Converting_channel_to_a_team')} + , + ); }); const onClickEnterRoom = useMutableCallback(() => onEnterRoom(room)); diff --git a/client/views/teams/ConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal.tsx new file mode 100644 index 0000000000000..5a941d43c0585 --- /dev/null +++ b/client/views/teams/ConvertToChannelModal.tsx @@ -0,0 +1,28 @@ +import React, { FC } from 'react'; + +import GenericModal from '../../components/GenericModal'; +import { useTranslation } from '../../contexts/TranslationContext'; + +type ConvertToChannelModalProps = { + onClose: () => void; + onConfirm: () => void; +}; + +const ConvertToChannelModal: FC = ({ onClose, onConfirm }) => { + const t = useTranslation(); + + return ( + + {t('Converting_channel_to_a_team')} + + ); +}; + +export default ConvertToChannelModal; diff --git a/client/views/teams/contextualBar/info/TeamsInfo.js b/client/views/teams/contextualBar/info/TeamsInfo.js index f4544f6069647..699721ac244c1 100644 --- a/client/views/teams/contextualBar/info/TeamsInfo.js +++ b/client/views/teams/contextualBar/info/TeamsInfo.js @@ -26,6 +26,7 @@ const TeamsInfo = ({ onClickEdit, onClickDelete, onClickViewChannels, + onClickConvertToChannel, }) => { const t = useTranslation(); @@ -66,8 +67,15 @@ const TeamsInfo = ({ icon: 'sign-out', }, }), + ...(onClickConvertToChannel && { + convertToChannel: { + label: t('Convert_to_channel'), + action: onClickConvertToChannel, + icon: 'key', + }, + }), }), - [t, onClickHide, onClickLeave, onClickEdit, onClickDelete], + [t, onClickHide, onClickLeave, onClickEdit, onClickDelete, onClickConvertToChannel], ); const { actions: actionsDefinition, menu: menuOptions } = useActionSpread(memoizedActions); diff --git a/client/views/teams/contextualBar/info/TeamsInfoWithLogic.js b/client/views/teams/contextualBar/info/TeamsInfoWithLogic.js index 312c680193de2..6b5c1a918ddf9 100644 --- a/client/views/teams/contextualBar/info/TeamsInfoWithLogic.js +++ b/client/views/teams/contextualBar/info/TeamsInfoWithLogic.js @@ -2,7 +2,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useCallback } from 'react'; import { roomTypes, UiTextContext } from '../../../../../app/utils/client'; -import { GenericModalDoNotAskAgain } from '../../../../components/GenericModal'; +import GenericModal, { GenericModalDoNotAskAgain } from '../../../../components/GenericModal'; import MarkdownText from '../../../../components/MarkdownText'; import { usePermission } from '../../../../contexts/AuthorizationContext'; import { useSetModal } from '../../../../contexts/ModalContext'; @@ -135,6 +135,21 @@ function TeamsInfoWithLogic({ room, openEditing }) { const onClickViewChannels = useCallback(() => openTabbar('team-channels'), [openTabbar]); + const onClickConvertToChannel = useMutableCallback(async () => { + // const data = type === 'c' ? { channelId: rid } : { roomId: rid }; + const onConfirm = async () => { + // try { + // await convertRoomToTeam(data); + // } catch (error) { + // dispatchToastMessage({ type: 'error', message: error }); + // } finally { + // closeModal(); + // } + }; + + setModal(); + }); + return ( From ad43a39a7cf4db88b48b70d50dea4ad3d84e9e28 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 25 Jun 2021 13:41:23 -0500 Subject: [PATCH 2/8] Add extra param to listRoomsOfUser to filter rooms user can delete --- app/api/server/v1/teams.ts | 4 ++-- server/sdk/types/ITeamService.ts | 2 +- server/services/team/service.ts | 14 +++++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/api/server/v1/teams.ts b/app/api/server/v1/teams.ts index 4f6758398af09..80242c6590a31 100644 --- a/app/api/server/v1/teams.ts +++ b/app/api/server/v1/teams.ts @@ -206,7 +206,7 @@ API.v1.addRoute('teams.listRooms', { authRequired: true }, { API.v1.addRoute('teams.listRoomsOfUser', { authRequired: true }, { get() { const { offset, count } = this.getPaginationItems(); - const { teamId, teamName, userId } = this.queryParams; + const { teamId, teamName, userId, canUserDelete = false } = this.queryParams; const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); if (!team) { @@ -219,7 +219,7 @@ API.v1.addRoute('teams.listRoomsOfUser', { authRequired: true }, { return API.v1.unauthorized(); } - const { records, total } = Promise.await(Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, { offset, count })); + const { records, total } = Promise.await(Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, canUserDelete, { offset, count })); return API.v1.success({ rooms: records, diff --git a/server/sdk/types/ITeamService.ts b/server/sdk/types/ITeamService.ts index d6b3ad49fb281..2db6f3e1c5a90 100644 --- a/server/sdk/types/ITeamService.ts +++ b/server/sdk/types/ITeamService.ts @@ -58,7 +58,7 @@ export interface ITeamService { addRooms(uid: string, rooms: Array, teamId: string): Promise>; removeRoom(uid: string, rid: string, teamId: string, canRemoveAnyRoom: boolean): Promise; listRooms(uid: string, teamId: string, filter: IListRoomsFilter, pagination: IPaginationOptions): Promise>; - listRoomsOfUser(uid: string, teamId: string, userId: string, allowPrivateTeam: boolean, pagination: IPaginationOptions): Promise>; + listRoomsOfUser(uid: string, teamId: string, userId: string, allowPrivateTeam: boolean, showCanDeleteOnly: boolean, pagination: IPaginationOptions): Promise>; updateRoom(uid: string, rid: string, isDefault: boolean, canUpdateAnyRoom: boolean): Promise; list(uid: string, paginationOptions?: IPaginationOptions, queryOptions?: IQueryOptions): Promise>; listAll(options?: IPaginationOptions): Promise>; diff --git a/server/services/team/service.ts b/server/services/team/service.ts index 4bc7c3cd60a54..0f1de5c7ae934 100644 --- a/server/services/team/service.ts +++ b/server/services/team/service.ts @@ -23,7 +23,7 @@ import { TEAM_TYPE, } from '../../../definition/ITeam'; import { IUser } from '../../../definition/IUser'; -import { Room } from '../../sdk'; +import { Room, Authorization } from '../../sdk'; import { IListRoomsFilter, ITeamCreateParams, @@ -458,7 +458,7 @@ export class TeamService extends ServiceClass implements ITeamService { }; } - async listRoomsOfUser(uid: string, teamId: string, userId: string, allowPrivateTeam: boolean, { offset: skip, count: limit }: IPaginationOptions = { offset: 0, count: 50 }): Promise> { + async listRoomsOfUser(uid: string, teamId: string, userId: string, allowPrivateTeam: boolean, showCanDeleteOnly: boolean, { offset: skip, count: limit }: IPaginationOptions = { offset: 0, count: 50 }): Promise> { if (!teamId) { throw new Error('missing-teamId'); } @@ -472,7 +472,15 @@ export class TeamService extends ServiceClass implements ITeamService { } const teamRooms = await this.RoomsModel.findByTeamId(teamId, { projection: { _id: 1, t: 1 } }).toArray(); - const teamRoomIds = teamRooms.filter((room) => room.t === 'p' || room.t === 'c').map((room) => room._id); + let teamRoomIds: any[]; + if (showCanDeleteOnly) { + const canDeleteCRoom = await Authorization.hasPermission(userId, 'delete-c'); + const canDeletePRoom = await Authorization.hasPermission(userId, 'delete-p'); + + teamRoomIds = teamRooms.filter((room) => (room.t === 'c' && canDeleteCRoom) || (room.t === 'p' && canDeletePRoom)); + } else { + teamRoomIds = teamRooms.filter((room) => room.t === 'p' || room.t === 'c').map((room) => room._id); + } const subscriptionsCursor = this.SubscriptionsModel.findByUserIdAndRoomIds(userId, teamRoomIds); const subscriptionRoomIds = (await subscriptionsCursor.toArray()).map((subscription) => subscription.rid); From 43eaee10dc6035ecfd42aa87c3b03afd2fd12cb2 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 25 Jun 2021 16:01:09 -0500 Subject: [PATCH 3/8] update the way permission was being checked --- server/services/team/service.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/services/team/service.ts b/server/services/team/service.ts index 0f1de5c7ae934..26af561db211c 100644 --- a/server/services/team/service.ts +++ b/server/services/team/service.ts @@ -474,10 +474,13 @@ export class TeamService extends ServiceClass implements ITeamService { const teamRooms = await this.RoomsModel.findByTeamId(teamId, { projection: { _id: 1, t: 1 } }).toArray(); let teamRoomIds: any[]; if (showCanDeleteOnly) { - const canDeleteCRoom = await Authorization.hasPermission(userId, 'delete-c'); - const canDeletePRoom = await Authorization.hasPermission(userId, 'delete-p'); + for await (const room of teamRooms) { + const roomType = room.t; + const canDeleteRoom = await Authorization.hasPermission(userId, roomType === 'c' ? 'delete-c' : 'delete-p', room._id); + room.userCanDelete = canDeleteRoom; + } - teamRoomIds = teamRooms.filter((room) => (room.t === 'c' && canDeleteCRoom) || (room.t === 'p' && canDeletePRoom)); + teamRoomIds = teamRooms.filter((room) => (room.t === 'c' || room.t === 'p') && room.userCanDelete); } else { teamRoomIds = teamRooms.filter((room) => room.t === 'p' || room.t === 'c').map((room) => room._id); } From 663dfb292722b6639242d8faee1b749356fd00f7 Mon Sep 17 00:00:00 2001 From: dougfabris Date: Sat, 26 Jun 2021 17:48:59 -0300 Subject: [PATCH 4/8] new: convert team to channel --- client/contexts/ServerContext/endpoints.ts | 2 + .../endpoints/v1/teams/listRoomsOfUser.ts | 17 ++++ .../ChannelDesertionTable.js | 8 +- client/views/teams/ConvertToChannelModal.tsx | 28 ------- .../BaseConvertToChannelModal.tsx | 82 +++++++++++++++++++ .../ConvertToChannelModal.tsx | 58 +++++++++++++ .../ModalSteps/FirstStep.tsx | 69 ++++++++++++++++ .../ModalSteps/SecondStep.tsx | 45 ++++++++++ .../teams/ConvertToChannelModal/index.ts | 1 + .../teams/contextualBar/info/Leave/StepOne.js | 2 +- .../teams/contextualBar/info/TeamsInfo.js | 2 +- ...sInfoWithLogic.js => TeamsInfoWithData.js} | 43 +++++++--- .../views/teams/contextualBar/info/index.js | 4 +- .../RemoveUsersModal/RemoveUsersFirstStep.js | 2 +- .../RemoveUsersModal/RemoveUsersModal.js | 1 - packages/rocketchat-i18n/i18n/en.i18n.json | 6 ++ server/services/team/service.ts | 2 +- 17 files changed, 320 insertions(+), 52 deletions(-) create mode 100644 client/contexts/ServerContext/endpoints/v1/teams/listRoomsOfUser.ts rename client/views/teams/{contextualBar => }/ChannelDesertionTable.js (86%) delete mode 100644 client/views/teams/ConvertToChannelModal.tsx create mode 100644 client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx create mode 100644 client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx create mode 100644 client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx create mode 100644 client/views/teams/ConvertToChannelModal/ModalSteps/SecondStep.tsx create mode 100644 client/views/teams/ConvertToChannelModal/index.ts rename client/views/teams/contextualBar/info/{TeamsInfoWithLogic.js => TeamsInfoWithData.js} (85%) diff --git a/client/contexts/ServerContext/endpoints.ts b/client/contexts/ServerContext/endpoints.ts index 61c402d809bd6..0d726a497fe27 100644 --- a/client/contexts/ServerContext/endpoints.ts +++ b/client/contexts/ServerContext/endpoints.ts @@ -24,6 +24,7 @@ import { AutocompleteAvailableForTeamsEndpoint as RoomsAutocompleteTeamsEndpoint import { AutocompleteChannelAndPrivateEndpoint as RoomsAutocompleteEndpoint } from './endpoints/v1/rooms/autocompleteChannelAndPrivate'; import { AddRoomsEndpoint as TeamsAddRoomsEndpoint } from './endpoints/v1/teams/addRooms'; import { ListRoomsEndpoint } from './endpoints/v1/teams/listRooms'; +import { ListRoomsOfUserEndpoint } from './endpoints/v1/teams/listRoomsOfUser'; import { AutocompleteEndpoint as UsersAutocompleteEndpoint } from './endpoints/v1/users/autocomplete'; export type ServerEndpoints = { @@ -47,6 +48,7 @@ export type ServerEndpoints = { 'rooms.autocomplete.channelAndPrivate': RoomsAutocompleteEndpoint; 'rooms.autocomplete.availableForTeams': RoomsAutocompleteTeamsEndpoint; 'teams.listRooms': ListRoomsEndpoint; + 'teams.listRoomsOfUser': ListRoomsOfUserEndpoint; 'teams.addRooms': TeamsAddRoomsEndpoint; 'livechat/visitors.info': LivechatVisitorInfoEndpoint; 'livechat/room.onHold': LivechatRoomOnHoldEndpoint; diff --git a/client/contexts/ServerContext/endpoints/v1/teams/listRoomsOfUser.ts b/client/contexts/ServerContext/endpoints/v1/teams/listRoomsOfUser.ts new file mode 100644 index 0000000000000..32e113b57ab9d --- /dev/null +++ b/client/contexts/ServerContext/endpoints/v1/teams/listRoomsOfUser.ts @@ -0,0 +1,17 @@ +import { IRoom } from '../../../../../../definition/IRoom'; +import { IRecordsWithTotal } from '../../../../../../definition/ITeam'; + +export type ListRoomsOfUserEndpoint = { + GET: (params: { + teamId: string; + teamName?: string; + userId?: string; + canUserDelete?: boolean; + offset?: number; + count?: number; + }) => Omit, 'records'> & { + count: number; + offset: number; + rooms: IRecordsWithTotal['records']; + }; +}; diff --git a/client/views/teams/contextualBar/ChannelDesertionTable.js b/client/views/teams/ChannelDesertionTable.js similarity index 86% rename from client/views/teams/contextualBar/ChannelDesertionTable.js rename to client/views/teams/ChannelDesertionTable.js index 5e1f276e9a583..c9203c6475b90 100644 --- a/client/views/teams/contextualBar/ChannelDesertionTable.js +++ b/client/views/teams/ChannelDesertionTable.js @@ -1,10 +1,10 @@ import { Box, CheckBox } from '@rocket.chat/fuselage'; import React from 'react'; -import GenericTable from '../../../components/GenericTable'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; -import ChannelRow from './ChannelRow'; +import GenericTable from '../../components/GenericTable'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useFormatDateAndTime } from '../../hooks/useFormatDateAndTime'; +import ChannelRow from './contextualBar/ChannelRow'; const ChannelDesertionTable = ({ rooms, diff --git a/client/views/teams/ConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal.tsx deleted file mode 100644 index 5a941d43c0585..0000000000000 --- a/client/views/teams/ConvertToChannelModal.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { FC } from 'react'; - -import GenericModal from '../../components/GenericModal'; -import { useTranslation } from '../../contexts/TranslationContext'; - -type ConvertToChannelModalProps = { - onClose: () => void; - onConfirm: () => void; -}; - -const ConvertToChannelModal: FC = ({ onClose, onConfirm }) => { - const t = useTranslation(); - - return ( - - {t('Converting_channel_to_a_team')} - - ); -}; - -export default ConvertToChannelModal; diff --git a/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx new file mode 100644 index 0000000000000..17c734f02d9c4 --- /dev/null +++ b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx @@ -0,0 +1,82 @@ +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import React, { FC, useState, useCallback } from 'react'; + +import { IRoom } from '../../../../definition/IRoom'; +import FirstStep from './ModalSteps/FirstStep'; +import SecondStep from './ModalSteps/SecondStep'; + +const STEPS = { + LIST_ROOMS: 'LIST_ROOMS', + CONFIRM_CONVERT: 'CONFIRM_CONVERT', +}; + +type BaseConvertToChannelModalProps = { + onClose: () => void; + onCancel: () => void; + onConfirm: () => Array; + currentStep?: string; + rooms: Array | undefined; + selectedRooms: { [key: string]: IRoom }; +}; + +const BaseConvertToChannelModal: FC = ({ + onClose, + onCancel, + onConfirm, + rooms, + currentStep = rooms?.length === 0 ? STEPS.CONFIRM_CONVERT : STEPS.LIST_ROOMS, +}) => { + const [step, setStep] = useState(currentStep); + const [selectedRooms, setSelectedRooms] = useState< + BaseConvertToChannelModalProps['selectedRooms'] + >({}); + + const onContinue = useMutableCallback(() => setStep(STEPS.CONFIRM_CONVERT)); + const onReturn = useMutableCallback(() => setStep(STEPS.LIST_ROOMS)); + + const eligibleRooms = rooms?.filter(({ isLastOwner }) => !isLastOwner); + + const onChangeRoomSelection = useCallback((room) => { + setSelectedRooms((selectedRooms) => { + if (selectedRooms[room._id]) { + delete selectedRooms[room._id]; + return { ...selectedRooms }; + } + return { ...selectedRooms, [room._id]: room }; + }); + }, []); + + const onToggleAllRooms = useMutableCallback(() => { + if (Object.values(selectedRooms).filter(Boolean).length === 0 && eligibleRooms) { + return setSelectedRooms(Object.fromEntries(eligibleRooms.map((room) => [room._id, room]))); + } + setSelectedRooms({}); + }); + + if (step === STEPS.CONFIRM_CONVERT) { + return ( + 0 ? onReturn : onCancel} + deletedRooms={selectedRooms} + rooms={rooms} + /> + ); + } + + return ( + + ); +}; + +export default BaseConvertToChannelModal; diff --git a/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx new file mode 100644 index 0000000000000..1560a42d50801 --- /dev/null +++ b/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx @@ -0,0 +1,58 @@ +import { Skeleton } from '@rocket.chat/fuselage'; +import React, { FC, useMemo } from 'react'; + +import { IRoom } from '../../../../definition/IRoom'; +import GenericModal from '../../../components/GenericModal'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointData } from '../../../hooks/useEndpointData'; +import { AsyncStatePhase } from '../../../lib/asyncState'; +import BaseConvertToChannelModal from './BaseConvertToChannelModal'; + +type ConvertToChannelModalProps = { + onClose: () => void; + onCancel: () => void; + onConfirm: () => Array; + teamId: string; + userId: string; +}; + +const ConvertToChannelModal: FC = ({ + onClose, + onCancel, + onConfirm, + teamId, + userId, +}) => { + const t = useTranslation(); + + const { value, phase } = useEndpointData( + 'teams.listRoomsOfUser', + useMemo(() => ({ teamId, userId, canUserDelete: true }), [teamId, userId]), + ); + + if (phase === AsyncStatePhase.LOADING) { + return ( + } + confirmText={t('Cancel')} + onConfirm={onClose} + > + + + ); + } + + return ( + + ); +}; + +export default ConvertToChannelModal; diff --git a/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx b/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx new file mode 100644 index 0000000000000..3e8891516535f --- /dev/null +++ b/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx @@ -0,0 +1,69 @@ +import { Box } from '@rocket.chat/fuselage'; +import React, { FC } from 'react'; + +import { IRoom } from '../../../../../definition/IRoom'; +import GenericModal from '../../../../components/GenericModal'; +import { useTranslation } from '../../../../contexts/TranslationContext'; +import ChannelDesertionTable from '../../ChannelDesertionTable'; + +type FirstStepProps = { + onClose: () => void; + onCancel: () => void; + onConfirm: () => void; + onToggleAllRooms: () => void; + onChangeRoomSelection: (room: IRoom) => void; + rooms: Array | undefined; + eligibleRoomsLength: number | undefined; + selectedRooms: { [key: string]: IRoom }; +}; + +const FirstStep: FC = ({ + onClose, + onCancel, + onConfirm, + rooms, + onToggleAllRooms, + onChangeRoomSelection, + selectedRooms, + eligibleRoomsLength, + ...props +}) => { + const t = useTranslation(); + + return ( + + + {t('Select_the_teams_channels_you_would_like_to_delete')} + + + + {t('Notice_that_public_channels_will_be_public_and_visible_to_everyone')} + + + { + null; + }} + onChangeRoomSelection={onChangeRoomSelection} + selectedRooms={selectedRooms} + eligibleRoomsLength={eligibleRoomsLength} + /> + + ); +}; + +export default FirstStep; diff --git a/client/views/teams/ConvertToChannelModal/ModalSteps/SecondStep.tsx b/client/views/teams/ConvertToChannelModal/ModalSteps/SecondStep.tsx new file mode 100644 index 0000000000000..1324c1bbfd1db --- /dev/null +++ b/client/views/teams/ConvertToChannelModal/ModalSteps/SecondStep.tsx @@ -0,0 +1,45 @@ +import { Icon } from '@rocket.chat/fuselage'; +import React, { FC } from 'react'; + +import { IRoom } from '../../../../../definition/IRoom'; +import GenericModal from '../../../../components/GenericModal'; +import { useTranslation } from '../../../../contexts/TranslationContext'; + +type SecondStepsProps = { + onClose: () => void; + onCancel: () => void; + onConfirm: (deletedRooms: { [key: string]: IRoom }) => void; + deletedRooms: { + [key: string]: IRoom; + }; + rooms: Array | undefined; +}; + +const SecondStep: FC = ({ + onClose, + onCancel, + onConfirm, + deletedRooms = {}, + rooms = [], + ...props +}) => { + const t = useTranslation(); + + return ( + } + cancelText={rooms?.length > 0 ? t('Back') : t('Cancel')} + confirmText={t('Convert')} + title={t('Confirmation')} + onClose={onClose} + onCancel={onCancel} + onConfirm={(): void => onConfirm(deletedRooms)} + > + {t('You_are_converting_team_to_channel')} + + ); +}; + +export default SecondStep; diff --git a/client/views/teams/ConvertToChannelModal/index.ts b/client/views/teams/ConvertToChannelModal/index.ts new file mode 100644 index 0000000000000..186e9f906ffba --- /dev/null +++ b/client/views/teams/ConvertToChannelModal/index.ts @@ -0,0 +1 @@ +export { default } from './ConvertToChannelModal'; diff --git a/client/views/teams/contextualBar/info/Leave/StepOne.js b/client/views/teams/contextualBar/info/Leave/StepOne.js index 6849ba0744666..c8242f18c24ff 100644 --- a/client/views/teams/contextualBar/info/Leave/StepOne.js +++ b/client/views/teams/contextualBar/info/Leave/StepOne.js @@ -2,7 +2,7 @@ import React from 'react'; import GenericModal from '../../../../../components/GenericModal'; import { useTranslation } from '../../../../../contexts/TranslationContext'; -import ChannelDesertionTable from '../../ChannelDesertionTable'; +import ChannelDesertionTable from '../../../ChannelDesertionTable'; export const StepOne = ({ rooms, diff --git a/client/views/teams/contextualBar/info/TeamsInfo.js b/client/views/teams/contextualBar/info/TeamsInfo.js index 699721ac244c1..b0e65d716e9c6 100644 --- a/client/views/teams/contextualBar/info/TeamsInfo.js +++ b/client/views/teams/contextualBar/info/TeamsInfo.js @@ -71,7 +71,7 @@ const TeamsInfo = ({ convertToChannel: { label: t('Convert_to_channel'), action: onClickConvertToChannel, - icon: 'key', + icon: 'hash', }, }), }), diff --git a/client/views/teams/contextualBar/info/TeamsInfoWithLogic.js b/client/views/teams/contextualBar/info/TeamsInfoWithData.js similarity index 85% rename from client/views/teams/contextualBar/info/TeamsInfoWithLogic.js rename to client/views/teams/contextualBar/info/TeamsInfoWithData.js index 6b5c1a918ddf9..4c34ca39f1327 100644 --- a/client/views/teams/contextualBar/info/TeamsInfoWithLogic.js +++ b/client/views/teams/contextualBar/info/TeamsInfoWithData.js @@ -2,7 +2,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useCallback } from 'react'; import { roomTypes, UiTextContext } from '../../../../../app/utils/client'; -import GenericModal, { GenericModalDoNotAskAgain } from '../../../../components/GenericModal'; +import { GenericModalDoNotAskAgain } from '../../../../components/GenericModal'; import MarkdownText from '../../../../components/MarkdownText'; import { usePermission } from '../../../../contexts/AuthorizationContext'; import { useSetModal } from '../../../../contexts/ModalContext'; @@ -11,9 +11,11 @@ import { useMethod } from '../../../../contexts/ServerContext'; import { useSetting } from '../../../../contexts/SettingsContext'; import { useToastMessageDispatch } from '../../../../contexts/ToastMessagesContext'; import { useTranslation } from '../../../../contexts/TranslationContext'; +import { useUserId } from '../../../../contexts/UserContext'; import { useDontAskAgain } from '../../../../hooks/useDontAskAgain'; import { useEndpointActionExperimental } from '../../../../hooks/useEndpointAction'; import { useTabBarClose, useTabBarOpen } from '../../../room/providers/ToolboxProvider'; +import ConvertToChannelModal from '../../ConvertToChannelModal'; import DeleteTeamModal from './Delete'; import LeaveTeamModal from './Leave'; import TeamsInfo from './TeamsInfo'; @@ -34,6 +36,7 @@ function TeamsInfoWithLogic({ room, openEditing }) { const onClickClose = useTabBarClose(); const openTabbar = useTabBarOpen(); const t = useTranslation(); + const userId = useUserId(); room.type = room.t; room.rid = room._id; @@ -56,6 +59,8 @@ function TeamsInfoWithLogic({ room, openEditing }) { const deleteTeam = useEndpointActionExperimental('POST', 'teams.delete'); const leaveTeam = useEndpointActionExperimental('POST', 'teams.leave'); + const convertTeamToChannel = useEndpointActionExperimental('POST', 'teams.convertToChannel'); + const hideTeam = useMethod('hideRoom'); const router = useRoute('home'); @@ -136,22 +141,35 @@ function TeamsInfoWithLogic({ room, openEditing }) { const onClickViewChannels = useCallback(() => openTabbar('team-channels'), [openTabbar]); const onClickConvertToChannel = useMutableCallback(async () => { - // const data = type === 'c' ? { channelId: rid } : { roomId: rid }; - const onConfirm = async () => { - // try { - // await convertRoomToTeam(data); - // } catch (error) { - // dispatchToastMessage({ type: 'error', message: error }); - // } finally { - // closeModal(); - // } + const onConfirm = async (roomsToRemove) => { + try { + await convertTeamToChannel({ + teamId: room.teamId, + roomsToRemove: Object.keys(roomsToRemove), + }); + + dispatchToastMessage({ type: 'success', message: t('Success') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + closeModal(); + } }; - setModal(); + setModal( + , + ); }); return ( } diff --git a/client/views/teams/contextualBar/info/index.js b/client/views/teams/contextualBar/info/index.js index 781b7fd66ccd8..acd71c705d039 100644 --- a/client/views/teams/contextualBar/info/index.js +++ b/client/views/teams/contextualBar/info/index.js @@ -7,7 +7,7 @@ import { useTranslation } from '../../../../contexts/TranslationContext'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; import { useEndpointData } from '../../../../hooks/useEndpointData'; import EditChannelWithData from '../../../room/contextualBar/Info/EditRoomInfo'; -import TeamsInfoWithLogic from './TeamsInfoWithLogic'; +import TeamsInfoWithData from './TeamsInfoWithData'; export default function TeamsInfoWithRooms({ rid }) { const [editing, setEditing] = useState(false); @@ -39,6 +39,6 @@ export default function TeamsInfoWithRooms({ rid }) { return editing ? ( ) : ( - + ); } diff --git a/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersFirstStep.js b/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersFirstStep.js index 5dbce1fa11213..f32aa67f00fb8 100644 --- a/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersFirstStep.js +++ b/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersFirstStep.js @@ -3,7 +3,7 @@ import React from 'react'; import GenericModal from '../../../../../components/GenericModal'; import { useTranslation } from '../../../../../contexts/TranslationContext'; -import ChannelDesertionTable from '../../ChannelDesertionTable'; +import ChannelDesertionTable from '../../../ChannelDesertionTable'; const RemoveUsersFirstStep = ({ onClose, diff --git a/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js b/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js index d2853ac591c2d..06fb36a103f7e 100644 --- a/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js +++ b/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js @@ -30,7 +30,6 @@ const RemoveUsersModal = ({ teamId, userId, onClose, onCancel, onConfirm }) => { variant='warning' onClose={onClose} title={} - confirmText={} confirmText={t('Cancel')} onConfirm={onClose} > diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 64475d5ecb0fd..f1cc11e034328 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -921,6 +921,8 @@ "Compact": "Compact", "Condensed": "Condensed", "Condition": "Condition", + "Convert_to_channel": "Convert to Channel", + "Converting_team_to_channel": "Converting Team to Channel", "Commit_details": "Commit Details", "Completed": "Completed", "Computer": "Computer", @@ -3003,6 +3005,7 @@ "Not_verified": "Not verified", "Nothing": "Nothing", "Nothing_found": "Nothing found", + "Notice_that_public_channels_will_be_public_and_visible_to_everyone": "Notice that public Channels will be public and visible to everyone.", "Notification_Desktop_Default_For": "Show Desktop Notifications For", "Notification_Desktop_Audio_Default_For": "Play Desktop Notifications Audio For", "Notification_Mobile_Default_For": "Push Mobile Notifications For", @@ -3611,6 +3614,7 @@ "Select_service_to_login": "Select a service to login to load your picture or upload one directly from your computer", "Select_tag": "Select a tag", "Select_the_channels_you_want_the_user_to_be_removed_from": "Select the channels you want the user to be removed from", + "Select_the_teams_channels_you_would_like_to_delete": "Select the Team’s Channels you would like to delete, the ones you do not select will be moved to the Workspace.", "Select_user": "Select user", "Select_users": "Select users", "Selected_agents": "Selected agents", @@ -3888,6 +3892,7 @@ "Teams": "Teams", "Teams_about_the_channels": "And about the Channels?", "Teams_channels_didnt_leave": "You did not select the following Channels so you are not leaving them:", + "Teams_channels_last_owner_delete_channel_warning": "You are the last owner of this Channel. Once you convert the Team into a channel, the Channel will be moved to the Workspace.", "Teams_channels_last_owner_leave_channel_warning": "You are the last owner of this Channel. Once you leave the Team, the Channel will be kept inside the Team but you will managing it from outside.", "Teams_leaving_team": "You are leaving this Team.", "Teams_channels": "Team's Channels", @@ -4507,6 +4512,7 @@ "yesterday": "yesterday", "Yesterday": "Yesterday", "You": "You", + "You_are_converting_team_to_channel": "You are converting this Team to a Channel.", "you_are_in_preview_mode_of": "You are in preview mode of channel #__room_name__", "you_are_in_preview_mode_of_incoming_livechat": "You are in preview mode of this chat", "You_are_logged_in_as": "You are logged in as", diff --git a/server/services/team/service.ts b/server/services/team/service.ts index 26af561db211c..3e8a5ffe79d0c 100644 --- a/server/services/team/service.ts +++ b/server/services/team/service.ts @@ -480,7 +480,7 @@ export class TeamService extends ServiceClass implements ITeamService { room.userCanDelete = canDeleteRoom; } - teamRoomIds = teamRooms.filter((room) => (room.t === 'c' || room.t === 'p') && room.userCanDelete); + teamRoomIds = teamRooms.filter((room) => (room.t === 'c' || room.t === 'p') && room.userCanDelete).map((room) => room._id); } else { teamRoomIds = teamRooms.filter((room) => room.t === 'p' || room.t === 'c').map((room) => room._id); } From 66cc8aabbf9c2ceb3ea00d4a6954fe993a16bad2 Mon Sep 17 00:00:00 2001 From: dougfabris Date: Tue, 29 Jun 2021 18:25:00 -0300 Subject: [PATCH 5/8] fix: lastOwner warning --- .../BaseConvertToChannelModal.tsx | 2 +- .../ConvertToChannelModal/ModalSteps/FirstStep.tsx | 6 ++---- client/views/teams/contextualBar/ChannelRow.js | 10 +++++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx index 17c734f02d9c4..181a0a723fea8 100644 --- a/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx +++ b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx @@ -34,7 +34,7 @@ const BaseConvertToChannelModal: FC = ({ const onContinue = useMutableCallback(() => setStep(STEPS.CONFIRM_CONVERT)); const onReturn = useMutableCallback(() => setStep(STEPS.LIST_ROOMS)); - const eligibleRooms = rooms?.filter(({ isLastOwner }) => !isLastOwner); + const eligibleRooms = rooms; const onChangeRoomSelection = useCallback((room) => { setSelectedRooms((selectedRooms) => { diff --git a/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx b/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx index 3e8891516535f..3eced02081e76 100644 --- a/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx +++ b/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx @@ -51,13 +51,11 @@ const FirstStep: FC = ({ { - null; - }} + onChangeParams={(): void => undefined} onChangeRoomSelection={onChangeRoomSelection} selectedRooms={selectedRooms} eligibleRoomsLength={eligibleRoomsLength} diff --git a/client/views/teams/contextualBar/ChannelRow.js b/client/views/teams/contextualBar/ChannelRow.js index 03f503dd0dfe3..822a2339aac45 100644 --- a/client/views/teams/contextualBar/ChannelRow.js +++ b/client/views/teams/contextualBar/ChannelRow.js @@ -4,7 +4,7 @@ import React from 'react'; import { useRoomIcon } from '../../../hooks/useRoomIcon'; -const ChannelRow = ({ onChange, selected, room, lastOwnerWarning = '', formatDate }) => { +const ChannelRow = ({ onChange, selected, room, lastOwnerWarning, formatDate }) => { const { name, fname, ts, isLastOwner } = room; const handleChange = useMutableCallback(() => onChange(room)); @@ -12,11 +12,15 @@ const ChannelRow = ({ onChange, selected, room, lastOwnerWarning = '', formatDat return ( - + {fname ?? name} - {isLastOwner && ( + {lastOwnerWarning && isLastOwner && ( )} From a411eb8d62e130986577eb88fd57ca0a77c557f3 Mon Sep 17 00:00:00 2001 From: dougfabris Date: Tue, 6 Jul 2021 12:14:27 -0300 Subject: [PATCH 6/8] fix: review --- .../teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx | 2 +- client/views/teams/contextualBar/info/TeamsInfoWithData.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx index 181a0a723fea8..445bdf9d257d7 100644 --- a/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx +++ b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx @@ -15,7 +15,7 @@ type BaseConvertToChannelModalProps = { onCancel: () => void; onConfirm: () => Array; currentStep?: string; - rooms: Array | undefined; + rooms?: Array; selectedRooms: { [key: string]: IRoom }; }; diff --git a/client/views/teams/contextualBar/info/TeamsInfoWithData.js b/client/views/teams/contextualBar/info/TeamsInfoWithData.js index 4c34ca39f1327..3bec8ea3def1b 100644 --- a/client/views/teams/contextualBar/info/TeamsInfoWithData.js +++ b/client/views/teams/contextualBar/info/TeamsInfoWithData.js @@ -140,7 +140,7 @@ function TeamsInfoWithLogic({ room, openEditing }) { const onClickViewChannels = useCallback(() => openTabbar('team-channels'), [openTabbar]); - const onClickConvertToChannel = useMutableCallback(async () => { + const onClickConvertToChannel = useMutableCallback(() => { const onConfirm = async (roomsToRemove) => { try { await convertTeamToChannel({ From 1d26f7e9c4b9d0fee8a0cf9cac81d29ee2fd2292 Mon Sep 17 00:00:00 2001 From: dougfabris Date: Tue, 6 Jul 2021 16:09:28 -0300 Subject: [PATCH 7/8] improve: convert ChannelDesertationTable to ts --- ...tionTable.js => ChannelDesertionTable.tsx} | 23 +++++++++++++++---- .../ConvertToChannelModal.tsx | 3 ++- .../ModalSteps/FirstStep.tsx | 2 -- 3 files changed, 21 insertions(+), 7 deletions(-) rename client/views/teams/{ChannelDesertionTable.js => ChannelDesertionTable.tsx} (70%) diff --git a/client/views/teams/ChannelDesertionTable.js b/client/views/teams/ChannelDesertionTable.tsx similarity index 70% rename from client/views/teams/ChannelDesertionTable.js rename to client/views/teams/ChannelDesertionTable.tsx index c9203c6475b90..dfa1401abe04a 100644 --- a/client/views/teams/ChannelDesertionTable.js +++ b/client/views/teams/ChannelDesertionTable.tsx @@ -1,12 +1,24 @@ import { Box, CheckBox } from '@rocket.chat/fuselage'; -import React from 'react'; +import React, { FC, ReactElement } from 'react'; +import { IRoom } from '../../../definition/IRoom'; import GenericTable from '../../components/GenericTable'; import { useTranslation } from '../../contexts/TranslationContext'; import { useFormatDateAndTime } from '../../hooks/useFormatDateAndTime'; import ChannelRow from './contextualBar/ChannelRow'; -const ChannelDesertionTable = ({ +type ChannelDesertionTableProps = { + lastOwnerWarning: boolean | undefined; + rooms: Array | undefined; + eligibleRoomsLength: number | undefined; + params?: {}; + onChangeParams?: () => void; + onChangeRoomSelection: (room: IRoom) => void; + selectedRooms: { [key: string]: IRoom }; + onToggleAllRooms: () => void; +}; + +const ChannelDesertionTable: FC = ({ rooms, eligibleRoomsLength, params, @@ -21,7 +33,10 @@ const ChannelDesertionTable = ({ const selectedRoomsLength = Object.values(selectedRooms).filter(Boolean).length; const checked = eligibleRoomsLength === selectedRoomsLength; - const indeterminate = eligibleRoomsLength > selectedRoomsLength && selectedRoomsLength > 0; + const indeterminate = + eligibleRoomsLength && eligibleRoomsLength > selectedRoomsLength + ? selectedRoomsLength > 0 + : false; const formatDate = useFormatDateAndTime(); @@ -51,7 +66,7 @@ const ChannelDesertionTable = ({ fixed={false} pagination={false} > - {(room, key) => ( + {(room: IRoom, key: string): ReactElement => ( = ({ userId, }) => { const t = useTranslation(); + const selectedRooms = {}; const { value, phase } = useEndpointData( 'teams.listRoomsOfUser', @@ -50,7 +51,7 @@ const ConvertToChannelModal: FC = ({ onCancel={onCancel} onConfirm={onConfirm} rooms={value?.rooms} - selectedRooms={{}} + selectedRooms={selectedRooms} /> ); }; diff --git a/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx b/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx index 3eced02081e76..4f033cbd7844c 100644 --- a/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx +++ b/client/views/teams/ConvertToChannelModal/ModalSteps/FirstStep.tsx @@ -54,8 +54,6 @@ const FirstStep: FC = ({ lastOwnerWarning={undefined} onToggleAllRooms={onToggleAllRooms} rooms={rooms} - params={{}} - onChangeParams={(): void => undefined} onChangeRoomSelection={onChangeRoomSelection} selectedRooms={selectedRooms} eligibleRoomsLength={eligibleRoomsLength} From d92aeec0e099d96526b165741e39e9e7e267a5c7 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 6 Jul 2021 17:36:48 -0300 Subject: [PATCH 8/8] Remove unused prop --- .../ConvertToChannelModal/BaseConvertToChannelModal.tsx | 5 +---- .../teams/ConvertToChannelModal/ConvertToChannelModal.tsx | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx index 445bdf9d257d7..8f535fe7c20ff 100644 --- a/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx +++ b/client/views/teams/ConvertToChannelModal/BaseConvertToChannelModal.tsx @@ -16,7 +16,6 @@ type BaseConvertToChannelModalProps = { onConfirm: () => Array; currentStep?: string; rooms?: Array; - selectedRooms: { [key: string]: IRoom }; }; const BaseConvertToChannelModal: FC = ({ @@ -27,9 +26,7 @@ const BaseConvertToChannelModal: FC = ({ currentStep = rooms?.length === 0 ? STEPS.CONFIRM_CONVERT : STEPS.LIST_ROOMS, }) => { const [step, setStep] = useState(currentStep); - const [selectedRooms, setSelectedRooms] = useState< - BaseConvertToChannelModalProps['selectedRooms'] - >({}); + const [selectedRooms, setSelectedRooms] = useState<{ [key: string]: IRoom }>({}); const onContinue = useMutableCallback(() => setStep(STEPS.CONFIRM_CONVERT)); const onReturn = useMutableCallback(() => setStep(STEPS.LIST_ROOMS)); diff --git a/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx b/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx index bee507339f6ed..faddf8c2931b3 100644 --- a/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx +++ b/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx @@ -24,7 +24,6 @@ const ConvertToChannelModal: FC = ({ userId, }) => { const t = useTranslation(); - const selectedRooms = {}; const { value, phase } = useEndpointData( 'teams.listRoomsOfUser', @@ -51,7 +50,6 @@ const ConvertToChannelModal: FC = ({ onCancel={onCancel} onConfirm={onConfirm} rooms={value?.rooms} - selectedRooms={selectedRooms} /> ); };