From c9a71beb81662c65d7622c002ef2e0bc66319c03 Mon Sep 17 00:00:00 2001 From: Nafees Nazik <84864519+G3root@users.noreply.github.com> Date: Tue, 10 Oct 2023 08:25:58 +0530 Subject: [PATCH] feat: delete draft document (#491) --- .../documents/data-table-action-dropdown.tsx | 17 +++- .../delete-draft-document-dialog.tsx | 89 +++++++++++++++++++ .../document/delete-draft-document.ts | 13 +++ .../trpc/server/document-router/router.ts | 21 +++++ .../trpc/server/document-router/schema.ts | 6 ++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx create mode 100644 packages/lib/server-only/document/delete-draft-document.ts diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx index 666930b65..2fb06833b 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx @@ -1,5 +1,7 @@ 'use client'; +import { useState } from 'react'; + import Link from 'next/link'; import { @@ -32,6 +34,8 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard'; +import { DeleteDraftDocumentDialog } from './delete-draft-document-dialog'; + export type DataTableActionDropdownProps = { row: Document & { User: Pick; @@ -44,6 +48,8 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = const { toast } = useToast(); const [, copyToClipboard] = useCopyToClipboard(); + const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false); + if (!session) { return null; } @@ -59,6 +65,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = // const isPending = row.status === DocumentStatus.PENDING; const isComplete = row.status === DocumentStatus.COMPLETED; // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; + const isDocumentDeletable = isOwner && row.status === DocumentStatus.DRAFT; const onShareClick = async () => { const { slug } = await createOrGetShareLink({ @@ -147,7 +154,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = Void - + setDeleteDialogOpen(true)} disabled={!isDocumentDeletable}> Delete @@ -168,6 +175,14 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = Share + + {isDocumentDeletable && ( + + )} ); }; diff --git a/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx b/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx new file mode 100644 index 000000000..1a458a13d --- /dev/null +++ b/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx @@ -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 ( + !isLoading && onOpenChange(value)}> + + + Do you want to delete this document? + + + Please note that this action is irreversible. Once confirmed, your document will be + permanently deleted. + + + + +
+ + + +
+
+
+
+ ); +}; diff --git a/packages/lib/server-only/document/delete-draft-document.ts b/packages/lib/server-only/document/delete-draft-document.ts new file mode 100644 index 000000000..6b0bc3511 --- /dev/null +++ b/packages/lib/server-only/document/delete-draft-document.ts @@ -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 } }); +}; diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index e436bb391..6d19afc0b 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -1,6 +1,7 @@ import { TRPCError } from '@trpc/server'; 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 { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; 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 { ZCreateDocumentMutationSchema, + ZDeleteDraftDocumentMutationSchema, ZGetDocumentByIdQuerySchema, ZGetDocumentByTokenQuerySchema, 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 .input(ZSetRecipientsForDocumentMutationSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts index c95417306..e5b27c0ea 100644 --- a/packages/trpc/server/document-router/schema.ts +++ b/packages/trpc/server/document-router/schema.ts @@ -61,3 +61,9 @@ export const ZSendDocumentMutationSchema = z.object({ }); export type TSendDocumentMutationSchema = z.infer; + +export const ZDeleteDraftDocumentMutationSchema = z.object({ + id: z.number().min(1), +}); + +export type TDeleteDraftDocumentMutationSchema = z.infer;