fix: clipboard & 2fa improvements
A workaround that shows the content that would have been copied if `navigator.clipboard` is unavailable for whatever reason. 2FA input autofocuses & submits on enter.
This commit is contained in:
parent
0848702f65
commit
8c5ff4f230
11 changed files with 171 additions and 93 deletions
|
@ -106,11 +106,18 @@ export default function File({
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${image.url}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}${image.url}`);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
showNotification({
|
if (!navigator.clipboard)
|
||||||
title: 'Copied to clipboard',
|
showNotification({
|
||||||
message: '',
|
title: 'Unable to copy to clipboard',
|
||||||
icon: <CopyIcon />,
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
});
|
color: 'red',
|
||||||
|
});
|
||||||
|
else
|
||||||
|
showNotification({
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
message: '',
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFavorite = async () => {
|
const handleFavorite = async () => {
|
||||||
|
|
|
@ -4,8 +4,10 @@ import {
|
||||||
Box,
|
Box,
|
||||||
Burger,
|
Burger,
|
||||||
Button,
|
Button,
|
||||||
|
Group,
|
||||||
Header,
|
Header,
|
||||||
Image,
|
Image,
|
||||||
|
Input,
|
||||||
MediaQuery,
|
MediaQuery,
|
||||||
Menu,
|
Menu,
|
||||||
Navbar,
|
Navbar,
|
||||||
|
@ -214,13 +216,29 @@ export default function Layout({ children, props }) {
|
||||||
labels: { confirm: 'Copy', cancel: 'Cancel' },
|
labels: { confirm: 'Copy', cancel: 'Cancel' },
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
clipboard.copy(token);
|
clipboard.copy(token);
|
||||||
|
if (!navigator.clipboard)
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Token Copied',
|
title: 'Unable to copy to clipboard',
|
||||||
message: 'Your token has been copied to your clipboard.',
|
message: (
|
||||||
color: 'green',
|
<Text size='sm'>
|
||||||
icon: <CheckIcon />,
|
Zipline is unable to copy to clipboard due to security reasons. However, you can still copy
|
||||||
});
|
the token manually.
|
||||||
|
<br />
|
||||||
|
<Group position='left' spacing='sm'>
|
||||||
|
<Text>Your token is:</Text>
|
||||||
|
<Input size='sm' onFocus={(e) => e.target.select()} type='text' value={token} />
|
||||||
|
</Group>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
else
|
||||||
|
showNotification({
|
||||||
|
title: 'Token Copied',
|
||||||
|
message: 'Your token has been copied to your clipboard.',
|
||||||
|
color: 'green',
|
||||||
|
icon: <CheckIcon />,
|
||||||
|
});
|
||||||
|
|
||||||
modals.closeAll();
|
modals.closeAll();
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,15 +52,22 @@ export default function Dashboard({ disableMediaPreview, exifEnabled }) {
|
||||||
|
|
||||||
const copyImage = async ({ original }) => {
|
const copyImage = async ({ original }) => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${original.url}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}${original.url}`);
|
||||||
showNotification({
|
if (!navigator.clipboard)
|
||||||
title: 'Copied to clipboard',
|
showNotification({
|
||||||
message: (
|
title: 'Unable to copy to clipboard',
|
||||||
<a
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
href={`${window.location.protocol}//${window.location.host}${original.url}`}
|
color: 'red',
|
||||||
>{`${window.location.protocol}//${window.location.host}${original.url}`}</a>
|
});
|
||||||
),
|
else
|
||||||
icon: <CopyIcon />,
|
showNotification({
|
||||||
});
|
title: 'Copied to clipboard',
|
||||||
|
message: (
|
||||||
|
<a
|
||||||
|
href={`${window.location.protocol}//${window.location.host}${original.url}`}
|
||||||
|
>{`${window.location.protocol}//${window.location.host}${original.url}`}</a>
|
||||||
|
),
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const viewImage = async ({ original }) => {
|
const viewImage = async ({ original }) => {
|
||||||
|
|
|
@ -165,16 +165,23 @@ export default function Folders({ disableMediaPreview, exifEnabled }) {
|
||||||
aria-label='copy link'
|
aria-label='copy link'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.copy(`${window.location.origin}/folder/${folder.id}`);
|
clipboard.copy(`${window.location.origin}/folder/${folder.id}`);
|
||||||
showNotification({
|
if (!navigator.clipboard)
|
||||||
title: 'Copied folder link',
|
showNotification({
|
||||||
message: (
|
title: 'Unable to copy to clipboard',
|
||||||
<>
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
Copied <Link href={`/folder/${folder.id}`}>folder link</Link> to clipboard
|
color: 'red',
|
||||||
</>
|
});
|
||||||
),
|
else
|
||||||
color: 'green',
|
showNotification({
|
||||||
icon: <CopyIcon />,
|
title: 'Copied folder link',
|
||||||
});
|
message: (
|
||||||
|
<>
|
||||||
|
Copied <Link href={`/folder/${folder.id}`}>folder link</Link> to clipboard
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
color: 'green',
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LinkIcon />
|
<LinkIcon />
|
||||||
|
|
|
@ -158,11 +158,18 @@ export default function Invites() {
|
||||||
|
|
||||||
const handleCopy = async (invite) => {
|
const handleCopy = async (invite) => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}/auth/register?code=${invite.code}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}/auth/register?code=${invite.code}`);
|
||||||
showNotification({
|
if (!navigator.clipboard)
|
||||||
title: 'Copied to clipboard',
|
showNotification({
|
||||||
message: '',
|
title: 'Unable to copy to clipboard',
|
||||||
icon: <CopyIcon />,
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
});
|
color: 'red',
|
||||||
|
});
|
||||||
|
else
|
||||||
|
showNotification({
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
message: '',
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateInvites = async () => {
|
const updateInvites = async () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Button, Center, Image, Modal, NumberInput, Text, Title } from '@mantine/core';
|
import { Button, Center, Image, Modal, NumberInput, Text, Title } from '@mantine/core';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
|
import { useForm } from '@mantine/form';
|
||||||
import { CheckIcon, CrossIcon } from 'components/icons';
|
import { CheckIcon, CrossIcon } from 'components/icons';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
@ -10,6 +11,7 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
|
||||||
const [disabled, setDisabled] = useState(false);
|
const [disabled, setDisabled] = useState(false);
|
||||||
const [code, setCode] = useState(undefined);
|
const [code, setCode] = useState(undefined);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const form = useForm();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -114,28 +116,35 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<NumberInput
|
<form
|
||||||
placeholder='2FA Code'
|
onSubmit={form.onSubmit(() => {
|
||||||
label='Verify'
|
deleteTotp ? disableTotp() : verifyCode();
|
||||||
size='xl'
|
})}
|
||||||
hideControls
|
|
||||||
maxLength={6}
|
|
||||||
minLength={6}
|
|
||||||
value={code}
|
|
||||||
onChange={(e) => setCode(e)}
|
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
disabled={disabled}
|
|
||||||
size='lg'
|
|
||||||
fullWidth
|
|
||||||
mt='md'
|
|
||||||
rightIcon={<CheckIcon />}
|
|
||||||
onClick={deleteTotp ? disableTotp : verifyCode}
|
|
||||||
>
|
>
|
||||||
Verify{deleteTotp ? ' and Disable' : ''}
|
<NumberInput
|
||||||
</Button>
|
placeholder='2FA Code'
|
||||||
|
label='Verify'
|
||||||
|
size='xl'
|
||||||
|
hideControls
|
||||||
|
maxLength={6}
|
||||||
|
minLength={6}
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => setCode(e)}
|
||||||
|
data-autofocus
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
size='lg'
|
||||||
|
fullWidth
|
||||||
|
mt='md'
|
||||||
|
rightIcon={<CheckIcon />}
|
||||||
|
onClick={deleteTotp ? disableTotp : verifyCode}
|
||||||
|
>
|
||||||
|
Verify{deleteTotp ? ' and Disable' : ''}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,18 @@ export default function MetadataView({ fileId }) {
|
||||||
|
|
||||||
const copy = (value) => {
|
const copy = (value) => {
|
||||||
clipboard.copy(value);
|
clipboard.copy(value);
|
||||||
showNotification({
|
if (!navigator.clipboard)
|
||||||
title: 'Copied to clipboard',
|
showNotification({
|
||||||
message: value,
|
title: 'Unable to copy to clipboard',
|
||||||
icon: <CopyIcon />,
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
});
|
color: 'red',
|
||||||
|
});
|
||||||
|
else
|
||||||
|
showNotification({
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
message: value,
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchValue = (value) => {
|
const searchValue = (value) => {
|
||||||
|
|
|
@ -128,6 +128,12 @@ export default function File({ chunks: chunks_config }) {
|
||||||
setTimeout(() => setProgress(0), 1000);
|
setTimeout(() => setProgress(0), 1000);
|
||||||
|
|
||||||
clipboard.copy(json.files[0]);
|
clipboard.copy(json.files[0]);
|
||||||
|
if (!navigator.clipboard)
|
||||||
|
showNotification({
|
||||||
|
title: 'Unable to copy to clipboard',
|
||||||
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ready = true;
|
ready = true;
|
||||||
|
|
|
@ -7,11 +7,18 @@ export default function showFilesModal(clipboard, modals, files: string[]) {
|
||||||
const open = (idx: number) => window.open(files[idx], '_blank');
|
const open = (idx: number) => window.open(files[idx], '_blank');
|
||||||
const copy = (idx: number) => {
|
const copy = (idx: number) => {
|
||||||
clipboard.copy(files[idx]);
|
clipboard.copy(files[idx]);
|
||||||
showNotification({
|
if (!navigator.clipboard)
|
||||||
title: 'Copied to clipboard',
|
showNotification({
|
||||||
message: <Link href={files[idx]}>{files[idx]}</Link>,
|
title: 'Unable to copy to clipboard',
|
||||||
icon: <CopyIcon />,
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
});
|
color: 'red',
|
||||||
|
});
|
||||||
|
else
|
||||||
|
showNotification({
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
message: <Link href={files[idx]}>{files[idx]}</Link>,
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
modals.openModal({
|
modals.openModal({
|
||||||
|
|
|
@ -14,11 +14,18 @@ export default function URLCard({ url }: { url: URLResponse }) {
|
||||||
|
|
||||||
const copyURL = (u) => {
|
const copyURL = (u) => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${u.url}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}${u.url}`);
|
||||||
showNotification({
|
if (!navigator.clipboard)
|
||||||
title: 'Copied to clipboard',
|
showNotification({
|
||||||
message: '',
|
title: 'Unable to copy to clipboard',
|
||||||
icon: <CopyIcon />,
|
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||||
});
|
color: 'red',
|
||||||
|
});
|
||||||
|
else
|
||||||
|
showNotification({
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
message: '',
|
||||||
|
icon: <CopyIcon />,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteURL = async (u) => {
|
const deleteURL = async (u) => {
|
||||||
|
|
|
@ -98,28 +98,24 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
||||||
title={<Title order={3}>Two-Factor Authentication Required</Title>}
|
title={<Title order={3}>Two-Factor Authentication Required</Title>}
|
||||||
size='lg'
|
size='lg'
|
||||||
>
|
>
|
||||||
<NumberInput
|
<form onSubmit={form.onSubmit(() => onSubmit(form.values))}>
|
||||||
placeholder='2FA Code'
|
<NumberInput
|
||||||
label='Verify'
|
placeholder='2FA Code'
|
||||||
size='xl'
|
label='Verify'
|
||||||
hideControls
|
size='xl'
|
||||||
maxLength={6}
|
hideControls
|
||||||
minLength={6}
|
maxLength={6}
|
||||||
value={code}
|
minLength={6}
|
||||||
onChange={(e) => setCode(e)}
|
value={code}
|
||||||
error={error}
|
onChange={(e) => setCode(e)}
|
||||||
/>
|
data-autofocus
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button
|
<Button disabled={disabled} size='lg' fullWidth mt='md' rightIcon={<CheckIcon />} type='submit'>
|
||||||
disabled={disabled}
|
Verify & Login
|
||||||
size='lg'
|
</Button>
|
||||||
fullWidth
|
</form>
|
||||||
mt='md'
|
|
||||||
rightIcon={<CheckIcon />}
|
|
||||||
onClick={() => onSubmit(form.values)}
|
|
||||||
>
|
|
||||||
Verify & Login
|
|
||||||
</Button>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
<Center sx={{ height: '100vh' }}>
|
<Center sx={{ height: '100vh' }}>
|
||||||
<div>
|
<div>
|
||||||
|
|
Loading…
Reference in a new issue