feat(frontend): added user deletion to the user list

also includes small updates to the api to prevent administrators from being deleted, as well as
migrations to cascade deletions to requests the users made

fixes #348
This commit is contained in:
sct
2020-12-17 16:03:27 +00:00
parent ee84f74f8a
commit 727fa06c18
7 changed files with 175 additions and 8 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import useSWR from 'swr';
import LoadingSpinner from '../Common/LoadingSpinner';
import type { User } from '../../../server/entity/User';
@@ -10,6 +10,11 @@ import { Permission } from '../../hooks/useUser';
import { useRouter } from 'next/router';
import Header from '../Common/Header';
import Table from '../Common/Table';
import Transition from '../Transition';
import Modal from '../Common/Modal';
import axios from 'axios';
import { useToasts } from 'react-toast-notifications';
import globalMessages from '../../i18n/globalMessages';
const messages = defineMessages({
userlist: 'User List',
@@ -24,12 +29,46 @@ const messages = defineMessages({
admin: 'Admin',
user: 'User',
plexuser: 'Plex User',
deleteuser: 'Delete User',
userdeleted: 'User deleted',
userdeleteerror: 'Something went wrong deleting the user',
deleteconfirm:
'Are you sure you want to delete this user? All existing request data from this user will be removed.',
});
const UserList: React.FC = () => {
const intl = useIntl();
const router = useRouter();
const { data, error } = useSWR<User[]>('/api/v1/user');
const { addToast } = useToasts();
const { data, error, revalidate } = useSWR<User[]>('/api/v1/user');
const [isDeleting, setDeleting] = useState(false);
const [deleteModal, setDeleteModal] = useState<{
isOpen: boolean;
user?: User;
}>({
isOpen: false,
});
const deleteUser = async () => {
setDeleting(true);
try {
await axios.delete(`/api/v1/user/${deleteModal.user?.id}`);
addToast(intl.formatMessage(messages.userdeleted), {
autoDismiss: true,
appearance: 'success',
});
setDeleteModal({ isOpen: false });
} catch (e) {
addToast(intl.formatMessage(messages.userdeleteerror), {
autoDismiss: true,
appearance: 'error',
});
} finally {
revalidate();
}
};
if (!data && !error) {
return <LoadingSpinner />;
@@ -37,6 +76,46 @@ const UserList: React.FC = () => {
return (
<>
<Transition
enter="opacity-0 transition duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={deleteModal.isOpen}
>
<Modal
onOk={() => deleteUser()}
okText={
isDeleting
? intl.formatMessage(globalMessages.deleting)
: intl.formatMessage(globalMessages.delete)
}
okDisabled={isDeleting}
okButtonType="danger"
onCancel={() => setDeleteModal({ isOpen: false })}
title={intl.formatMessage(messages.deleteuser)}
iconSvg={
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
}
>
{intl.formatMessage(messages.deleteconfirm)}
</Modal>
</Transition>
<Header extraMargin={4}>{intl.formatMessage(messages.userlist)}</Header>
<Table>
<thead>
@@ -104,7 +183,11 @@ const UserList: React.FC = () => {
>
{intl.formatMessage(messages.edit)}
</Button>
<Button buttonType="danger">
<Button
buttonType="danger"
disabled={hasPermission(Permission.ADMIN, user.permissions)}
onClick={() => setDeleteModal({ isOpen: true, user })}
>
{intl.formatMessage(messages.delete)}
</Button>
</Table.TD>