feat: delete draft document (#491)

This commit is contained in:
Nafees Nazik
2023-10-10 08:25:58 +05:30
committed by Mythie
parent 0d6efba3cf
commit c9a71beb81
5 changed files with 145 additions and 1 deletions

View File

@ -1,5 +1,7 @@
'use client'; 'use client';
import { useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { import {
@ -32,6 +34,8 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard'; import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard';
import { DeleteDraftDocumentDialog } from './delete-draft-document-dialog';
export type DataTableActionDropdownProps = { export type DataTableActionDropdownProps = {
row: Document & { row: Document & {
User: Pick<User, 'id' | 'name' | 'email'>; User: Pick<User, 'id' | 'name' | 'email'>;
@ -44,6 +48,8 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
const { toast } = useToast(); const { toast } = useToast();
const [, copyToClipboard] = useCopyToClipboard(); const [, copyToClipboard] = useCopyToClipboard();
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
if (!session) { if (!session) {
return null; return null;
} }
@ -59,6 +65,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
// const isPending = row.status === DocumentStatus.PENDING; // const isPending = row.status === DocumentStatus.PENDING;
const isComplete = row.status === DocumentStatus.COMPLETED; const isComplete = row.status === DocumentStatus.COMPLETED;
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
const isDocumentDeletable = isOwner && row.status === DocumentStatus.DRAFT;
const onShareClick = async () => { const onShareClick = async () => {
const { slug } = await createOrGetShareLink({ const { slug } = await createOrGetShareLink({
@ -147,7 +154,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
Void Void
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem disabled> <DropdownMenuItem onClick={() => setDeleteDialogOpen(true)} disabled={!isDocumentDeletable}>
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>
@ -168,6 +175,14 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
Share Share
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
{isDocumentDeletable && (
<DeleteDraftDocumentDialog
id={row.id}
open={isDeleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
/>
)}
</DropdownMenu> </DropdownMenu>
); );
}; };

View File

@ -0,0 +1,89 @@
import { useRouter } from 'next/navigation';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
type DeleteDraftDocumentDialogProps = {
id: number;
open: boolean;
onOpenChange: (_open: boolean) => void;
};
export const DeleteDraftDocumentDialog = ({
id,
open,
onOpenChange,
}: DeleteDraftDocumentDialogProps) => {
const router = useRouter();
const { toast } = useToast();
const { mutateAsync: deleteDocument, isLoading } =
trpcReact.document.deleteDraftDocument.useMutation({
onSuccess: () => {
router.refresh();
toast({
title: 'Document deleted',
description: 'Your document has been successfully deleted.',
duration: 5000,
});
onOpenChange(false);
},
});
const onDraftDelete = async () => {
try {
await deleteDocument({ id });
} catch {
toast({
title: 'Something went wrong',
description: 'This document could not be deleted at this time. Please try again.',
variant: 'destructive',
duration: 7500,
});
}
};
return (
<Dialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Do you want to delete this document?</DialogTitle>
<DialogDescription>
Please note that this action is irreversible. Once confirmed, your document will be
permanently deleted.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
variant="secondary"
onClick={() => onOpenChange(false)}
className="flex-1"
>
Cancel
</Button>
<Button type="button" loading={isLoading} onClick={onDraftDelete} className="flex-1">
Delete
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@ -0,0 +1,13 @@
'use server';
import { prisma } from '@documenso/prisma';
import { DocumentStatus } from '@documenso/prisma/client';
export type DeleteDraftDocumentOptions = {
id: number;
userId: number;
};
export const deleteDraftDocument = async ({ id, userId }: DeleteDraftDocumentOptions) => {
return await prisma.document.delete({ where: { id, userId, status: DocumentStatus.DRAFT } });
};

View File

@ -1,6 +1,7 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { createDocument } from '@documenso/lib/server-only/document/create-document'; import { createDocument } from '@documenso/lib/server-only/document/create-document';
import { deleteDraftDocument } from '@documenso/lib/server-only/document/delete-draft-document';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { sendDocument } from '@documenso/lib/server-only/document/send-document'; import { sendDocument } from '@documenso/lib/server-only/document/send-document';
@ -10,6 +11,7 @@ import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/s
import { authenticatedProcedure, procedure, router } from '../trpc'; import { authenticatedProcedure, procedure, router } from '../trpc';
import { import {
ZCreateDocumentMutationSchema, ZCreateDocumentMutationSchema,
ZDeleteDraftDocumentMutationSchema,
ZGetDocumentByIdQuerySchema, ZGetDocumentByIdQuerySchema,
ZGetDocumentByTokenQuerySchema, ZGetDocumentByTokenQuerySchema,
ZSendDocumentMutationSchema, ZSendDocumentMutationSchema,
@ -76,6 +78,25 @@ export const documentRouter = router({
} }
}), }),
deleteDraftDocument: authenticatedProcedure
.input(ZDeleteDraftDocumentMutationSchema)
.mutation(async ({ input, ctx }) => {
try {
const { id } = input;
const userId = ctx.user.id;
return await deleteDraftDocument({ id, userId });
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to delete this document. Please try again later.',
});
}
}),
setRecipientsForDocument: authenticatedProcedure setRecipientsForDocument: authenticatedProcedure
.input(ZSetRecipientsForDocumentMutationSchema) .input(ZSetRecipientsForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -61,3 +61,9 @@ export const ZSendDocumentMutationSchema = z.object({
}); });
export type TSendDocumentMutationSchema = z.infer<typeof ZSendDocumentMutationSchema>; export type TSendDocumentMutationSchema = z.infer<typeof ZSendDocumentMutationSchema>;
export const ZDeleteDraftDocumentMutationSchema = z.object({
id: z.number().min(1),
});
export type TDeleteDraftDocumentMutationSchema = z.infer<typeof ZDeleteDraftDocumentMutationSchema>;