feat: switch to mantine v5
This commit is contained in:
parent
12baadd563
commit
3ea24ddf0c
26 changed files with 1196 additions and 1149 deletions
0
.yarn/releases/yarn-3.2.1.cjs
vendored
Normal file → Executable file
0
.yarn/releases/yarn-3.2.1.cjs
vendored
Normal file → Executable file
|
@ -37,7 +37,7 @@ const { rm } = require('fs/promises');
|
|||
write: true,
|
||||
watch,
|
||||
incremental: watch,
|
||||
sourcemap: false,
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
});
|
||||
})();
|
|
@ -8,9 +8,6 @@ module.exports = {
|
|||
},
|
||||
];
|
||||
},
|
||||
api: {
|
||||
responseLimit: false,
|
||||
},
|
||||
poweredByHeader: false,
|
||||
reactStrictMode: true,
|
||||
};
|
28
package.json
28
package.json
|
@ -3,7 +3,7 @@
|
|||
"version": "3.4.8",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.js && REACT_EDITOR=code NODE_ENV=development node dist/server",
|
||||
"dev": "node esbuild.config.js && REACT_EDITOR=code NODE_ENV=development node --enable-source-maps dist/server",
|
||||
"build": "npm-run-all build:server build:schema build:next",
|
||||
"build:server": "node esbuild.config.js",
|
||||
"build:next": "next build",
|
||||
|
@ -16,17 +16,21 @@
|
|||
"docker:build-dev": "docker-compose --file docker-compose.dev.yml up --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/server": "^11.4.0",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@mantine/core": "^4.2.9",
|
||||
"@mantine/dropzone": "^4.2.9",
|
||||
"@mantine/hooks": "^4.2.9",
|
||||
"@mantine/modals": "^4.2.9",
|
||||
"@mantine/next": "^4.2.9",
|
||||
"@mantine/notifications": "^4.2.9",
|
||||
"@mantine/prism": "^4.2.9",
|
||||
"@prisma/client": "^3.15.2",
|
||||
"@prisma/migrate": "^3.15.2",
|
||||
"@prisma/sdk": "^3.15.2",
|
||||
"@mantine/core": "^5.0.0",
|
||||
"@mantine/dropzone": "^5.0.0",
|
||||
"@mantine/form": "^5.0.0",
|
||||
"@mantine/hooks": "^5.0.0",
|
||||
"@mantine/modals": "^5.0.0",
|
||||
"@mantine/next": "^5.0.0",
|
||||
"@mantine/notifications": "^5.0.0",
|
||||
"@mantine/nprogress": "^5.0.0",
|
||||
"@mantine/prism": "^5.0.0",
|
||||
"@prisma/client": "^4.1.0",
|
||||
"@prisma/internals": "^4.1.0",
|
||||
"@prisma/migrate": "^4.1.0",
|
||||
"@reduxjs/toolkit": "^1.8.2",
|
||||
"argon2": "^0.28.5",
|
||||
"colorette": "^2.0.19",
|
||||
|
@ -39,7 +43,7 @@
|
|||
"minio": "^7.0.28",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"next": "^12.1.6",
|
||||
"prisma": "^3.15.2",
|
||||
"prisma": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-feather": "^2.0.10",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Button, Card, Grid, Group, Image as MImage, Modal, Stack, Text, Title, useMantineTheme } from '@mantine/core';
|
||||
import { Button, Card, Group, Modal, Stack, Text, Title, useMantineTheme } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useState } from 'react';
|
||||
import Type from './Type';
|
||||
|
@ -21,7 +21,6 @@ export function FileMeta({ Icon, title, subtitle }) {
|
|||
|
||||
export default function File({ image, updateImages }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const notif = useNotifications();
|
||||
const clipboard = useClipboard();
|
||||
const theme = useMantineTheme();
|
||||
|
||||
|
@ -29,14 +28,14 @@ export default function File({ image, updateImages }) {
|
|||
const res = await useFetch('/api/user/files', 'DELETE', { id: image.id });
|
||||
if (!res.error) {
|
||||
updateImages(true);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'File Deleted',
|
||||
message: '',
|
||||
color: 'green',
|
||||
icon: <DeleteIcon />,
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to delete file',
|
||||
message: res.error,
|
||||
color: 'red',
|
||||
|
@ -50,7 +49,7 @@ export default function File({ image, updateImages }) {
|
|||
const handleCopy = () => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${image.url}`);
|
||||
setOpen(false);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <CopyIcon />,
|
||||
|
@ -60,7 +59,7 @@ export default function File({ image, updateImages }) {
|
|||
const handleFavorite = async () => {
|
||||
const data = await useFetch('/api/user/files', 'PATCH', { id: image.id, favorite: !image.favorite });
|
||||
if (!data.error) updateImages(true);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Image is now ' + (!image.favorite ? 'favorited' : 'unfavorited'),
|
||||
message: '',
|
||||
icon: <StarIcon />,
|
||||
|
@ -75,8 +74,6 @@ export default function File({ image, updateImages }) {
|
|||
onClose={() => setOpen(false)}
|
||||
title={<Title>{image.file}</Title>}
|
||||
size='xl'
|
||||
overlayBlur={3}
|
||||
overlayColor={theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white'}
|
||||
>
|
||||
<Stack>
|
||||
<Type
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AppShell, Box, Burger, Divider, Group, Header, MediaQuery, Navbar, Paper, Popover, ScrollArea, Select, Text, ThemeIcon, Title, UnstyledButton, useMantineTheme } from '@mantine/core';
|
||||
import { AppShell, Box, Burger, Button, Divider, Header, MediaQuery, Navbar, NavLink, Paper, Popover, ScrollArea, Select, Stack, Text, Title, UnstyledButton, useMantineTheme, Group } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { updateUser } from 'lib/redux/reducers/user';
|
||||
import { useStoreDispatch } from 'lib/redux/store';
|
||||
|
@ -64,37 +64,50 @@ function MenuItem(props) {
|
|||
|
||||
const items = [
|
||||
{
|
||||
icon: <HomeIcon />,
|
||||
icon: <HomeIcon size={18} />,
|
||||
text: 'Home',
|
||||
link: '/dashboard',
|
||||
},
|
||||
{
|
||||
icon: <FileIcon />,
|
||||
icon: <FileIcon size={18} />,
|
||||
text: 'Files',
|
||||
link: '/dashboard/files',
|
||||
},
|
||||
{
|
||||
icon: <ActivityIcon />,
|
||||
icon: <ActivityIcon size={18} />,
|
||||
text: 'Stats',
|
||||
link: '/dashboard/stats',
|
||||
},
|
||||
{
|
||||
icon: <LinkIcon />,
|
||||
icon: <LinkIcon size={18} />,
|
||||
text: 'URLs',
|
||||
link: '/dashboard/urls',
|
||||
},
|
||||
{
|
||||
icon: <UploadIcon />,
|
||||
icon: <UploadIcon size={18} />,
|
||||
text: 'Upload',
|
||||
link: '/dashboard/upload',
|
||||
},
|
||||
{
|
||||
icon: <TypeIcon />,
|
||||
icon: <TypeIcon size={18} />,
|
||||
text: 'Upload Text',
|
||||
link: '/dashboard/text',
|
||||
},
|
||||
];
|
||||
|
||||
const admin_items = [
|
||||
{
|
||||
icon: <UserIcon size={18} />,
|
||||
text: 'Users',
|
||||
link: '/dashboard/users',
|
||||
},
|
||||
{
|
||||
icon: <TagIcon size={18} />,
|
||||
text: 'Invites',
|
||||
link: '/dashboard/invites',
|
||||
},
|
||||
];
|
||||
|
||||
export default function Layout({ children, user, title }) {
|
||||
const [token, setToken] = useState(user?.token);
|
||||
const [systemTheme, setSystemTheme] = useState(user.systemTheme ?? 'system');
|
||||
|
@ -104,7 +117,6 @@ export default function Layout({ children, user, title }) {
|
|||
const dispatch = useStoreDispatch();
|
||||
const theme = useMantineTheme();
|
||||
const modals = useModals();
|
||||
const notif = useNotifications();
|
||||
const clipboard = useClipboard();
|
||||
|
||||
const handleUpdateTheme = async value => {
|
||||
|
@ -116,7 +128,7 @@ export default function Layout({ children, user, title }) {
|
|||
dispatch(updateUser(newUser));
|
||||
router.replace(router.pathname);
|
||||
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: `Theme changed to ${friendlyThemeName[value]}`,
|
||||
message: '',
|
||||
color: 'green',
|
||||
|
@ -126,8 +138,6 @@ export default function Layout({ children, user, title }) {
|
|||
|
||||
const openResetToken = () => modals.openConfirmModal({
|
||||
title: 'Reset Token',
|
||||
centered: true,
|
||||
overlayBlur: 3,
|
||||
children: (
|
||||
<Text size='sm'>
|
||||
Once you reset your token, you will have to update any uploaders to use this new token.
|
||||
|
@ -138,14 +148,14 @@ export default function Layout({ children, user, title }) {
|
|||
const a = await useFetch('/api/user/token', 'PATCH');
|
||||
if (!a.success) {
|
||||
setToken(a.success);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Token Reset Failed',
|
||||
message: a.error,
|
||||
color: 'red',
|
||||
icon: <CrossIcon />,
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Token Reset',
|
||||
message: 'Your token has been reset. You will need to update any uploaders to use this new token.',
|
||||
color: 'green',
|
||||
|
@ -159,8 +169,6 @@ export default function Layout({ children, user, title }) {
|
|||
|
||||
const openCopyToken = () => modals.openConfirmModal({
|
||||
title: 'Copy Token',
|
||||
centered: true,
|
||||
overlayBlur: 3,
|
||||
children: (
|
||||
<Text size='sm'>
|
||||
Make sure you don't share this token with anyone as they will be able to upload files on your behalf.
|
||||
|
@ -170,7 +178,7 @@ export default function Layout({ children, user, title }) {
|
|||
onConfirm: async () => {
|
||||
clipboard.copy(token);
|
||||
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Token Copied',
|
||||
message: 'Your token has been copied to your clipboard.',
|
||||
color: 'green',
|
||||
|
@ -187,7 +195,7 @@ export default function Layout({ children, user, title }) {
|
|||
fixed
|
||||
navbar={
|
||||
<Navbar
|
||||
p='md'
|
||||
pt='sm'
|
||||
hiddenBreakpoint='sm'
|
||||
hidden={!opened}
|
||||
width={{ sm: 200, lg: 230 }}
|
||||
|
@ -195,84 +203,37 @@ export default function Layout({ children, user, title }) {
|
|||
<Navbar.Section
|
||||
grow
|
||||
component={ScrollArea}
|
||||
ml={-10}
|
||||
mr={-10}
|
||||
sx={{ paddingLeft: 10, paddingRight: 10 }}
|
||||
>
|
||||
{items.map(({ icon, text, link }) => (
|
||||
<Link href={link} key={text} passHref>
|
||||
<UnstyledButton
|
||||
sx={{
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
padding: theme.spacing.xs,
|
||||
borderRadius: theme.radius.sm,
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.other.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Group>
|
||||
<ThemeIcon color='primary' variant='filled'>
|
||||
{icon}
|
||||
</ThemeIcon>
|
||||
|
||||
<Text size='lg'>{text}</Text>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
<NavLink
|
||||
component='a'
|
||||
label={text}
|
||||
icon={icon}
|
||||
active={router.pathname === link}
|
||||
variant='light'
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
{user.administrator && (
|
||||
<>
|
||||
<Link href='/dashboard/users' passHref>
|
||||
<UnstyledButton
|
||||
sx={{
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
padding: theme.spacing.xs,
|
||||
borderRadius: theme.radius.sm,
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.other.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Group>
|
||||
<ThemeIcon color='primary' variant='filled'>
|
||||
<UserIcon />
|
||||
</ThemeIcon>
|
||||
|
||||
<Text size='lg'>Users</Text>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</Link>
|
||||
<Link href='/dashboard/invites' passHref>
|
||||
<UnstyledButton
|
||||
sx={{
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
padding: theme.spacing.xs,
|
||||
borderRadius: theme.radius.sm,
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.other.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Group>
|
||||
<ThemeIcon color='primary' variant='filled'>
|
||||
<TagIcon />
|
||||
</ThemeIcon>
|
||||
|
||||
<Text size='lg'>Invites</Text>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</Link>
|
||||
</>
|
||||
<NavLink
|
||||
label='Administration'
|
||||
icon={<SettingsIcon />}
|
||||
childrenOffset={28}
|
||||
defaultOpened={admin_items.map(x => x.link).includes(router.pathname)}
|
||||
>
|
||||
{admin_items.map(({ icon, text, link }) => (
|
||||
<Link href={link} key={text} passHref>
|
||||
<NavLink
|
||||
component='a'
|
||||
label={text}
|
||||
icon={icon}
|
||||
active={router.pathname === link}
|
||||
variant='light'
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
</NavLink>
|
||||
)}
|
||||
</Navbar.Section>
|
||||
</Navbar>
|
||||
|
@ -291,75 +252,78 @@ export default function Layout({ children, user, title }) {
|
|||
<Title ml='md'>{title}</Title>
|
||||
<Box sx={{ marginLeft: 'auto', marginRight: 0 }}>
|
||||
<Popover
|
||||
position='top'
|
||||
placement='end'
|
||||
spacing={4}
|
||||
position='bottom-end'
|
||||
opened={open}
|
||||
onClose={() => setOpen(false)}
|
||||
target={
|
||||
<UnstyledButton
|
||||
onClick={() => setOpen(!open)}
|
||||
sx={{
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
padding: theme.spacing.xs,
|
||||
borderRadius: theme.radius.sm,
|
||||
color: theme.other.color,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.other.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Group>
|
||||
<ThemeIcon color='primary' variant='filled'>
|
||||
<SettingsIcon />
|
||||
</ThemeIcon>
|
||||
<Text>{user.username}</Text>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
}
|
||||
>
|
||||
<Group direction='column' spacing={2}>
|
||||
<Text sx={{
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],
|
||||
fontWeight: 500,
|
||||
fontSize: theme.fontSizes.sm,
|
||||
padding: `${theme.spacing.xs / 2}px ${theme.spacing.sm}px`,
|
||||
cursor: 'default',
|
||||
}}
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftIcon={<SettingsIcon />}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
sx={t => ({
|
||||
backgroundColor: '#00000000',
|
||||
'&:hover': {
|
||||
backgroundColor: t.other.hover,
|
||||
},
|
||||
})}
|
||||
size='xl'
|
||||
p='sm'
|
||||
>
|
||||
{user.username}
|
||||
</Text>
|
||||
<MenuItemLink icon={<SettingsIcon />} href='/dashboard/manage'>Manage Account</MenuItemLink>
|
||||
<MenuItem icon={<CopyIcon />} onClick={() => {setOpen(false);openCopyToken();}}>Copy Token</MenuItem>
|
||||
<MenuItem icon={<DeleteIcon />} onClick={() => {setOpen(false);openResetToken();}} color='red'>Reset Token</MenuItem>
|
||||
<MenuItemLink icon={<LogoutIcon />} href='/auth/logout' color='red'>Logout</MenuItemLink>
|
||||
<Divider
|
||||
variant='solid'
|
||||
my={theme.spacing.xs / 2}
|
||||
sx={theme => ({
|
||||
width: '110%',
|
||||
borderTopColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[2],
|
||||
margin: `${theme.spacing.xs / 2}px -4px`,
|
||||
})}
|
||||
/>
|
||||
<MenuItem icon={<PencilIcon />}>
|
||||
<Select
|
||||
size='xs'
|
||||
data={Object.keys(themes).map(t => ({ value: t, label: friendlyThemeName[t] }))}
|
||||
value={systemTheme}
|
||||
onChange={handleUpdateTheme}
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
|
||||
<Popover.Dropdown p={4}>
|
||||
<Stack spacing={2}>
|
||||
<Text sx={{
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],
|
||||
fontWeight: 500,
|
||||
fontSize: theme.fontSizes.sm,
|
||||
padding: `${theme.spacing.xs / 2}px ${theme.spacing.sm}px`,
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
{user.username}
|
||||
</Text>
|
||||
<MenuItemLink icon={<SettingsIcon />} href='/dashboard/manage'>Manage Account</MenuItemLink>
|
||||
<MenuItem icon={<CopyIcon />} onClick={() => {setOpen(false);openCopyToken();}}>Copy Token</MenuItem>
|
||||
<MenuItem icon={<DeleteIcon />} onClick={() => {setOpen(false);openResetToken();}} color='red'>Reset Token</MenuItem>
|
||||
<MenuItemLink icon={<LogoutIcon />} href='/auth/logout' color='red'>Logout</MenuItemLink>
|
||||
<Divider
|
||||
variant='solid'
|
||||
my={theme.spacing.xs / 2}
|
||||
sx={theme => ({
|
||||
width: '110%',
|
||||
borderTopColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[2],
|
||||
margin: `${theme.spacing.xs / 2}px -4px`,
|
||||
})}
|
||||
/>
|
||||
</MenuItem>
|
||||
</Group>
|
||||
<MenuItem icon={<PencilIcon />}>
|
||||
<Select
|
||||
size='xs'
|
||||
data={Object.keys(themes).map(t => ({ value: t, label: friendlyThemeName[t] }))}
|
||||
value={systemTheme}
|
||||
onChange={handleUpdateTheme}
|
||||
/>
|
||||
</MenuItem>
|
||||
</Stack>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Box>
|
||||
</div>
|
||||
</Header>
|
||||
}
|
||||
>
|
||||
<Paper withBorder p='md' shadow='xs'>{children}</Paper>
|
||||
<Paper
|
||||
withBorder
|
||||
p='md'
|
||||
shadow='xs'
|
||||
sx={t => ({
|
||||
borderColor: t.colorScheme === 'dark' ? t.colors.dark[5] : t.colors.dark[0],
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</Paper>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Text } from '@mantine/core';
|
||||
|
||||
export default function MutedText({ children, ...props }) {
|
||||
return <Text color='gray' size='xl' {...props}>{children}</Text>;
|
||||
return <Text color='dimmed' size='xl' {...props}>{children}</Text>;
|
||||
}
|
|
@ -49,27 +49,30 @@ export default function PasswordStrength({ value, setValue, setStrength, ...prop
|
|||
return (
|
||||
<Popover
|
||||
opened={popoverOpened}
|
||||
position='bottom'
|
||||
placement='start'
|
||||
position='top'
|
||||
width='target'
|
||||
withArrow
|
||||
trapFocus={false}
|
||||
transition='pop-top-left'
|
||||
onFocusCapture={() => setPopoverOpened(true)}
|
||||
onBlurCapture={() => setPopoverOpened(false)}
|
||||
styles={{ root: { width: '100%' } }}
|
||||
target={
|
||||
<PasswordInput
|
||||
label='Password'
|
||||
description='Strong password should include letters in lower and uppercase, at least 1 number, at least 1 special symbol'
|
||||
value={value}
|
||||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Progress color={color} value={strength} size={7} mb='md' />
|
||||
<PasswordRequirement label='Includes at least 8 characters' meets={value.length > 7} />
|
||||
{checks}
|
||||
<Popover.Target>
|
||||
<div
|
||||
onFocusCapture={() => setPopoverOpened(true)}
|
||||
onBlurCapture={() => setPopoverOpened(false)}
|
||||
>
|
||||
<PasswordInput
|
||||
label='Password'
|
||||
description='A strong password should include letters in lower and uppercase, at least 1 number, at least 1 special symbol'
|
||||
value={value}
|
||||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown sx={{ }}>
|
||||
<Progress color={color} value={strength} size={7} mb='md' />
|
||||
<PasswordRequirement label='Includes at least 8 characters' meets={value.length > 7} />
|
||||
{checks}
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -64,24 +64,34 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
|
|||
<MantineProvider
|
||||
withGlobalStyles
|
||||
withNormalizeCSS
|
||||
theme={theme}
|
||||
styles={{
|
||||
AppShell: t => ({
|
||||
root: {
|
||||
backgroundColor: t.other.AppShell_backgroundColor,
|
||||
theme={{
|
||||
...theme,
|
||||
components: {
|
||||
AppShell: {
|
||||
styles: t => ({
|
||||
root: {
|
||||
backgroundColor: t.other.AppShell_backgroundColor,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
Popover: {
|
||||
inner: {
|
||||
width: 200,
|
||||
NavLink: {
|
||||
styles: t => ({
|
||||
icon: {
|
||||
paddingLeft: t.spacing.sm,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Accordion: {
|
||||
itemTitle: {
|
||||
border: 0,
|
||||
Modal: {
|
||||
defaultProps: {
|
||||
centered: true,
|
||||
overlayBlur: 3,
|
||||
overlayColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
|
||||
},
|
||||
},
|
||||
itemOpened: {
|
||||
border: 0,
|
||||
Popover: {
|
||||
defaultProps: {
|
||||
transition: 'pop',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { Group, Image, Stack, Text } from '@mantine/core';
|
||||
import { Group, Image, Text } from '@mantine/core';
|
||||
import { Prism } from '@mantine/prism';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AudioIcon, FileIcon, PlayIcon, TypeIcon, VideoIcon } from './icons';
|
||||
import MutedText from './MutedText';
|
||||
import { AudioIcon, FileIcon, PlayIcon } from './icons';
|
||||
|
||||
function Placeholder({ text, Icon, ...props }) {
|
||||
return (
|
||||
<Image height={200} withPlaceholder placeholder={
|
||||
<Group>
|
||||
<Icon size={48} />
|
||||
<Text>{text}</Text>
|
||||
<Text size='md'>{text}</Text>
|
||||
</Group>
|
||||
} {...props} />
|
||||
);
|
||||
|
|
|
@ -1,52 +1,24 @@
|
|||
import React from 'react';
|
||||
import { Dropzone as MantineDropzone } from '@mantine/dropzone';
|
||||
import { Group, Text, useMantineTheme } from '@mantine/core';
|
||||
import { CrossIcon, UploadIcon, ImageIcon } from 'components/icons';
|
||||
|
||||
function ImageUploadIcon({ status, ...props }) {
|
||||
if (status.accepted) {
|
||||
return <UploadIcon {...props} />;
|
||||
}
|
||||
|
||||
if (status.rejected) {
|
||||
return <CrossIcon {...props} />;
|
||||
}
|
||||
|
||||
return <ImageIcon {...props} />;
|
||||
}
|
||||
|
||||
function getIconColor(status, theme) {
|
||||
return status.accepted
|
||||
? theme.colors[theme.primaryColor][6]
|
||||
: status.rejected
|
||||
? theme.colors.red[6]
|
||||
: theme.colorScheme === 'dark'
|
||||
? theme.colors.dark[0]
|
||||
: theme.black;
|
||||
}
|
||||
|
||||
import { ImageIcon } from 'components/icons';
|
||||
|
||||
export default function Dropzone({ loading, onDrop, children }) {
|
||||
const theme = useMantineTheme();
|
||||
|
||||
return (
|
||||
<MantineDropzone loading={loading} onDrop={onDrop}>
|
||||
{status => (
|
||||
<>
|
||||
<Group position='center' spacing='xl' style={{ minHeight: 440, pointerEvents: 'none' }}>
|
||||
<ImageUploadIcon
|
||||
status={status}
|
||||
style={{ width: 80, height: 80, color: getIconColor(status, theme) }}
|
||||
/>
|
||||
<MantineDropzone onDrop={onDrop}>
|
||||
<Group position='center' spacing='xl' style={{ minHeight: 440 }}>
|
||||
<ImageIcon size={80} />
|
||||
|
||||
<Text size='xl' inline>
|
||||
Drag images here or click to select files
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size='xl' inline>
|
||||
Drag images here or click to select files
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
<div style={{ pointerEvents: 'all' }}>
|
||||
{children}
|
||||
</div>
|
||||
</MantineDropzone>
|
||||
);
|
||||
}
|
|
@ -11,6 +11,7 @@ export function FilePreview({ file }: { file: File }) {
|
|||
style={{ maxWidth: '10vw', maxHeight: '100vh' }}
|
||||
src={URL.createObjectURL(file)}
|
||||
alt={file.name}
|
||||
popup
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -21,13 +22,11 @@ export default function FileDropzone({ file }: { file: File }) {
|
|||
return (
|
||||
<Tooltip
|
||||
position='top'
|
||||
placement='center'
|
||||
allowPointerEvents
|
||||
label={
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<FilePreview file={file} />
|
||||
|
||||
<Table sx={{ color: theme.colorScheme === 'dark' ? 'black' : 'white' }} ml='md'>
|
||||
<Table sx={{ color: theme.colorScheme === 'dark' ? 'white' : 'white' }} ml='md'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SimpleGrid, Skeleton, Text, Title } from '@mantine/core';
|
||||
import { randomId, useClipboard } from '@mantine/hooks';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import Card from 'components/Card';
|
||||
import File from 'components/File';
|
||||
import { CopyIcon, CrossIcon, DeleteIcon } from 'components/icons';
|
||||
|
@ -21,7 +21,6 @@ export default function Dashboard() {
|
|||
const [recent, setRecent] = useState([]);
|
||||
const [stats, setStats] = useState(null);
|
||||
const clipboard = useClipboard();
|
||||
const notif = useNotifications();
|
||||
|
||||
const updateImages = async () => {
|
||||
const imgs = await useFetch('/api/user/files');
|
||||
|
@ -36,14 +35,14 @@ export default function Dashboard() {
|
|||
const res = await useFetch('/api/user/files', 'DELETE', { id: original.id });
|
||||
if (!res.error) {
|
||||
updateImages();
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Image Deleted',
|
||||
message: '',
|
||||
color: 'green',
|
||||
icon: <DeleteIcon />,
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to delete image',
|
||||
message: res.error,
|
||||
color: 'red',
|
||||
|
@ -55,7 +54,7 @@ export default function Dashboard() {
|
|||
|
||||
const copyImage = async ({ original }) => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${original.url}`);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <CopyIcon />,
|
||||
|
@ -73,7 +72,7 @@ export default function Dashboard() {
|
|||
return (
|
||||
<>
|
||||
<Title>Welcome back, {user?.username}</Title>
|
||||
<Text color='gray' mb='sm'>You have <b>{images.length ? images.length : '...'}</b> files</Text>
|
||||
<MutedText size='md'>You have <b>{images.length ? images.length : '...'}</b> files</MutedText>
|
||||
|
||||
<Title>Recent Files</Title>
|
||||
<SimpleGrid
|
||||
|
@ -85,15 +84,15 @@ export default function Dashboard() {
|
|||
>
|
||||
{recent.length ? recent.map(image => (
|
||||
<File key={randomId()} image={image} updateImages={updateImages} />
|
||||
)) : [1,2,3,4].map(x => (
|
||||
)) : [1, 2, 3, 4].map(x => (
|
||||
<div key={x}>
|
||||
<Skeleton width='100%' height={220} sx={{ borderRadius: 1 }}/>
|
||||
<Skeleton width='100%' height={220} sx={{ borderRadius: 1 }} />
|
||||
</div>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
<Title mt='md'>Stats</Title>
|
||||
<Text>View more stats here <Link href='/dashboard/stats'>here</Link>.</Text>
|
||||
<MutedText size='md'>View more stats here <Link href='/dashboard/stats'>here</Link>.</MutedText>
|
||||
<SimpleGrid
|
||||
cols={3}
|
||||
spacing='lg'
|
||||
|
@ -117,7 +116,7 @@ export default function Dashboard() {
|
|||
</SimpleGrid>
|
||||
|
||||
<Title mt='md'>Files</Title>
|
||||
<Text>View your gallery <Link href='/dashboard/files'>here</Link>.</Text>
|
||||
<MutedText size='md'>View your gallery <Link href='/dashboard/files'>here</Link>.</MutedText>
|
||||
<ImagesTable
|
||||
columns={[
|
||||
{ accessor: 'file', Header: 'Name', minWidth: 170, align: 'inherit' as Aligns },
|
||||
|
|
|
@ -34,39 +34,37 @@ export default function Files() {
|
|||
</Group>
|
||||
{favoritePages.length ? (
|
||||
<Accordion
|
||||
offsetIcon={false}
|
||||
sx={t => ({
|
||||
marginTop: 2,
|
||||
border: '1px solid',
|
||||
marginBottom: 12,
|
||||
borderColor: t.colorScheme === 'dark' ? t.colors.dark[6] : t.colors.gray[0] ,
|
||||
})}
|
||||
variant='contained'
|
||||
mb='sm'
|
||||
>
|
||||
<Accordion.Item label={<Title>Favorite Files</Title>}>
|
||||
<SimpleGrid
|
||||
cols={3}
|
||||
spacing='lg'
|
||||
breakpoints={[
|
||||
{ maxWidth: 'sm', cols: 1, spacing: 'sm' },
|
||||
]}
|
||||
>
|
||||
{favoritePages.length ? favoritePages[(favoritePage - 1) ?? 0].map(image => (
|
||||
<div key={image.id}>
|
||||
<File image={image} updateImages={() => updatePages(true)} />
|
||||
</div>
|
||||
)) : null}
|
||||
</SimpleGrid>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingTop: 12,
|
||||
paddingBottom: 3,
|
||||
}}
|
||||
>
|
||||
<Pagination total={favoritePages.length} page={favoritePage} onChange={setFavoritePage}/>
|
||||
</Box>
|
||||
<Accordion.Item value='favorite'>
|
||||
<Accordion.Control>Favorite Files</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<SimpleGrid
|
||||
cols={3}
|
||||
spacing='lg'
|
||||
breakpoints={[
|
||||
{ maxWidth: 'sm', cols: 1, spacing: 'sm' },
|
||||
]}
|
||||
>
|
||||
{favoritePages.length ? favoritePages[(favoritePage - 1) ?? 0].map(image => (
|
||||
<div key={image.id}>
|
||||
<File image={image} updateImages={() => updatePages(true)} />
|
||||
</div>
|
||||
)) : null}
|
||||
</SimpleGrid>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingTop: 12,
|
||||
paddingBottom: 3,
|
||||
}}
|
||||
>
|
||||
<Pagination total={favoritePages.length} page={favoritePage} onChange={setFavoritePage}/>
|
||||
</Box>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
) : null}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { ActionIcon, Avatar, Button, Card, Group, Modal, Select, SimpleGrid, Skeleton, Stack, Switch, TextInput, Title } from '@mantine/core';
|
||||
import { useClipboard, useForm } from '@mantine/hooks';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { CopyIcon, CrossIcon, DeleteIcon, PlusIcon, TagIcon } from 'components/icons';
|
||||
import MutedText from 'components/MutedText';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
|
@ -26,7 +27,6 @@ function CreateInviteModal({ open, setOpen, updateInvites }) {
|
|||
expires: '30m',
|
||||
},
|
||||
});
|
||||
const notif = useNotifications();
|
||||
|
||||
const onSubmit = async values => {
|
||||
if (!expires.includes(values.expires)) return form.setFieldError('expires', 'Invalid expiration');
|
||||
|
@ -47,14 +47,14 @@ function CreateInviteModal({ open, setOpen, updateInvites }) {
|
|||
});
|
||||
|
||||
if (res.error) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to create invite',
|
||||
message: res.error,
|
||||
icon: <CrossIcon />,
|
||||
color: 'red',
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Created invite',
|
||||
message: '',
|
||||
icon: <TagIcon />,
|
||||
|
@ -70,9 +70,6 @@ function CreateInviteModal({ open, setOpen, updateInvites }) {
|
|||
opened={open}
|
||||
onClose={() => setOpen(false)}
|
||||
title={<Title>Create Invite</Title>}
|
||||
overlayBlur={3}
|
||||
centered={true}
|
||||
|
||||
>
|
||||
<form onSubmit={form.onSubmit(v => onSubmit(v))}>
|
||||
<Select
|
||||
|
@ -103,7 +100,6 @@ function CreateInviteModal({ open, setOpen, updateInvites }) {
|
|||
|
||||
export default function Users() {
|
||||
const router = useRouter();
|
||||
const notif = useNotifications();
|
||||
const modals = useModals();
|
||||
const clipboard = useClipboard();
|
||||
|
||||
|
@ -118,14 +114,14 @@ export default function Users() {
|
|||
onConfirm: async () => {
|
||||
const res = await useFetch(`/api/auth/invite?code=${invite.code}`, 'DELETE');
|
||||
if (res.error) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to delete invite ${invite.code}',
|
||||
message: res.error,
|
||||
icon: <CrossIcon />,
|
||||
color: 'red',
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: `Deleted invite ${invite.code}`,
|
||||
message: '',
|
||||
icon: <DeleteIcon />,
|
||||
|
@ -139,7 +135,7 @@ export default function Users() {
|
|||
|
||||
const handleCopy = async invite => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}/invite/${invite.code}`);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <CopyIcon />,
|
||||
|
@ -156,7 +152,6 @@ export default function Users() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(invites);
|
||||
updateInvites();
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Box, Button, Card, ColorInput, Group, MultiSelect, Space, Text, TextInput, PasswordInput, Title, Tooltip } from '@mantine/core';
|
||||
import { randomId, useForm, useInterval } from '@mantine/hooks';
|
||||
import { randomId, useInterval } from '@mantine/hooks';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||
import { CrossIcon, DeleteIcon } from 'components/icons';
|
||||
import DownloadIcon from 'components/icons/DownloadIcon';
|
||||
import Link from 'components/Link';
|
||||
|
@ -11,31 +12,15 @@ import { bytesToRead } from 'lib/clientUtils';
|
|||
import { updateUser } from 'lib/redux/reducers/user';
|
||||
import { useStoreDispatch, useStoreSelector } from 'lib/redux/store';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function VarsTooltip({ children }) {
|
||||
return (
|
||||
<Tooltip position='top' placement='center' color='' label={
|
||||
<>
|
||||
<Text><b>{'{image.file}'}</b> - file name</Text>
|
||||
<Text><b>{'{image.mimetype}'}</b> - mimetype</Text>
|
||||
<Text><b>{'{image.id}'}</b> - id of the image</Text>
|
||||
<Text><b>{'{user.name}'}</b> - your username</Text>
|
||||
visit <Link href='https://zipl.vercel.app/docs/variables'>the docs</Link> for more variables
|
||||
</>
|
||||
}>
|
||||
{children}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
import MutedText from 'components/MutedText';
|
||||
|
||||
function ExportDataTooltip({ children }) {
|
||||
return <Tooltip position='top' placement='center' color='' label='After clicking, if you have a lot of files the export can take a while to complete. A list of previous exports will be below to download.'>{children}</Tooltip>;
|
||||
return <Tooltip position='top' color='' label='After clicking, if you have a lot of files the export can take a while to complete. A list of previous exports will be below to download.'>{children}</Tooltip>;
|
||||
}
|
||||
|
||||
export default function Manage() {
|
||||
const user = useStoreSelector(state => state.user);
|
||||
const dispatch = useStoreDispatch();
|
||||
const notif = useNotifications();
|
||||
const modals = useModals();
|
||||
|
||||
const [exports, setExports] = useState([]);
|
||||
|
@ -87,7 +72,8 @@ export default function Manage() {
|
|||
|
||||
if (cleanUsername === '') return form.setFieldError('username', 'Username can\'t be nothing');
|
||||
|
||||
const id = notif.showNotification({
|
||||
showNotification({
|
||||
id: 'update-user',
|
||||
title: 'Updating user...',
|
||||
message: '',
|
||||
loading: true,
|
||||
|
@ -107,7 +93,8 @@ export default function Manage() {
|
|||
|
||||
if (newUser.error) {
|
||||
if (newUser.invalidDomains) {
|
||||
notif.updateNotification(id, {
|
||||
updateNotification({
|
||||
id: 'update-user',
|
||||
message: <>
|
||||
<Text mt='xs'>The following domains are invalid:</Text>
|
||||
{newUser.invalidDomains.map(err => (
|
||||
|
@ -121,7 +108,8 @@ export default function Manage() {
|
|||
icon: <CrossIcon />,
|
||||
});
|
||||
}
|
||||
notif.updateNotification(id, {
|
||||
updateNotification({
|
||||
id: 'update-user',
|
||||
title: 'Couldn\'t save user',
|
||||
message: newUser.error,
|
||||
color: 'red',
|
||||
|
@ -129,7 +117,8 @@ export default function Manage() {
|
|||
});
|
||||
} else {
|
||||
dispatch(updateUser(newUser));
|
||||
notif.updateNotification(id, {
|
||||
updateNotification({
|
||||
id: 'update-user',
|
||||
title: 'Saved User',
|
||||
message: '',
|
||||
});
|
||||
|
@ -139,7 +128,7 @@ export default function Manage() {
|
|||
const exportData = async () => {
|
||||
const res = await useFetch('/api/user/export', 'POST');
|
||||
if (res.url) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Export started...',
|
||||
loading: true,
|
||||
message: 'If you have a lot of files, the export may take a while. The list of exports will be updated every 30s.',
|
||||
|
@ -163,14 +152,14 @@ export default function Manage() {
|
|||
});
|
||||
|
||||
if (!res.count) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Couldn\'t delete files',
|
||||
message: res.error,
|
||||
color: 'red',
|
||||
icon: <CrossIcon />,
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Deleted files',
|
||||
message: `${res.count} files deleted`,
|
||||
color: 'green',
|
||||
|
@ -182,14 +171,10 @@ export default function Manage() {
|
|||
const openDeleteModal = () => modals.openConfirmModal({
|
||||
title: 'Are you sure you want to delete all of your images?',
|
||||
closeOnConfirm: false,
|
||||
centered: true,
|
||||
overlayBlur: 3,
|
||||
labels: { confirm: 'Yes', cancel: 'No' },
|
||||
onConfirm: () => {
|
||||
modals.openConfirmModal({
|
||||
title: 'Are you really sure?',
|
||||
centered: true,
|
||||
overlayBlur: 3,
|
||||
labels: { confirm: 'Yes', cancel: 'No' },
|
||||
onConfirm: () => {
|
||||
handleDelete();
|
||||
|
@ -211,9 +196,7 @@ export default function Manage() {
|
|||
return (
|
||||
<>
|
||||
<Title>Manage User</Title>
|
||||
<VarsTooltip>
|
||||
<Text color='gray'>Want to use variables in embed text? Hover on this or visit <Link href='https://zipline.diced.cf/docs/variables'>the docs</Link> for more variables</Text>
|
||||
</VarsTooltip>
|
||||
<MutedText size='md'>Want to use variables in embed text? Visit <Link href='https://zipline.diced.cf/docs/variables'>the docs</Link> for variables</MutedText>
|
||||
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
|
||||
<TextInput id='username' label='Username' {...form.getInputProps('username')} />
|
||||
<PasswordInput id='password' label='Password' description='Leave blank to keep your old password' {...form.getInputProps('password')} />
|
||||
|
@ -242,7 +225,7 @@ export default function Manage() {
|
|||
|
||||
<Box mb='md'>
|
||||
<Title>Manage Data</Title>
|
||||
<Text color='gray'>Delete, or export your data into a zip file.</Text>
|
||||
<MutedText size='md'>Delete, or export your data into a zip file.</MutedText>
|
||||
</Box>
|
||||
|
||||
<Group>
|
||||
|
@ -250,17 +233,21 @@ export default function Manage() {
|
|||
<ExportDataTooltip><Button onClick={exportData} rightIcon={<DownloadIcon />}>Export Data</Button></ExportDataTooltip>
|
||||
</Group>
|
||||
<Card mt={22}>
|
||||
<SmallTable
|
||||
columns={[
|
||||
{ id: 'name', name: 'Name' },
|
||||
{ id: 'date', name: 'Date' },
|
||||
{ id: 'size', name: 'Size' },
|
||||
]}
|
||||
rows={exports ? exports.map((x, i) => ({
|
||||
name: <Link href={'/api/user/export?name=' + x.full}>Export {i + 1}</Link>,
|
||||
date: x.date.toLocaleString(),
|
||||
size: bytesToRead(x.size),
|
||||
})) : []} />
|
||||
{exports && exports.length ? (
|
||||
<SmallTable
|
||||
columns={[
|
||||
{ id: 'name', name: 'Name' },
|
||||
{ id: 'date', name: 'Date' },
|
||||
{ id: 'size', name: 'Size' },
|
||||
]}
|
||||
rows={exports ? exports.map((x, i) => ({
|
||||
name: <Link href={'/api/user/export?name=' + x.full}>Export {i + 1}</Link>,
|
||||
date: x.date.toLocaleString(),
|
||||
size: bytesToRead(x.size),
|
||||
})) : []} />
|
||||
) : (
|
||||
<Text>No exports yet</Text>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Title my='md'>ShareX Config</Title>
|
||||
|
|
|
@ -43,7 +43,7 @@ export default function Stats() {
|
|||
</Card>
|
||||
</SimpleGrid>
|
||||
|
||||
{stats.count_by_user.length ? (
|
||||
{stats && stats.count_by_user.length ? (
|
||||
<Card name='Files per User' mt={22}>
|
||||
<SmallTable
|
||||
columns={[
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Button, Collapse, Group, Progress, Title } from '@mantine/core';
|
||||
import { randomId, useClipboard } from '@mantine/hooks';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||
import Dropzone from 'components/dropzone/Dropzone';
|
||||
import FileDropzone from 'components/dropzone/DropzoneFile';
|
||||
import { CrossIcon, UploadIcon } from 'components/icons';
|
||||
|
@ -9,7 +9,6 @@ import { useStoreSelector } from 'lib/redux/store';
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function Upload() {
|
||||
const notif = useNotifications();
|
||||
const clipboard = useClipboard();
|
||||
const user = useStoreSelector(state => state.user);
|
||||
|
||||
|
@ -22,7 +21,7 @@ export default function Upload() {
|
|||
const item = Array.from(e.clipboardData.items).find(x => /^image/.test(x.type));
|
||||
const file = item.getAsFile();
|
||||
setFiles([...files, file]);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Image imported from clipboard',
|
||||
message: '',
|
||||
});
|
||||
|
@ -35,7 +34,8 @@ export default function Upload() {
|
|||
const body = new FormData();
|
||||
for (let i = 0; i !== files.length; ++i) body.append('file', files[i]);
|
||||
|
||||
const id = notif.showNotification({
|
||||
showNotification({
|
||||
id: 'upload',
|
||||
title: 'Uploading Images...',
|
||||
message: '',
|
||||
loading: true,
|
||||
|
@ -55,7 +55,8 @@ export default function Upload() {
|
|||
setLoading(false);
|
||||
|
||||
if (json.error === undefined) {
|
||||
notif.updateNotification(id, {
|
||||
updateNotification({
|
||||
id: 'upload',
|
||||
title: 'Upload Successful',
|
||||
message: <>Copied first image to clipboard! <br />{json.files.map(x => (<Link key={x} href={x}>{x}<br /></Link>))}</>,
|
||||
color: 'green',
|
||||
|
@ -64,7 +65,8 @@ export default function Upload() {
|
|||
clipboard.copy(json.files[0]);
|
||||
setFiles([]);
|
||||
} else {
|
||||
notif.updateNotification(id, {
|
||||
updateNotification({
|
||||
id: 'upload',
|
||||
title: 'Upload Failed',
|
||||
message: json.error,
|
||||
color: 'red',
|
||||
|
@ -85,7 +87,7 @@ export default function Upload() {
|
|||
|
||||
<Dropzone loading={loading} onDrop={(f) => setFiles([...files, ...f])}>
|
||||
<Group position='center' spacing='md'>
|
||||
{files.map(file => (<FileDropzone key={randomId()} file={file} />))}
|
||||
{files.map(file => (<FileDropzone key={randomId()} file={file} />))}
|
||||
</Group>
|
||||
</Dropzone>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Button, Group, LoadingOverlay, Select, Title } from '@mantine/core';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { Button, Group, Select, Title } from '@mantine/core';
|
||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||
import CodeInput from 'components/CodeInput';
|
||||
import { TypeIcon, UploadIcon } from 'components/icons';
|
||||
import Link from 'components/Link';
|
||||
|
@ -8,7 +8,6 @@ import { useStoreSelector } from 'lib/redux/store';
|
|||
import { useState } from 'react';
|
||||
|
||||
export default function Upload() {
|
||||
const notif = useNotifications();
|
||||
const user = useStoreSelector(state => state.user);
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
|
@ -17,7 +16,8 @@ export default function Upload() {
|
|||
const handleUpload = async () => {
|
||||
const file = new File([value], 'text.' + lang);
|
||||
|
||||
const id = notif.showNotification({
|
||||
showNotification({
|
||||
id: 'upload-text',
|
||||
title: 'Uploading...',
|
||||
message: '',
|
||||
loading: true,
|
||||
|
@ -30,7 +30,8 @@ export default function Upload() {
|
|||
const json = JSON.parse(e.target.response);
|
||||
|
||||
if (!json.error) {
|
||||
notif.updateNotification(id, {
|
||||
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>))}</>,
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ActionIcon, Button, Card, Group, Modal, SimpleGrid, Skeleton, TextInput, Title } from '@mantine/core';
|
||||
import { useClipboard, useForm } from '@mantine/hooks';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { CopyIcon, CrossIcon, DeleteIcon, LinkIcon, PlusIcon } from 'components/icons';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useStoreSelector } from 'lib/redux/store';
|
||||
|
@ -8,7 +9,6 @@ import { useEffect, useState } from 'react';
|
|||
|
||||
export default function Urls() {
|
||||
const user = useStoreSelector(state => state.user);
|
||||
const notif = useNotifications();
|
||||
const clipboard = useClipboard();
|
||||
|
||||
const [urls, setURLS] = useState([]);
|
||||
|
@ -23,14 +23,14 @@ export default function Urls() {
|
|||
const deleteURL = async u => {
|
||||
const url = await useFetch('/api/user/urls', 'DELETE', { id: u.id });
|
||||
if (url.error) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to delete URL',
|
||||
message: url.error,
|
||||
icon: <DeleteIcon />,
|
||||
color: 'red',
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Deleted URL',
|
||||
message: '',
|
||||
icon: <CrossIcon />,
|
||||
|
@ -43,7 +43,7 @@ export default function Urls() {
|
|||
|
||||
const copyURL = u => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${u.url}`);
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <CopyIcon />,
|
||||
|
@ -86,14 +86,14 @@ export default function Urls() {
|
|||
const json = await res.json();
|
||||
|
||||
if (json.error) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to create URL',
|
||||
message: json.error,
|
||||
color: 'red',
|
||||
icon: <CrossIcon />,
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'URL shortened',
|
||||
message: json.url,
|
||||
color: 'green',
|
||||
|
@ -128,7 +128,7 @@ export default function Urls() {
|
|||
|
||||
<Group mb='md'>
|
||||
<Title>URLs</Title>
|
||||
<ActionIcon variant='filled' color='primary' onClick={() => setCreateOpen(true)}><PlusIcon/></ActionIcon>
|
||||
<ActionIcon variant='filled' color='primary' onClick={() => setCreateOpen(true)}><PlusIcon /></ActionIcon>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid
|
||||
|
@ -145,7 +145,7 @@ export default function Urls() {
|
|||
<Title>{url.vanity ?? url.id}</Title>
|
||||
</Group>
|
||||
<Group position='right'>
|
||||
<ActionIcon href={url.url} component='a' target='_blank'><LinkIcon/></ActionIcon>
|
||||
<ActionIcon href={url.url} component='a' target='_blank'><LinkIcon /></ActionIcon>
|
||||
<ActionIcon aria-label='copy' onClick={() => copyURL(url)}>
|
||||
<CopyIcon />
|
||||
</ActionIcon>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { ActionIcon, Avatar, Button, Card, Group, Modal, SimpleGrid, Skeleton, Switch, TextInput, Title } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import { ActionIcon, Avatar, Button, Card, Group, Modal, SimpleGrid, Skeleton, Stack, Switch, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { CrossIcon, DeleteIcon, PlusIcon } from 'components/icons';
|
||||
import MutedText from 'components/MutedText';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useStoreSelector } from 'lib/redux/store';
|
||||
import { useRouter } from 'next/router';
|
||||
|
@ -17,7 +18,6 @@ function CreateUserModal({ open, setOpen, updateUsers }) {
|
|||
administrator: false,
|
||||
},
|
||||
});
|
||||
const notif = useNotifications();
|
||||
|
||||
const onSubmit = async values => {
|
||||
const cleanUsername = values.username.trim();
|
||||
|
@ -34,14 +34,14 @@ function CreateUserModal({ open, setOpen, updateUsers }) {
|
|||
setOpen(false);
|
||||
const res = await useFetch('/api/auth/create', 'POST', data);
|
||||
if (res.error) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to create user',
|
||||
message: res.error,
|
||||
icon: <DeleteIcon />,
|
||||
color: 'red',
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Created user: ' + cleanUsername,
|
||||
message: '',
|
||||
icon: <PlusIcon />,
|
||||
|
@ -75,7 +75,6 @@ function CreateUserModal({ open, setOpen, updateUsers }) {
|
|||
export default function Users() {
|
||||
const user = useStoreSelector(state => state.user);
|
||||
const router = useRouter();
|
||||
const notif = useNotifications();
|
||||
const modals = useModals();
|
||||
|
||||
const [users, setUsers] = useState([]);
|
||||
|
@ -87,14 +86,14 @@ export default function Users() {
|
|||
delete_images,
|
||||
});
|
||||
if (res.error) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Failed to delete user',
|
||||
message: res.error,
|
||||
color: 'red',
|
||||
icon: <CrossIcon />,
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'User deleted',
|
||||
message: '',
|
||||
color: 'green',
|
||||
|
@ -108,8 +107,6 @@ export default function Users() {
|
|||
const openDeleteModal = user => modals.openConfirmModal({
|
||||
title: `Delete ${user.username}?`,
|
||||
closeOnConfirm: false,
|
||||
centered: true,
|
||||
overlayBlur: 3,
|
||||
labels: { confirm: 'Yes', cancel: 'No' },
|
||||
onConfirm: () => {
|
||||
modals.openConfirmModal({
|
||||
|
@ -160,8 +157,12 @@ export default function Users() {
|
|||
<Card key={user.id} sx={{ maxWidth: '100%' }}>
|
||||
<Group position='apart'>
|
||||
<Group position='left'>
|
||||
<Avatar color={user.administrator ? 'primary' : 'dark'}>{user.username[0]}</Avatar>
|
||||
<Title>{user.username}</Title>
|
||||
<Avatar size='lg' color={user.administrator ? 'primary' : 'dark'}>{user.username[0]}</Avatar>
|
||||
<Stack spacing={0}>
|
||||
<Title>{user.username}</Title>
|
||||
<MutedText size='sm'>ID: {user.id}</MutedText>
|
||||
<MutedText size='sm'>Administrator: {user.administrator ? 'yes' : 'no'}</MutedText>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Group position='right'>
|
||||
<ActionIcon aria-label='delete' onClick={() => openDeleteModal(user)}>
|
||||
|
|
|
@ -176,7 +176,6 @@ export class Swift extends Datasource {
|
|||
|
||||
public constructor(public config: ConfigSwiftDatasource) {
|
||||
super();
|
||||
console.log(config);
|
||||
this.container = new SwiftContainer({
|
||||
auth_endpoint_url: config.auth_endpoint,
|
||||
credentials: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Button, Center, TextInput, Title, PasswordInput } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import { useForm } from '@mantine/form';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useState } from 'react';
|
|||
import { Button, Card, Center, Group, PasswordInput, Stepper, TextInput } from '@mantine/core';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import PasswordStrength from 'components/PasswordStrength';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { CrossIcon, UserIcon } from 'components/icons';
|
||||
import { useStoreDispatch } from 'lib/redux/store';
|
||||
import { updateUser } from 'lib/redux/reducers/user';
|
||||
|
@ -20,7 +20,6 @@ export default function Invite({ code, title }) {
|
|||
const [verifyPasswordError, setVerifyPasswordError] = useState('');
|
||||
const [strength, setStrength] = useState(0);
|
||||
|
||||
const notif = useNotifications();
|
||||
const dispatch = useStoreDispatch();
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -53,14 +52,14 @@ export default function Invite({ code, title }) {
|
|||
const createUser = async () => {
|
||||
const res = await useFetch('/api/auth/create', 'POST', { code, username, password });
|
||||
if (res.error) {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'Error while creating user',
|
||||
message: res.error,
|
||||
color: 'red',
|
||||
icon: <CrossIcon />,
|
||||
});
|
||||
} else {
|
||||
notif.showNotification({
|
||||
showNotification({
|
||||
title: 'User created',
|
||||
message: 'You will be logged in shortly...',
|
||||
color: 'green',
|
||||
|
|
|
@ -13,10 +13,11 @@ import { version } from '../../package.json';
|
|||
import type { Config } from 'lib/config/Config';
|
||||
import type { Datasource } from 'lib/datasources';
|
||||
|
||||
const dev = process.env.NODE_ENV === 'development';
|
||||
let config: Config, datasource: Datasource;
|
||||
|
||||
const logger = Logger.get('server');
|
||||
logger.info(`starting zipline@${version} server`);
|
||||
logger.info(`starting ${process.env.NODE_ENV || 'production'} zipline@${version} server`);
|
||||
|
||||
start();
|
||||
|
||||
|
@ -40,8 +41,6 @@ async function start() {
|
|||
process.exit(1);
|
||||
};
|
||||
|
||||
const dev = process.env.NODE_ENV === 'development';
|
||||
|
||||
process.env.DATABASE_URL = config.core.database_url;
|
||||
await migrations();
|
||||
|
||||
|
|
Loading…
Reference in a new issue