1
Fork 0
mirror of https://github.com/diced/zipline.git synced 2025-03-28 23:11:22 -05:00

chore: update to mantine@6

* refactor: remove old File.tsx

* feat: initial move to mantine v6

* feat: use api option

* remove: useless size modifier

* fix: user button

* feat: use pininput for 2fa

* fix: breaking changes in migrating mantine v6

---------

Co-authored-by: TacticalCoderJay <gogojayvin923@gmail.com>
This commit is contained in:
dicedtomato 2023-03-04 17:08:43 -08:00 committed by GitHub
parent 986858345e
commit bc4b528ac6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 343 additions and 574 deletions

View file

@ -29,14 +29,14 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",
"@mantine/core": "^5.10.5",
"@mantine/dropzone": "^5.10.5",
"@mantine/form": "^5.10.5",
"@mantine/hooks": "^5.10.5",
"@mantine/modals": "^5.10.5",
"@mantine/next": "^5.10.5",
"@mantine/notifications": "^5.10.5",
"@mantine/prism": "^5.10.5",
"@mantine/core": "^6.0.0",
"@mantine/dropzone": "^6.0.0",
"@mantine/form": "^6.0.0",
"@mantine/hooks": "^6.0.0",
"@mantine/modals": "^6.0.0",
"@mantine/next": "^6.0.0",
"@mantine/notifications": "^6.0.0",
"@mantine/prism": "^6.0.0",
"@prisma/client": "^4.10.1",
"@prisma/internals": "^4.10.1",
"@prisma/migrate": "^4.10.1",

View file

