feat: overhaul upload frontend
This commit is contained in:
parent
577195b578
commit
2bec45411f
10 changed files with 433 additions and 325 deletions
5
src/components/icons/KeyIcon.tsx
Normal file
5
src/components/icons/KeyIcon.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { Key } from 'react-feather';
|
||||||
|
|
||||||
|
export default function KeyIcon({ ...props }) {
|
||||||
|
return <Key size={15} {...props} />;
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import DiscordIcon from './DiscordIcon';
|
||||||
import GoogleIcon from './GoogleIcon';
|
import GoogleIcon from './GoogleIcon';
|
||||||
import EyeIcon from './EyeIcon';
|
import EyeIcon from './EyeIcon';
|
||||||
import RefreshIcon from './RefreshIcon';
|
import RefreshIcon from './RefreshIcon';
|
||||||
|
import KeyIcon from './KeyIcon';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ActivityIcon,
|
ActivityIcon,
|
||||||
|
@ -68,4 +69,5 @@ export {
|
||||||
GoogleIcon,
|
GoogleIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
RefreshIcon,
|
RefreshIcon,
|
||||||
|
KeyIcon,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,27 +10,29 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { randomId, useClipboard } from '@mantine/hooks';
|
import { randomId, useClipboard } from '@mantine/hooks';
|
||||||
|
import { useModals } from '@mantine/modals';
|
||||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||||
import Dropzone from 'components/dropzone/Dropzone';
|
import Dropzone from 'components/dropzone/Dropzone';
|
||||||
import FileDropzone from 'components/dropzone/DropzoneFile';
|
import FileDropzone from 'components/dropzone/DropzoneFile';
|
||||||
import { ClockIcon, CrossIcon, UploadIcon } from 'components/icons';
|
import { ClockIcon, CrossIcon, UploadIcon } from 'components/icons';
|
||||||
import Link from 'components/Link';
|
|
||||||
import { invalidateFiles } from 'lib/queries/files';
|
import { invalidateFiles } from 'lib/queries/files';
|
||||||
import { userSelector } from 'lib/recoil/user';
|
import { userSelector } from 'lib/recoil/user';
|
||||||
import { randomChars } from 'lib/utils/client';
|
import { expireReadToDate, randomChars } from 'lib/utils/client';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import showFilesModal from './showFilesModal';
|
||||||
|
import useUploadOptions from './useUploadOptions';
|
||||||
|
|
||||||
export default function Upload({ chunks: chunks_config }) {
|
export default function File({ chunks: chunks_config }) {
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
|
const modals = useModals();
|
||||||
const user = useRecoilValue(userSelector);
|
const user = useRecoilValue(userSelector);
|
||||||
|
|
||||||
const [files, setFiles] = useState([]);
|
const [files, setFiles] = useState([]);
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [expires, setExpires] = useState('never');
|
|
||||||
const [password, setPassword] = useState('');
|
const [options, setOpened, OptionsModal] = useUploadOptions();
|
||||||
const [maxViews, setMaxViews] = useState<number>(undefined);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('paste', (e: ClipboardEvent) => {
|
window.addEventListener('paste', (e: ClipboardEvent) => {
|
||||||
|
@ -110,21 +112,11 @@ export default function Upload({ chunks: chunks_config }) {
|
||||||
updateNotification({
|
updateNotification({
|
||||||
id: 'upload-chunked',
|
id: 'upload-chunked',
|
||||||
title: 'Upload Successful',
|
title: 'Upload Successful',
|
||||||
message: (
|
message: '',
|
||||||
<>
|
|
||||||
Copied first file to clipboard! <br />
|
|
||||||
{json.files.map((x) => (
|
|
||||||
<Link key={x} href={x}>
|
|
||||||
{x}
|
|
||||||
<br />
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
color: 'green',
|
color: 'green',
|
||||||
icon: <UploadIcon />,
|
icon: <UploadIcon />,
|
||||||
});
|
});
|
||||||
|
showFilesModal(clipboard, modals, json.files);
|
||||||
invalidateFiles();
|
invalidateFiles();
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
setProgress(100);
|
setProgress(100);
|
||||||
|
@ -157,9 +149,16 @@ export default function Upload({ chunks: chunks_config }) {
|
||||||
req.setRequestHeader('X-Zipline-Partial-MimeType', file.type);
|
req.setRequestHeader('X-Zipline-Partial-MimeType', file.type);
|
||||||
req.setRequestHeader('X-Zipline-Partial-Identifier', identifier);
|
req.setRequestHeader('X-Zipline-Partial-Identifier', identifier);
|
||||||
req.setRequestHeader('X-Zipline-Partial-LastChunk', j === chunks.length - 1 ? 'true' : 'false');
|
req.setRequestHeader('X-Zipline-Partial-LastChunk', j === chunks.length - 1 ? 'true' : 'false');
|
||||||
expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
|
options.expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
|
||||||
password !== '' && req.setRequestHeader('Password', password);
|
options.password.trim() !== '' && req.setRequestHeader('Password', options.password);
|
||||||
maxViews && maxViews !== 0 && req.setRequestHeader('Max-Views', String(maxViews));
|
options.maxViews &&
|
||||||
|
options.maxViews !== 0 &&
|
||||||
|
req.setRequestHeader('Max-Views', String(options.maxViews));
|
||||||
|
options.compression !== 'none' &&
|
||||||
|
req.setRequestHeader('Image-Compression-Percent', options.compression);
|
||||||
|
options.embedded && req.setRequestHeader('Embed', 'true');
|
||||||
|
options.zeroWidth && req.setRequestHeader('Zws', 'true');
|
||||||
|
options.format !== 'default' && req.setRequestHeader('Format', options.format);
|
||||||
|
|
||||||
req.send(body);
|
req.send(body);
|
||||||
|
|
||||||
|
@ -169,40 +168,7 @@ export default function Upload({ chunks: chunks_config }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpload = async () => {
|
const handleUpload = async () => {
|
||||||
const expires_at =
|
const expires_at = options.expires === 'never' ? null : expireReadToDate(options.expires);
|
||||||
expires === 'never'
|
|
||||||
? null
|
|
||||||
: new Date(
|
|
||||||
{
|
|
||||||
'5min': Date.now() + 5 * 60 * 1000,
|
|
||||||
'10min': Date.now() + 10 * 60 * 1000,
|
|
||||||
'15min': Date.now() + 15 * 60 * 1000,
|
|
||||||
'30min': Date.now() + 30 * 60 * 1000,
|
|
||||||
'1h': Date.now() + 60 * 60 * 1000,
|
|
||||||
'2h': Date.now() + 2 * 60 * 60 * 1000,
|
|
||||||
'3h': Date.now() + 3 * 60 * 60 * 1000,
|
|
||||||
'4h': Date.now() + 4 * 60 * 60 * 1000,
|
|
||||||
'5h': Date.now() + 5 * 60 * 60 * 1000,
|
|
||||||
'6h': Date.now() + 6 * 60 * 60 * 1000,
|
|
||||||
'8h': Date.now() + 8 * 60 * 60 * 1000,
|
|
||||||
'12h': Date.now() + 12 * 60 * 60 * 1000,
|
|
||||||
'1d': Date.now() + 24 * 60 * 60 * 1000,
|
|
||||||
'3d': Date.now() + 3 * 24 * 60 * 60 * 1000,
|
|
||||||
'5d': Date.now() + 5 * 24 * 60 * 60 * 1000,
|
|
||||||
'7d': Date.now() + 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'1w': Date.now() + 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'1.5w': Date.now() + 1.5 * 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'2w': Date.now() + 2 * 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'3w': Date.now() + 3 * 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'1m': Date.now() + 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'1.5m': Date.now() + 1.5 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'2m': Date.now() + 2 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'3m': Date.now() + 3 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'6m': Date.now() + 6 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'8m': Date.now() + 8 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'1y': Date.now() + 365 * 24 * 60 * 60 * 1000,
|
|
||||||
}[expires]
|
|
||||||
);
|
|
||||||
|
|
||||||
setProgress(0);
|
setProgress(0);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -254,25 +220,15 @@ export default function Upload({ chunks: chunks_config }) {
|
||||||
const json = JSON.parse(e.target.response);
|
const json = JSON.parse(e.target.response);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
if (json.error === undefined) {
|
if (!json.error) {
|
||||||
updateNotification({
|
updateNotification({
|
||||||
id: 'upload',
|
id: 'upload',
|
||||||
title: 'Upload Successful',
|
title: 'Upload Successful',
|
||||||
message: (
|
message: '',
|
||||||
<>
|
|
||||||
Copied first file to clipboard! <br />
|
|
||||||
{json.files.map((x) => (
|
|
||||||
<Link key={x} href={x}>
|
|
||||||
{x}
|
|
||||||
<br />
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
color: 'green',
|
color: 'green',
|
||||||
icon: <UploadIcon />,
|
icon: <UploadIcon />,
|
||||||
});
|
});
|
||||||
clipboard.copy(json.files[0]);
|
showFilesModal(clipboard, modals, json.files);
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
invalidateFiles();
|
invalidateFiles();
|
||||||
|
|
||||||
|
@ -304,9 +260,16 @@ export default function Upload({ chunks: chunks_config }) {
|
||||||
if (bodyLength !== 0) {
|
if (bodyLength !== 0) {
|
||||||
req.open('POST', '/api/upload');
|
req.open('POST', '/api/upload');
|
||||||
req.setRequestHeader('Authorization', user.token);
|
req.setRequestHeader('Authorization', user.token);
|
||||||
expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
|
options.expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
|
||||||
password !== '' && req.setRequestHeader('Password', password);
|
options.password.trim() !== '' && req.setRequestHeader('Password', options.password);
|
||||||
maxViews && maxViews !== 0 && req.setRequestHeader('Max-Views', String(maxViews));
|
options.maxViews &&
|
||||||
|
options.maxViews !== 0 &&
|
||||||
|
req.setRequestHeader('Max-Views', String(options.maxViews));
|
||||||
|
options.compression !== 'none' &&
|
||||||
|
req.setRequestHeader('Image-Compression-Percent', options.compression);
|
||||||
|
options.embedded && req.setRequestHeader('Embed', 'true');
|
||||||
|
options.zeroWidth && req.setRequestHeader('Zws', 'true');
|
||||||
|
options.format !== 'default' && req.setRequestHeader('Format', options.format);
|
||||||
|
|
||||||
req.send(body);
|
req.send(body);
|
||||||
}
|
}
|
||||||
|
@ -314,6 +277,7 @@ export default function Upload({ chunks: chunks_config }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<OptionsModal />
|
||||||
<Title mb='md'>Upload Files</Title>
|
<Title mb='md'>Upload Files</Title>
|
||||||
|
|
||||||
<Dropzone loading={loading} onDrop={(f) => setFiles([...files, ...f])}>
|
<Dropzone loading={loading} onDrop={(f) => setFiles([...files, ...f])}>
|
||||||
|
@ -329,54 +293,12 @@ export default function Upload({ chunks: chunks_config }) {
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
<Group position='right' mt='md'>
|
<Group position='right' mt='md'>
|
||||||
<Tooltip label='After the file reaches this amount of views, it will be deleted automatically. Leave blank for no limit.'>
|
<Button onClick={() => setOpened(true)} variant='outline'>
|
||||||
<NumberInput placeholder='Max Views' min={0} value={maxViews} onChange={(x) => setMaxViews(x)} />
|
Options
|
||||||
</Tooltip>
|
</Button>
|
||||||
<Tooltip label='Add a password to your files (optional, leave blank for none)'>
|
<Button onClick={() => setFiles([])} color='red' variant='outline'>
|
||||||
<PasswordInput
|
Clear Files
|
||||||
style={{ width: '252px' }}
|
</Button>
|
||||||
placeholder='Password'
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label='Set an expiration date for your files (optional, defaults to never)'>
|
|
||||||
<Select
|
|
||||||
value={expires}
|
|
||||||
onChange={(e) => setExpires(e)}
|
|
||||||
icon={<ClockIcon size={14} />}
|
|
||||||
data={[
|
|
||||||
{ value: 'never', label: 'Never' },
|
|
||||||
{ value: '5min', label: '5 minutes' },
|
|
||||||
{ value: '10min', label: '10 minutes' },
|
|
||||||
{ value: '15min', label: '15 minutes' },
|
|
||||||
{ value: '30min', label: '30 minutes' },
|
|
||||||
{ value: '1h', label: '1 hour' },
|
|
||||||
{ value: '2h', label: '2 hours' },
|
|
||||||
{ value: '3h', label: '3 hours' },
|
|
||||||
{ value: '4h', label: '4 hours' },
|
|
||||||
{ value: '5h', label: '5 hours' },
|
|
||||||
{ value: '6h', label: '6 hours' },
|
|
||||||
{ value: '8h', label: '8 hours' },
|
|
||||||
{ value: '12h', label: '12 hours' },
|
|
||||||
{ value: '1d', label: '1 day' },
|
|
||||||
{ value: '3d', label: '3 days' },
|
|
||||||
{ value: '5d', label: '5 days' },
|
|
||||||
{ value: '7d', label: '7 days' },
|
|
||||||
{ value: '1w', label: '1 week' },
|
|
||||||
{ value: '1.5w', label: '1.5 weeks' },
|
|
||||||
{ value: '2w', label: '2 weeks' },
|
|
||||||
{ value: '3w', label: '3 weeks' },
|
|
||||||
{ value: '1m', label: '1 month' },
|
|
||||||
{ value: '1.5m', label: '1.5 months' },
|
|
||||||
{ value: '2m', label: '2 months' },
|
|
||||||
{ value: '3m', label: '3 months' },
|
|
||||||
{ value: '6m', label: '6 months' },
|
|
||||||
{ value: '8m', label: '8 months' },
|
|
||||||
{ value: '1y', label: '1 year' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Button leftIcon={<UploadIcon />} onClick={handleUpload} disabled={files.length === 0 ? true : false}>
|
<Button leftIcon={<UploadIcon />} onClick={handleUpload} disabled={files.length === 0 ? true : false}>
|
||||||
Upload
|
Upload
|
||||||
</Button>
|
</Button>
|
127
src/components/pages/Upload/Text.tsx
Normal file
127
src/components/pages/Upload/Text.tsx
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import { Button, Group, NumberInput, PasswordInput, Select, Tabs, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { useClipboard } from '@mantine/hooks';
|
||||||
|
import { useModals } from '@mantine/modals';
|
||||||
|
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||||
|
import { Prism } from '@mantine/prism';
|
||||||
|
import CodeInput from 'components/CodeInput';
|
||||||
|
import { ClockIcon, ImageIcon, TypeIcon, UploadIcon } from 'components/icons';
|
||||||
|
import exts from 'lib/exts';
|
||||||
|
import { userSelector } from 'lib/recoil/user';
|
||||||
|
import { expireReadToDate } from 'lib/utils/client';
|
||||||
|
import { Language } from 'prism-react-renderer';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import showFilesModal from './showFilesModal';
|
||||||
|
import useUploadOptions from './useUploadOptions';
|
||||||
|
|
||||||
|
export default function Text() {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
const modals = useModals();
|
||||||
|
const user = useRecoilValue(userSelector);
|
||||||
|
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const [lang, setLang] = useState('txt');
|
||||||
|
|
||||||
|
const [options, setOpened, OptionsModal] = useUploadOptions();
|
||||||
|
|
||||||
|
const handleUpload = async () => {
|
||||||
|
const file = new File([value], 'text.' + lang);
|
||||||
|
|
||||||
|
const expires_at = options.expires === 'never' ? null : expireReadToDate(options.expires);
|
||||||
|
|
||||||
|
showNotification({
|
||||||
|
id: 'upload-text',
|
||||||
|
title: 'Uploading...',
|
||||||
|
message: '',
|
||||||
|
loading: true,
|
||||||
|
autoClose: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = new XMLHttpRequest();
|
||||||
|
req.addEventListener('load', (e) => {
|
||||||
|
// @ts-ignore not sure why it thinks response doesnt exist, but it does.
|
||||||
|
const json = JSON.parse(e.target.response);
|
||||||
|
|
||||||
|
if (!json.error) {
|
||||||
|
updateNotification({
|
||||||
|
id: 'upload-text',
|
||||||
|
title: 'Upload Successful',
|
||||||
|
message: '',
|
||||||
|
});
|
||||||
|
showFilesModal(clipboard, modals, json.files);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = new FormData();
|
||||||
|
body.append('file', file);
|
||||||
|
|
||||||
|
req.open('POST', '/api/upload');
|
||||||
|
req.setRequestHeader('Authorization', user.token);
|
||||||
|
req.setRequestHeader('UploadText', 'true');
|
||||||
|
|
||||||
|
options.expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
|
||||||
|
options.password.trim() !== '' && req.setRequestHeader('Password', options.password);
|
||||||
|
options.maxViews && options.maxViews !== 0 && req.setRequestHeader('Max-Views', String(options.maxViews));
|
||||||
|
options.compression !== 'none' && req.setRequestHeader('Image-Compression-Percent', options.compression);
|
||||||
|
options.embedded && req.setRequestHeader('Embed', 'true');
|
||||||
|
options.zeroWidth && req.setRequestHeader('Zws', 'true');
|
||||||
|
options.format !== 'default' && req.setRequestHeader('Format', options.format);
|
||||||
|
|
||||||
|
req.send(body);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<OptionsModal />
|
||||||
|
<Title mb='md'>Upload Text</Title>
|
||||||
|
|
||||||
|
<Tabs defaultValue='text' variant='pills'>
|
||||||
|
<Tabs.List>
|
||||||
|
<Tabs.Tab value='text' icon={<TypeIcon />}>
|
||||||
|
Text
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab value='preview' icon={<ImageIcon />}>
|
||||||
|
Preview
|
||||||
|
</Tabs.Tab>
|
||||||
|
</Tabs.List>
|
||||||
|
|
||||||
|
<Tabs.Panel mt='sm' value='text'>
|
||||||
|
<CodeInput value={value} onChange={(e) => setValue(e.target.value)} />
|
||||||
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
<Tabs.Panel mt='sm' value='preview'>
|
||||||
|
<Prism
|
||||||
|
sx={(t) => ({ height: '80vh', backgroundColor: t.colors.dark[8] })}
|
||||||
|
withLineNumbers
|
||||||
|
language={lang as Language}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Prism>
|
||||||
|
</Tabs.Panel>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Group position='right' mt='md'>
|
||||||
|
<Select
|
||||||
|
value={lang}
|
||||||
|
onChange={setLang}
|
||||||
|
dropdownPosition='top'
|
||||||
|
data={Object.keys(exts).map((x) => ({ value: x, label: exts[x] }))}
|
||||||
|
icon={<TypeIcon />}
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button onClick={() => setOpened(true)} variant='outline'>
|
||||||
|
Options
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
leftIcon={<UploadIcon />}
|
||||||
|
onClick={handleUpload}
|
||||||
|
disabled={value.trim().length === 0 ? true : false}
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
44
src/components/pages/Upload/showFilesModal.tsx
Normal file
44
src/components/pages/Upload/showFilesModal.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { Button, Table, Title } from '@mantine/core';
|
||||||
|
import { showNotification } from '@mantine/notifications';
|
||||||
|
import { CopyIcon } from 'components/icons';
|
||||||
|
import Link from 'components/Link';
|
||||||
|
|
||||||
|
export default function showFilesModal(clipboard, modals, files: string[]) {
|
||||||
|
const open = (idx: number) => window.open(files[idx], '_blank');
|
||||||
|
const copy = (idx: number) => {
|
||||||
|
clipboard.copy(files[idx]);
|
||||||
|
showNotification({
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
message: <Link href={files[idx]}>{files[idx]}</Link>,
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
modals.openModal({
|
||||||
|
title: <Title>Uploaded Files</Title>,
|
||||||
|
size: 'auto',
|
||||||
|
children: (
|
||||||
|
<Table withBorder={false} withColumnBorders={false} highlightOnHover horizontalSpacing={'sm'}>
|
||||||
|
<tbody>
|
||||||
|
{files.map((file, idx) => (
|
||||||
|
<tr key={file}>
|
||||||
|
<td>
|
||||||
|
<Link href={file}>{file}</Link>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Button.Group>
|
||||||
|
<Button variant='outline' onClick={() => copy(idx)}>
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline' onClick={() => open(idx)}>
|
||||||
|
Open
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
174
src/components/pages/Upload/useUploadOptions.tsx
Normal file
174
src/components/pages/Upload/useUploadOptions.tsx
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Modal,
|
||||||
|
NumberInput,
|
||||||
|
PasswordInput,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Switch,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { ClockIcon, ImageIcon, KeyIcon, TypeIcon, UserIcon } from 'components/icons';
|
||||||
|
import React, { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
|
||||||
|
export default function useUploadOptions(): [
|
||||||
|
{
|
||||||
|
expires: string;
|
||||||
|
password: string;
|
||||||
|
maxViews: number;
|
||||||
|
compression: string;
|
||||||
|
zeroWidth: boolean;
|
||||||
|
embedded: boolean;
|
||||||
|
format: string;
|
||||||
|
},
|
||||||
|
Dispatch<SetStateAction<boolean>>,
|
||||||
|
React.FC
|
||||||
|
] {
|
||||||
|
const [expires, setExpires] = useState('never');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [maxViews, setMaxViews] = useState(0);
|
||||||
|
const [compression, setCompression] = useState<string>('none');
|
||||||
|
const [zeroWidth, setZeroWidth] = useState(false);
|
||||||
|
const [embedded, setEmbedded] = useState(false);
|
||||||
|
const [format, setFormat] = useState('default');
|
||||||
|
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setExpires('never');
|
||||||
|
setPassword('');
|
||||||
|
setMaxViews(0);
|
||||||
|
setCompression('none');
|
||||||
|
setZeroWidth(false);
|
||||||
|
setEmbedded(false);
|
||||||
|
setFormat('default');
|
||||||
|
};
|
||||||
|
|
||||||
|
const OptionsModal: React.FC = () => (
|
||||||
|
<Modal title={<Title>Upload Options</Title>} size='auto' opened={opened} onClose={() => setOpened(false)}>
|
||||||
|
<Stack>
|
||||||
|
<NumberInput
|
||||||
|
label='Max Views'
|
||||||
|
description='The maximum number of times this file can be viewed. Leave blank for unlimited views.'
|
||||||
|
value={maxViews}
|
||||||
|
onChange={setMaxViews}
|
||||||
|
min={0}
|
||||||
|
icon={<UserIcon />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label='Expires'
|
||||||
|
description='The date and time this file will expire. Leave blank for never.'
|
||||||
|
value={expires}
|
||||||
|
onChange={(e) => setExpires(e)}
|
||||||
|
icon={<ClockIcon size={14} />}
|
||||||
|
data={[
|
||||||
|
{ value: 'never', label: 'Never' },
|
||||||
|
{ value: '5min', label: '5 minutes' },
|
||||||
|
{ value: '10min', label: '10 minutes' },
|
||||||
|
{ value: '15min', label: '15 minutes' },
|
||||||
|
{ value: '30min', label: '30 minutes' },
|
||||||
|
{ value: '1h', label: '1 hour' },
|
||||||
|
{ value: '2h', label: '2 hours' },
|
||||||
|
{ value: '3h', label: '3 hours' },
|
||||||
|
{ value: '4h', label: '4 hours' },
|
||||||
|
{ value: '5h', label: '5 hours' },
|
||||||
|
{ value: '6h', label: '6 hours' },
|
||||||
|
{ value: '8h', label: '8 hours' },
|
||||||
|
{ value: '12h', label: '12 hours' },
|
||||||
|
{ value: '1d', label: '1 day' },
|
||||||
|
{ value: '3d', label: '3 days' },
|
||||||
|
{ value: '5d', label: '5 days' },
|
||||||
|
{ value: '7d', label: '7 days' },
|
||||||
|
{ value: '1w', label: '1 week' },
|
||||||
|
{ value: '1.5w', label: '1.5 weeks' },
|
||||||
|
{ value: '2w', label: '2 weeks' },
|
||||||
|
{ value: '3w', label: '3 weeks' },
|
||||||
|
{ value: '1m', label: '1 month' },
|
||||||
|
{ value: '1.5m', label: '1.5 months' },
|
||||||
|
{ value: '2m', label: '2 months' },
|
||||||
|
{ value: '3m', label: '3 months' },
|
||||||
|
{ value: '6m', label: '6 months' },
|
||||||
|
{ value: '8m', label: '8 months' },
|
||||||
|
{ value: '1y', label: '1 year' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label='Compression'
|
||||||
|
description='The compression level to use when uploading this file. Leave blank for default.'
|
||||||
|
value={compression}
|
||||||
|
onChange={(e) => setCompression(e)}
|
||||||
|
icon={<ImageIcon />}
|
||||||
|
data={[
|
||||||
|
{ value: 'none', label: 'None' },
|
||||||
|
{ value: '25', label: 'Low (25%)' },
|
||||||
|
{ value: '50', label: 'Medium (50%)' },
|
||||||
|
{ value: '75', label: 'High (75%)' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label='Format'
|
||||||
|
description="The file name format to use when uploading this file. Leave blank for the server's default."
|
||||||
|
value={format}
|
||||||
|
onChange={(e) => setFormat(e)}
|
||||||
|
icon={<TypeIcon />}
|
||||||
|
data={[
|
||||||
|
{ value: 'default', label: 'Default' },
|
||||||
|
{ value: 'RANDOM', label: 'Random' },
|
||||||
|
{ value: 'NAME', label: 'Original Name' },
|
||||||
|
{ value: 'DATE', label: 'Date (format configured by server)' },
|
||||||
|
{ value: 'UUID', label: 'UUID' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PasswordInput
|
||||||
|
label='Password'
|
||||||
|
description='The password required to view this file. Leave blank for no password.'
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
icon={<KeyIcon />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group>
|
||||||
|
<Switch
|
||||||
|
label='Zero Width'
|
||||||
|
description='Whether or not to use zero width characters for the file name.'
|
||||||
|
checked={zeroWidth}
|
||||||
|
onChange={(e) => setZeroWidth(e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
label='Embedded'
|
||||||
|
description='Whether or not to embed with OG tags for this file.'
|
||||||
|
checked={embedded}
|
||||||
|
onChange={(e) => setEmbedded(e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group grow>
|
||||||
|
<Button onClick={() => reset()} color='red'>
|
||||||
|
Reset Options
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setOpened(false)}>Close</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
expires,
|
||||||
|
password,
|
||||||
|
maxViews,
|
||||||
|
compression,
|
||||||
|
zeroWidth,
|
||||||
|
embedded,
|
||||||
|
format,
|
||||||
|
},
|
||||||
|
setOpened,
|
||||||
|
OptionsModal,
|
||||||
|
];
|
||||||
|
}
|
|
@ -1,202 +0,0 @@
|
||||||
import { Button, Group, NumberInput, PasswordInput, Select, Tabs, Title, Tooltip } from '@mantine/core';
|
|
||||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
|
||||||
import { Prism } from '@mantine/prism';
|
|
||||||
import CodeInput from 'components/CodeInput';
|
|
||||||
import { ClockIcon, ImageIcon, TypeIcon, UploadIcon } from 'components/icons';
|
|
||||||
import Link from 'components/Link';
|
|
||||||
import exts from 'lib/exts';
|
|
||||||
import { userSelector } from 'lib/recoil/user';
|
|
||||||
import { Language } from 'prism-react-renderer';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
export default function Upload() {
|
|
||||||
const user = useRecoilValue(userSelector);
|
|
||||||
|
|
||||||
const [value, setValue] = useState('');
|
|
||||||
const [lang, setLang] = useState('txt');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
const [expires, setExpires] = useState('never');
|
|
||||||
const [maxViews, setMaxViews] = useState<number>(undefined);
|
|
||||||
|
|
||||||
const handleUpload = async () => {
|
|
||||||
const file = new File([value], 'text.' + lang);
|
|
||||||
|
|
||||||
const expires_at =
|
|
||||||
expires === 'never'
|
|
||||||
? null
|
|
||||||
: new Date(
|
|
||||||
{
|
|
||||||
'5min': Date.now() + 5 * 60 * 1000,
|
|
||||||
'10min': Date.now() + 10 * 60 * 1000,
|
|
||||||
'15min': Date.now() + 15 * 60 * 1000,
|
|
||||||
'30min': Date.now() + 30 * 60 * 1000,
|
|
||||||
'1h': Date.now() + 60 * 60 * 1000,
|
|
||||||
'2h': Date.now() + 2 * 60 * 60 * 1000,
|
|
||||||
'3h': Date.now() + 3 * 60 * 60 * 1000,
|
|
||||||
'4h': Date.now() + 4 * 60 * 60 * 1000,
|
|
||||||
'5h': Date.now() + 5 * 60 * 60 * 1000,
|
|
||||||
'6h': Date.now() + 6 * 60 * 60 * 1000,
|
|
||||||
'8h': Date.now() + 8 * 60 * 60 * 1000,
|
|
||||||
'12h': Date.now() + 12 * 60 * 60 * 1000,
|
|
||||||
'1d': Date.now() + 24 * 60 * 60 * 1000,
|
|
||||||
'3d': Date.now() + 3 * 24 * 60 * 60 * 1000,
|
|
||||||
'5d': Date.now() + 5 * 24 * 60 * 60 * 1000,
|
|
||||||
'7d': Date.now() + 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'1w': Date.now() + 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'1.5w': Date.now() + 1.5 * 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'2w': Date.now() + 2 * 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'3w': Date.now() + 3 * 7 * 24 * 60 * 60 * 1000,
|
|
||||||
'1m': Date.now() + 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'1.5m': Date.now() + 1.5 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'2m': Date.now() + 2 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'3m': Date.now() + 3 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'6m': Date.now() + 6 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'8m': Date.now() + 8 * 30 * 24 * 60 * 60 * 1000,
|
|
||||||
'1y': Date.now() + 365 * 24 * 60 * 60 * 1000,
|
|
||||||
}[expires]
|
|
||||||
);
|
|
||||||
|
|
||||||
showNotification({
|
|
||||||
id: 'upload-text',
|
|
||||||
title: 'Uploading...',
|
|
||||||
message: '',
|
|
||||||
loading: true,
|
|
||||||
autoClose: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const req = new XMLHttpRequest();
|
|
||||||
req.addEventListener('load', (e) => {
|
|
||||||
// @ts-ignore not sure why it thinks response doesnt exist, but it does.
|
|
||||||
const json = JSON.parse(e.target.response);
|
|
||||||
|
|
||||||
if (!json.error) {
|
|
||||||
updateNotification({
|
|
||||||
id: 'upload-text',
|
|
||||||
title: 'Upload Successful',
|
|
||||||
message: (
|
|
||||||
<>
|
|
||||||
Copied first file to clipboard! <br />
|
|
||||||
{json.files.map((x) => (
|
|
||||||
<Link key={x} href={x}>
|
|
||||||
{x}
|
|
||||||
<br />
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const body = new FormData();
|
|
||||||
body.append('file', file);
|
|
||||||
|
|
||||||
req.open('POST', '/api/upload');
|
|
||||||
req.setRequestHeader('Authorization', user.token);
|
|
||||||
req.setRequestHeader('UploadText', 'true');
|
|
||||||
|
|
||||||
expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
|
|
||||||
password !== '' && req.setRequestHeader('Password', password);
|
|
||||||
maxViews && maxViews !== 0 && req.setRequestHeader('Max-Views', String(maxViews));
|
|
||||||
|
|
||||||
req.send(body);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Title mb='md'>Upload Text</Title>
|
|
||||||
|
|
||||||
<Tabs defaultValue='text' variant='pills'>
|
|
||||||
<Tabs.List>
|
|
||||||
<Tabs.Tab value='text' icon={<TypeIcon />}>
|
|
||||||
Text
|
|
||||||
</Tabs.Tab>
|
|
||||||
<Tabs.Tab value='preview' icon={<ImageIcon />}>
|
|
||||||
Preview
|
|
||||||
</Tabs.Tab>
|
|
||||||
</Tabs.List>
|
|
||||||
|
|
||||||
<Tabs.Panel mt='sm' value='text'>
|
|
||||||
<CodeInput value={value} onChange={(e) => setValue(e.target.value)} />
|
|
||||||
</Tabs.Panel>
|
|
||||||
|
|
||||||
<Tabs.Panel mt='sm' value='preview'>
|
|
||||||
<Prism
|
|
||||||
sx={(t) => ({ height: '80vh', backgroundColor: t.colors.dark[8] })}
|
|
||||||
withLineNumbers
|
|
||||||
language={lang as Language}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</Prism>
|
|
||||||
</Tabs.Panel>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<Group position='right' mt='md'>
|
|
||||||
<Select
|
|
||||||
value={lang}
|
|
||||||
onChange={setLang}
|
|
||||||
dropdownPosition='top'
|
|
||||||
data={Object.keys(exts).map((x) => ({ value: x, label: exts[x] }))}
|
|
||||||
icon={<TypeIcon />}
|
|
||||||
searchable
|
|
||||||
/>
|
|
||||||
<Tooltip label='After the file reaches this amount of views, it will be deleted automatically. Leave blank for no limit.'>
|
|
||||||
<NumberInput placeholder='Max Views' min={0} value={maxViews} onChange={(x) => setMaxViews(x)} />
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label='Add a password to your files (optional, leave blank for none)'>
|
|
||||||
<PasswordInput
|
|
||||||
style={{ width: '252px' }}
|
|
||||||
placeholder='Password'
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label='Set an expiration date for your files (optional, defaults to never)'>
|
|
||||||
<Select
|
|
||||||
value={expires}
|
|
||||||
onChange={(e) => setExpires(e)}
|
|
||||||
icon={<ClockIcon size={14} />}
|
|
||||||
data={[
|
|
||||||
{ value: 'never', label: 'Never' },
|
|
||||||
{ value: '5min', label: '5 minutes' },
|
|
||||||
{ value: '10min', label: '10 minutes' },
|
|
||||||
{ value: '15min', label: '15 minutes' },
|
|
||||||
{ value: '30min', label: '30 minutes' },
|
|
||||||
{ value: '1h', label: '1 hour' },
|
|
||||||
{ value: '2h', label: '2 hours' },
|
|
||||||
{ value: '3h', label: '3 hours' },
|
|
||||||
{ value: '4h', label: '4 hours' },
|
|
||||||
{ value: '5h', label: '5 hours' },
|
|
||||||
{ value: '6h', label: '6 hours' },
|
|
||||||
{ value: '8h', label: '8 hours' },
|
|
||||||
{ value: '12h', label: '12 hours' },
|
|
||||||
{ value: '1d', label: '1 day' },
|
|
||||||
{ value: '3d', label: '3 days' },
|
|
||||||
{ value: '5d', label: '5 days' },
|
|
||||||
{ value: '7d', label: '7 days' },
|
|
||||||
{ value: '1w', label: '1 week' },
|
|
||||||
{ value: '1.5w', label: '1.5 weeks' },
|
|
||||||
{ value: '2w', label: '2 weeks' },
|
|
||||||
{ value: '3w', label: '3 weeks' },
|
|
||||||
{ value: '1m', label: '1 month' },
|
|
||||||
{ value: '1.5m', label: '1.5 months' },
|
|
||||||
{ value: '2m', label: '2 months' },
|
|
||||||
{ value: '3m', label: '3 months' },
|
|
||||||
{ value: '6m', label: '6 months' },
|
|
||||||
{ value: '8m', label: '8 months' },
|
|
||||||
{ value: '1y', label: '1 year' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Button
|
|
||||||
leftIcon={<UploadIcon />}
|
|
||||||
onClick={handleUpload}
|
|
||||||
disabled={value.trim().length === 0 ? true : false}
|
|
||||||
>
|
|
||||||
Upload
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -102,3 +102,39 @@ export function expireText(to_: string, from_: string = new Date().toLocaleStrin
|
||||||
return `Expired ${dayjs(from).to(to)}`;
|
return `Expired ${dayjs(from).to(to)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function expireReadToDate(expires: string): Date {
|
||||||
|
if (expires === 'never') return null;
|
||||||
|
|
||||||
|
return new Date(
|
||||||
|
{
|
||||||
|
'5min': Date.now() + 5 * 60 * 1000,
|
||||||
|
'10min': Date.now() + 10 * 60 * 1000,
|
||||||
|
'15min': Date.now() + 15 * 60 * 1000,
|
||||||
|
'30min': Date.now() + 30 * 60 * 1000,
|
||||||
|
'1h': Date.now() + 60 * 60 * 1000,
|
||||||
|
'2h': Date.now() + 2 * 60 * 60 * 1000,
|
||||||
|
'3h': Date.now() + 3 * 60 * 60 * 1000,
|
||||||
|
'4h': Date.now() + 4 * 60 * 60 * 1000,
|
||||||
|
'5h': Date.now() + 5 * 60 * 60 * 1000,
|
||||||
|
'6h': Date.now() + 6 * 60 * 60 * 1000,
|
||||||
|
'8h': Date.now() + 8 * 60 * 60 * 1000,
|
||||||
|
'12h': Date.now() + 12 * 60 * 60 * 1000,
|
||||||
|
'1d': Date.now() + 24 * 60 * 60 * 1000,
|
||||||
|
'3d': Date.now() + 3 * 24 * 60 * 60 * 1000,
|
||||||
|
'5d': Date.now() + 5 * 24 * 60 * 60 * 1000,
|
||||||
|
'7d': Date.now() + 7 * 24 * 60 * 60 * 1000,
|
||||||
|
'1w': Date.now() + 7 * 24 * 60 * 60 * 1000,
|
||||||
|
'1.5w': Date.now() + 1.5 * 7 * 24 * 60 * 60 * 1000,
|
||||||
|
'2w': Date.now() + 2 * 7 * 24 * 60 * 60 * 1000,
|
||||||
|
'3w': Date.now() + 3 * 7 * 24 * 60 * 60 * 1000,
|
||||||
|
'1m': Date.now() + 30 * 24 * 60 * 60 * 1000,
|
||||||
|
'1.5m': Date.now() + 1.5 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
'2m': Date.now() + 2 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
'3m': Date.now() + 3 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
'6m': Date.now() + 6 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
'8m': Date.now() + 8 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
'1y': Date.now() + 365 * 24 * 60 * 60 * 1000,
|
||||||
|
}[expires]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { LoadingOverlay } from '@mantine/core';
|
import { LoadingOverlay } from '@mantine/core';
|
||||||
import Layout from 'components/Layout';
|
import Layout from 'components/Layout';
|
||||||
import Upload from 'components/pages/Upload';
|
import File from 'components/pages/Upload/File';
|
||||||
import useLogin from 'hooks/useLogin';
|
import useLogin from 'hooks/useLogin';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
export { getServerSideProps } from 'middleware/getServerSideProps';
|
export { getServerSideProps } from 'middleware/getServerSideProps';
|
||||||
|
@ -17,7 +17,7 @@ export default function UploadPage(props) {
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Layout props={props}>
|
<Layout props={props}>
|
||||||
<Upload chunks={{ chunks_size: props.chunks_size, max_size: props.max_size }} />
|
<File chunks={{ chunks_size: props.chunks_size, max_size: props.max_size }} />
|
||||||
</Layout>
|
</Layout>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { LoadingOverlay } from '@mantine/core';
|
import { LoadingOverlay } from '@mantine/core';
|
||||||
import Layout from 'components/Layout';
|
import Layout from 'components/Layout';
|
||||||
import UploadText from 'components/pages/UploadText';
|
import Text from 'components/pages/Upload/Text';
|
||||||
import useLogin from 'hooks/useLogin';
|
import useLogin from 'hooks/useLogin';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
export { getServerSideProps } from 'middleware/getServerSideProps';
|
export { getServerSideProps } from 'middleware/getServerSideProps';
|
||||||
|
@ -17,7 +17,7 @@ export default function UploadTextPage(props) {
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Layout props={props}>
|
<Layout props={props}>
|
||||||
<UploadText />
|
<Text />
|
||||||
</Layout>
|
</Layout>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue