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:
Jayvin Hernandez 2023-02-26 11:33:57 -08:00 committed by GitHub
parent 0848702f65
commit 8c5ff4f230
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 171 additions and 93 deletions

View file

@ -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 () => {

View file

@ -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();
}, },

View file

@ -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 }) => {

View file

@ -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 />

View file

@ -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 () => {

View file

@ -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>
); );
} }

View file

@ -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) => {

View file

@ -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;

View file

@ -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({

View file

@ -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) => {

View file

@ -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 &amp; Login
size='lg' </Button>
fullWidth </form>
mt='md'
rightIcon={<CheckIcon />}
onClick={() => onSubmit(form.values)}
>
Verify &amp; Login
</Button>
</Modal> </Modal>
<Center sx={{ height: '100vh' }}> <Center sx={{ height: '100vh' }}>
<div> <div>