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,
|
reducedActions = false,
|
||||||
exifEnabled,
|
exifEnabled,
|
||||||
compress,
|
compress,
|
||||||
|
otherUser = false,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
|
@ -58,6 +59,7 @@ export default function FileModal({
|
||||||
reducedActions?: boolean;
|
reducedActions?: boolean;
|
||||||
exifEnabled?: boolean;
|
exifEnabled?: boolean;
|
||||||
compress: boolean;
|
compress: boolean;
|
||||||
|
otherUser: boolean;
|
||||||
}) {
|
}) {
|
||||||
const deleteFile = useFileDelete();
|
const deleteFile = useFileDelete();
|
||||||
const favoriteFile = useFileFavorite();
|
const favoriteFile = useFileFavorite();
|
||||||
|
@ -276,7 +278,7 @@ export default function FileModal({
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{reducedActions ? null : inFolder && !folders.isLoading ? (
|
{reducedActions || otherUser ? null : inFolder && !folders.isLoading ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={`Remove from folder "${folders.data.find((f) => f.id === file.folderId)?.name ?? ''}"`}
|
label={`Remove from folder "${folders.data.find((f) => f.id === file.folderId)?.name ?? ''}"`}
|
||||||
>
|
>
|
||||||
|
|
|
@ -32,9 +32,10 @@ export default function File({
|
||||||
image,
|
image,
|
||||||
disableMediaPreview,
|
disableMediaPreview,
|
||||||
exifEnabled,
|
exifEnabled,
|
||||||
refreshImages,
|
refreshImages = undefined,
|
||||||
reducedActions = false,
|
reducedActions = false,
|
||||||
onDash,
|
onDash,
|
||||||
|
otherUser = false,
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const deleteFile = useFileDelete();
|
const deleteFile = useFileDelete();
|
||||||
|
@ -44,7 +45,7 @@ export default function File({
|
||||||
const folders = useFolders();
|
const folders = useFolders();
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
refreshImages();
|
if (!otherUser) refreshImages();
|
||||||
folders.refetch();
|
folders.refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ export default function File({
|
||||||
reducedActions={reducedActions}
|
reducedActions={reducedActions}
|
||||||
exifEnabled={exifEnabled}
|
exifEnabled={exifEnabled}
|
||||||
compress={onDash}
|
compress={onDash}
|
||||||
|
otherUser={otherUser}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}>
|
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}>
|
||||||
|
|
|
@ -126,6 +126,7 @@ export default function Dashboard({ disableMediaPreview, exifEnabled, compress }
|
||||||
reducedActions={false}
|
reducedActions={false}
|
||||||
exifEnabled={exifEnabled}
|
exifEnabled={exifEnabled}
|
||||||
compress={compress}
|
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 {
|
import {
|
||||||
IconClipboardCopy,
|
IconClipboardCopy,
|
||||||
IconEdit,
|
IconEdit,
|
||||||
|
IconExternalLink,
|
||||||
IconGridDots,
|
IconGridDots,
|
||||||
IconList,
|
IconList,
|
||||||
IconUserExclamation,
|
IconUserExclamation,
|
||||||
|
@ -116,6 +117,10 @@ export default function Users() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openUser = async (user) => {
|
||||||
|
await router.push(`/dashboard/users/${user.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateUsers();
|
updateUsers();
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -181,6 +186,13 @@ export default function Users() {
|
||||||
<IconEdit size='1rem' />
|
<IconEdit size='1rem' />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</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>
|
</Group>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,6 +14,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
where: {
|
where: {
|
||||||
id: Number(id),
|
id: Number(id),
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
files: true,
|
||||||
|
Folder: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!target) return res.notFound('user not found');
|
if (!target) return res.notFound('user not found');
|
||||||
|
@ -175,6 +179,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
} else {
|
} else {
|
||||||
delete target.password;
|
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);
|
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} />;
|
if (loading) return <LoadingOverlay visible={loading} />;
|
||||||
|
|
||||||
const title = `${props.title} - User`;
|
const title = `${props.title} - Users`;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
Loading…
Reference in a new issue