feat: view other user files (#408)
* feat: Add the capability of viewing another user's images as admin. * fix: add columns, oops... * fix: Gotta check if the user's legit before letting them see * fix: made administrators non-viewable * Please don't reference yourself * fix: superAdmin > admin --------- Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
This commit is contained in:
parent
a2c085719a
commit
f40d65a9f7
8 changed files with 153 additions and 4 deletions
|
@ -49,6 +49,7 @@ export default function FileModal({
|
|||
reducedActions = false,
|
||||
exifEnabled,
|
||||
compress,
|
||||
otherUser = false,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
|
@ -58,6 +59,7 @@ export default function FileModal({
|
|||
reducedActions?: boolean;
|
||||
exifEnabled?: boolean;
|
||||
compress: boolean;
|
||||
otherUser: boolean;
|
||||
}) {
|
||||
const deleteFile = useFileDelete();
|
||||
const favoriteFile = useFileFavorite();
|
||||
|
@ -276,7 +278,7 @@ export default function FileModal({
|
|||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
{reducedActions ? null : inFolder && !folders.isLoading ? (
|
||||
{reducedActions || otherUser ? null : inFolder && !folders.isLoading ? (
|
||||
<Tooltip
|
||||
label={`Remove from folder "${folders.data.find((f) => f.id === file.folderId)?.name ?? ''}"`}
|
||||
>
|
||||
|
|
|
@ -32,9 +32,10 @@ export default function File({
|
|||
image,
|
||||
disableMediaPreview,
|
||||
exifEnabled,
|
||||
refreshImages,
|
||||
refreshImages = undefined,
|
||||
reducedActions = false,
|
||||
onDash,
|
||||
otherUser = false,
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const deleteFile = useFileDelete();
|
||||
|
@ -44,7 +45,7 @@ export default function File({
|
|||
const folders = useFolders();
|
||||
|
||||
const refresh = () => {
|
||||
refreshImages();
|
||||
if (!otherUser) refreshImages();
|
||||
folders.refetch();
|
||||
};
|
||||
|
||||
|
@ -59,6 +60,7 @@ export default function File({
|
|||
reducedActions={reducedActions}
|
||||
exifEnabled={exifEnabled}
|
||||
compress={onDash}
|
||||
otherUser={otherUser}
|
||||
/>
|
||||
|
||||
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}>
|
||||
|
|
|
@ -126,6 +126,7 @@ export default function Dashboard({ disableMediaPreview, exifEnabled, compress }
|
|||
reducedActions={false}
|
||||
exifEnabled={exifEnabled}
|
||||
compress={compress}
|
||||
otherUser={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
82
src/components/pages/Users/UserFiles.tsx
Normal file
82
src/components/pages/Users/UserFiles.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { ActionIcon, Button, Center, Group, SimpleGrid, Title } from '@mantine/core';
|
||||
import { File } from '@prisma/client';
|
||||
import { IconArrowLeft, IconFile } from '@tabler/icons-react';
|
||||
import FileComponent from 'components/File';
|
||||
import MutedText from 'components/MutedText';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { userSelector } from 'lib/recoil/user';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
type UserFiles = {
|
||||
id: number;
|
||||
username: string;
|
||||
files?: File[];
|
||||
error?: unknown;
|
||||
};
|
||||
|
||||
export default function UserFiles({ userId, disableMediaPreview, exifEnabled, compress }) {
|
||||
const [currentUser, viewUser] = useState<UserFiles>({ id: 0, username: 'user' });
|
||||
const [self] = useRecoilState(userSelector);
|
||||
|
||||
const { push } = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (self.id == userId) push('/dashboard/files');
|
||||
(async () => {
|
||||
const user: UserFiles = await useFetch(`/api/user/${userId}`);
|
||||
if (!user.error) {
|
||||
viewUser(user);
|
||||
} else {
|
||||
push('/dashboard');
|
||||
}
|
||||
})();
|
||||
}, [userId]);
|
||||
|
||||
if (!currentUser.files || currentUser.files.length === 0) {
|
||||
return (
|
||||
<Center sx={{ flexDirection: 'column' }}>
|
||||
<Group>
|
||||
<div>
|
||||
<IconFile size={48} />
|
||||
</div>
|
||||
<div>
|
||||
<Title>Nothing here</Title>
|
||||
<MutedText size='md'>
|
||||
{currentUser.username} seems to have not uploaded any files... yet
|
||||
</MutedText>
|
||||
</div>
|
||||
<Button size='md' onClick={() => push('/dashboard/users')}>
|
||||
Head back?
|
||||
</Button>
|
||||
</Group>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group mb='md'>
|
||||
<ActionIcon size='lg' onClick={() => push('/dashboard/users')} color='primary'>
|
||||
<IconArrowLeft />
|
||||
</ActionIcon>
|
||||
<Title>{currentUser.username}'s Files</Title>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid cols={3} spacing='lg' breakpoints={[{ maxWidth: 'sm', cols: 1, spacing: 'sm' }]}>
|
||||
{currentUser.files.map((file) => (
|
||||
<div key={file.id}>
|
||||
<FileComponent
|
||||
image={file}
|
||||
disableMediaPreview={disableMediaPreview}
|
||||
exifEnabled={exifEnabled}
|
||||
onDash={compress}
|
||||
otherUser={true}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -6,6 +6,7 @@ import type { User } from '@prisma/client';
|
|||
import {
|
||||
IconClipboardCopy,
|
||||
IconEdit,
|
||||
IconExternalLink,
|
||||
IconGridDots,
|
||||
IconList,
|
||||
IconUserExclamation,
|
||||
|
@ -116,6 +117,10 @@ export default function Users() {
|
|||
}
|
||||
};
|
||||
|
||||
const openUser = async (user) => {
|
||||
await router.push(`/dashboard/users/${user.id}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateUsers();
|
||||
}, []);
|
||||
|
@ -181,6 +186,13 @@ export default function Users() {
|
|||
<IconEdit size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{(!self.superAdmin && user.superAdmin) || (self.superAdmin && user.superAdmin) ? null : (
|
||||
<Tooltip label='Open user'>
|
||||
<ActionIcon color='cyan' onClick={() => openUser(user)}>
|
||||
<IconExternalLink size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -14,6 +14,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
where: {
|
||||
id: Number(id),
|
||||
},
|
||||
include: {
|
||||
files: true,
|
||||
Folder: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!target) return res.notFound('user not found');
|
||||
|
@ -175,6 +179,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
} else {
|
||||
delete target.password;
|
||||
|
||||
if (user.superAdmin && target.superAdmin) delete target.files;
|
||||
if (user.administrator && !user.superAdmin && (target.administrator || target.superAdmin))
|
||||
delete target.files;
|
||||
|
||||
return res.json(target);
|
||||
}
|
||||
}
|
||||
|
|
42
src/pages/dashboard/users/[id].tsx
Normal file
42
src/pages/dashboard/users/[id].tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { LoadingOverlay } from '@mantine/core';
|
||||
import Layout from 'components/Layout';
|
||||
import UserFiles from 'components/pages/Users/UserFiles';
|
||||
import useLogin from 'hooks/useLogin';
|
||||
import Head from 'next/head';
|
||||
import { getServerSideProps as middlewareProps } from 'middleware/getServerSideProps';
|
||||
import { GetServerSideProps } from 'next';
|
||||
|
||||
export default function UsersId(props) {
|
||||
const { loading } = useLogin();
|
||||
|
||||
if (loading) return <LoadingOverlay visible={loading} />;
|
||||
|
||||
const title = `${props.title} - User - ${props.userId}`;
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
</Head>
|
||||
<Layout props={props}>
|
||||
<UserFiles
|
||||
userId={props.userId}
|
||||
disableMediaPreview={props.disable_media_preview}
|
||||
exifEnabled={props.exif_enabled}
|
||||
compress={props.compress}
|
||||
/>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
// @ts-ignore
|
||||
const { props } = await middlewareProps(context);
|
||||
return {
|
||||
props: {
|
||||
userId: id,
|
||||
...props,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -10,7 +10,7 @@ export default function UsersPage(props) {
|
|||
|
||||
if (loading) return <LoadingOverlay visible={loading} />;
|
||||
|
||||
const title = `${props.title} - User`;
|
||||
const title = `${props.title} - Users`;
|
||||
return (
|
||||
<>
|
||||
<Head>
|
Loading…
Reference in a new issue