import type { ChangeEvent } from 'react'; import type { FC } from '../../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, useState, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiAvailableReaction, ApiChat, ApiChatBannedRights, ApiChatFullInfo, ApiExportedInvite, } from '../../../api/types'; import { ApiMediaFormat } from '../../../api/types'; import { ManagementProgress, ManagementScreens } from '../../../types'; import { getChatAvatarHash, getHasAdminRight, isChatBasicGroup, isChatPublic, } from '../../../global/helpers'; import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors'; import { debounce } from '../../../util/schedulers'; import { formatInteger } from '../../../util/textFormat'; import renderText from '../../common/helpers/renderText'; import useFlag from '../../../hooks/useFlag'; import useHistoryBack from '../../../hooks/useHistoryBack'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useMedia from '../../../hooks/useMedia'; import AvatarEditable from '../../ui/AvatarEditable'; import Checkbox from '../../ui/Checkbox'; import ConfirmDialog from '../../ui/ConfirmDialog'; import FloatingActionButton from '../../ui/FloatingActionButton'; import InputText from '../../ui/InputText'; import ListItem from '../../ui/ListItem'; import Spinner from '../../ui/Spinner'; import Switcher from '../../ui/Switcher'; import TextArea from '../../ui/TextArea'; import './Management.scss'; type OwnProps = { chatId: string; onScreenSelect: (screen: ManagementScreens) => void; onClose: NoneToVoidFunction; isActive: boolean; }; type StateProps = { chat: ApiChat; chatFullInfo?: ApiChatFullInfo; progress?: ManagementProgress; isBasicGroup: boolean; hasLinkedChannel: boolean; canChangeInfo?: boolean; canBanUsers?: boolean; canInvite?: boolean; canEditForum?: boolean; exportedInvites?: ApiExportedInvite[]; isChannelsPremiumLimitReached: boolean; availableReactions?: ApiAvailableReaction[]; }; const GROUP_TITLE_EMPTY = 'Group title can\'t be empty'; const GROUP_MAX_DESCRIPTION = 255; const ALL_PERMISSIONS: Array = [ 'sendMessages', 'embedLinks', 'sendPolls', 'changeInfo', 'inviteUsers', 'pinMessages', 'manageTopics', 'sendPhotos', 'sendVideos', 'sendRoundvideos', 'sendVoices', 'sendAudios', 'sendDocs', ]; // Some checkboxes control multiple rights, and some rights are not controlled from Permissions screen, // so we need to define the amount manually const TOTAL_PERMISSIONS_COUNT = ALL_PERMISSIONS.length + 1; const runDebounced = debounce((cb) => cb(), 500, false); const ManageGroup: FC = ({ chatId, chat, chatFullInfo, progress, isBasicGroup, hasLinkedChannel, canChangeInfo, canBanUsers, canInvite, canEditForum, isActive, exportedInvites, isChannelsPremiumLimitReached, availableReactions, onScreenSelect, onClose, }) => { const { togglePreHistoryHidden, updateChat, deleteChat, leaveChannel, deleteChannel, closeManagement, openChat, loadExportedChatInvites, loadChatJoinRequests, toggleForum, } = getActions(); const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag(); const currentTitle = chat.title; const currentAbout = chatFullInfo?.about || ''; const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false); const [title, setTitle] = useState(currentTitle); const [about, setAbout] = useState(currentAbout); const [photo, setPhoto] = useState(); const [error, setError] = useState(); const [isForumEnabled, setIsForumEnabled] = useState(chat.isForum); const imageHash = getChatAvatarHash(chat); const currentAvatarBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl); const isPublicGroup = useMemo(() => isChatPublic(chat), [chat]); const lang = useLang(); // eslint-disable-next-line no-null/no-null const isPreHistoryHiddenCheckboxRef = useRef(null); useHistoryBack({ isActive, onBack: onClose, }); useEffect(() => { if (canInvite) { loadExportedChatInvites({ chatId }); loadExportedChatInvites({ chatId, isRevoked: true }); loadChatJoinRequests({ chatId }); } }, [chatId, canInvite]); // Resetting `isForum` switch on flood wait error useEffect(() => { setIsForumEnabled(Boolean(chat.isForum)); }, [chat.isForum]); useEffect(() => { if (progress === ManagementProgress.Complete) { setIsProfileFieldsTouched(false); setError(undefined); } }, [progress]); const handleClickEditType = useLastCallback(() => { onScreenSelect(ManagementScreens.ChatPrivacyType); }); const handleClickDiscussion = useLastCallback(() => { onScreenSelect(ManagementScreens.Discussion); }); const handleClickReactions = useLastCallback(() => { onScreenSelect(ManagementScreens.Reactions); }); const handleClickPermissions = useLastCallback(() => { onScreenSelect(ManagementScreens.GroupPermissions); }); const handleClickAdministrators = useLastCallback(() => { onScreenSelect(ManagementScreens.ChatAdministrators); }); const handleClickInvites = useLastCallback(() => { onScreenSelect(ManagementScreens.Invites); }); const handleClickRequests = useLastCallback(() => { onScreenSelect(ManagementScreens.JoinRequests); }); const handleSetPhoto = useLastCallback((file: File) => { setPhoto(file); setIsProfileFieldsTouched(true); }); const handleTitleChange = useLastCallback((e: ChangeEvent) => { setTitle(e.target.value); setIsProfileFieldsTouched(true); }); const handleAboutChange = useLastCallback((e: ChangeEvent) => { setAbout(e.target.value); setIsProfileFieldsTouched(true); }); const handleUpdateGroup = useLastCallback(() => { const trimmedTitle = title.trim(); const trimmedAbout = about.trim(); if (!trimmedTitle.length) { setError(GROUP_TITLE_EMPTY); return; } updateChat({ chatId, title: trimmedTitle, about: trimmedAbout, photo, }); }); const handleClickMembers = useLastCallback(() => { onScreenSelect(ManagementScreens.GroupMembers); }); const handleTogglePreHistory = useLastCallback(() => { if (!chatFullInfo) { return; } const { isPreHistoryHidden } = chatFullInfo; togglePreHistoryHidden({ chatId: chat.id, isEnabled: !isPreHistoryHidden }); }); const handleForumToggle = useLastCallback(() => { setIsForumEnabled((current) => { const newIsForumEnabled = !current; runDebounced(() => { toggleForum({ chatId, isEnabled: newIsForumEnabled }); }); return newIsForumEnabled; }); }); useEffect(() => { if (!isChannelsPremiumLimitReached) { return; } // Teact does not have full support of controlled form components, we need to "disable" input value change manually // TODO Teact support added, this can now be removed const checkbox = isPreHistoryHiddenCheckboxRef.current?.querySelector('input') as HTMLInputElement; checkbox.checked = !chatFullInfo?.isPreHistoryHidden; }, [isChannelsPremiumLimitReached, chatFullInfo?.isPreHistoryHidden]); const chatReactionsDescription = useMemo(() => { if (!chatFullInfo?.enabledReactions) { return lang('ReactionsOff'); } if (chatFullInfo.enabledReactions.type === 'all') { return lang('ReactionsAll'); } const enabledLength = chatFullInfo.enabledReactions.allowed.length; const totalLength = availableReactions?.filter((reaction) => !reaction.isInactive).length || 0; return totalLength ? `${enabledLength} / ${totalLength}` : `${enabledLength}`; }, [availableReactions, chatFullInfo?.enabledReactions, lang]); const enabledPermissionsCount = useMemo(() => { if (!chat.defaultBannedRights) { return 0; } let totalCount = ALL_PERMISSIONS.filter( (key) => { if (key === 'manageTopics' && !isForumEnabled) return false; return !chat.defaultBannedRights![key as keyof ApiChatBannedRights]; }, ).length; const { sendStickers, sendGifs } = chat.defaultBannedRights; // These two rights are controlled with a single checkbox if (!sendStickers && !sendGifs) { totalCount += 1; } return totalCount; }, [chat.defaultBannedRights, isForumEnabled]); const adminsCount = useMemo(() => { return Object.keys(chatFullInfo?.adminMembersById || {}).length; }, [chatFullInfo?.adminMembersById]); const handleDeleteGroup = useLastCallback(() => { if (isBasicGroup) { deleteChat({ chatId: chat.id }); } else if (!chat.isCreator) { leaveChannel({ chatId: chat.id }); } else { deleteChannel({ chatId: chat.id }); } closeDeleteDialog(); closeManagement(); openChat({ id: undefined }); }); if (chat.isRestricted || chat.isForbidden) { return undefined; } const isLoading = progress === ManagementProgress.InProgress; return (