/* * 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, useCallback, useEffect, useMemo, useState} from 'react'; import cx from 'classnames'; import {container} from 'tsyringe'; import {Button, ButtonVariant} from '@wireapp/react-ui-kit'; import * as Icon from 'Components/Icon'; import {PrimaryModal} from 'Components/Modals/PrimaryModal'; import {RadioGroup} from 'Components/Radio'; import {SelectText} from 'Components/SelectText'; import {BaseToggle} from 'Components/toggle/BaseToggle'; import {TeamState} from 'src/script/team/TeamState'; import {copyText} from 'Util/ClipboardUtil'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; import {Config} from '../../../../../Config'; import {ACCESS_STATE} from '../../../../../conversation/AccessState'; import {teamPermissionsForAccessState} from '../../../../../conversation/ConversationAccessPermission'; import {ConversationRepository} from '../../../../../conversation/ConversationRepository'; import {Conversation} from '../../../../../entity/Conversation'; import {TeamRepository} from '../../../../../team/TeamRepository'; const COPY_LINK_CONFIRM_DURATION = 1500; enum PasswordPreference { PASSWORD_SECURED = 'Password secured', NOT_PASSWORD_SECURED = 'Not password secured', } interface GuestOptionsProps { activeConversation: Conversation; conversationRepository: ConversationRepository; teamRepository: TeamRepository; toggleAccessState: (accessType: number, text: string, hasService: boolean) => void; setIsRequestOngoing: (isRequestOngoing: boolean) => void; isRequestOngoing?: boolean; isTeamStateGuestLinkEnabled?: boolean; isToggleDisabled?: boolean; isPasswordSupported?: boolean; teamState?: TeamState; } const GuestOptions: FC = ({ activeConversation, conversationRepository, teamRepository, toggleAccessState, setIsRequestOngoing, isRequestOngoing = false, isTeamStateGuestLinkEnabled = false, isToggleDisabled = false, isPasswordSupported = false, teamState = container.resolve(TeamState), }) => { const [isLinkCopied, setIsLinkCopied] = useState(false); const [conversationHasGuestLinkEnabled, setConversationHasGuestLinkEnabled] = useState(false); const [optionPasswordSecured, setOptionPasswordSecured] = useState( PasswordPreference.PASSWORD_SECURED, ); const {accessCode, accessCodeHasPassword, hasGuest, isGuestAndServicesRoom, isGuestRoom, isServicesRoom} = useKoSubscribableChildren(activeConversation, [ 'accessCode', 'accessCodeHasPassword', 'hasGuest', 'isGuestAndServicesRoom', 'isGuestRoom', 'isServicesRoom', ]); const inTeam = teamState.isInTeam(activeConversation); const isGuestEnabled = isGuestRoom || isGuestAndServicesRoom; const isGuestLinkEnabled = inTeam ? isTeamStateGuestLinkEnabled : isTeamStateGuestLinkEnabled && conversationHasGuestLinkEnabled; const isServicesEnabled = isServicesRoom || isGuestAndServicesRoom; const hasAccessCode: boolean = isGuestEnabled ? !!accessCode : false; const guestInfoText = useMemo(() => { if (!inTeam) { return t('guestRoomToggleInfoDisabled'); } if (accessCodeHasPassword) { return isGuestEnabled ? ( {t('guestOptionsInfoTextWithPassword')} {'\n'} {t('guestOptionsInfoTextForgetPassword')} ) : ( t('guestRoomToggleInfo') ); } return isGuestEnabled ? t('guestOptionsInfoText', Config.getConfig().BRAND_NAME) : t('guestRoomToggleInfo'); }, [inTeam, isGuestEnabled, accessCodeHasPassword]); const guestLinkDisabledInfo = !isTeamStateGuestLinkEnabled ? t('guestLinkDisabled') : t('guestLinkDisabledByOtherTeam'); const toggleGuestAccess = async () => { await toggleAccessState( teamPermissionsForAccessState(ACCESS_STATE.TEAM.GUEST_FEATURES), t('modalConversationRemoveGuestsMessage'), hasGuest, ); }; const copyLink = async () => { if (!isLinkCopied) { await copyText(accessCode); setIsLinkCopied(true); window.setTimeout(() => setIsLinkCopied(false), COPY_LINK_CONFIRM_DURATION); } }; const revokeAccessCode = () => { PrimaryModal.show(PrimaryModal.type.CONFIRM, { preventClose: true, primaryAction: { action: async (): Promise => { if (!isRequestOngoing) { setIsRequestOngoing(true); await conversationRepository.stateHandler.revokeAccessCode(activeConversation); setIsRequestOngoing(false); } }, text: t('modalConversationRevokeLinkAction'), }, text: { message: t('modalConversationRevokeLinkMessage'), title: t('modalConversationRevokeLinkHeadline'), }, }); }; const openForcePasswordCopyModal = async (password: string) => { PrimaryModal.show( PrimaryModal.type.CONFIRM, { closeOnConfirm: true, preventClose: false, primaryAction: { action: async () => { await copyText(password); await requestAccessCode(password); }, text: t('guestOptionsPasswordCopyToClipboard'), }, text: { title: t('guestOptionsPasswordCopyToClipboard'), message: t('guestOptionsPasswordForceToCopy'), }, }, undefined, ); }; const createGuestLinkWithPassword = async () => { const onCreate = async (password: string, didCopyPassword: boolean) => { if (!didCopyPassword) { await openForcePasswordCopyModal(password); return; } await requestAccessCode(password); }; PrimaryModal.show( PrimaryModal.type.GUEST_LINK_PASSWORD, { copyPassword: true, closeOnConfirm: true, preventClose: false, primaryAction: { action: onCreate, text: t('guestOptionsInfoModalAction'), }, secondaryAction: { text: t('modalConfirmSecondary'), }, text: { closeBtnLabel: t('guestOptionsInfoModalCancel'), input: t('guestOptionsInfoModalFormLabel'), message: ( <> {t('guestOptionsInfoModalTitleSubTitle')} {'\n'} {t('guestOptionsInfoModalTitleBoldSubTitle')} ), title: t('guestOptionsInfoModalTitle'), }, }, undefined, ); }; const requestAccessCode = async (password?: string) => { if (!isGuestEnabled && !isServicesEnabled) { await conversationRepository.stateHandler.changeAccessState(activeConversation, ACCESS_STATE.TEAM.GUEST_ROOM); } if (!isRequestOngoing) { setIsRequestOngoing(true); await conversationRepository.stateHandler.requestAccessCode(activeConversation, password); setIsRequestOngoing(false); } }; const createLink = async () => { if (optionPasswordSecured === PasswordPreference.PASSWORD_SECURED) { await createGuestLinkWithPassword(); return; } await requestAccessCode(); }; const updateCode = useCallback(async () => { const canUpdateCode = (isGuestRoom || isGuestAndServicesRoom) && !accessCode && isGuestLinkEnabled; if (canUpdateCode) { setIsRequestOngoing(true); await conversationRepository.stateHandler.getAccessCode(activeConversation); setIsRequestOngoing(false); } }, [accessCode, activeConversation, isGuestAndServicesRoom, isGuestLinkEnabled, isGuestRoom, setIsRequestOngoing]); const initializeOptions = useCallback(async () => { if (!inTeam && !isGuestLinkEnabled) { const hasGuestLink = await teamRepository.conversationHasGuestLinkEnabled(activeConversation.id); setConversationHasGuestLinkEnabled(hasGuestLink); } await updateCode(); }, [activeConversation, inTeam, isGuestLinkEnabled, updateCode]); useEffect(() => { initializeOptions(); }, [initializeOptions]); const saveOptionPasswordSecured = (preference: PasswordPreference) => { setOptionPasswordSecured(preference); }; return ( <>

{hasAccessCode && accessCodeHasPassword ? ( {t('guestOptionsInfoPasswordSecured')} ) : ( t('guestRoomToggleInfoHead') )}

{guestInfoText}

{isGuestEnabled && ( <> {isGuestLinkEnabled && ( <> {hasAccessCode && ( <> )} {!hasAccessCode && !isPasswordSupported && (
)} {!hasAccessCode && isPasswordSupported && ( <>

{t('guestOptionsInfoTextSecureWithPassword')}

)} )} {!isGuestLinkEnabled && (

{guestLinkDisabledInfo}

)} )} ); }; export {GuestOptions};