/* * Wire * Copyright (C) 2022 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * */ import {FC, useMemo, useState} from 'react'; import {ConversationProtocol} from '@wireapp/api-client/lib/conversation'; import {TabIndex} from '@wireapp/react-ui-kit/lib/types/enums'; import cx from 'classnames'; import {Button, ButtonVariant} from '@wireapp/react-ui-kit'; import {FadingScrollbar} from 'Components/FadingScrollbar'; import * as Icon from 'Components/Icon'; import {SearchInput} from 'Components/SearchInput'; import {ServiceList} from 'Components/ServiceList/ServiceList'; import {UserSearchableList} from 'Components/UserSearchableList'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {handleKeyDown} from 'Util/KeyboardUtil'; import {t} from 'Util/LocalizerUtil'; import {safeWindowOpen} from 'Util/SanitizationUtil'; import {sortUsersByPriority} from 'Util/StringUtil'; import {ConversationRepository} from '../../../conversation/ConversationRepository'; import {Conversation} from '../../../entity/Conversation'; import {User} from '../../../entity/User'; import {getManageServicesUrl} from '../../../externalRoute'; import {IntegrationRepository} from '../../../integration/IntegrationRepository'; import {ServiceEntity} from '../../../integration/ServiceEntity'; import {SearchRepository} from '../../../search/SearchRepository'; import {TeamRepository} from '../../../team/TeamRepository'; import {TeamState} from '../../../team/TeamState'; import {generatePermissionHelpers} from '../../../user/UserPermission'; import {UserState} from '../../../user/UserState'; import {PanelHeader} from '../PanelHeader'; import {PanelEntity, PanelState} from '../RightSidebar'; const ENABLE_ADD_ACTIONS_LENGTH = 0; const ENABLE_IS_SEARCHING_LENGTH = 0; enum PARTICIPANTS_STATE { ADD_PEOPLE = 'ADD_PEOPLE', ADD_SERVICE = 'ADD_SERVICE', } interface AddParticipantsProps { activeConversation: Conversation; onBack: () => void; onClose: () => void; conversationRepository: ConversationRepository; integrationRepository: IntegrationRepository; searchRepository: SearchRepository; togglePanel: (panel: PanelState, entity: PanelEntity, addMode?: boolean) => void; teamRepository: TeamRepository; teamState: TeamState; userState: UserState; selfUser: User; } const AddParticipants: FC = ({ activeConversation, onBack, onClose, conversationRepository, integrationRepository, searchRepository, togglePanel, teamRepository, teamState, userState, selfUser, }) => { const { firstUserEntity, inTeam, isGroup, isGuestAndServicesRoom, isServicesRoom, isTeamOnly, participating_user_ids: participatingUserIds, } = useKoSubscribableChildren(activeConversation, [ 'firstUserEntity', 'inTeam', 'isGroup', 'isGuestAndServicesRoom', 'isServicesRoom', 'isTeamOnly', 'participating_user_ids', ]); const {isTeam, teamMembers, teamUsers} = useKoSubscribableChildren(teamState, ['isTeam', 'teamMembers', 'teamUsers']); const {connectedUsers} = useKoSubscribableChildren(userState, ['connectedUsers']); const {teamRole} = useKoSubscribableChildren(selfUser, ['teamRole']); const {services} = useKoSubscribableChildren(integrationRepository, ['services']); const {canManageServices} = generatePermissionHelpers(teamRole); const [currentState, setCurrentState] = useState(PARTICIPANTS_STATE.ADD_PEOPLE); const isAddPeopleState = currentState === PARTICIPANTS_STATE.ADD_PEOPLE; const isAddServiceState = currentState === PARTICIPANTS_STATE.ADD_SERVICE; const [searchInput, setSearchInput] = useState(''); const [selectedContacts, setSelectedContacts] = useState([]); const [isInitialServiceSearch, setIsInitialServiceSearch] = useState(true); const contacts = useMemo(() => { if (isTeam) { const isTeamOrServices = isTeamOnly || isServicesRoom; return isTeamOrServices ? teamMembers.sort(sortUsersByPriority) : teamUsers; } return connectedUsers; }, [connectedUsers, isServicesRoom, isTeam, isTeamOnly, teamMembers, teamUsers]); const enabledAddAction = selectedContacts.length > ENABLE_ADD_ACTIONS_LENGTH; const headerText = selectedContacts.length ? t('addParticipantsHeaderWithCounter', selectedContacts.length) : t('addParticipantsHeader'); const showIntegrations = useMemo(() => { const isServicesEnabled = isServicesRoom || isGuestAndServicesRoom; const isService = !!firstUserEntity?.isService; const allowIntegrations = isGroup || isService; return ( isTeam && allowIntegrations && inTeam && !isTeamOnly && isServicesEnabled && activeConversation.protocol !== ConversationProtocol.MLS ); }, [firstUserEntity?.isService, inTeam, isGroup, isGuestAndServicesRoom, isServicesRoom, isTeam, isTeamOnly]); const manageServicesUrl = getManageServicesUrl('client_landing'); const isSearching = searchInput.length > ENABLE_IS_SEARCHING_LENGTH; const onAddPeople = () => setCurrentState(PARTICIPANTS_STATE.ADD_PEOPLE); const searchServices = async (value: string) => { await integrationRepository.searchForServices(value); setIsInitialServiceSearch(false); }; const onAddServices = async () => { setCurrentState(PARTICIPANTS_STATE.ADD_SERVICE); await searchServices(searchInput); }; const openManageServices = () => { if (manageServicesUrl) { safeWindowOpen(manageServicesUrl); } }; const onServiceSelect = (entity: ServiceEntity) => togglePanel(PanelState.GROUP_PARTICIPANT_SERVICE, entity, true); const addUsers = async () => { const userEntities = selectedContacts.slice(); await conversationRepository.addUsers(activeConversation, userEntities); }; const onAddParticipants = async () => { await addUsers(); onBack(); }; const onSearchInput = async (value: string) => { setSearchInput(value); if (isAddServiceState) { await searchServices(value); } }; return (
{showIntegrations && (
handleKeyDown(event, onAddPeople)} data-uie-name="do-add-people" > {t('addParticipantsTabsPeople')}
handleKeyDown(event, onAddServices)} data-uie-name="do-add-services" > {t('addParticipantsTabsServices')}
)} {isAddPeopleState && ( )} {isAddServiceState && ( <> {!!services.length && ( <> {canManageServices() && !!manageServicesUrl && (
  • handleKeyDown(event, openManageServices)} data-uie-name="go-manage-services" >
    {t('addParticipantsManageServices')}
)} )} {!services.length && !isInitialServiceSearch && (
{canManageServices() && !!manageServicesUrl && ( <>
{t('addParticipantsNoServicesManager')}
)} {!canManageServices() && (
{t('addParticipantsNoServicesMember')}
)}
)} )}
{isAddPeopleState && (
)}
); }; export {AddParticipants};