feat: implement settings.warnDeletion

This commit is contained in:
diced
2023-08-14 20:29:17 -07:00
parent 84cc111e73
commit d2d1dbfc0f
11 changed files with 87 additions and 37 deletions

View File

@@ -34,6 +34,7 @@ import useSWR from 'swr';
import { Response } from '@/lib/api/response';
import { Folder } from '@/lib/db/models/folder';
import { bytes } from '@/lib/bytes';
import { useSettingsStore } from '@/lib/store/settings';
function ActionButton({
Icon,
@@ -67,6 +68,7 @@ export default function FileModal({
reduce?: boolean;
}) {
const clipboard = useClipboard();
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
const { data: folders } = useSWR<Extract<Response['/api/user/folders'], Folder[]>>(
'/api/user/folders?noincl=true'
@@ -145,7 +147,7 @@ export default function FileModal({
<>
<ActionButton
Icon={IconTrashFilled}
onClick={() => deleteFile(file, setOpen)}
onClick={() => deleteFile(warnDeletion, file, setOpen)}
tooltip='Delete file'
color='red'
/>

View File

@@ -2,8 +2,11 @@ import { Response } from '@/lib/api/response';
import type { File } from '@/lib/db/models/file';
import { Folder } from '@/lib/db/models/folder';
import { fetchApi } from '@/lib/fetchApi';
import { useSettingsStore } from '@/lib/store/settings';
import { conditionalWarning } from '@/lib/warningModal';
import { Anchor } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { modals } from '@mantine/modals';
import { notifications, notifications as notifs } from '@mantine/notifications';
import {
IconCopy,
@@ -45,7 +48,15 @@ export function copyFile(file: File, clipboard: ReturnType<typeof useClipboard>)
});
}
export async function deleteFile(file: File, setOpen: (open: boolean) => void) {
export async function deleteFile(warnDeletion: boolean, file: File, setOpen: (open: boolean) => void) {
conditionalWarning(warnDeletion, {
confirmLabel: `Delete ${file.name}`,
message: `Are you sure you want to delete ${file.name}? This action cannot be undone.`,
onConfirm: () => handleDeleteFile(file, setOpen),
});
}
export async function handleDeleteFile(file: File, setOpen: (open: boolean) => void) {
const { error } = await fetchApi(`/api/user/files/${file.id}`, 'DELETE');
if (error) {

View File

@@ -81,7 +81,10 @@ function SearchFilter({
export default function FileTable({ id }: { id?: string }) {
const router = useRouter();
const clipboard = useClipboard();
const searchThreshold = useSettingsStore((state) => state.settings.searchThreshold);
const [searchThreshold, warnDeletion] = useSettingsStore((state) => [
state.settings.searchThreshold,
state.settings.warnDeletion,
]);
const [page, setPage] = useState<number>(router.query.page ? parseInt(router.query.page as string) : 1);
const [perpage, setPerpage] = useState<number>(20);
@@ -321,7 +324,7 @@ export default function FileTable({ id }: { id?: string }) {
color='red'
onClick={(e) => {
e.stopPropagation();
deleteFile(file, () => {});
deleteFile(warnDeletion, file, () => {});
}}
>
<IconTrashFilled size='1rem' />

View File

@@ -4,10 +4,13 @@ import { ActionIcon, Card, Group, Menu, Stack, Text } from '@mantine/core';
import { IconCopy, IconDots, IconTrashFilled } from '@tabler/icons-react';
import { copyInviteUrl, deleteInvite } from './actions';
import { useClipboard } from '@mantine/hooks';
import { useSettingsStore } from '@/lib/store/settings';
export default function InviteCard({ invite }: { invite: Invite }) {
const clipboard = useClipboard();
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
return (
<>
<Card withBorder shadow='sm' radius='sm'>
@@ -31,7 +34,7 @@ export default function InviteCard({ invite }: { invite: Invite }) {
<Menu.Item
icon={<IconTrashFilled size='1rem' />}
color='red'
onClick={() => deleteInvite(invite)}
onClick={() => deleteInvite(warnDeletion, invite)}
>
Delete
</Menu.Item>

View File

@@ -1,6 +1,7 @@
import { Response } from '@/lib/api/response';
import { Invite } from '@/lib/db/models/invite';
import { fetchApi } from '@/lib/fetchApi';
import { conditionalWarning } from '@/lib/warningModal';
import { Anchor, Title } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { modals } from '@mantine/modals';
@@ -9,17 +10,11 @@ import { IconCheck, IconCopy, IconTagOff } from '@tabler/icons-react';
import Link from 'next/link';
import { mutate } from 'swr';
export async function deleteInvite(invite: Invite) {
modals.openConfirmModal({
title: <Title>Delete invite "{invite.code}"</Title>,
children: `Are you sure you want to delete invite ${invite.code}? This action cannot be undone.`,
labels: {
cancel: 'Cancel',
confirm: 'Delete',
},
confirmProps: { color: 'red' },
export async function deleteInvite(warnDeletion: boolean, invite: Invite) {
conditionalWarning(warnDeletion, {
message: `Are you sure you want to delete invite ${invite.code}? This action cannot be undone.`,
onConfirm: () => handleDeleteInvite(invite),
onCancel: modals.closeAll,
confirmLabel: `Delete ${invite.code}`,
});
}

View File

@@ -1,4 +1,3 @@
import { useConfig } from '@/components/ConfigProvider';
import RelativeDate from '@/components/RelativeDate';
import { Response } from '@/lib/api/response';
import { Invite } from '@/lib/db/models/invite';
@@ -9,10 +8,11 @@ import { DataTable, DataTableSortStatus } from 'mantine-datatable';
import { useEffect, useState } from 'react';
import useSWR from 'swr';
import { copyInviteUrl, deleteInvite } from '../actions';
import { useSettingsStore } from '@/lib/store/settings';
export default function InviteTableView() {
const config = useConfig();
const clipboard = useClipboard();
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
const { data, isLoading } = useSWR<Extract<Response['/api/auth/invites'], Invite[]>>('/api/auth/invites');
@@ -105,7 +105,7 @@ export default function InviteTableView() {
color='red'
onClick={(e) => {
e.stopPropagation();
deleteInvite(invite);
deleteInvite(warnDeletion, invite);
}}
>
<IconTrashFilled size='1rem' />

View File

@@ -65,7 +65,7 @@ export default function SettingsDashboard() {
/>
<Switch
label='Warn on deletion'
description='Show a warning when deleting files. This is useful to prevent accidental deletion of files.'
description='Show a warning when deleting stuff. When this is disabled, files, urls, etc will be deleted with no prior warning! Folders, users, and bulk-transactions are exempt from this rule and will always warn you before deleting anything.'
checked={settings.warnDeletion}
onChange={(event) => update('warnDeletion', event.currentTarget.checked)}
/>
@@ -73,7 +73,7 @@ export default function SettingsDashboard() {
<NumberInput
label='Search Threshold'
description='When performing a similarity check on file searches, this is the minimum percentage of similarity required to show the file. The lower the number, the more results will be shown though they will be less relevant to the actual search query. A recomended value is between 0.1 and 0.4 as this will yield moderate-relevancy results that should match your queries.'
description='When performing a similarity check on file/url searches, this is the minimum percentage of similarity required to show the file/url. The lower the number, the more results will be shown though they will be less relevant to the actual search query. A recomended value is between 0.1 and 0.4 as this will yield moderate-relevancy results that should match your queries.'
min={0}
max={100}
value={settings.searchThreshold}

View File

@@ -3,15 +3,17 @@ import RelativeDate from '@/components/RelativeDate';
import { Url } from '@/lib/db/models/url';
import { formatRootUrl } from '@/lib/url';
import { ActionIcon, Anchor, Card, Group, Menu, Stack, Text } from '@mantine/core';
import { IconCopy, IconDots, IconTrashFilled } from '@tabler/icons-react';
import { useState } from 'react';
import { copyUrl, deleteUrl } from './actions';
import { useClipboard } from '@mantine/hooks';
import { IconCopy, IconDots, IconTrashFilled } from '@tabler/icons-react';
import { copyUrl, deleteUrl } from './actions';
import { useSettingsStore } from '@/lib/store/settings';
export default function UserCard({ url }: { url: Url }) {
const config = useConfig();
const clipboard = useClipboard();
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
return (
<>
<Card withBorder shadow='sm' radius='sm'>
@@ -40,7 +42,11 @@ export default function UserCard({ url }: { url: Url }) {
<Menu.Item icon={<IconCopy size='1rem' />} onClick={() => copyUrl(url, config, clipboard)}>
Copy
</Menu.Item>
<Menu.Item icon={<IconTrashFilled size='1rem' />} color='red' onClick={() => deleteUrl(url)}>
<Menu.Item
icon={<IconTrashFilled size='1rem' />}
color='red'
onClick={() => deleteUrl(warnDeletion, url)}
>
Delete
</Menu.Item>
</Menu.Dropdown>

View File

@@ -3,6 +3,7 @@ import type { SafeConfig } from '@/lib/config/safe';
import { Url } from '@/lib/db/models/url';
import { fetchApi } from '@/lib/fetchApi';
import { formatRootUrl } from '@/lib/url';
import { conditionalWarning } from '@/lib/warningModal';
import { Anchor, Title } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { modals } from '@mantine/modals';
@@ -11,18 +12,11 @@ import { IconCheck, IconCopy, IconLinkOff } from '@tabler/icons-react';
import Link from 'next/link';
import { mutate } from 'swr';
export async function deleteUrl(url: Url) {
modals.openConfirmModal({
centered: true,
title: <Title>Delete {url.code ?? url.vanity}?</Title>,
children: `Are you sure you want to delete ${url.code ?? url.vanity}? This action cannot be undone.`,
labels: {
cancel: 'Cancel',
confirm: 'Delete',
},
confirmProps: { color: 'red' },
export async function deleteUrl(warnDeletion: boolean, url: Url) {
conditionalWarning(warnDeletion, {
message: `Are you sure you want to delete ${url.code ?? url.vanity}? This action cannot be undone.`,
onConfirm: () => handleDeleteUrl(url),
onCancel: modals.closeAll,
confirmLabel: `Delete '${url.code ?? url.vanity}'`,
});
}

View File

@@ -109,7 +109,10 @@ export default function UrlTableView() {
destination: '',
}
);
const searchThreshold = useSettingsStore((s) => s.settings.searchThreshold);
const [warnDeletion, searchThreshold] = useSettingsStore((s) => [
s.settings.warnDeletion,
s.settings.searchThreshold,
]);
const { data, isLoading } = useSWR<Extract<Response['/api/user/urls'], Url[]>>(
{
@@ -259,7 +262,7 @@ export default function UrlTableView() {
color='red'
onClick={(e) => {
e.stopPropagation();
deleteUrl(url);
deleteUrl(warnDeletion, url);
}}
>
<IconTrashFilled size='1rem' />

33
src/lib/warningModal.tsx Normal file
View File

@@ -0,0 +1,33 @@
import { Title } from '@mantine/core';
import { modals } from '@mantine/modals';
type WarningModalOptions = {
message: string | React.ReactNode;
confirmLabel: string;
onConfirm: () => void;
};
export function openWarningModal(options: WarningModalOptions) {
modals.openConfirmModal({
title: <Title order={3}>Are you sure?</Title>,
labels: {
cancel: 'Cancel',
confirm: options.confirmLabel,
},
children: options.message,
confirmProps: {
color: 'red',
},
onCancel: () => modals.closeAll(),
onConfirm: options.onConfirm,
zIndex: 10320948239487,
});
}
export function conditionalWarning(on: boolean, options: WarningModalOptions) {
if (on) {
openWarningModal(options);
} else {
options.onConfirm();
}
}