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