@ -1,16 +1,15 @@
import { createStyles, MantineSize, Textarea } from '@mantine/core';
import { createStyles, Textarea } from '@mantine/core';
import { useEffect } from 'react';
const useStyles = createStyles((theme, { size }: { size: MantineSize }) => ({
const useStyles = createStyles(() => ({
input: {
fontFamily: 'monospace',
fontSize: theme.fn.size({ size, sizes: theme.fontSizes }) - 2,
height: '80vh',
},
}));
export default function CodeInput({ ...props }) {
const { classes } = useStyles({ size: 'md' }, { name: 'CodeInput' });
const { classes } = useStyles(null, { name: 'CodeInput' });
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {

View file

@ -1,398 +0,0 @@
import {
ActionIcon,
Card,
Group,
LoadingOverlay,
Modal,
Select,
SimpleGrid,
Stack,
Text,
Title,
Tooltip,
} from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
import useFetch from 'hooks/useFetch';
import { useFileDelete, useFileFavorite } from 'lib/queries/files';
import { useFolders } from 'lib/queries/folders';
import { bytesToHuman } from 'lib/utils/bytes';
import { relativeTime } from 'lib/utils/client';
import { useState } from 'react';
import {
CalendarIcon,
ClockIcon,
CopyIcon,
CrossIcon,
DeleteIcon,
DownloadIcon,
ExternalLinkIcon,
EyeIcon,
HardDriveIcon,
FileIcon,
FolderMinusIcon,
FolderPlusIcon,
HashIcon,
ImageIcon,
InfoIcon,
StarIcon,
} from './icons';
import MutedText from './MutedText';
import Type from './Type';
export function FileMeta({ Icon, title, subtitle, ...other }) {
return other.tooltip ? (
<Group>
<Icon size={24} />
<Tooltip label={other.tooltip}>
<Stack spacing={1}>
<Text>{title}</Text>
<MutedText size='md'>{subtitle}</MutedText>
</Stack>
</Tooltip>
</Group>
) : (
<Group>
<Icon size={24} />
<Stack spacing={1}>
<Text>{title}</Text>
<MutedText size='md'>{subtitle}</MutedText>
</Stack>
</Group>
);
}
export default function File({
image,
disableMediaPreview,
exifEnabled,
refreshImages,
reducedActions = false,
}) {
const [open, setOpen] = useState(false);
const [overrideRender, setOverrideRender] = useState(false);
const deleteFile = useFileDelete();
const favoriteFile = useFileFavorite();
const clipboard = useClipboard();
const folders = useFolders();
const loading = deleteFile.isLoading || favoriteFile.isLoading;
const handleDelete = async () => {
deleteFile.mutate(image.id, {
onSuccess: () => {
showNotification({
title: 'File Deleted',
message: '',
color: 'green',
icon: <DeleteIcon />,
});
},
onError: (res: any) => {
showNotification({
title: 'Failed to delete file',
message: res.error,
color: 'red',
icon: <CrossIcon />,
});
},
onSettled: () => {
setOpen(false);
},
});
};
const handleCopy = () => {
clipboard.copy(`${window.location.protocol}//${window.location.host}${image.url}`);
setOpen(false);
showNotification({
title: 'Copied to clipboard',
message: '',
icon: <CopyIcon />,
});
};
const handleFavorite = async () => {
favoriteFile.mutate(
{ id: image.id, favorite: !image.favorite },
{
onSuccess: () => {
showNotification({
title: 'Image is now ' + (!image.favorite ? 'favorited' : 'unfavorited'),
message: '',
icon: <StarIcon />,
});
},
onError: (res: any) => {
showNotification({
title: 'Failed to favorite file',
message: res.error,
color: 'red',
icon: <CrossIcon />,
});
},
}
);
};
const inFolder = image.folderId;
const refresh = () => {
refreshImages();
folders.refetch();
};
const removeFromFolder = async () => {
const res = await useFetch('/api/user/folders/' + image.folderId, 'DELETE', {
file: Number(image.id),
});
refresh();
if (!res.error) {
showNotification({
title: 'Removed from folder',
message: res.name,
color: 'green',
icon: <FolderMinusIcon />,
});
} else {
showNotification({
title: 'Failed to remove from folder',
message: res.error,
color: 'red',
icon: <CrossIcon />,
});
}
};
const addToFolder = async (t) => {
const res = await useFetch('/api/user/folders/' + t, 'POST', {
file: Number(image.id),
});
refresh();
if (!res.error) {
showNotification({
title: 'Added to folder',
message: res.name,
color: 'green',
icon: <FolderPlusIcon />,
});
} else {
showNotification({
title: 'Failed to add to folder',
message: res.error,
color: 'red',
icon: <CrossIcon />,
});
}
};
const createFolder = (t) => {
useFetch('/api/user/folders', 'POST', {
name: t,
add: [Number(image.id)],
}).then((res) => {
refresh();
if (!res.error) {
showNotification({
title: 'Created & added to folder',
message: res.name,
color: 'green',
icon: <FolderPlusIcon />,
});
} else {
showNotification({
title: 'Failed to create folder',
message: res.error,
color: 'red',
icon: <CrossIcon />,
});
}
});
return { value: t, label: t };
};
return (
<>
<Modal opened={open} onClose={() => setOpen(false)} title={<Title>{image.name}</Title>} size='xl'>
<LoadingOverlay visible={loading} />
<Stack>
<Type
file={image}
src={`/r/${encodeURI(image.name)}`}
alt={image.name}
popup
sx={{ minHeight: 200 }}
style={{ minHeight: 200 }}
disableMediaPreview={false}
overrideRender={overrideRender}
setOverrideRender={setOverrideRender}
/>
<SimpleGrid
my='md'
cols={3}
breakpoints={[
{ maxWidth: 600, cols: 1 },
{ maxWidth: 900, cols: 2 },
{ maxWidth: 1200, cols: 3 },
]}
>
<FileMeta Icon={FileIcon} title='Name' subtitle={image.name} />
<FileMeta Icon={ImageIcon} title='Type' subtitle={image.mimetype} />
<FileMeta Icon={HardDriveIcon} title='Size' subtitle={bytesToHuman(image.size || 0)} />
<FileMeta Icon={EyeIcon} title='Views' subtitle={image?.views?.toLocaleString()} />
{image.maxViews && (
<FileMeta
Icon={EyeIcon}
title='Max views'
subtitle={image?.maxViews?.toLocaleString()}
tooltip={`This file will be deleted after being viewed ${image?.maxViews?.toLocaleString()} times.`}
/>
)}
<FileMeta
Icon={CalendarIcon}
title='Uploaded'
subtitle={relativeTime(new Date(image.createdAt))}
tooltip={new Date(image?.createdAt).toLocaleString()}
/>
{image.expiresAt && !reducedActions && (
<FileMeta
Icon={ClockIcon}
title='Expires'
subtitle={relativeTime(new Date(image.expiresAt))}
tooltip={new Date(image.expiresAt).toLocaleString()}
/>
)}
<FileMeta Icon={HashIcon} title='ID' subtitle={image.id} />
</SimpleGrid>
</Stack>
<Group position='apart' my='md'>
<Group position='left'>
{exifEnabled && !reducedActions && (
<Tooltip label='View Metadata'>
<ActionIcon
color='blue'
variant='filled'
onClick={() => window.open(`/dashboard/metadata/${image.id}`, '_blank')}
>
<InfoIcon />
</ActionIcon>
</Tooltip>
)}
{reducedActions ? null : inFolder && !folders.isLoading ? (
<Tooltip
label={`Remove from folder "${
folders.data.find((f) => f.id === image.folderId)?.name ?? ''
}"`}
>
<ActionIcon
color='red'
variant='filled'
onClick={removeFromFolder}
loading={folders.isLoading}
>
<FolderMinusIcon />
</ActionIcon>
</Tooltip>
) : (
<Tooltip label='Add to folder'>
<Select
onChange={addToFolder}
placeholder='Add to folder'
data={[
...(folders.data ? folders.data : []).map((folder) => ({
value: String(folder.id),
label: `${folder.id}: ${folder.name}`,
})),
]}
searchable
creatable
getCreateLabel={(query) => `Create folder "${query}"`}
onCreate={createFolder}
/>
</Tooltip>
)}
</Group>
<Group position='right'>
{reducedActions ? null : (
<>
<Tooltip label='Delete file'>
<ActionIcon color='red' variant='filled' onClick={handleDelete}>
<DeleteIcon />
</ActionIcon>
</Tooltip>
<Tooltip label={image.favorite ? 'Unfavorite' : 'Favorite'}>
<ActionIcon
color={image.favorite ? 'yellow' : 'gray'}
variant='filled'
onClick={handleFavorite}
>
<StarIcon />
</ActionIcon>
</Tooltip>
</>
)}
<Tooltip label='Open in new tab'>
<ActionIcon color='blue' variant='filled' onClick={() => window.open(image.url, '_blank')}>
<ExternalLinkIcon />
</ActionIcon>
</Tooltip>
<Tooltip label='Copy URL'>
<ActionIcon color='blue' variant='filled' onClick={handleCopy}>
<CopyIcon />
</ActionIcon>
</Tooltip>
<Tooltip label='Download'>
<ActionIcon
color='blue'
variant='filled'
onClick={() => window.open(`/r/${encodeURI(image.name)}?download=true`, '_blank')}
>
<DownloadIcon />
</ActionIcon>
</Tooltip>
</Group>
</Group>
</Modal>
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md'>
<Card.Section>
<LoadingOverlay visible={loading} />
<Type
file={image}
sx={{
minHeight: 200,
maxHeight: 320,
fontSize: 70,
width: '100%',
cursor: 'pointer',
}}
style={{
minHeight: 200,
maxHeight: 320,
fontSize: 70,
width: '100%',
cursor: 'pointer',
}}
src={`/r/${encodeURI(image.name)}`}
alt={image.name}
onClick={() => setOpen(true)}
disableMediaPreview={disableMediaPreview}
/>
</Card.Section>
</Card>
</>
);
}

View file

@ -14,6 +14,7 @@ import { showNotification } from '@mantine/notifications';
import useFetch from 'hooks/useFetch';
import { useFileDelete, useFileFavorite } from 'lib/queries/files';
import { useFolders } from 'lib/queries/folders';
import { bytesToHuman } from 'lib/utils/bytes';
import { relativeTime } from 'lib/utils/client';
import { useState } from 'react';
import { FileMeta } from '.';
@ -29,6 +30,7 @@ import {
FileIcon,
FolderMinusIcon,
FolderPlusIcon,
HardDriveIcon,
HashIcon,
ImageIcon,
InfoIcon,
@ -229,6 +231,7 @@ export default function FileModal({
>
<FileMeta Icon={FileIcon} title='Name' subtitle={file.name} />
<FileMeta Icon={ImageIcon} title='Type' subtitle={file.mimetype} />
<FileMeta Icon={HardDriveIcon} title='Size' subtitle={bytesToHuman(file.size || 0)} />
<FileMeta Icon={EyeIcon} title='Views' subtitle={file?.views?.toLocaleString()} />
{file.maxViews && (
<FileMeta

View file

@ -13,6 +13,7 @@ import {
Navbar,
NavLink,
Paper,
rem,
ScrollArea,
Select,
Text,
@ -354,13 +355,9 @@ export default function Layout({ children, props }) {
<Menu.Target>
<Button
leftIcon={avatar ? <Image src={avatar} height={32} radius='md' /> : <SettingsIcon />}
sx={(t) => ({
backgroundColor: 'inherit',
'&:hover': {
backgroundColor: t.other.hover,
},
color: t.colorScheme === 'dark' ? 'white' : 'black',
})}
variant='subtle'
color='gray'
compact
size='xl'
p='sm'
>
@ -442,8 +439,12 @@ export default function Layout({ children, props }) {
withBorder
p='md'
shadow='xs'
sx={(t) => ({
borderColor: t.colorScheme === 'dark' ? t.colors.dark[5] : t.colors.dark[0],
sx={(theme) => ({
'&[data-with-border]': {
border: `${rem(1)} solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[0]
}`,
},
})}
>
{children}

View file

@ -1,5 +1,6 @@
import { NextLink } from '@mantine/next';
import { Anchor } from '@mantine/core';
import * as NextLink from 'next/link';
export default function Link(props) {
return <NextLink legacyBehavior {...props} />;
return <Anchor component={NextLink} legacyBehavior {...props} />;
}

View file

@ -3,7 +3,7 @@ import { ArrowDownRight, ArrowUpRight } from 'react-feather';
const useStyles = createStyles((theme) => ({
root: {
padding: theme.spacing.xl * 1.5,
padding: `calc(${theme.spacing.xl} * 1.5)`,
},
value: {

View file

@ -12,10 +12,10 @@ import matcha_dark_azul from 'lib/themes/matcha_dark_azul';
import nord from 'lib/themes/nord';
import qogir_dark from 'lib/themes/qogir_dark';
import { createEmotionCache, MantineProvider, MantineThemeOverride } from '@mantine/core';
import { createEmotionCache, MantineProvider, MantineThemeOverride, Modal, ScrollArea } from '@mantine/core';
import { useColorScheme } from '@mantine/hooks';
import { ModalsProvider } from '@mantine/modals';
import { NotificationsProvider } from '@mantine/notifications';
import { Notifications } from '@mantine/notifications';
import { userSelector } from 'lib/recoil/user';
import { useRecoilValue } from 'recoil';
@ -78,8 +78,9 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
components: {
AppShell: {
styles: (t) => ({
root: {
main: {
backgroundColor: t.other.AppShell_backgroundColor,
// backgroundColor: '#fff',
},
}),
},
@ -93,9 +94,14 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
Modal: {
defaultProps: {
centered: true,
overlayBlur: 3,
overlayColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
exitTransitionDuration: 100,
transitionProps: {
exitDuration: 100,
},
overlayProps: {
blur: 6,
color: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
},
// scrollAreaComponent: Modal.NativeScrollArea,
},
},
Popover: {
@ -106,8 +112,10 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
},
LoadingOverlay: {
defaultProps: {
overlayBlur: 3,
overlayColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
overlayProps: {
blur: 3,
color: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
},
},
},
Loader: {
@ -133,9 +141,8 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
}}
>
<ModalsProvider>
<NotificationsProvider position='top-center' style={{ marginTop: -10 }}>
{props.children ? props.children : <Component {...pageProps} />}
</NotificationsProvider>
<Notifications position='top-center' style={{ marginTop: -10 }} />
{props.children ? props.children : <Component {...pageProps} />}
</ModalsProvider>
</MantineProvider>
);

View file

@ -96,7 +96,7 @@ export default function FilePagation({ disableMediaPreview, exifEnabled, queryPa
}}
>
{!isMobile && <div></div>}
<Pagination total={numPages} page={page} onChange={setPage} withEdges />
<Pagination total={numPages} value={page} onChange={setPage} withEdges />
{!isMobile && (
<Checkbox
label='Show non-media files'

View file

@ -64,7 +64,7 @@ export default function Files({ disableMediaPreview, exifEnabled, queryPage }) {
paddingBottom: 3,
}}
>
<Pagination total={favoriteNumPages} page={favoritePage} onChange={setFavoritePage} />
<Pagination total={favoriteNumPages} value={favoritePage} onChange={setFavoritePage} />
</Box>
</Accordion.Panel>
</Accordion.Item>

View file

@ -132,7 +132,7 @@ export default function Invites() {
modals.openConfirmModal({
title: `Delete ${invite.code}?`,
centered: true,
overlayBlur: 3,
overlayProps: { blur: 3 },
labels: { confirm: 'Yes', cancel: 'No' },
onConfirm: async () => {
const res = await useFetch(`/api/auth/invite?code=${invite.code}`, 'DELETE');

View file

@ -1,4 +1,4 @@
import { Button, Center, Image, Modal, NumberInput, Text, Title } from '@mantine/core';
import { Button, Center, Image, Modal, NumberInput, PinInput, Text, Title } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import { useForm } from '@mantine/form';
import { CheckIcon, CrossIcon } from 'components/icons';
@ -9,9 +9,7 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
const [secret, setSecret] = useState('');
const [qrCode, setQrCode] = useState('');
const [disabled, setDisabled] = useState(false);
const [code, setCode] = useState(undefined);
const [error, setError] = useState('');
const form = useForm();
useEffect(() => {
(async () => {
@ -34,15 +32,15 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
})();
}, [opened]);
const disableTotp = async () => {
const disableTotp = async (code) => {
setDisabled(true);
const str = code.toString();
if (str.length !== 6) {
if (code.length !== 6) {
setDisabled(false);
return setError('Code must be 6 digits');
}
const resp = await useFetch('/api/user/mfa/totp', 'DELETE', {
code: str,
code,
});
if (resp.error) {
@ -63,16 +61,16 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
setDisabled(false);
};
const verifyCode = async () => {
const verifyCode = async (code) => {
setDisabled(true);
const str = code.toString();
if (str.length !== 6) {
if (code.length !== 6) {
setDisabled(false);
return setError('Code must be 6 digits');
}
const resp = await useFetch('/api/user/mfa/totp', 'POST', {
secret,
code: str,
code,
register: true,
});
@ -94,6 +92,13 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
setDisabled(false);
};
const handlePinChange = (value) => {
if (value.length === 6) {
setDisabled(true);
deleteTotp ? disableTotp(value) : verifyCode(value);
}
};
return (
<Modal
opened={opened}
@ -112,39 +117,39 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
<Center>
<Image height={180} width={180} src={qrCode} alt='QR Code' withPlaceholder />
</Center>
<Text my='sm'>QR Code not working? Try manually entering the code into your app: {secret}</Text>
</>
)}
<form
onSubmit={form.onSubmit(() => {
deleteTotp ? disableTotp() : verifyCode();
})}
>
<NumberInput
placeholder='2FA Code'
label='Verify'
size='xl'
hideControls
maxLength={6}
minLength={6}
value={code}
onChange={(e) => setCode(e)}
<Center my='md'>
<PinInput
data-autofocus
error={error}
/>
<Button
length={6}
oneTimeCode
type='number'
placeholder=''
onChange={handlePinChange}
autoFocus={true}
error={!!error}
disabled={disabled}
size='lg'
fullWidth
mt='md'
rightIcon={<CheckIcon />}
onClick={deleteTotp ? disableTotp : verifyCode}
>
Verify{deleteTotp ? ' and Disable' : ''}
</Button>
</form>
size='xl'
/>
</Center>
{error && (
<Text my='sm' size='sm' color='red' align='center'>
{error}
</Text>
)}
{!deleteTotp && (
<Text my='sm' size='sm' color='gray' align='center'>
QR Code not working? Try manually entering the code into your app: {secret}
</Text>
)}
<Button disabled={disabled} size='lg' fullWidth mt='md' rightIcon={<CheckIcon />} type='submit'>
Verify{deleteTotp ? ' and Disable' : ''}
</Button>
</Modal>
);
}

View file

@ -413,7 +413,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
<Box my='md'>
<Title>Two Factor Authentication</Title>
<MutedText size='md'>
{user.totpSecret
{totpEnabled
? 'You have two factor authentication enabled.'
: 'You do not have two factor authentication enabled.'}
</MutedText>
@ -489,15 +489,11 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
<Text>Preview:</Text>
<Button
leftIcon={fileDataURL ? <Image src={fileDataURL} height={32} radius='md' /> : <SettingsIcon />}
sx={(t) => ({
backgroundColor: '#00000000',
'&:hover': {
backgroundColor: t.other.hover,
},
color: t.colorScheme === 'dark' ? 'white' : 'black',
})}
size='xl'
p='sm'
variant='subtle'
color='gray'
compact
>
{user.username}
</Button>

View file

@ -118,6 +118,11 @@ export function OptionsModal({
{ value: '6m', label: '6 months' },
{ value: '8m', label: '8 months' },
{ value: '1y', label: '1 year' },
{
value: null,
label: 'Need more freedom? Set an exact date and time through the API.',
disabled: true,
},
]}
/>
<Select
@ -131,6 +136,12 @@ export function OptionsModal({
{ value: '25', label: 'Low (25%)' },
{ value: '50', label: 'Medium (50%)' },
{ value: '75', label: 'High (75%)' },
{ value: '100', label: 'Maximum (100%)' },
{
value: null,
label: 'Need more freedom? Set a custom compression level through the API.',
disabled: true,
},
]}
/>
<Select

View file

@ -55,7 +55,7 @@ export default function Users() {
title: `Delete ${user.username}'s files?`,
labels: { confirm: 'Yes', cancel: 'No' },
centered: true,
overlayBlur: 3,
overlayProps: { blur: 3 },
onConfirm: () => {
handleDelete(user, true);
modals.closeAll();

View file

@ -6,6 +6,8 @@ import {
Modal,
NumberInput,
PasswordInput,
PinInput,
Text,
TextInput,
Title,
} from '@mantine/core';
@ -23,10 +25,11 @@ export default function Login({ title, user_registration, oauth_registration, oa
// totp modal
const [totpOpen, setTotpOpen] = useState(false);
const [code, setCode] = useState(undefined);
const [error, setError] = useState('');
const [disabled, setDisabled] = useState(false);
const [loading, setLoading] = useState(false);
const oauth_providers = JSON.parse(unparsed);
const icons = {
@ -46,8 +49,10 @@ export default function Login({ title, user_registration, oauth_registration, oa
},
});
const onSubmit = async (values) => {
const onSubmit = async (values, code = null) => {
setLoading(true);
setError('');
setDisabled(true);
const username = values.username.trim();
const password = values.password.trim();
@ -65,20 +70,31 @@ export default function Login({ title, user_registration, oauth_registration, oa
} else if (res.totp) {
if (res.code === 400) {
setError('Invalid code');
setDisabled(false);
setLoading(false);
} else {
setError('');
setDisabled(false);
setLoading(false);
}
setTotpOpen(true);
} else {
form.setFieldError('username', 'Invalid username');
form.setFieldError('password', 'Invalid password');
setLoading(false);
}
} else {
await router.push((router.query.url as string) || '/dashboard');
}
};
const handlePinChange = (value) => {
if (value.length === 6) {
onSubmit(form.values, value);
}
};
useEffect(() => {
(async () => {
const a = await fetch('/api/user');
@ -98,24 +114,38 @@ export default function Login({ title, user_registration, oauth_registration, oa
title={<Title order={3}>Two-Factor Authentication Required</Title>}
size='lg'
>
<form onSubmit={form.onSubmit(() => onSubmit(form.values))}>
<NumberInput
placeholder='2FA Code'
label='Verify'
size='xl'
hideControls
maxLength={6}
minLength={6}
value={code}
onChange={(e) => setCode(e)}
<Center my='md'>
<PinInput
data-autofocus
error={error}
length={6}
oneTimeCode
type='number'
placeholder=''
onChange={handlePinChange}
autoFocus={true}
error={!!error}
disabled={disabled}
size='xl'
/>
</Center>
<Button disabled={disabled} size='lg' fullWidth mt='md' rightIcon={<CheckIcon />} type='submit'>
Verify &amp; Login
</Button>
</form>
{error && (
<Text my='sm' size='sm' color='red' align='center'>
{error}
</Text>
)}
<Button
loading={loading}
disabled={disabled}
size='lg'
fullWidth
mt='md'
rightIcon={<CheckIcon />}
type='submit'
>
Verify &amp; Login
</Button>
</Modal>
<Center sx={{ height: '100vh' }}>
<div>
@ -133,7 +163,7 @@ export default function Login({ title, user_registration, oauth_registration, oa
{...form.getInputProps('password')}
/>
<Button size='lg' my='sm' fullWidth type='submit'>
<Button size='lg' my='sm' fullWidth type='submit' loading={loading}>
Login
</Button>
</form>

View file

@ -129,7 +129,7 @@ export default function EmbeddedFile({
withCloseButton={true}
closeOnEscape={false}
closeOnClickOutside={false}
overlayBlur={3}
overlayProps={{ blur: 3 }}
>
<PasswordInput
label='Password'

256
yarn.lock
View file

@ -1569,135 +1569,136 @@ __metadata:
languageName: node
linkType: hard
"@mantine/core@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/core@npm:5.10.5"
"@mantine/core@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/core@npm:6.0.0"
dependencies:
"@floating-ui/react": ^0.19.1
"@mantine/styles": 5.10.5
"@mantine/utils": 5.10.5
"@mantine/styles": 6.0.0
"@mantine/utils": 6.0.0
"@radix-ui/react-scroll-area": 1.0.2
react-remove-scroll: ^2.5.5
react-textarea-autosize: 8.3.4
peerDependencies:
"@mantine/hooks": 5.10.5
"@mantine/hooks": 6.0.0
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 8456db8518e8f743f21f96765e7c97b7693dcdfd942ce6ffe76fbfce21ba5eb24f0f4c69969eb73cc57b50fc6a90ec2f2b4a35d89b44e86d76a6df211cd34b41
checksum: 7a93ea2879b46dbead97599639d7ef922869ce9b2721d88874a2d625bbe702479618e7ab54ddeb30cdd576bfe5897e204c2af017f8e84d21617e976f016dff74
languageName: node
linkType: hard
"@mantine/dropzone@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/dropzone@npm:5.10.5"
"@mantine/dropzone@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/dropzone@npm:6.0.0"
dependencies:
"@mantine/utils": 5.10.5
"@mantine/utils": 6.0.0
react-dropzone: 14.2.3
peerDependencies:
"@mantine/core": 5.10.5
"@mantine/hooks": 5.10.5
"@mantine/core": 6.0.0
"@mantine/hooks": 6.0.0
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 549cd9c0f2f4351f0cebe7936d5dee6c3c0984a99524b6ffa8e72170712db2746449b96834e50aa946e649afcc823a3563d51bfbf60ca3d9a27d9cf64b4f59c9
checksum: a0956160d7ad94938cab3cb86d650a9edce2ff51f204ca07b3d7b7046acb905f246bddc2cea6622beb2b2cf73c8de042b2f9ed3022b2649a868a2dd73ee26045
languageName: node
linkType: hard
"@mantine/form@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/form@npm:5.10.5"
"@mantine/form@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/form@npm:6.0.0"
dependencies:
fast-deep-equal: ^3.1.3
klona: ^2.0.5
peerDependencies:
react: ">=16.8.0"
checksum: 0dc4ce123b68d21a05d99faac85209aea0915559971c46eb3e451b58951691b3e6c5baf7f037a115b0f675ed5e9dd6a43d3e8bc0a41891431c89d26876cccbf9
checksum: a992fb860d4493b7c68a4f5464f6577b1a9c3ef2d68eee07199c133e771c1d2ae64e831a1290f3b8f277da22f3bd20f350beafbd7b7eded8ab897a0027958038
languageName: node
linkType: hard
"@mantine/hooks@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/hooks@npm:5.10.5"
"@mantine/hooks@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/hooks@npm:6.0.0"
peerDependencies:
react: ">=16.8.0"
checksum: 5af61db880d2ae4146282771ee627b258fe1c001819f12365028a3686bbf7e1a0cd672757b27f2f828579f0741e0470b0f5fac61fcb7f304b6810b829f9f3974
checksum: becb583cd29ca90871195ea6fb21d118f17ae05f63aa91e932c93621dc879d0c8851f9a3102cb56deb77bc2a8aac2409a9e2685d5b4b8e1a9a7ecc3c5e601da1
languageName: node
linkType: hard
"@mantine/modals@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/modals@npm:5.10.5"
"@mantine/modals@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/modals@npm:6.0.0"
dependencies:
"@mantine/utils": 5.10.5
"@mantine/utils": 6.0.0
peerDependencies:
"@mantine/core": 5.10.5
"@mantine/hooks": 5.10.5
"@mantine/core": 6.0.0
"@mantine/hooks": 6.0.0
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 609820545bf4ccbd58143ced12d48fcf12eb763de372de5c69308848ec212bc19995d10ebc8ebab5bafb5185eeaa19e43e0b9aeea478b9e9b6fa0086342a281c
checksum: 23e0348821294eceab8e70fa6eb46902b6f8cb3acddd7ec6354a3636a878100a0bdcc36eb38d86e315ca4a148bc77afacb4206bcbee5b77639d9f0785d4f5260
languageName: node
linkType: hard
"@mantine/next@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/next@npm:5.10.5"
"@mantine/next@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/next@npm:6.0.0"
dependencies:
"@mantine/ssr": 5.10.5
"@mantine/styles": 5.10.5
"@mantine/ssr": 6.0.0
"@mantine/styles": 6.0.0
peerDependencies:
next: "*"
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 767caba05493f65929e44a95da851a50d9ba104b188f807e98a376aa84ec7d45860163f6b356bad7f5fb3d35fe27e48125fc543de2c1457bf11ba4b5f8ef458b
checksum: 05d5a66fcdb130e4f97b787477feaf76db06a32d45794af7104563b1928cac755b9be52dfd23e778b9e847b8d2f38a064423f01f5192570d90121d4743c40b1b
languageName: node
linkType: hard
"@mantine/notifications@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/notifications@npm:5.10.5"
"@mantine/notifications@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/notifications@npm:6.0.0"
dependencies:
"@mantine/utils": 5.10.5
"@mantine/utils": 6.0.0
react-transition-group: 4.4.2
peerDependencies:
"@mantine/core": 5.10.5
"@mantine/hooks": 5.10.5
"@mantine/core": 6.0.0
"@mantine/hooks": 6.0.0
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 0122f45d2d9b6f2439822347f761122e50b45379254655f854e181ae0bc2a15921812597794d34f6d762994e336439f58915edfb18aa75e54bb19f589f63dd9f
checksum: 3815227d8384be58f2d48507c4d6ee7133cf533a0a7377d8e010ee485ce8c87a8a2fbbe9e81d1f5c2f98d73154fd52444b3c4910a96b1bc908e9c46f511f579f
languageName: node
linkType: hard
"@mantine/prism@npm:^5.10.5":
version: 5.10.5
resolution: "@mantine/prism@npm:5.10.5"
"@mantine/prism@npm:^6.0.0":
version: 6.0.0
resolution: "@mantine/prism@npm:6.0.0"
dependencies:
"@mantine/utils": 5.10.5
"@mantine/utils": 6.0.0
prism-react-renderer: ^1.2.1
peerDependencies:
"@mantine/core": 5.10.5
"@mantine/hooks": 5.10.5
"@mantine/core": 6.0.0
"@mantine/hooks": 6.0.0
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: a81a8ef2d523c609f9c3cb1bf632cacf68da0f8c792c565568990247770bdda2d433a06c74b85446a0e758958cd3ebdda5b6c656c8f685663be88de650032a13
checksum: cec1c08ab3373e225cdcb676ccc98be130435432c3ea44b05ef01830ef4e448bcc3761963dd98025ec0276201ffe66abe7522ad082d1bf0703159c458ea204ff
languageName: node
linkType: hard
"@mantine/ssr@npm:5.10.5":
version: 5.10.5
resolution: "@mantine/ssr@npm:5.10.5"
"@mantine/ssr@npm:6.0.0":
version: 6.0.0
resolution: "@mantine/ssr@npm:6.0.0"
dependencies:
"@mantine/styles": 5.10.5
"@mantine/styles": 6.0.0
html-react-parser: 1.4.12
peerDependencies:
"@emotion/react": ">=11.9.0"
"@emotion/server": ">=11.4.0"
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 34058994cc207fd83e3b1e7ba5c6da76ce495fe45134fe9d31ed64a0201e4f1ce9100a26ededb3e50f1082c0c4b8d0c28d3235c64f27695b1a7948c75ad9b869
checksum: eea337164c7d8afb0b2cf05531ed3448430e555eb567d64599d20cb1751f32fd3518ac490a45638396b512d313710b0a1a7c4bfcdbffbc02523f8fcb9128d2d1
languageName: node
linkType: hard
"@mantine/styles@npm:5.10.5":
version: 5.10.5
resolution: "@mantine/styles@npm:5.10.5"
"@mantine/styles@npm:6.0.0":
version: 6.0.0
resolution: "@mantine/styles@npm:6.0.0"
dependencies:
clsx: 1.1.1
csstype: 3.0.9
@ -1705,16 +1706,16 @@ __metadata:
"@emotion/react": ">=11.9.0"
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 7c7322779e21681a58c80dddfb30a9ce5cfecfa0854bc35921533ebe8fb101199f9626cd38bfa2dea31e9d7ffc5a25e2005c62a5feaf3cb766b540c480a449da
checksum: c369d10719bab36b03cdfa6b46a495b767dcf76ea4f1f5d5f9e5596b52ef0f1b0c295c8cc854d12e9bb7778c9ff80f810a44a929dd9646638da783589fedbf73
languageName: node
linkType: hard
"@mantine/utils@npm:5.10.5":
version: 5.10.5
resolution: "@mantine/utils@npm:5.10.5"
"@mantine/utils@npm:6.0.0":
version: 6.0.0
resolution: "@mantine/utils@npm:6.0.0"
peerDependencies:
react: ">=16.8.0"
checksum: fe8975a2cffb0493e9c226f2dd7d56473250806e56bdc7843c8f6cd1c8ce90d42daf3d33a31a91ec71bca3a584d8adb3fb5b805639c0f242d1d2f8887e9dd56d
checksum: a0f876ebb910ccebc6bbeedb093c14fe22c319d08d8504be6e6085f2e935f3aad8fc9cb1e0ede182c762a03a35e02618cf304ba3ba556a4ba3951945cf8cfc56
languageName: node
linkType: hard
@ -4420,6 +4421,13 @@ __metadata:
languageName: node
linkType: hard
"detect-node-es@npm:^1.1.0":
version: 1.1.0
resolution: "detect-node-es@npm:1.1.0"
checksum: e46307d7264644975b71c104b9f028ed1d3d34b83a15b8a22373640ce5ea630e5640b1078b8ea15f202b54641da71e4aa7597093bd4b91f113db520a26a37449
languageName: node
linkType: hard
"diff@npm:^5.0.0":
version: 5.1.0
resolution: "diff@npm:5.1.0"
@ -5703,6 +5711,13 @@ __metadata:
languageName: node
linkType: hard
"get-nonce@npm:^1.0.0":
version: 1.0.1
resolution: "get-nonce@npm:1.0.1"
checksum: e2614e43b4694c78277bb61b0f04583d45786881289285c73770b07ded246a98be7e1f78b940c80cbe6f2b07f55f0b724e6db6fd6f1bcbd1e8bdac16521074ed
languageName: node
linkType: hard
"get-stdin@npm:8.0.0":
version: 8.0.0
resolution: "get-stdin@npm:8.0.0"
@ -6260,6 +6275,15 @@ __metadata:
languageName: node
linkType: hard
"invariant@npm:^2.2.4":
version: 2.2.4
resolution: "invariant@npm:2.2.4"
dependencies:
loose-envify: ^1.0.0
checksum: cc3182d793aad82a8d1f0af697b462939cb46066ec48bbf1707c150ad5fad6406137e91a262022c269702e01621f35ef60269f6c0d7fd178487959809acdfb14
languageName: node
linkType: hard
"ip@npm:^2.0.0":
version: 2.0.0
resolution: "ip@npm:2.0.0"
@ -7036,7 +7060,7 @@ __metadata:
languageName: node
linkType: hard
"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
version: 1.4.0
resolution: "loose-envify@npm:1.4.0"
dependencies:
@ -9321,6 +9345,41 @@ __metadata:
languageName: node
linkType: hard
"react-remove-scroll-bar@npm:^2.3.3":
version: 2.3.4
resolution: "react-remove-scroll-bar@npm:2.3.4"
dependencies:
react-style-singleton: ^2.2.1
tslib: ^2.0.0
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: b5ce5f2f98d65c97a3e975823ae4043a4ba2a3b63b5ba284b887e7853f051b5cd6afb74abde6d57b421931c52f2e1fdbb625dc858b1cb5a32c27c14ab85649d4
languageName: node
linkType: hard
"react-remove-scroll@npm:^2.5.5":
version: 2.5.5
resolution: "react-remove-scroll@npm:2.5.5"
dependencies:
react-remove-scroll-bar: ^2.3.3
react-style-singleton: ^2.2.1
tslib: ^2.1.0
use-callback-ref: ^1.3.0
use-sidecar: ^1.1.2
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 2c7fe9cbd766f5e54beb4bec2e2efb2de3583037b23fef8fa511ab426ed7f1ae992382db5acd8ab5bfb030a4b93a06a2ebca41377d6eeaf0e6791bb0a59616a4
languageName: node
linkType: hard
"react-resize-detector@npm:^7.1.2":
version: 7.1.2
resolution: "react-resize-detector@npm:7.1.2"
@ -9347,6 +9406,23 @@ __metadata:
languageName: node
linkType: hard
"react-style-singleton@npm:^2.2.1":
version: 2.2.1
resolution: "react-style-singleton@npm:2.2.1"
dependencies:
get-nonce: ^1.0.0
invariant: ^2.2.4
tslib: ^2.0.0
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 7ee8ef3aab74c7ae1d70ff34a27643d11ba1a8d62d072c767827d9ff9a520905223e567002e0bf6c772929d8ea1c781a3ba0cc4a563e92b1e3dc2eaa817ecbe8
languageName: node
linkType: hard
"react-textarea-autosize@npm:8.3.4":
version: 8.3.4
resolution: "react-textarea-autosize@npm:8.3.4"
@ -10848,6 +10924,13 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^2.1.0":
version: 2.5.0
resolution: "tslib@npm:2.5.0"
checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1
languageName: node
linkType: hard
"tsup@npm:^6.6.3":
version: 6.6.3
resolution: "tsup@npm:6.6.3"
@ -11126,6 +11209,21 @@ __metadata:
languageName: node
linkType: hard
"use-callback-ref@npm:^1.3.0":
version: 1.3.0
resolution: "use-callback-ref@npm:1.3.0"
dependencies:
tslib: ^2.0.0
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 7913df383a5a6fcb399212eedefaac2e0c6f843555202d4e3010bac3848afe38ecaa3d0d6500ad1d936fbeffd637e6c517e68edb024af5e6beca7f27f3ce7b21
languageName: node
linkType: hard
"use-composed-ref@npm:^1.3.0":
version: 1.3.0
resolution: "use-composed-ref@npm:1.3.0"
@ -11161,6 +11259,22 @@ __metadata:
languageName: node
linkType: hard
"use-sidecar@npm:^1.1.2":
version: 1.1.2
resolution: "use-sidecar@npm:1.1.2"
dependencies:
detect-node-es: ^1.1.0
tslib: ^2.0.0
peerDependencies:
"@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 925d1922f9853e516eaad526b6fed1be38008073067274f0ecc3f56b17bb8ab63480140dd7c271f94150027c996cea4efe83d3e3525e8f3eda22055f6a39220b
languageName: node
linkType: hard
"use-sync-external-store@npm:^1.2.0":
version: 1.2.0
resolution: "use-sync-external-store@npm:1.2.0"
@ -11554,14 +11668,14 @@ __metadata:
dependencies:
"@emotion/react": ^11.10.6
"@emotion/server": ^11.10.0
"@mantine/core": ^5.10.5
"@mantine/dropzone": ^5.10.5
"@mantine/form": ^5.10.5
"@mantine/hooks": ^5.10.5
"@mantine/modals": ^5.10.5
"@mantine/next": ^5.10.5
"@mantine/notifications": ^5.10.5
"@mantine/prism": ^5.10.5
"@mantine/core": ^6.0.0
"@mantine/dropzone": ^6.0.0
"@mantine/form": ^6.0.0
"@mantine/hooks": ^6.0.0
"@mantine/modals": ^6.0.0
"@mantine/next": ^6.0.0
"@mantine/notifications": ^6.0.0
"@mantine/prism": ^6.0.0
"@prisma/client": ^4.10.1
"@prisma/internals": ^4.10.1
"@prisma/migrate": ^4.10.1