0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Wired delete and owner actions for staff users in adminX

refs https://github.com/TryGhost/Team/issues/3351

- allows owners/admins to delete users based on their permission level
- allows admins to be made owner users only if owner is logged in
This commit is contained in:
Rishabh 2023-06-06 09:17:54 +05:30
parent 885531b345
commit c88a073a5c
3 changed files with 77 additions and 31 deletions

View file

@ -5,7 +5,7 @@ import Icon from '../../../../admin-x-ds/global/Icon';
import ImageUpload from '../../../../admin-x-ds/global/ImageUpload'; import ImageUpload from '../../../../admin-x-ds/global/ImageUpload';
import Menu from '../../../../admin-x-ds/global/Menu'; import Menu from '../../../../admin-x-ds/global/Menu';
import Modal from '../../../../admin-x-ds/global/Modal'; import Modal from '../../../../admin-x-ds/global/Modal';
import NiceModal from '@ebay/nice-modal-react'; import NiceModal, {useModal} from '@ebay/nice-modal-react';
import Radio from '../../../../admin-x-ds/global/Radio'; import Radio from '../../../../admin-x-ds/global/Radio';
import React, {useContext, useEffect, useRef, useState} from 'react'; import React, {useContext, useEffect, useRef, useState} from 'react';
import SettingGroup from '../../../../admin-x-ds/settings/SettingGroup'; import SettingGroup from '../../../../admin-x-ds/settings/SettingGroup';
@ -14,8 +14,10 @@ import TextField from '../../../../admin-x-ds/global/TextField';
import Toggle from '../../../../admin-x-ds/global/Toggle'; import Toggle from '../../../../admin-x-ds/global/Toggle';
import useRoles from '../../../../hooks/useRoles'; import useRoles from '../../../../hooks/useRoles';
import {FileService, ServicesContext} from '../../../providers/ServiceProvider'; import {FileService, ServicesContext} from '../../../providers/ServiceProvider';
import {MenuItem} from '../../../../admin-x-ds/global/Menu';
import {User} from '../../../../types/api'; import {User} from '../../../../types/api';
import {isOwnerUser} from '../../../../utils/helpers'; import {isAdminUser, isOwnerUser} from '../../../../utils/helpers';
import {showToast} from '../../../../admin-x-ds/global/Toast';
interface CustomHeadingProps { interface CustomHeadingProps {
children?: React.ReactNode; children?: React.ReactNode;
@ -393,34 +395,12 @@ const UserMenuTrigger = () => (
</div> </div>
); );
const confirmMakeOwner = () => {
NiceModal.show(ConfirmationModal, {
title: 'Transfer Ownership',
prompt: 'Are you sure you want to transfer the ownership of this blog? You will not be able to undo this action.',
okLabel: 'Yep — I\'m sure',
okColor: 'red'
});
};
const confirmDelete = () => {
NiceModal.show(ConfirmationModal, {
title: 'Are you sure you want to delete this user?',
prompt: (
<>
<p className='mb-3'>The [user] will be permanently deleted and all their posts will be automatically assigned to the [site owner name].</p>
<p>To make these easy to find in the future, each post will be given an internal tag of [new internal tag with username]</p>
</>
),
okLabel: 'Delete user',
okColor: 'red'
});
};
const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => { const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
const {api} = useContext(ServicesContext); const {api} = useContext(ServicesContext);
const [userData, setUserData] = useState(user); const [userData, setUserData] = useState(user);
const [saveState, setSaveState] = useState(''); const [saveState, setSaveState] = useState('');
const {fileService} = useContext(ServicesContext) as {fileService: FileService}; const {fileService} = useContext(ServicesContext) as {fileService: FileService};
const mainModal = useModal();
const confirmSuspend = (_user: User) => { const confirmSuspend = (_user: User) => {
let warningText = 'This user will no longer be able to log in but their posts will be kept.'; let warningText = 'This user will no longer be able to log in but their posts will be kept.';
@ -447,6 +427,46 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
}); });
}; };
const confirmDelete = (_user: User) => {
NiceModal.show(ConfirmationModal, {
title: 'Are you sure you want to delete this user?',
prompt: (
<>
<p className='mb-3'>The [user] will be permanently deleted and all their posts will be automatically assigned to the [site owner name].</p>
<p>To make these easy to find in the future, each post will be given an internal tag of [new internal tag with username]</p>
</>
),
okLabel: 'Delete user',
okColor: 'red',
onOk: async (modal) => {
await api.users.delete(_user?.id);
modal?.remove();
mainModal?.remove();
showToast({
message: 'User deleted',
type: 'success'
});
}
});
};
const confirmMakeOwner = () => {
NiceModal.show(ConfirmationModal, {
title: 'Transfer Ownership',
prompt: 'Are you sure you want to transfer the ownership of this blog? You will not be able to undo this action.',
okLabel: 'Yep — I\'m sure',
okColor: 'red',
onOk: async (modal) => {
await api.users.makeOwner(user.id);
modal?.remove();
showToast({
message: 'Ownership transferred',
type: 'success'
});
}
});
};
const handleImageUpload = async (image: string, file: File) => { const handleImageUpload = async (image: string, file: File) => {
try { try {
const imageUrl = await fileService.uploadImage(file); const imageUrl = await fileService.uploadImage(file);
@ -477,17 +497,22 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
let suspendUserLabel = user?.status === 'inactive' ? 'Un-suspend user' : 'Suspend user'; let suspendUserLabel = user?.status === 'inactive' ? 'Un-suspend user' : 'Suspend user';
const menuItems = [ let menuItems: MenuItem[] = [];
{
if (isAdminUser(user)) {
menuItems.push({
id: 'make-owner', id: 'make-owner',
label: 'Make owner', label: 'Make owner',
onClick: confirmMakeOwner onClick: confirmMakeOwner
}, });
}
menuItems = menuItems.concat([
{ {
id: 'delete-user', id: 'delete-user',
label: 'Delete user', label: 'Delete user',
onClick: () => { onClick: () => {
confirmDelete(); confirmDelete(user);
} }
}, },
{ {
@ -499,9 +524,12 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
}, },
{ {
id: 'view-user-activity', id: 'view-user-activity',
label: 'View user activity' label: 'View user activity',
onClick: () => {
// TODO: show user activity
}
} }
]; ]);
let okLabel = saveState === 'saved' ? 'Saved' : 'Save'; let okLabel = saveState === 'saved' ? 'Saved' : 'Save';
if (saveState === 'saving') { if (saveState === 'saving') {

View file

@ -98,6 +98,7 @@ interface API {
edit: (editedUser: User) => Promise<UsersResponseType>; edit: (editedUser: User) => Promise<UsersResponseType>;
delete: (userId: string) => Promise<DeleteUserResponse>; delete: (userId: string) => Promise<DeleteUserResponse>;
updatePassword: (options: UpdatePasswordOptions) => Promise<PasswordUpdateResponseType>; updatePassword: (options: UpdatePasswordOptions) => Promise<PasswordUpdateResponseType>;
makeOwner: (userId: string) => Promise<UsersResponseType>;
}; };
roles: { roles: {
browse: (options?: BrowseRoleOptions) => Promise<RolesResponseType>; browse: (options?: BrowseRoleOptions) => Promise<RolesResponseType>;
@ -219,6 +220,19 @@ function setupGhostApi({ghostVersion}: GhostApiOptions): API {
}); });
const data: DeleteUserResponse = await response.json(); const data: DeleteUserResponse = await response.json();
return data; return data;
},
makeOwner: async (userId: string) => {
const payload = JSON.stringify({
owner: [{
id: userId
}]
});
const response = await fetcher(`/users/owner/`, {
method: 'PUT',
body: payload
});
const data: UsersResponseType = await response.json();
return data;
} }
}, },
roles: { roles: {

View file

@ -62,3 +62,7 @@ export function generateAvatarColor(name: string) {
export function isOwnerUser(user: User) { export function isOwnerUser(user: User) {
return user.roles.some(role => role.name === 'Owner'); return user.roles.some(role => role.name === 'Owner');
} }
export function isAdminUser(user: User) {
return user.roles.some(role => role.name === 'Administrator');
}