Management: Support promoting members to admins (#1629)
This commit is contained in:
parent
bad220ff29
commit
0b40f27ed9
@ -133,6 +133,8 @@ const RightColumn: FC<StateProps> = ({
|
||||
setIsPromotedByCurrentUser(undefined);
|
||||
break;
|
||||
case ManagementScreens.ChatAdminRights:
|
||||
case ManagementScreens.ChatNewAdminRights:
|
||||
case ManagementScreens.GroupAddAdmins:
|
||||
case ManagementScreens.GroupRecentActions:
|
||||
setManagementScreen(ManagementScreens.ChatAdministrators);
|
||||
break;
|
||||
|
||||
@ -77,7 +77,9 @@ enum HeaderContent {
|
||||
ManageGroupUserPermissions,
|
||||
ManageGroupRecentActions,
|
||||
ManageGroupAdminRights,
|
||||
ManageGroupNewAdminRights,
|
||||
ManageGroupMembers,
|
||||
ManageGroupAddAdmins,
|
||||
StickerSearch,
|
||||
GifSearch,
|
||||
PollResults,
|
||||
@ -187,8 +189,12 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
HeaderContent.ManageGroupRecentActions
|
||||
) : managementScreen === ManagementScreens.ChatAdminRights ? (
|
||||
HeaderContent.ManageGroupAdminRights
|
||||
) : managementScreen === ManagementScreens.ChatNewAdminRights ? (
|
||||
HeaderContent.ManageGroupNewAdminRights
|
||||
) : managementScreen === ManagementScreens.GroupMembers ? (
|
||||
HeaderContent.ManageGroupMembers
|
||||
) : managementScreen === ManagementScreens.GroupAddAdmins ? (
|
||||
HeaderContent.ManageGroupAddAdmins
|
||||
) : undefined // Never reached
|
||||
) : undefined; // When column is closed
|
||||
|
||||
@ -235,6 +241,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
return <h3>{lang('Group.Info.AdminLog')}</h3>;
|
||||
case HeaderContent.ManageGroupAdminRights:
|
||||
return <h3>{lang('EditAdminRights')}</h3>;
|
||||
case HeaderContent.ManageGroupNewAdminRights:
|
||||
return <h3>{lang('SetAsAdmin')}</h3>;
|
||||
case HeaderContent.ManageGroupPermissions:
|
||||
return <h3>{lang('ChannelPermissions')}</h3>;
|
||||
case HeaderContent.ManageGroupRemovedUsers:
|
||||
@ -243,6 +251,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
return <h3>{lang('ChannelAddException')}</h3>;
|
||||
case HeaderContent.ManageGroupUserPermissions:
|
||||
return <h3>{lang('UserRestrictions')}</h3>;
|
||||
case HeaderContent.ManageGroupAddAdmins:
|
||||
return <h3>{lang('Channel.Management.AddModerator')}</h3>;
|
||||
case HeaderContent.StickerSearch:
|
||||
return (
|
||||
<SearchInput
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useMemo,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
import { getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ManagementScreens } from '../../../types';
|
||||
import { ApiChat, ApiChatMember, ApiUser } from '../../../api/types';
|
||||
import { ApiChat, ApiChatMember } from '../../../api/types';
|
||||
|
||||
import { getUserFullName, isChatChannel } from '../../../modules/helpers';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
@ -13,6 +13,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
@ -26,14 +27,12 @@ type StateProps = {
|
||||
chat: ApiChat;
|
||||
currentUserId?: string;
|
||||
isChannel: boolean;
|
||||
usersById: Record<string, ApiUser>;
|
||||
};
|
||||
|
||||
const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
|
||||
chat,
|
||||
isChannel,
|
||||
currentUserId,
|
||||
usersById,
|
||||
onScreenSelect,
|
||||
onChatMemberSelect,
|
||||
onClose,
|
||||
@ -68,11 +67,17 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
|
||||
onScreenSelect(ManagementScreens.ChatAdminRights);
|
||||
}, [currentUserId, onChatMemberSelect, onScreenSelect]);
|
||||
|
||||
const handleAddAdminClick = useCallback(() => {
|
||||
onScreenSelect(ManagementScreens.GroupAddAdmins);
|
||||
}, [onScreenSelect]);
|
||||
|
||||
const getMemberStatus = useCallback((member: ApiChatMember) => {
|
||||
if (member.isOwner) {
|
||||
return lang('ChannelCreator');
|
||||
}
|
||||
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
const promotedByUser = member.promotedByUserId ? usersById[member.promotedByUserId] : undefined;
|
||||
|
||||
if (promotedByUser) {
|
||||
@ -80,7 +85,7 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
return lang('ChannelAdmin');
|
||||
}, [lang, usersById]);
|
||||
}, [lang]);
|
||||
|
||||
return (
|
||||
<div className="Management">
|
||||
@ -116,6 +121,14 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
|
||||
<FloatingActionButton
|
||||
isShown
|
||||
onClick={handleAddAdminClick}
|
||||
ariaLabel={lang('Channel.Management.AddModerator')}
|
||||
>
|
||||
<i className="icon-add-user-filled" />
|
||||
</FloatingActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -125,13 +138,11 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId)!;
|
||||
const { byId: usersById } = global.users;
|
||||
|
||||
return {
|
||||
chat,
|
||||
currentUserId: global.currentUserId,
|
||||
isChannel: isChatChannel(chat),
|
||||
usersById,
|
||||
};
|
||||
},
|
||||
)(ManageChatAdministrators));
|
||||
|
||||
@ -24,6 +24,7 @@ type OwnProps = {
|
||||
chatId: string;
|
||||
selectedChatMemberId?: string;
|
||||
isPromotedByCurrentUser?: boolean;
|
||||
isNewAdmin?: boolean;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
@ -35,12 +36,15 @@ type StateProps = {
|
||||
currentUserId?: string;
|
||||
isChannel: boolean;
|
||||
isFormFullyDisabled: boolean;
|
||||
defaultRights?: ApiChatAdminRights;
|
||||
};
|
||||
|
||||
const CUSTOM_TITLE_MAX_LENGTH = 16;
|
||||
|
||||
const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
isNewAdmin,
|
||||
selectedChatMemberId,
|
||||
defaultRights,
|
||||
onScreenSelect,
|
||||
chat,
|
||||
usersById,
|
||||
@ -53,7 +57,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
const { updateChatAdmin } = getDispatch();
|
||||
|
||||
const [permissions, setPermissions] = useState<ApiChatAdminRights>({});
|
||||
const [isTouched, setIsTouched] = useState(false);
|
||||
const [isTouched, setIsTouched] = useState(isNewAdmin);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isDismissConfirmationDialogOpen, openDismissConfirmationDialog, closeDismissConfirmationDialog] = useFlag();
|
||||
const [customTitle, setCustomTitle] = useState('');
|
||||
@ -62,12 +66,18 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const selectedChatMember = useMemo(() => {
|
||||
if (!chat.fullInfo || !chat.fullInfo.adminMembers) {
|
||||
return undefined;
|
||||
const selectedAdminMember = chat.fullInfo?.adminMembers?.find(({ userId }) => userId === selectedChatMemberId);
|
||||
|
||||
if (isNewAdmin) {
|
||||
// If selectedAdminMember is fullfilled, it means that we are editing an existing admin (after a user
|
||||
// has been promoted as admin)
|
||||
return selectedAdminMember
|
||||
? undefined
|
||||
: chat.fullInfo?.members?.find(({ userId }) => userId === selectedChatMemberId);
|
||||
}
|
||||
|
||||
return chat.fullInfo.adminMembers.find(({ userId }) => userId === selectedChatMemberId);
|
||||
}, [chat, selectedChatMemberId]);
|
||||
return selectedAdminMember;
|
||||
}, [chat.fullInfo, isNewAdmin, selectedChatMemberId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chat?.fullInfo && selectedChatMemberId && !selectedChatMember) {
|
||||
@ -76,11 +86,11 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
}, [chat, onScreenSelect, selectedChatMember, selectedChatMemberId]);
|
||||
|
||||
useEffect(() => {
|
||||
setPermissions((selectedChatMember?.adminRights) || {});
|
||||
setCustomTitle(((selectedChatMember?.customTitle) || '').substr(0, CUSTOM_TITLE_MAX_LENGTH));
|
||||
setIsTouched(false);
|
||||
setPermissions((isNewAdmin ? defaultRights : selectedChatMember?.adminRights) || {});
|
||||
setCustomTitle(((isNewAdmin ? 'admin' : selectedChatMember?.customTitle) || '').substr(0, CUSTOM_TITLE_MAX_LENGTH));
|
||||
setIsTouched(Boolean(isNewAdmin));
|
||||
setIsLoading(false);
|
||||
}, [selectedChatMember]);
|
||||
}, [defaultRights, isNewAdmin, selectedChatMember]);
|
||||
|
||||
const handlePermissionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name } = e.target;
|
||||
@ -108,7 +118,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
adminRights: permissions,
|
||||
customTitle,
|
||||
});
|
||||
}, [chat, selectedChatMemberId, permissions, customTitle, updateChatAdmin]);
|
||||
}, [selectedChatMemberId, updateChatAdmin, chat.id, permissions, customTitle]);
|
||||
|
||||
const handleDismissAdmin = useCallback(() => {
|
||||
if (!selectedChatMemberId) {
|
||||
@ -136,7 +146,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
}, [chat, isFormFullyDisabled]);
|
||||
|
||||
const memberStatus = useMemo(() => {
|
||||
if (!selectedChatMember) {
|
||||
if (isNewAdmin || !selectedChatMember) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -153,7 +163,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
return lang('ChannelAdmin');
|
||||
}, [selectedChatMember, usersById, lang]);
|
||||
}, [isNewAdmin, selectedChatMember, usersById, lang]);
|
||||
|
||||
const handleCustomTitleChange = useCallback((e) => {
|
||||
const { value } = e.target;
|
||||
@ -307,7 +317,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentUserId !== selectedChatMemberId && !isFormFullyDisabled && (
|
||||
{currentUserId !== selectedChatMemberId && !isFormFullyDisabled && !isNewAdmin && (
|
||||
<ListItem icon="delete" ripple destructive onClick={openDismissConfirmationDialog}>
|
||||
{lang('EditAdminRemoveAdmin')}
|
||||
</ListItem>
|
||||
@ -328,14 +338,16 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
</FloatingActionButton>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={isDismissConfirmationDialogOpen}
|
||||
onClose={closeDismissConfirmationDialog}
|
||||
text="Are you sure you want to dismiss this admin?"
|
||||
confirmLabel="Dismiss"
|
||||
confirmHandler={handleDismissAdmin}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
{!isNewAdmin && (
|
||||
<ConfirmDialog
|
||||
isOpen={isDismissConfirmationDialogOpen}
|
||||
onClose={closeDismissConfirmationDialog}
|
||||
text="Are you sure you want to dismiss this admin?"
|
||||
confirmLabel={lang('Channel.Admin.Dismiss')}
|
||||
confirmHandler={handleDismissAdmin}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -354,6 +366,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
currentUserId,
|
||||
isChannel,
|
||||
isFormFullyDisabled,
|
||||
defaultRights: chat.adminRights,
|
||||
};
|
||||
},
|
||||
)(ManageGroupAdminRights));
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useMemo,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||
import { getDispatch, getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiChatMember, ApiUserStatus } from '../../../api/types';
|
||||
import { ManagementScreens } from '../../../types';
|
||||
|
||||
import { ApiChatMember, ApiUser, ApiUserStatus } from '../../../api/types';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import { sortUserIds, isChatChannel } from '../../../modules/helpers';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
@ -14,46 +16,62 @@ import ListItem from '../../ui/ListItem';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
noAdmins?: boolean;
|
||||
onClose: NoneToVoidFunction;
|
||||
onScreenSelect?: (screen: ManagementScreens) => void;
|
||||
onChatMemberSelect?: (memberId: string, isPromotedByCurrentUser?: boolean) => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
usersById: Record<string, ApiUser>;
|
||||
userStatusesById: Record<string, ApiUserStatus>;
|
||||
members?: ApiChatMember[];
|
||||
adminMembers?: ApiChatMember[];
|
||||
isChannel?: boolean;
|
||||
serverTimeOffset: number;
|
||||
};
|
||||
|
||||
const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
||||
noAdmins,
|
||||
members,
|
||||
usersById,
|
||||
adminMembers,
|
||||
userStatusesById,
|
||||
isChannel,
|
||||
onClose,
|
||||
isActive,
|
||||
serverTimeOffset,
|
||||
onClose,
|
||||
onScreenSelect,
|
||||
onChatMemberSelect,
|
||||
}) => {
|
||||
const { openUserInfo } = getDispatch();
|
||||
|
||||
const memberIds = useMemo(() => {
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
if (!members || !usersById) {
|
||||
return undefined;
|
||||
}
|
||||
const adminIds = noAdmins ? adminMembers?.map(({ userId }) => userId) || [] : [];
|
||||
|
||||
return sortUserIds(
|
||||
const userIds = sortUserIds(
|
||||
members.map(({ userId }) => userId),
|
||||
usersById,
|
||||
userStatusesById,
|
||||
undefined,
|
||||
serverTimeOffset,
|
||||
);
|
||||
}, [members, serverTimeOffset, usersById, userStatusesById]);
|
||||
|
||||
return noAdmins ? userIds.filter((userId) => !adminIds.includes(userId)) : userIds;
|
||||
}, [members, noAdmins, adminMembers, userStatusesById, serverTimeOffset]);
|
||||
|
||||
const handleMemberClick = useCallback((id: string) => {
|
||||
openUserInfo({ id });
|
||||
}, [openUserInfo]);
|
||||
if (noAdmins) {
|
||||
onChatMemberSelect!(id, false);
|
||||
onScreenSelect!(ManagementScreens.ChatNewAdminRights);
|
||||
} else {
|
||||
openUserInfo({ id });
|
||||
}
|
||||
}, [noAdmins, onChatMemberSelect, onScreenSelect, openUserInfo]);
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
@ -88,13 +106,14 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||
const { statusesById: userStatusesById } = global.users;
|
||||
const members = chat?.fullInfo?.members;
|
||||
const adminMembers = chat?.fullInfo?.adminMembers;
|
||||
const isChannel = chat && isChatChannel(chat);
|
||||
|
||||
return {
|
||||
members,
|
||||
usersById,
|
||||
adminMembers,
|
||||
userStatusesById,
|
||||
isChannel,
|
||||
serverTimeOffset: global.serverTimeOffset,
|
||||
|
||||
@ -73,6 +73,7 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
ManagementScreens.GroupUserPermissionsCreate,
|
||||
ManagementScreens.GroupUserPermissions,
|
||||
ManagementScreens.ChatAdminRights,
|
||||
ManagementScreens.ChatNewAdminRights,
|
||||
ManagementScreens.GroupRecentActions,
|
||||
].includes(currentScreen)}
|
||||
/>
|
||||
@ -90,6 +91,7 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
ManagementScreens.Discussion,
|
||||
ManagementScreens.ChatPrivacyType,
|
||||
ManagementScreens.ChatAdminRights,
|
||||
ManagementScreens.ChatNewAdminRights,
|
||||
ManagementScreens.GroupRecentActions,
|
||||
].includes(currentScreen)}
|
||||
/>
|
||||
@ -175,6 +177,7 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
onChatMemberSelect={onChatMemberSelect}
|
||||
isActive={isActive || [
|
||||
ManagementScreens.ChatAdminRights,
|
||||
ManagementScreens.ChatNewAdminRights,
|
||||
ManagementScreens.GroupRecentActions,
|
||||
].includes(currentScreen)}
|
||||
onClose={onClose}
|
||||
@ -202,6 +205,19 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
);
|
||||
|
||||
case ManagementScreens.ChatNewAdminRights:
|
||||
return (
|
||||
<ManageGroupAdminRights
|
||||
chatId={chatId}
|
||||
isNewAdmin
|
||||
selectedChatMemberId={selectedChatMemberId}
|
||||
isPromotedByCurrentUser={isPromotedByCurrentUser}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
case ManagementScreens.ChannelSubscribers:
|
||||
case ManagementScreens.GroupMembers:
|
||||
return (
|
||||
@ -211,6 +227,18 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
case ManagementScreens.GroupAddAdmins:
|
||||
return (
|
||||
<ManageGroupMembers
|
||||
chatId={chatId}
|
||||
noAdmins
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
onScreenSelect={onScreenSelect}
|
||||
onChatMemberSelect={onChatMemberSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return undefined; // Never reached
|
||||
|
||||
@ -763,8 +763,8 @@ addReducer('updateChatAdmin', (global, actions, payload) => {
|
||||
chat, user, adminRights, customTitle,
|
||||
});
|
||||
|
||||
const chatAfterUpdate = await callApi('fetchFullChat', chat);
|
||||
const newGlobal = getGlobal();
|
||||
const chatAfterUpdate = selectChat(newGlobal, chatId);
|
||||
|
||||
if (!chatAfterUpdate || !chatAfterUpdate.fullInfo) {
|
||||
return;
|
||||
|
||||
@ -317,7 +317,9 @@ export enum ManagementScreens {
|
||||
ChatAdministrators,
|
||||
GroupRecentActions,
|
||||
ChatAdminRights,
|
||||
ChatNewAdminRights,
|
||||
GroupMembers,
|
||||
GroupAddAdmins,
|
||||
}
|
||||
|
||||
export type ManagementType = 'user' | 'group' | 'channel';
|
||||
|
||||
@ -63,6 +63,11 @@ const READABLE_ERROR_MESSAGES: Record<string, string> = {
|
||||
USER_ALREADY_PARTICIPANT: 'You already in the group',
|
||||
SCHEDULE_DATE_INVALID: 'Invalid schedule date provided',
|
||||
WALLPAPER_DIMENSIONS_INVALID: 'The wallpaper dimensions are invalid, please select another file',
|
||||
ADMINS_TOO_MUCH: 'There are too many admins',
|
||||
ADMIN_RANK_EMOJI_NOT_ALLOWED: 'An admin rank cannot contain emojis',
|
||||
ADMIN_RANK_INVALID: 'The specified admin rank is invalid',
|
||||
FRESH_CHANGE_ADMINS_FORBIDDEN: 'You were just elected admin, you can\'t add or modify other admins yet',
|
||||
INPUT_USER_DEACTIVATED: 'The specified user was deleted',
|
||||
};
|
||||
|
||||
export const SHIPPING_ERRORS: Record<string, ApiFieldError> = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user