From d0b9cee500b8262d1991a522440f482b498b09d5 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 12 Mar 2024 18:55:59 +0530 Subject: [PATCH 01/23] feat: created the dialog file for delete of document --- .../admin/documents/[id]/delete-document-dialog.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx new file mode 100644 index 000000000..339659ccc --- /dev/null +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx @@ -0,0 +1,11 @@ +'use client'; + +import type { Document } from '@documenso/prisma/client'; + +export type DeleteDocumentDialogProps = { + document: Document; +}; + +export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => { + return
; +}; From 884eab36eb6df985b59a9cff2225028f91f74e53 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 12 Mar 2024 20:02:05 +0530 Subject: [PATCH 02/23] feat: adding the schema for the admin delete document mutation --- packages/trpc/server/admin-router/schema.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/trpc/server/admin-router/schema.ts b/packages/trpc/server/admin-router/schema.ts index cfedb06ba..a26d92fa6 100644 --- a/packages/trpc/server/admin-router/schema.ts +++ b/packages/trpc/server/admin-router/schema.ts @@ -48,3 +48,10 @@ export const ZAdminDeleteUserMutationSchema = z.object({ }); export type TAdminDeleteUserMutationSchema = z.infer; + +export const ZAdminDeleteDocumentMutationSchema = z.object({ + id: z.number().min(1), + userId: z.number(), +}); + +export type TAdminDeleteDocomentMutationSchema = z.infer; From c10cfbf6e15714bba98d30840ce76d64b6c98bde Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 12 Mar 2024 20:03:34 +0530 Subject: [PATCH 03/23] feat: adding the router for the delete document in the admin router --- packages/trpc/server/admin-router/router.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index 5be3ad9db..7955f7a18 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -3,6 +3,7 @@ import { TRPCError } from '@trpc/server'; import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents'; import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient'; import { updateUser } from '@documenso/lib/server-only/admin/update-user'; +import { deleteDocument } from '@documenso/lib/server-only/document/delete-document'; import { sealDocument } from '@documenso/lib/server-only/document/seal-document'; import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upsert-site-setting'; import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; @@ -10,6 +11,7 @@ import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; import { adminProcedure, router } from '../trpc'; import { + ZAdminDeleteDocumentMutationSchema, ZAdminDeleteUserMutationSchema, ZAdminFindDocumentsQuerySchema, ZAdminResealDocumentMutationSchema, @@ -118,4 +120,18 @@ export const adminRouter = router({ }); } }), + deleteDocument: adminProcedure + .input(ZAdminDeleteDocumentMutationSchema) + .mutation(async ({ input }) => { + const { id, userId } = input; + try { + return await deleteDocument({ id, userId }); + } catch (err) { + console.log(err); + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'We were unable to delete the specified document. Please try again.', + }); + } + }), }); From d8911ee97b30c412b84d11a079adedb0f7cb5f55 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 12 Mar 2024 20:16:48 +0530 Subject: [PATCH 04/23] feat: added the dialog delete file --- .../documents/[id]/delete-document-dialog.tsx | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx index 339659ccc..aacb49d65 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx @@ -1,11 +1,122 @@ 'use client'; +import { useState } from 'react'; + +import { useRouter } from 'next/navigation'; + import type { Document } from '@documenso/prisma/client'; +import { TRPCClientError } from '@documenso/trpc/client'; +import { trpc } from '@documenso/trpc/react'; +import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; +import { Button } from '@documenso/ui/primitives/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@documenso/ui/primitives/dialog'; +import { Input } from '@documenso/ui/primitives/input'; +import { useToast } from '@documenso/ui/primitives/use-toast'; export type DeleteDocumentDialogProps = { document: Document; }; export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => { - return
; + const router = useRouter(); + const { toast } = useToast(); + const [reason, setReason] = useState(''); + const { mutateAsync: deleteDocument, isLoading: isDeletingDocument } = + trpc.admin.deleteDocument.useMutation(); + + const handleDeleteDocument = async () => { + try { + await deleteDocument({ id: 1, userId: 1 }); + toast({ + title: 'Document deleted', + description: 'The Document has been deleted successfully.', + duration: 5000, + }); + router.push('admin/documents'); + } catch (err) { + if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { + toast({ + title: 'An error occurred', + description: err.message, + variant: 'destructive', + }); + } else { + toast({ + title: 'An unknown error occurred', + variant: 'destructive', + description: + err.message ?? + 'We encountered an unknown error while attempting to delete your document. Please try again later.', + }); + } + } + }; + + return ( +
+
+ +
+ Delete Account + + Delete the users account and all its contents. This action is irreversible and will + cancel their subscription, so proceed with caution. + +
+ +
+ + + + + + + + Delete Account + + + + This action is not reversible. Please be certain. + + + + +
+ To confirm, please the reason + + setReason(e.target.value)} + /> +
+ + + + +
+
+
+
+
+
+ ); }; From 3b65447b0ff21623ae848e32cc243ad9208df79b Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 12 Mar 2024 20:38:11 +0530 Subject: [PATCH 05/23] feat: updating the dialog and page of document --- .../documents/[id]/delete-document-dialog.tsx | 15 +++++++-------- .../app/(dashboard)/admin/documents/[id]/page.tsx | 5 +++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx index aacb49d65..c38ca03e2 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx @@ -34,7 +34,7 @@ export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => const handleDeleteDocument = async () => { try { - await deleteDocument({ id: 1, userId: 1 }); + await deleteDocument({ id: document.id, userId: document.userId }); toast({ title: 'Document deleted', description: 'The Document has been deleted successfully.', @@ -68,22 +68,21 @@ export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => variant="neutral" >
- Delete Account + Delete Document - Delete the users account and all its contents. This action is irreversible and will - cancel their subscription, so proceed with caution. + Delete the document. This action is irreversible so proceed with caution.
- + - Delete Account + Delete Document @@ -93,7 +92,7 @@ export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) =>
- To confirm, please the reason + To confirm, please enter the reason loading={isDeletingDocument} variant="destructive" > - {isDeletingDocument ? 'Deleting account...' : 'Delete Account'} + {isDeletingDocument ? 'Deleting document...' : 'Delete Document'} diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx index a22345457..5135a6236 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx @@ -13,6 +13,7 @@ import { DocumentStatus } from '~/components/formatter/document-status'; import { LocaleDate } from '~/components/formatter/locale-date'; import { AdminActions } from './admin-actions'; +import { DeleteDocumentDialog } from './delete-document-dialog'; import { RecipientItem } from './recipient-item'; type AdminDocumentDetailsPageProps = { @@ -81,6 +82,10 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument ))}
+ +
+ + {document && }
); } From a8413fa031d34676163ecf300e6614ca503015ea Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 12 Mar 2024 20:42:13 +0530 Subject: [PATCH 06/23] feat: disabled reason condition is updated on the dialog form --- .../(dashboard)/admin/documents/[id]/delete-document-dialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx index c38ca03e2..80715f220 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx @@ -107,6 +107,7 @@ export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => onClick={handleDeleteDocument} loading={isDeletingDocument} variant="destructive" + disabled={!reason} > {isDeletingDocument ? 'Deleting document...' : 'Delete Document'} From 4dc9e1295b9fbbc2c14e6142add8a7f2a7b8def6 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 12 Mar 2024 21:15:17 +0530 Subject: [PATCH 07/23] feat: added the templates for the delete of the documents from the admin --- .../template-document-delete.tsx | 35 ++++++++++ packages/email/templates/document-delete.tsx | 69 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 packages/email/template-components/template-document-delete.tsx create mode 100644 packages/email/templates/document-delete.tsx diff --git a/packages/email/template-components/template-document-delete.tsx b/packages/email/template-components/template-document-delete.tsx new file mode 100644 index 000000000..99cbe9706 --- /dev/null +++ b/packages/email/template-components/template-document-delete.tsx @@ -0,0 +1,35 @@ +import { Section, Text } from '../components'; +import { TemplateDocumentImage } from './template-document-image'; + +export interface TemplateDocumentDeleteProps { + inviterName: string; + inviterEmail: string; + reason: string; + documentName: string; + assetBaseUrl: string; +} + +export const TemplateDocumentDelete = ({ + reason, + documentName, + assetBaseUrl, +}: TemplateDocumentDeleteProps) => { + return ( + <> + + +
+ + Your document has been deleted +
"{documentName}" +
+ + Reason as below +
"{reason}" +
+
+ + ); +}; + +export default TemplateDocumentDelete; diff --git a/packages/email/templates/document-delete.tsx b/packages/email/templates/document-delete.tsx new file mode 100644 index 000000000..79e40e3d8 --- /dev/null +++ b/packages/email/templates/document-delete.tsx @@ -0,0 +1,69 @@ +import config from '@documenso/tailwind-config'; + +import { Body, Container, Head, Hr, Html, Img, Preview, Section, Tailwind } from '../components'; +import { + TemplateDocumentDelete, + type TemplateDocumentDeleteProps, +} from '../template-components/template-document-delete'; +import { TemplateFooter } from '../template-components/template-footer'; + +export type DocumentDeleteEmailTemplateProps = Partial; + +export const DocumentDeleteTemplate = ({ + inviterName = 'Lucas Smith', + inviterEmail = 'lucas@documenso.com', + documentName = 'Open Source Pledge.pdf', + assetBaseUrl = 'http://localhost:3002', + reason = 'Unknown', +}: DocumentDeleteEmailTemplateProps) => { + const previewText = `${inviterName} has cancelled the document ${documentName}, you don't need to sign it anymore.`; + + const getAssetUrl = (path: string) => { + return new URL(path, assetBaseUrl).toString(); + }; + + return ( + + + {previewText} + + +
+ +
+ Documenso Logo + +
+
+ +
+ + + + +
+ +
+ + ); +}; + +export default DocumentDeleteTemplate; From 3fb57c877ef0f71910f9b86e22641c1334f778f0 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Wed, 13 Mar 2024 10:54:53 +0530 Subject: [PATCH 08/23] feat: send delete email is added --- .../template-document-delete.tsx | 2 - packages/email/templates/document-delete.tsx | 10 ++-- .../server-only/document/send-delete-email.ts | 51 +++++++++++++++++++ 3 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 packages/lib/server-only/document/send-delete-email.ts diff --git a/packages/email/template-components/template-document-delete.tsx b/packages/email/template-components/template-document-delete.tsx index 99cbe9706..b87b4d5bd 100644 --- a/packages/email/template-components/template-document-delete.tsx +++ b/packages/email/template-components/template-document-delete.tsx @@ -2,8 +2,6 @@ import { Section, Text } from '../components'; import { TemplateDocumentImage } from './template-document-image'; export interface TemplateDocumentDeleteProps { - inviterName: string; - inviterEmail: string; reason: string; documentName: string; assetBaseUrl: string; diff --git a/packages/email/templates/document-delete.tsx b/packages/email/templates/document-delete.tsx index 79e40e3d8..87e6b6e9a 100644 --- a/packages/email/templates/document-delete.tsx +++ b/packages/email/templates/document-delete.tsx @@ -9,14 +9,12 @@ import { TemplateFooter } from '../template-components/template-footer'; export type DocumentDeleteEmailTemplateProps = Partial; -export const DocumentDeleteTemplate = ({ - inviterName = 'Lucas Smith', - inviterEmail = 'lucas@documenso.com', +export const DocumentDeleteEmailTemplate = ({ documentName = 'Open Source Pledge.pdf', assetBaseUrl = 'http://localhost:3002', reason = 'Unknown', }: DocumentDeleteEmailTemplateProps) => { - const previewText = `${inviterName} has cancelled the document ${documentName}, you don't need to sign it anymore.`; + const previewText = `Admin has deleted your document ${documentName}.`; const getAssetUrl = (path: string) => { return new URL(path, assetBaseUrl).toString(); @@ -45,11 +43,9 @@ export const DocumentDeleteTemplate = ({ className="mb-4 h-6" /> @@ -66,4 +62,4 @@ export const DocumentDeleteTemplate = ({ ); }; -export default DocumentDeleteTemplate; +export default DocumentDeleteEmailTemplate; diff --git a/packages/lib/server-only/document/send-delete-email.ts b/packages/lib/server-only/document/send-delete-email.ts new file mode 100644 index 000000000..8594a1c3c --- /dev/null +++ b/packages/lib/server-only/document/send-delete-email.ts @@ -0,0 +1,51 @@ +import { createElement } from 'react'; + +import { mailer } from '@documenso/email/mailer'; +import { render } from '@documenso/email/render'; +import { DocumentDeleteEmailTemplate } from '@documenso/email/templates/document-delete'; +import { prisma } from '@documenso/prisma'; + +import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; + +export interface SendDeleteEmailOptions { + documentId: number; + reason: string; +} + +export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOptions) => { + const document = await prisma.document.findFirst({ + where: { + id: documentId, + }, + include: { + User: true, + }, + }); + + if (!document) { + throw new Error('Document not found'); + } + + const { email, name } = document.User; + + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; + + const template = createElement(DocumentDeleteEmailTemplate, { + documentName: document.title, + assetBaseUrl, + }); + + await mailer.sendMail({ + to: { + address: email, + name: name || '', + }, + from: { + name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', + address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', + }, + subject: 'Document Deleted!', + html: render(template), + text: render(template, { plainText: true }), + }); +}; From 487bc026f90812a3aaf1b0c22e30bbcca23327ac Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Wed, 13 Mar 2024 11:06:35 +0530 Subject: [PATCH 09/23] feat: reason is added to the component props --- packages/lib/server-only/document/send-delete-email.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/lib/server-only/document/send-delete-email.ts b/packages/lib/server-only/document/send-delete-email.ts index 8594a1c3c..4046d5f0f 100644 --- a/packages/lib/server-only/document/send-delete-email.ts +++ b/packages/lib/server-only/document/send-delete-email.ts @@ -32,6 +32,7 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt const template = createElement(DocumentDeleteEmailTemplate, { documentName: document.title, + reason, assetBaseUrl, }); From 35c1b0bceef5cf32b9b43b50ed803e4f8662a098 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Wed, 13 Mar 2024 11:15:06 +0530 Subject: [PATCH 10/23] feat: corrected the document redirection after delete --- .../(dashboard)/admin/documents/[id]/delete-document-dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx index 80715f220..2e97eabf1 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx @@ -40,7 +40,7 @@ export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => description: 'The Document has been deleted successfully.', duration: 5000, }); - router.push('admin/documents'); + router.push('/admin/documents'); } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ From af6ec5df4290dadd54c0b432cda97f9b2a5de225 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Wed, 13 Mar 2024 11:30:20 +0530 Subject: [PATCH 11/23] feat: reason is added to the email --- .../admin/documents/[id]/delete-document-dialog.tsx | 2 +- packages/trpc/server/admin-router/router.ts | 7 +++++-- packages/trpc/server/admin-router/schema.ts | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx index 2e97eabf1..7414390b0 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx @@ -34,7 +34,7 @@ export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => const handleDeleteDocument = async () => { try { - await deleteDocument({ id: document.id, userId: document.userId }); + await deleteDocument({ id: document.id, userId: document.userId, reason }); toast({ title: 'Document deleted', description: 'The Document has been deleted successfully.', diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index 7955f7a18..1215f1c39 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -5,6 +5,7 @@ import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipie import { updateUser } from '@documenso/lib/server-only/admin/update-user'; import { deleteDocument } from '@documenso/lib/server-only/document/delete-document'; import { sealDocument } from '@documenso/lib/server-only/document/seal-document'; +import { sendDeleteEmail } from '@documenso/lib/server-only/document/send-delete-email'; import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upsert-site-setting'; import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; @@ -123,9 +124,11 @@ export const adminRouter = router({ deleteDocument: adminProcedure .input(ZAdminDeleteDocumentMutationSchema) .mutation(async ({ input }) => { - const { id, userId } = input; + const { id, userId, reason } = input; try { - return await deleteDocument({ id, userId }); + await deleteDocument({ id, userId }); + await sendDeleteEmail({ documentId: id, reason }); + return; } catch (err) { console.log(err); throw new TRPCError({ diff --git a/packages/trpc/server/admin-router/schema.ts b/packages/trpc/server/admin-router/schema.ts index a26d92fa6..91b0df3c1 100644 --- a/packages/trpc/server/admin-router/schema.ts +++ b/packages/trpc/server/admin-router/schema.ts @@ -52,6 +52,7 @@ export type TAdminDeleteUserMutationSchema = z.infer; From 364aaa4cb6573318b258bb7a5b634b911b34b60a Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Wed, 13 Mar 2024 11:32:14 +0530 Subject: [PATCH 12/23] feat: reason label is changed --- packages/email/template-components/template-document-delete.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/email/template-components/template-document-delete.tsx b/packages/email/template-components/template-document-delete.tsx index b87b4d5bd..df8266e8b 100644 --- a/packages/email/template-components/template-document-delete.tsx +++ b/packages/email/template-components/template-document-delete.tsx @@ -22,7 +22,7 @@ export const TemplateDocumentDelete = ({
"{documentName}" - Reason as below + Reason
"{reason}"
From bba1ea81d63015af5af55156ce0285209219f8be Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Wed, 13 Mar 2024 11:40:12 +0530 Subject: [PATCH 13/23] feat: updated the condition of the delete dialog in the detail page --- apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx index 5135a6236..2257a5986 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx @@ -85,7 +85,7 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument
- {document && } + {document && !document.deletedAt && } ); } From abb49c349cc795b98980193602c1e9669945bbe7 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 26 Mar 2024 18:48:35 +0530 Subject: [PATCH 14/23] fix: delete document file is changed to super delete document file --- apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx | 4 ++-- ...e-document-dialog.tsx => super-delete-document-dialog.tsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename apps/web/src/app/(dashboard)/admin/documents/[id]/{delete-document-dialog.tsx => super-delete-document-dialog.tsx} (96%) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx index 2257a5986..3d2c96f89 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx @@ -13,8 +13,8 @@ import { DocumentStatus } from '~/components/formatter/document-status'; import { LocaleDate } from '~/components/formatter/locale-date'; import { AdminActions } from './admin-actions'; -import { DeleteDocumentDialog } from './delete-document-dialog'; import { RecipientItem } from './recipient-item'; +import { SuperDeleteDocumentDialog } from './super-delete-document-dialog'; type AdminDocumentDetailsPageProps = { params: { @@ -85,7 +85,7 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument
- {document && !document.deletedAt && } + {document && !document.deletedAt && } ); } diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx similarity index 96% rename from apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx rename to apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx index 7414390b0..346a4eccc 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx @@ -21,11 +21,11 @@ import { import { Input } from '@documenso/ui/primitives/input'; import { useToast } from '@documenso/ui/primitives/use-toast'; -export type DeleteDocumentDialogProps = { +export type SuperDeleteDocumentDialogProps = { document: Document; }; -export const DeleteDocumentDialog = ({ document }: DeleteDocumentDialogProps) => { +export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialogProps) => { const router = useRouter(); const { toast } = useToast(); const [reason, setReason] = useState(''); From a6ddc114d9a1a385ba0d1d996b755eb24dc0e105 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 26 Mar 2024 18:53:03 +0530 Subject: [PATCH 15/23] fix: a condition is added for the reason in the handler --- .../[id]/super-delete-document-dialog.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx index 346a4eccc..6d89c7e7b 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx @@ -34,13 +34,15 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo const handleDeleteDocument = async () => { try { - await deleteDocument({ id: document.id, userId: document.userId, reason }); - toast({ - title: 'Document deleted', - description: 'The Document has been deleted successfully.', - duration: 5000, - }); - router.push('/admin/documents'); + if (reason) { + await deleteDocument({ id: document.id, userId: document.userId, reason }); + toast({ + title: 'Document deleted', + description: 'The Document has been deleted successfully.', + duration: 5000, + }); + router.push('/admin/documents'); + } } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ From 6603aa6f2e59b6b6e7994e9b7c9a89b64dcb8397 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 26 Mar 2024 18:57:19 +0530 Subject: [PATCH 16/23] fix: removed the condition for deletedAt flag inside the document --- apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx index 3d2c96f89..0682e6c5e 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx @@ -85,7 +85,7 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument
- {document && !document.deletedAt && } + {document && } ); } From 2296924ef6d0760dc77233ff81e73c63eea3fbb7 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 26 Mar 2024 19:01:52 +0530 Subject: [PATCH 17/23] fix: reason for delete document is changed --- .../email/template-components/template-document-delete.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/email/template-components/template-document-delete.tsx b/packages/email/template-components/template-document-delete.tsx index df8266e8b..b23bddcc8 100644 --- a/packages/email/template-components/template-document-delete.tsx +++ b/packages/email/template-components/template-document-delete.tsx @@ -18,7 +18,8 @@ export const TemplateDocumentDelete = ({
- Your document has been deleted + This document can not be recovered, if you would like to dispute the reason for future + documents please contact support.
"{documentName}"
From bd703fb620b83491f1df5ba49b6afdafa7edcde5 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 26 Mar 2024 19:19:02 +0530 Subject: [PATCH 18/23] fix: return of document after delete --- packages/trpc/server/admin-router/router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index 1215f1c39..c4eb779b3 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -126,9 +126,9 @@ export const adminRouter = router({ .mutation(async ({ input }) => { const { id, userId, reason } = input; try { - await deleteDocument({ id, userId }); + const document = await deleteDocument({ id, userId }); await sendDeleteEmail({ documentId: id, reason }); - return; + return document; } catch (err) { console.log(err); throw new TRPCError({ From 5b4152ffc5cfe184b2b8f43658877373d19e79ca Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 26 Mar 2024 20:36:45 +0530 Subject: [PATCH 19/23] fix: updated the super delete file --- .../document/super-delete-document.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 packages/lib/server-only/document/super-delete-document.ts diff --git a/packages/lib/server-only/document/super-delete-document.ts b/packages/lib/server-only/document/super-delete-document.ts new file mode 100644 index 000000000..c81a5f746 --- /dev/null +++ b/packages/lib/server-only/document/super-delete-document.ts @@ -0,0 +1,90 @@ +'use server'; + +import { createElement } from 'react'; + +import { mailer } from '@documenso/email/mailer'; +import { render } from '@documenso/email/render'; +import DocumentCancelTemplate from '@documenso/email/templates/document-cancel'; +import { prisma } from '@documenso/prisma'; +import { DocumentStatus } from '@documenso/prisma/client'; + +import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; +import { FROM_ADDRESS, FROM_NAME } from '../../constants/email'; +import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; +import type { RequestMetadata } from '../../universal/extract-request-metadata'; +import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; + +export type SuperDeleteDocumentOptions = { + id: number; + userId: number; + requestMetadata?: RequestMetadata; +}; + +export const SuperDeleteDocument = async ({ + id, + userId, + requestMetadata, +}: SuperDeleteDocumentOptions) => { + const document = await prisma.document.findUnique({ + where: { + id, + userId, + }, + include: { + Recipient: true, + documentMeta: true, + User: true, + }, + }); + + if (!document) { + throw new Error('Document not found'); + } + + const { status, User: user } = document; + + // if the document is pending, send cancellation emails to all recipients + if (status === DocumentStatus.PENDING && document.Recipient.length > 0) { + await Promise.all( + document.Recipient.map(async (recipient) => { + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; + const template = createElement(DocumentCancelTemplate, { + documentName: document.title, + inviterName: user.name || undefined, + inviterEmail: user.email, + assetBaseUrl, + }); + + await mailer.sendMail({ + to: { + address: recipient.email, + name: recipient.name, + }, + from: { + name: FROM_NAME, + address: FROM_ADDRESS, + }, + subject: 'Document Cancelled', + html: render(template), + text: render(template, { plainText: true }), + }); + }), + ); + } + + // always hard delete if deleted from admin + return await prisma.$transaction(async (tx) => { + await tx.documentAuditLog.create({ + data: createDocumentAuditLogData({ + documentId: id, + type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED, + user, + requestMetadata, + data: { + type: 'HARD', + }, + }), + }); + return await tx.document.delete({ where: { id } }); + }); +}; From 26141050b7fc9f9ee83b2899eaeed3f518d7d232 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Tue, 26 Mar 2024 20:42:33 +0530 Subject: [PATCH 20/23] fix: document super delete function calling --- packages/lib/server-only/document/super-delete-document.ts | 2 +- packages/trpc/server/admin-router/router.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/lib/server-only/document/super-delete-document.ts b/packages/lib/server-only/document/super-delete-document.ts index c81a5f746..1d93da4db 100644 --- a/packages/lib/server-only/document/super-delete-document.ts +++ b/packages/lib/server-only/document/super-delete-document.ts @@ -20,7 +20,7 @@ export type SuperDeleteDocumentOptions = { requestMetadata?: RequestMetadata; }; -export const SuperDeleteDocument = async ({ +export const superDeleteDocument = async ({ id, userId, requestMetadata, diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index c4eb779b3..248d7d9ea 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -3,9 +3,9 @@ import { TRPCError } from '@trpc/server'; import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents'; import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient'; import { updateUser } from '@documenso/lib/server-only/admin/update-user'; -import { deleteDocument } from '@documenso/lib/server-only/document/delete-document'; import { sealDocument } from '@documenso/lib/server-only/document/seal-document'; import { sendDeleteEmail } from '@documenso/lib/server-only/document/send-delete-email'; +import { superDeleteDocument } from '@documenso/lib/server-only/document/super-delete-document'; import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upsert-site-setting'; import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; @@ -126,7 +126,7 @@ export const adminRouter = router({ .mutation(async ({ input }) => { const { id, userId, reason } = input; try { - const document = await deleteDocument({ id, userId }); + const document = await superDeleteDocument({ id, userId }); await sendDeleteEmail({ documentId: id, reason }); return document; } catch (err) { From 0aa111cd6e75ea10f46b27f4d50729dbbb079cf2 Mon Sep 17 00:00:00 2001 From: Rohit Saluja Date: Wed, 27 Mar 2024 09:55:30 +0530 Subject: [PATCH 21/23] fix: fixed the no document error --- packages/trpc/server/admin-router/router.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index 248d7d9ea..459c9a396 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -126,9 +126,8 @@ export const adminRouter = router({ .mutation(async ({ input }) => { const { id, userId, reason } = input; try { - const document = await superDeleteDocument({ id, userId }); await sendDeleteEmail({ documentId: id, reason }); - return document; + return await superDeleteDocument({ id, userId }); } catch (err) { console.log(err); throw new TRPCError({ From a56bf6a19202599a72a99bbbb8ea953838aeadd1 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Thu, 28 Mar 2024 06:55:01 +0000 Subject: [PATCH 22/23] fix: update email template and tidy code --- .../[id]/super-delete-document-dialog.tsx | 24 ++++++++++++------- ...tsx => template-document-super-delete.tsx} | 21 ++++++++++++---- ...t-delete.tsx => document-super-delete.tsx} | 9 +++---- .../document/super-delete-document.ts | 9 ++----- packages/trpc/server/admin-router/router.ts | 13 +++++++--- packages/trpc/server/admin-router/schema.ts | 1 - 6 files changed, 48 insertions(+), 29 deletions(-) rename packages/email/template-components/{template-document-delete.tsx => template-document-super-delete.tsx} (52%) rename packages/email/templates/{document-delete.tsx => document-super-delete.tsx} (87%) diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx index 6d89c7e7b..63ad88a3f 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx @@ -26,23 +26,29 @@ export type SuperDeleteDocumentDialogProps = { }; export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialogProps) => { - const router = useRouter(); const { toast } = useToast(); + const router = useRouter(); + const [reason, setReason] = useState(''); + const { mutateAsync: deleteDocument, isLoading: isDeletingDocument } = trpc.admin.deleteDocument.useMutation(); const handleDeleteDocument = async () => { try { - if (reason) { - await deleteDocument({ id: document.id, userId: document.userId, reason }); - toast({ - title: 'Document deleted', - description: 'The Document has been deleted successfully.', - duration: 5000, - }); - router.push('/admin/documents'); + if (!reason) { + return; } + + await deleteDocument({ id: document.id, reason }); + + toast({ + title: 'Document deleted', + description: 'The Document has been deleted successfully.', + duration: 5000, + }); + + router.push('/admin/documents'); } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ diff --git a/packages/email/template-components/template-document-delete.tsx b/packages/email/template-components/template-document-super-delete.tsx similarity index 52% rename from packages/email/template-components/template-document-delete.tsx rename to packages/email/template-components/template-document-super-delete.tsx index b23bddcc8..9cb0a9e71 100644 --- a/packages/email/template-components/template-document-delete.tsx +++ b/packages/email/template-components/template-document-super-delete.tsx @@ -17,14 +17,25 @@ export const TemplateDocumentDelete = ({
- + + Your document has been deleted by an admin! + + + + "{documentName}" has been deleted by an admin. + + + This document can not be recovered, if you would like to dispute the reason for future documents please contact support. -
"{documentName}"
- - Reason -
"{reason}" + + + The reason provided for deletion is the following: + + + + {reason}
diff --git a/packages/email/templates/document-delete.tsx b/packages/email/templates/document-super-delete.tsx similarity index 87% rename from packages/email/templates/document-delete.tsx rename to packages/email/templates/document-super-delete.tsx index 87e6b6e9a..68384e119 100644 --- a/packages/email/templates/document-delete.tsx +++ b/packages/email/templates/document-super-delete.tsx @@ -4,17 +4,17 @@ import { Body, Container, Head, Hr, Html, Img, Preview, Section, Tailwind } from import { TemplateDocumentDelete, type TemplateDocumentDeleteProps, -} from '../template-components/template-document-delete'; +} from '../template-components/template-document-super-delete'; import { TemplateFooter } from '../template-components/template-footer'; export type DocumentDeleteEmailTemplateProps = Partial; -export const DocumentDeleteEmailTemplate = ({ +export const DocumentSuperDeleteEmailTemplate = ({ documentName = 'Open Source Pledge.pdf', assetBaseUrl = 'http://localhost:3002', reason = 'Unknown', }: DocumentDeleteEmailTemplateProps) => { - const previewText = `Admin has deleted your document ${documentName}.`; + const previewText = `An admin has deleted your document "${documentName}".`; const getAssetUrl = (path: string) => { return new URL(path, assetBaseUrl).toString(); @@ -42,6 +42,7 @@ export const DocumentDeleteEmailTemplate = ({ alt="Documenso Logo" className="mb-4 h-6" /> + { +export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDocumentOptions) => { const document = await prisma.document.findUnique({ where: { id, - userId, }, include: { Recipient: true, @@ -85,6 +79,7 @@ export const superDeleteDocument = async ({ }, }), }); + return await tx.document.delete({ where: { id } }); }); }; diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index 459c9a396..b37510be7 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -9,6 +9,7 @@ import { superDeleteDocument } from '@documenso/lib/server-only/document/super-d import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upsert-site-setting'; import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; +import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { adminProcedure, router } from '../trpc'; import { @@ -121,15 +122,21 @@ export const adminRouter = router({ }); } }), + deleteDocument: adminProcedure .input(ZAdminDeleteDocumentMutationSchema) - .mutation(async ({ input }) => { - const { id, userId, reason } = input; + .mutation(async ({ ctx, input }) => { + const { id, reason } = input; try { await sendDeleteEmail({ documentId: id, reason }); - return await superDeleteDocument({ id, userId }); + + return await superDeleteDocument({ + id, + requestMetadata: extractNextApiRequestMetadata(ctx.req), + }); } catch (err) { console.log(err); + throw new TRPCError({ code: 'BAD_REQUEST', message: 'We were unable to delete the specified document. Please try again.', diff --git a/packages/trpc/server/admin-router/schema.ts b/packages/trpc/server/admin-router/schema.ts index 91b0df3c1..6bb567dbd 100644 --- a/packages/trpc/server/admin-router/schema.ts +++ b/packages/trpc/server/admin-router/schema.ts @@ -51,7 +51,6 @@ export type TAdminDeleteUserMutationSchema = z.infer Date: Thu, 28 Mar 2024 07:01:57 +0000 Subject: [PATCH 23/23] fix: build error from renaming --- packages/lib/server-only/document/send-delete-email.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lib/server-only/document/send-delete-email.ts b/packages/lib/server-only/document/send-delete-email.ts index 4046d5f0f..cc1101942 100644 --- a/packages/lib/server-only/document/send-delete-email.ts +++ b/packages/lib/server-only/document/send-delete-email.ts @@ -2,7 +2,7 @@ import { createElement } from 'react'; import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; -import { DocumentDeleteEmailTemplate } from '@documenso/email/templates/document-delete'; +import { DocumentSuperDeleteEmailTemplate } from '@documenso/email/templates/document-super-delete'; import { prisma } from '@documenso/prisma'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; @@ -30,7 +30,7 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; - const template = createElement(DocumentDeleteEmailTemplate, { + const template = createElement(DocumentSuperDeleteEmailTemplate, { documentName: document.title, reason, assetBaseUrl,