TelegramPWA/src/components/right/management/ManageGroupPermissions.tsx

435 lines
14 KiB
TypeScript

import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { ManagementScreens } from '../../../types';
import type { ApiChat, ApiChatBannedRights, ApiChatMember } from '../../../api/types';
import stopEvent from '../../../util/stopEvent';
import buildClassName from '../../../util/buildClassName';
import { isChatPublic } from '../../../global/helpers';
import useLang from '../../../hooks/useLang';
import { selectChat } from '../../../global/selectors';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useManagePermissions from '../hooks/useManagePermissions';
import ListItem from '../../ui/ListItem';
import Checkbox from '../../ui/Checkbox';
import FloatingActionButton from '../../ui/FloatingActionButton';
import Spinner from '../../ui/Spinner';
import PrivateChatInfo from '../../common/PrivateChatInfo';
type OwnProps = {
chatId: string;
onScreenSelect: (screen: ManagementScreens) => void;
onChatMemberSelect: (memberId: string, isPromotedByCurrentUser?: boolean) => void;
onClose: NoneToVoidFunction;
isActive: boolean;
};
type StateProps = {
chat?: ApiChat;
currentUserId?: string;
hasLinkedChat?: boolean;
};
const ITEM_HEIGHT = 24 + 32;
const BEFORE_ITEMS_COUNT = 2;
const ITEMS_COUNT = 9;
function getLangKeyForBannedRightKey(key: keyof ApiChatBannedRights) {
switch (key) {
case 'sendMessages':
return 'UserRestrictionsNoSend';
case 'sendMedia':
return 'UserRestrictionsNoSendMedia';
case 'sendStickers':
return 'UserRestrictionsNoSendStickers';
case 'embedLinks':
return 'UserRestrictionsNoEmbedLinks';
case 'sendPolls':
return 'UserRestrictionsNoSendPolls';
case 'changeInfo':
return 'UserRestrictionsNoChangeInfo';
case 'inviteUsers':
return 'UserRestrictionsInviteUsers';
case 'pinMessages':
return 'UserRestrictionsPinMessages';
case 'manageTopics':
return 'GroupPermission.NoManageTopics';
case 'sendPlain':
return 'UserRestrictionsNoSendText';
case 'sendDocs':
return 'UserRestrictionsNoSendDocs';
case 'sendRoundvideos':
return 'UserRestrictionsNoSendRound';
case 'sendVoices':
return 'UserRestrictionsNoSendVoice';
case 'sendAudios':
return 'UserRestrictionsNoSendMusic';
case 'sendVideos':
return 'UserRestrictionsNoSendVideos';
case 'sendPhotos':
return 'UserRestrictionsNoSendPhotos';
default:
return undefined;
}
}
const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
onScreenSelect,
onChatMemberSelect,
chat,
currentUserId,
hasLinkedChat,
onClose,
isActive,
}) => {
const { updateChatDefaultBannedRights, showNotification } = getActions();
const {
permissions, havePermissionChanged, isLoading, handlePermissionChange, setIsLoading,
} = useManagePermissions(chat?.defaultBannedRights);
const lang = useLang();
const { isForum } = chat || {};
const isPublic = useMemo(() => chat && isChatPublic(chat), [chat]);
const shouldDisablePermissionForPublicGroup = hasLinkedChat || isPublic;
useHistoryBack({
isActive,
onBack: onClose,
});
const handleRemovedUsersClick = useCallback(() => {
onScreenSelect(ManagementScreens.GroupRemovedUsers);
}, [onScreenSelect]);
const handleAddExceptionClick = useCallback(() => {
onScreenSelect(ManagementScreens.GroupUserPermissionsCreate);
}, [onScreenSelect]);
const handleExceptionMemberClick = useCallback((member: ApiChatMember) => {
onChatMemberSelect(member.userId, member.promotedByUserId === currentUserId);
onScreenSelect(ManagementScreens.GroupUserPermissions);
}, [currentUserId, onChatMemberSelect, onScreenSelect]);
const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false);
const handleOpenMediaDropdown = useCallback((e: React.MouseEvent) => {
stopEvent(e);
setIsMediaDropdownOpen(!isMediaDropdownOpen);
}, [isMediaDropdownOpen]);
const handleDisabledClick = useCallback(() => {
showNotification({ message: lang('lng_rights_permission_unavailable') });
}, [lang, showNotification]);
const handleSavePermissions = useCallback(() => {
if (!chat) {
return;
}
setIsLoading(true);
updateChatDefaultBannedRights({ chatId: chat.id, bannedRights: permissions });
}, [chat, permissions, setIsLoading, updateChatDefaultBannedRights]);
const removedUsersCount = useMemo(() => {
if (!chat || !chat.fullInfo || !chat.fullInfo.kickedMembers) {
return 0;
}
return chat.fullInfo.kickedMembers.length;
}, [chat]);
const exceptionMembers = useMemo(() => {
if (!chat || !chat.fullInfo || !chat.fullInfo.members) {
return [];
}
return chat.fullInfo.members.filter(({ bannedRights }) => Boolean(bannedRights));
}, [chat]);
const getMemberExceptions = useCallback((member: ApiChatMember) => {
const { bannedRights } = member;
if (!bannedRights || !chat) {
return undefined;
}
const { defaultBannedRights } = chat;
return Object.keys(bannedRights).reduce((result, k) => {
const key = k as keyof ApiChatBannedRights;
if (
!bannedRights[key]
|| (defaultBannedRights?.[key])
|| key === 'sendInline' || key === 'viewMessages' || key === 'sendGames'
) {
return result;
}
const langKey = getLangKeyForBannedRightKey(key);
if (!langKey) {
return result;
}
const translatedString = lang(langKey);
return `${result}${!result.length ? translatedString : `, ${translatedString}`}`;
}, '');
}, [chat, lang]);
return (
<div
className="Management with-shifted-dropdown"
style={`--shift-height: ${ITEMS_COUNT * ITEM_HEIGHT}px;`
+ `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT}px;`}
>
<div className="custom-scroll">
<div className="section without-bottom-shadow">
<h3 className="section-heading" dir="auto">{lang('ChannelPermissionsHeader')}</h3>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendPlain"
checked={!permissions.sendPlain}
label={lang('UserRestrictionsSend')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendMedia"
checked={!permissions.sendMedia}
label={lang('UserRestrictionsSendMedia')}
blocking
rightIcon={isMediaDropdownOpen ? 'up' : 'down'}
onChange={handlePermissionChange}
onClickLabel={handleOpenMediaDropdown}
/>
</div>
<div className="DropdownListTrap">
<div
className={buildClassName(
'DropdownList',
isMediaDropdownOpen && 'DropdownList--open',
)}
>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendPhotos"
checked={!permissions.sendPhotos}
label={lang('UserRestrictionsSendPhotos')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendVideos"
checked={!permissions.sendVideos}
label={lang('UserRestrictionsSendVideos')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendStickers"
checked={!permissions.sendStickers && !permissions.sendGifs}
label={lang('UserRestrictionsSendStickers')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendAudios"
checked={!permissions.sendAudios}
label={lang('UserRestrictionsSendMusic')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendDocs"
checked={!permissions.sendDocs}
label={lang('UserRestrictionsSendFiles')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendVoices"
checked={!permissions.sendVoices}
label={lang('UserRestrictionsSendVoices')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendRoundvideos"
checked={!permissions.sendRoundvideos}
label={lang('UserRestrictionsSendRound')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="embedLinks"
checked={!permissions.embedLinks}
label={lang('UserRestrictionsEmbedLinks')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="sendPolls"
checked={!permissions.sendPolls}
label={lang('UserRestrictionsSendPolls')}
blocking
onChange={handlePermissionChange}
/>
</div>
</div>
</div>
<div className={buildClassName('part', isMediaDropdownOpen && 'shifted')}>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="inviteUsers"
checked={!permissions.inviteUsers}
label={lang('UserRestrictionsInviteUsers')}
blocking
onChange={handlePermissionChange}
/>
</div>
<div
className="ListItem no-selection with-checkbox"
onClick={shouldDisablePermissionForPublicGroup ? handleDisabledClick : undefined}
>
<Checkbox
name="pinMessages"
checked={!permissions.pinMessages}
label={lang('UserRestrictionsPinMessages')}
disabled={shouldDisablePermissionForPublicGroup}
blocking
onChange={handlePermissionChange}
/>
</div>
<div
className="ListItem no-selection with-checkbox"
onClick={shouldDisablePermissionForPublicGroup ? handleDisabledClick : undefined}
>
<Checkbox
name="changeInfo"
checked={!permissions.changeInfo}
label={lang('UserRestrictionsChangeInfo')}
blocking
disabled={shouldDisablePermissionForPublicGroup}
onChange={handlePermissionChange}
/>
</div>
{isForum && (
<div className="ListItem no-selection with-checkbox">
<Checkbox
name="manageTopics"
checked={!permissions.manageTopics}
label={lang('CreateTopicsPermission')}
blocking
onChange={handlePermissionChange}
/>
</div>
)}
</div>
</div>
<div
className={buildClassName(
'section',
isMediaDropdownOpen && 'shifted',
)}
>
<ListItem
icon="delete-user"
multiline
narrow
onClick={handleRemovedUsersClick}
>
<span className="title">{lang('ChannelBlockedUsers')}</span>
<span className="subtitle">{removedUsersCount}</span>
</ListItem>
</div>
<div
className={buildClassName(
'section',
isMediaDropdownOpen && 'shifted',
)}
>
<h3 className="section-heading" dir="auto">{lang('PrivacyExceptions')}</h3>
<ListItem
icon="add-user"
onClick={handleAddExceptionClick}
>
{lang('ChannelAddException')}
</ListItem>
{exceptionMembers.map((member) => (
<ListItem
key={member.userId}
className="chat-item-clickable exceptions-member"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleExceptionMemberClick(member)}
>
<PrivateChatInfo
userId={member.userId}
status={getMemberExceptions(member)}
forceShowSelf
/>
</ListItem>
))}
</div>
</div>
<FloatingActionButton
isShown={havePermissionChanged}
onClick={handleSavePermissions}
ariaLabel={lang('Save')}
disabled={isLoading}
>
{isLoading ? (
<Spinner color="white" />
) : (
<i className="icon-check" />
)}
</FloatingActionButton>
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global, { chatId }): StateProps => {
const chat = selectChat(global, chatId);
const hasLinkedChat = Boolean(chat?.fullInfo?.linkedChatId);
return { chat, currentUserId: global.currentUserId, hasLinkedChat };
},
)(ManageGroupPermissions));