Compare commits

...

5 Commits

Author SHA1 Message Date
diced
04b27a2dee fix: build error 2025-11-03 15:40:15 -08:00
diced
6f4c3271c1 fix: #914 2025-11-03 15:36:09 -08:00
diced
b014f10240 fix: #916 2025-11-03 15:36:03 -08:00
diced
d3a417aff0 fix: #921 2025-11-03 15:24:14 -08:00
diced
63596d983e fix: #919 2025-10-28 12:10:06 -07:00
6 changed files with 106 additions and 53 deletions

View File

@@ -6,12 +6,16 @@ import FileTable from './views/FileTable';
import Files from './views/Files';
import TagsButton from './tags/TagsButton';
import PendingFilesButton from './PendingFilesButton';
import { IconFileUpload } from '@tabler/icons-react';
import { IconFileUpload, IconGridPatternFilled, IconTableOptions } from '@tabler/icons-react';
import { Link } from 'react-router-dom';
import { useState } from 'react';
export default function DashboardFiles() {
const view = useViewStore((state) => state.files);
const [tableEditOpen, setTableEditOpen] = useState(false);
const [idSearchOpen, setIdSearchOpen] = useState(false);
return (
<>
<Group>
@@ -28,6 +32,23 @@ export default function DashboardFiles() {
<TagsButton />
<PendingFilesButton />
<Tooltip label='Table Options'>
<ActionIcon variant='outline' onClick={() => setTableEditOpen((open) => !open)}>
<IconTableOptions size='1rem' />
</ActionIcon>
</Tooltip>
<Tooltip label='Search by ID'>
<ActionIcon
variant='outline'
onClick={() => {
setIdSearchOpen((open) => !open);
}}
>
<IconGridPatternFilled size='1rem' />
</ActionIcon>
</Tooltip>
<GridTableSwitcher type='files' />
</Group>
@@ -38,7 +59,16 @@ export default function DashboardFiles() {
<Files />
</>
) : (
<FileTable />
<FileTable
idSearch={{
open: idSearchOpen,
setOpen: setIdSearchOpen,
}}
tableEdit={{
open: tableEditOpen,
setOpen: setTableEditOpen,
}}
/>
)}
</>
);

View File

@@ -34,9 +34,7 @@ import {
IconDownload,
IconExternalLink,
IconFile,
IconGridPatternFilled,
IconStar,
IconTableOptions,
IconTrashFilled,
} from '@tabler/icons-react';
import { DataTable } from 'mantine-datatable';
@@ -175,12 +173,24 @@ function TagsFilter({
);
}
export default function FileTable({ id }: { id?: string }) {
export default function FileTable({
id,
tableEdit,
idSearch,
}: {
id?: string;
tableEdit: {
open: boolean;
setOpen: (open: boolean) => void;
};
idSearch: {
open: boolean;
setOpen: (open: boolean) => void;
};
}) {
const clipboard = useClipboard();
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
const [tableEditOpen, setTableEditOpen] = useState(false);
const fields = useFileTableSettingsStore((state) => state.fields);
const { data: folders } = useSWR<Extract<Response['/api/user/folders'], Folder[]>>(
@@ -204,7 +214,6 @@ export default function FileTable({ id }: { id?: string }) {
const [order, setOrder] = useState<'asc' | 'desc'>('desc');
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [idSearchOpen, setIdSearchOpen] = useState(false);
const [searchField, setSearchField] = useState<'name' | 'originalName' | 'type' | 'tags' | 'id'>('name');
const [searchQuery, setSearchQuery] = useReducer(
(state: ReducerQuery['state'], action: ReducerQuery['action']) => {
@@ -218,13 +227,13 @@ export default function FileTable({ id }: { id?: string }) {
const [debouncedQuery, setDebouncedQuery] = useState(searchQuery);
useEffect(() => {
if (idSearchOpen) return;
if (idSearch.open) return;
setSearchQuery({
field: 'id',
query: '',
});
}, [idSearchOpen]);
}, [idSearch.open]);
useEffect(() => {
const handler = setTimeout(() => setDebouncedQuery(searchQuery), 300);
@@ -389,33 +398,9 @@ export default function FileTable({ id }: { id?: string }) {
file={selectedFile}
/>
<TableEditModal opened={tableEditOpen} onCLose={() => setTableEditOpen(false)} />
<TableEditModal opened={tableEdit.open} onCLose={() => tableEdit.setOpen(false)} />
<Box>
<Group>
<Tooltip label='Table Options'>
<ActionIcon
variant='outline'
onClick={() => setTableEditOpen((open) => !open)}
style={{ position: 'relative', top: '-36.4px', left: '221px', margin: 0 }}
>
<IconTableOptions size='1rem' />
</ActionIcon>
</Tooltip>
<Tooltip label='Search by ID'>
<ActionIcon
variant='outline'
onClick={() => {
setIdSearchOpen((open) => !open);
}}
// lol if it works it works :shrug:
style={{ position: 'relative', top: '-36.4px', left: '221px', margin: 0 }}
>
<IconGridPatternFilled size='1rem' />
</ActionIcon>
</Tooltip>
</Group>
<Collapse in={selectedFiles.length > 0}>
<Paper withBorder p='sm' my='sm'>
<Text size='sm' c='dimmed' mb='xs'>
@@ -500,8 +485,8 @@ export default function FileTable({ id }: { id?: string }) {
</Paper>
</Collapse>
<Collapse in={idSearchOpen}>
<Paper withBorder p='sm' my='sm'>
<Collapse in={idSearch.open}>
<Paper withBorder p='sm' mt='sm'>
<TextInput
placeholder='Search by ID'
value={searchQuery.id}
@@ -519,6 +504,7 @@ export default function FileTable({ id }: { id?: string }) {
{/* @ts-ignore */}
<DataTable
mt='xs'
borderRadius='sm'
withTableBorder
minHeight={200}

View File

@@ -27,9 +27,9 @@ export default function TwoFAButton() {
const [totpOpen, setTotpOpen] = useState(false);
const {
data: twoData,
error: twoError,
isLoading: twoLoading,
data: mfaData,
error: mfaError,
isLoading: mfaLoading,
} = useSWR<Extract<Response['/api/user/mfa/totp'], { secret: string; qrcode: string }>>(
totpOpen && !user?.totpSecret ? '/api/user/mfa/totp' : null,
null,
@@ -51,7 +51,7 @@ export default function TwoFAButton() {
'POST',
{
code: pin,
secret: twoData!.secret,
secret: mfaData!.secret,
},
);
@@ -156,25 +156,20 @@ export default function TwoFAButton() {
</Text>
<Box pos='relative'>
{twoLoading && !twoError ? (
{mfaLoading && !mfaError ? (
<Box w={180} h={180}>
<LoadingOverlay visible pos='relative' />
</Box>
) : (
<Center>
<Image
width={180}
height={180}
src={twoData?.qrcode}
alt={'qr code ' + twoData?.secret}
/>
<Image h={180} w={180} src={mfaData?.qrcode} alt={'qr code ' + mfaData?.secret} />
</Center>
)}
</Box>
<Text size='sm' c='dimmed'>
If you can&apos;t scan the QR code, you can manually enter the following code into your
authenticator app: <Code>{twoData?.secret ?? ''}</Code>
authenticator app: <Code>{mfaData?.secret ?? ''}</Code>
</Text>
<Text size='sm' c='dimmed'>

View File

@@ -2,10 +2,11 @@ import { type loader } from '@/client/pages/dashboard/admin/users/[id]/files';
import GridTableSwitcher from '@/components/GridTableSwitcher';
import { useViewStore } from '@/lib/store/view';
import { ActionIcon, Group, Title, Tooltip } from '@mantine/core';
import { IconArrowBackUp } from '@tabler/icons-react';
import { IconArrowBackUp, IconGridPatternFilled, IconTableOptions } from '@tabler/icons-react';
import { Link, useLoaderData } from 'react-router-dom';
import FileTable from '../files/views/FileTable';
import Files from '../files/views/Files';
import { useState } from 'react';
export default function ViewUserFiles() {
const data = useLoaderData<typeof loader>();
@@ -16,6 +17,9 @@ export default function ViewUserFiles() {
const view = useViewStore((state) => state.files);
const [tableEditOpen, setTableEditOpen] = useState(false);
const [idSearchOpen, setIdSearchOpen] = useState(false);
return (
<>
<Group>
@@ -26,10 +30,41 @@ export default function ViewUserFiles() {
</ActionIcon>
</Tooltip>
<Tooltip label='Table Options'>
<ActionIcon variant='outline' onClick={() => setTableEditOpen((open) => !open)}>
<IconTableOptions size='1rem' />
</ActionIcon>
</Tooltip>
<Tooltip label='Search by ID'>
<ActionIcon
variant='outline'
onClick={() => {
setIdSearchOpen((open) => !open);
}}
>
<IconGridPatternFilled size='1rem' />
</ActionIcon>
</Tooltip>
<GridTableSwitcher type='files' />
</Group>
{view === 'grid' ? <Files id={user.id} /> : <FileTable id={user.id} />}
{view === 'grid' ? (
<Files id={user.id} />
) : (
<FileTable
id={user.id}
tableEdit={{
open: tableEditOpen,
setOpen: setTableEditOpen,
}}
idSearch={{
open: idSearchOpen,
setOpen: setIdSearchOpen,
}}
/>
)}
</>
);
}

View File

@@ -39,9 +39,12 @@ export async function queryStats(): Promise<MetricData> {
});
for (let i = 0; i !== filesByUser.length; ++i) {
const id = filesByUser[i].userId;
if (!id) continue;
const user = await prisma.user.findUnique({
where: {
id: filesByUser[i].userId!,
id,
},
});
@@ -49,9 +52,12 @@ export async function queryStats(): Promise<MetricData> {
}
for (let i = 0; i !== urlsByUser.length; ++i) {
const id = urlsByUser[i].userId;
if (!id) continue;
const user = await prisma.user.findUnique({
where: {
id: urlsByUser[i].userId!,
id,
},
});

View File

@@ -28,6 +28,7 @@ export async function filesRoute(
if (file.User?.view.enabled) return res.redirect(`/view/${encodeURIComponent(file.name)}`);
if (file.type.startsWith('text/')) return res.redirect(`/view/${encodeURIComponent(file.name)}`);
if (file.password) return res.redirect(`/view/${encodeURIComponent(file.name)}`);
return rawFileHandler(req, res);
}