From fd6350b3973a72365c7175d8c4cd5f2694ecbb77 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Sun, 3 Sep 2023 23:30:48 +0530 Subject: [PATCH 01/34] feat: added 404 page for marketing app Signed-off-by: Adithya Krishna --- apps/marketing/src/app/not-found.tsx | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 apps/marketing/src/app/not-found.tsx diff --git a/apps/marketing/src/app/not-found.tsx b/apps/marketing/src/app/not-found.tsx new file mode 100644 index 000000000..2935c5698 --- /dev/null +++ b/apps/marketing/src/app/not-found.tsx @@ -0,0 +1,65 @@ +'use client'; + +import Image from 'next/image'; +import { useRouter } from 'next/navigation'; + +import { motion } from 'framer-motion'; +import { ChevronLeft } from 'lucide-react'; + +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; + +import backgroundPattern from '~/assets/background-pattern.png'; + +export default function NotFound() { + const router = useRouter(); + return ( + +
+ + background pattern + +
+
+
+

+ 404 Page not found +

+

+ Sorry, the page you are looking for doesn't exist or has been moved. +

+ +
+ + +
+
+
+
+ ); +} From 118483b6cc40a2c5ce4989de9384e0300b7bdccf Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Sun, 3 Sep 2023 23:52:51 +0530 Subject: [PATCH 02/34] chore: updated 404 page for marketing app Signed-off-by: Adithya Krishna --- apps/marketing/src/app/not-found.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/marketing/src/app/not-found.tsx b/apps/marketing/src/app/not-found.tsx index 2935c5698..7dfaf9dba 100644 --- a/apps/marketing/src/app/not-found.tsx +++ b/apps/marketing/src/app/not-found.tsx @@ -25,6 +25,7 @@ export default function NotFound() { src={backgroundPattern} alt="background pattern" className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover md:scale-100 lg:scale-[100%]" + priority={true} /> From 12c45fb882c6700eea2fbf3199ac0ae70fe805dd Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Sun, 3 Sep 2023 23:53:07 +0530 Subject: [PATCH 03/34] feat: added 404 page for web app Signed-off-by: Adithya Krishna --- apps/web/src/app/not-found.tsx | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 apps/web/src/app/not-found.tsx diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx new file mode 100644 index 000000000..c723d5255 --- /dev/null +++ b/apps/web/src/app/not-found.tsx @@ -0,0 +1,66 @@ +'use client'; + +import Image from 'next/image'; +import { useRouter } from 'next/navigation'; + +import { motion } from 'framer-motion'; +import { ChevronLeft } from 'lucide-react'; + +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; + +import backgroundPattern from '~/assets/background-pattern.png'; + +export default function NotFound() { + const router = useRouter(); + return ( + +
+ + background pattern + +
+
+
+

404 Error

+

+ Oops! You found a secret page +

+

+ The page you are looking for may not exist :/ +

+
+ + +
+
+
+
+ ); +} From 2524458b0c10914c487bedd40674afd9b60032fa Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Sun, 3 Sep 2023 23:59:02 +0530 Subject: [PATCH 04/34] chore: updated wording Signed-off-by: Adithya Krishna --- apps/web/src/app/not-found.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx index c723d5255..07124c6ca 100644 --- a/apps/web/src/app/not-found.tsx +++ b/apps/web/src/app/not-found.tsx @@ -31,7 +31,7 @@ export default function NotFound() {
-

404 Error

+

404 Page not found

Oops! You found a secret page

From d524ea77abe2bc0bb40dac159a1e3d2f063a5793 Mon Sep 17 00:00:00 2001 From: Mythie Date: Tue, 5 Sep 2023 13:15:45 +1000 Subject: [PATCH 05/34] fix: update styling --- apps/marketing/src/app/not-found.tsx | 51 ++++++------ apps/web/src/app/not-found.tsx | 78 +++++-------------- apps/web/src/components/motion.tsx | 7 -- .../web/src/components/partials/not-found.tsx | 66 ++++++++++++++++ 4 files changed, 110 insertions(+), 92 deletions(-) delete mode 100644 apps/web/src/components/motion.tsx create mode 100644 apps/web/src/components/partials/not-found.tsx diff --git a/apps/marketing/src/app/not-found.tsx b/apps/marketing/src/app/not-found.tsx index 7dfaf9dba..9f87cdc88 100644 --- a/apps/marketing/src/app/not-found.tsx +++ b/apps/marketing/src/app/not-found.tsx @@ -1,6 +1,7 @@ 'use client'; import Image from 'next/image'; +import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { motion } from 'framer-motion'; @@ -13,54 +14,52 @@ import backgroundPattern from '~/assets/background-pattern.png'; export default function NotFound() { const router = useRouter(); + return ( - +
background pattern
-
-
-

- 404 Page not found -

-

- Sorry, the page you are looking for doesn't exist or has been moved. + +

+
+

404 Page not found

+ +

Oops! Something went wrong.

+ +

+ The page you are looking for was moved, removed, renamed or might never have existed.

-
+
-
- +
); } diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx index 07124c6ca..c8dc15086 100644 --- a/apps/web/src/app/not-found.tsx +++ b/apps/web/src/app/not-found.tsx @@ -1,66 +1,26 @@ -'use client'; +import Link from 'next/link'; -import Image from 'next/image'; -import { useRouter } from 'next/navigation'; - -import { motion } from 'framer-motion'; -import { ChevronLeft } from 'lucide-react'; - -import { cn } from '@documenso/ui/lib/utils'; +import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; import { Button } from '@documenso/ui/primitives/button'; -import backgroundPattern from '~/assets/background-pattern.png'; +import NotFoundPartial from '~/components/partials/not-found'; + +export default async function NotFound() { + const session = await getServerComponentSession(); -export default function NotFound() { - const router = useRouter(); return ( - -
- - background pattern - -
-
-
-

404 Page not found

-

- Oops! You found a secret page -

-

- The page you are looking for may not exist :/ -

-
- - -
-
-
-
+ + {session && ( + + )} + + {!session && ( + + )} + ); } diff --git a/apps/web/src/components/motion.tsx b/apps/web/src/components/motion.tsx deleted file mode 100644 index 2e9d19eae..000000000 --- a/apps/web/src/components/motion.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -import { motion } from 'framer-motion'; - -export * from 'framer-motion'; - -export const MotionDiv = motion.div; diff --git a/apps/web/src/components/partials/not-found.tsx b/apps/web/src/components/partials/not-found.tsx new file mode 100644 index 000000000..0b5c2ad18 --- /dev/null +++ b/apps/web/src/components/partials/not-found.tsx @@ -0,0 +1,66 @@ +'use client'; + +import Image from 'next/image'; +import { useRouter } from 'next/navigation'; + +import { motion } from 'framer-motion'; +import { ChevronLeft } from 'lucide-react'; + +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; + +import backgroundPattern from '~/assets/background-pattern.png'; + +export type NotFoundPartialProps = { + children?: React.ReactNode; +}; + +export default function NotFoundPartial({ children }: NotFoundPartialProps) { + const router = useRouter(); + + return ( +
+
+ + background pattern + +
+ +
+
+

404 Page not found

+ +

Oops! Something went wrong.

+ +

+ The page you are looking for was moved, removed, renamed or might never have existed. +

+ +
+ + + {children} +
+
+
+
+ ); +} From 551918ab9b6fa6037a3585c5d85d5e8b98fcbfd7 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Tue, 5 Sep 2023 13:53:18 +0000 Subject: [PATCH 06/34] feat: redirect signed document to completed page --- apps/web/src/app/(signing)/sign/[token]/page.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx index 35621068a..a55687196 100644 --- a/apps/web/src/app/(signing)/sign/[token]/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx @@ -1,4 +1,4 @@ -import { notFound } from 'next/navigation'; +import { notFound, redirect } from 'next/navigation'; import { match } from 'ts-pattern'; @@ -44,6 +44,10 @@ export default async function SigningPage({ params: { token } }: SigningPageProp return notFound(); } + if (document?.status === 'COMPLETED') { + redirect(`/sign/${token}/complete`); + } + const user = await getServerComponentSession(); const documentUrl = `data:application/pdf;base64,${document.document}`; From a74374e39f07c0001d63de356a3f8395b25b400a Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 6 Sep 2023 11:11:13 +0000 Subject: [PATCH 07/34] feat: add initial empty state for no results --- apps/web/src/app/(dashboard)/documents/page.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx index 4ea55936b..3c4c13e30 100644 --- a/apps/web/src/app/(dashboard)/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/page.tsx @@ -1,5 +1,7 @@ import Link from 'next/link'; +import { CheckCircle2 } from 'lucide-react'; + import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; import { getStats } from '@documenso/lib/server-only/document/get-stats'; @@ -95,8 +97,21 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
- + {results.count > 0 && } + {results.count === 0 && }
); } + +const EmptyDocumentState = () => { + return ( +
+ +
+

All done

+

All documents signed for now.

+
+
+ ); +}; From 8220b2f086d1a422adfd643c0acf836c64321161 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 6 Sep 2023 11:47:58 +0000 Subject: [PATCH 08/34] feat: add empty state for different status --- .../app/(dashboard)/documents/empty-state.tsx | 46 +++++++++++++++++++ .../src/app/(dashboard)/documents/page.tsx | 17 +------ 2 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/app/(dashboard)/documents/empty-state.tsx diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx new file mode 100644 index 000000000..fef3af5cc --- /dev/null +++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx @@ -0,0 +1,46 @@ +import { ArrowRight, CheckCircle2 } from 'lucide-react'; + +export default function EmptyDocumentState({ status }: { status: string }) { + let headerText = 'All done'; + let bodyText = 'All documents signed for now.'; + let extraText = ''; + let showArrow = false; + + switch (status) { + case 'COMPLETED': + headerText = 'Nothing here'; + bodyText = 'There are no signed documents yet.'; + extraText = 'Start by adding a document'; + showArrow = true; + break; + case 'DRAFT': + headerText = 'Nothing here'; + bodyText = 'There are no drafts yet.'; + extraText = 'Start by adding a document'; + showArrow = true; + break; + case 'ALL': + headerText = 'Nothing here'; + bodyText = 'There are no documents yet.'; + extraText = 'Start by adding a document'; + showArrow = true; + break; + default: + break; + } + + return ( +
+ +
+

{headerText}

+

{bodyText}

+ {extraText && ( +

+ {extraText} {showArrow && } +

+ )} +
+
+ ); +} diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx index 3c4c13e30..0a90d1535 100644 --- a/apps/web/src/app/(dashboard)/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/page.tsx @@ -1,7 +1,5 @@ import Link from 'next/link'; -import { CheckCircle2 } from 'lucide-react'; - import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; import { getStats } from '@documenso/lib/server-only/document/get-stats'; @@ -15,6 +13,7 @@ import { DocumentStatus } from '~/components/formatter/document-status'; import { UploadDocument } from '../dashboard/upload-document'; import { DocumentsDataTable } from './data-table'; +import EmptyDocumentState from './empty-state'; export type DocumentsPageProps = { searchParams?: { @@ -98,20 +97,8 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
{results.count > 0 && } - {results.count === 0 && } + {results.count === 0 && }
); } - -const EmptyDocumentState = () => { - return ( -
- -
-

All done

-

All documents signed for now.

-
-
- ); -}; From 1ba7767f8e7675aa81d4799e1e59dd2d1a73750f Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 6 Sep 2023 11:52:15 +0000 Subject: [PATCH 09/34] chore: correct types on component --- apps/web/src/app/(dashboard)/documents/empty-state.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx index fef3af5cc..680f67bf5 100644 --- a/apps/web/src/app/(dashboard)/documents/empty-state.tsx +++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx @@ -1,6 +1,10 @@ import { ArrowRight, CheckCircle2 } from 'lucide-react'; -export default function EmptyDocumentState({ status }: { status: string }) { +import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; + +export type EmptyDocumentProps = { status: ExtendedDocumentStatus }; + +export default function EmptyDocumentState({ status }: EmptyDocumentProps) { let headerText = 'All done'; let bodyText = 'All documents signed for now.'; let extraText = ''; From 1f027d75d3b85e4ff403ecc6f1581258634e26b4 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 6 Sep 2023 11:55:02 +0000 Subject: [PATCH 10/34] chore: fix eslint errors --- .../lib/server-only/pdf/insert-field-in-pdf.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/lib/server-only/pdf/insert-field-in-pdf.ts b/packages/lib/server-only/pdf/insert-field-in-pdf.ts index 61726f53c..e7b1e7c5a 100644 --- a/packages/lib/server-only/pdf/insert-field-in-pdf.ts +++ b/packages/lib/server-only/pdf/insert-field-in-pdf.ts @@ -50,10 +50,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu let imageWidth = image.width; let imageHeight = image.height; - const initialDimensions = { - width: imageWidth, - height: imageHeight, - }; + // const initialDimensions = { + // width: imageWidth, + // height: imageHeight, + // }; const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1); @@ -76,10 +76,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu let textWidth = font.widthOfTextAtSize(field.customText, fontSize); const textHeight = font.heightAtSize(fontSize); - const initialDimensions = { - width: textWidth, - height: textHeight, - }; + // const initialDimensions = { + // width: textWidth, + // height: textHeight, + // }; const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1); From f6e49e3f2143c428710c96c0c8df2af85b94ad15 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 6 Sep 2023 12:00:23 +0000 Subject: [PATCH 11/34] chore: remove code from different branch --- apps/web/src/app/(signing)/sign/[token]/page.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx index a55687196..f25183968 100644 --- a/apps/web/src/app/(signing)/sign/[token]/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx @@ -44,10 +44,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp return notFound(); } - if (document?.status === 'COMPLETED') { - redirect(`/sign/${token}/complete`); - } - const user = await getServerComponentSession(); const documentUrl = `data:application/pdf;base64,${document.document}`; From 0c145fab0bff601d5ce1cdcca042cd5248b99ed9 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 6 Sep 2023 12:01:30 +0000 Subject: [PATCH 12/34] chore: fix eslint errors --- apps/web/src/app/(signing)/sign/[token]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx index f25183968..35621068a 100644 --- a/apps/web/src/app/(signing)/sign/[token]/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx @@ -1,4 +1,4 @@ -import { notFound, redirect } from 'next/navigation'; +import { notFound } from 'next/navigation'; import { match } from 'ts-pattern'; From dbbe17a0a8a662ff9ea43d0db9dc244134ea190c Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Thu, 7 Sep 2023 19:58:48 +0000 Subject: [PATCH 13/34] feat: send email when recipient is done signing --- .../document/complete-document-with-token.ts | 3 ++ .../document/send-recipient-signed-email.ts | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 packages/lib/server-only/document/send-recipient-signed-email.ts diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index a013d5e69..4d4bacabf 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -4,6 +4,7 @@ import { prisma } from '@documenso/prisma'; import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import { sealDocument } from './seal-document'; +import { sendPendingEmail } from './send-recipient-signed-email'; export type CompleteDocumentWithTokenOptions = { token: string; @@ -69,6 +70,8 @@ export const completeDocumentWithToken = async ({ }, }); + await sendPendingEmail({ document, recipient }); + const documents = await prisma.document.updateMany({ where: { id: document.id, diff --git a/packages/lib/server-only/document/send-recipient-signed-email.ts b/packages/lib/server-only/document/send-recipient-signed-email.ts new file mode 100644 index 000000000..5deed2343 --- /dev/null +++ b/packages/lib/server-only/document/send-recipient-signed-email.ts @@ -0,0 +1,36 @@ +import { createElement } from 'react'; + +import { mailer } from '@documenso/email/mailer'; +import { render } from '@documenso/email/render'; +import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending'; +import { Document, Recipient } from '@documenso/prisma/client'; + +export interface SendPendingEmailOptions { + document: Document; + recipient: Recipient; +} + +export const sendPendingEmail = async ({ document, recipient }: SendPendingEmailOptions) => { + const { email, name } = recipient; + + const assetBaseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; + + const template = createElement(DocumentPendingEmailTemplate, { + documentName: document.title, + assetBaseUrl, + }); + + await mailer.sendMail({ + to: { + address: email, + name, + }, + from: { + name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', + address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', + }, + subject: 'You are done signing.', + html: render(template), + text: render(template, { plainText: true }), + }); +}; From da2033692c157f12f57832bb26287f8e49038569 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Thu, 7 Sep 2023 20:14:04 +0000 Subject: [PATCH 14/34] feat: send email when all recipients have signed --- .../document/complete-document-with-token.ts | 1 + .../lib/server-only/document/seal-document.ts | 3 + .../document/send-completed-email.ts | 57 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 packages/lib/server-only/document/send-completed-email.ts diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index 4d4bacabf..77ab33d04 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -70,6 +70,7 @@ export const completeDocumentWithToken = async ({ }, }); + // TODO: Send email to documents with two or more recipients await sendPendingEmail({ document, recipient }); const documents = await prisma.document.updateMany({ diff --git a/packages/lib/server-only/document/seal-document.ts b/packages/lib/server-only/document/seal-document.ts index 1a74cfaac..f0806919f 100644 --- a/packages/lib/server-only/document/seal-document.ts +++ b/packages/lib/server-only/document/seal-document.ts @@ -6,6 +6,7 @@ import { prisma } from '@documenso/prisma'; import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import { insertFieldInPDF } from '../pdf/insert-field-in-pdf'; +import { sendCompletedEmail } from './send-completed-email'; export type SealDocumentOptions = { documentId: number; @@ -67,4 +68,6 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => { document: Buffer.from(pdfBytes).toString('base64'), }, }); + + await sendCompletedEmail({ documentId }); }; diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts new file mode 100644 index 000000000..b8d50dba4 --- /dev/null +++ b/packages/lib/server-only/document/send-completed-email.ts @@ -0,0 +1,57 @@ +import { createElement } from 'react'; + +import { mailer } from '@documenso/email/mailer'; +import { render } from '@documenso/email/render'; +import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed'; +import { prisma } from '@documenso/prisma'; + +export interface SendDocumentOptions { + documentId: number; +} + +export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => { + const document = await prisma.document.findUnique({ + where: { + id: documentId, + }, + include: { + Recipient: true, + }, + }); + + if (!document) { + throw new Error('Document not found'); + } + + if (document.Recipient.length === 0) { + throw new Error('Document has no recipients'); + } + + await Promise.all([ + document.Recipient.map(async (recipient) => { + const { email, name } = recipient; + + const assetBaseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; + + const template = createElement(DocumentCompletedEmailTemplate, { + documentName: document.title, + assetBaseUrl, + downloadLink: 'https://documenso.com', + }); + + await mailer.sendMail({ + to: { + address: email, + name, + }, + from: { + name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', + address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', + }, + subject: 'Everyone has signed!', + html: render(template), + text: render(template, { plainText: true }), + }); + }), + ]); +}; From 863e53a2d5cc8077302ac7a37789f69e7163de92 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Thu, 7 Sep 2023 20:38:18 +0000 Subject: [PATCH 15/34] refactor: pass document id as arguments --- .../document/complete-document-with-token.ts | 2 +- .../document/send-recipient-signed-email.ts | 36 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index 77ab33d04..ea440beb9 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -71,7 +71,7 @@ export const completeDocumentWithToken = async ({ }); // TODO: Send email to documents with two or more recipients - await sendPendingEmail({ document, recipient }); + await sendPendingEmail({ documentId, recipientId: recipient.id }); const documents = await prisma.document.updateMany({ where: { diff --git a/packages/lib/server-only/document/send-recipient-signed-email.ts b/packages/lib/server-only/document/send-recipient-signed-email.ts index 5deed2343..ece75caec 100644 --- a/packages/lib/server-only/document/send-recipient-signed-email.ts +++ b/packages/lib/server-only/document/send-recipient-signed-email.ts @@ -3,14 +3,42 @@ import { createElement } from 'react'; import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending'; -import { Document, Recipient } from '@documenso/prisma/client'; +import { prisma } from '@documenso/prisma'; export interface SendPendingEmailOptions { - document: Document; - recipient: Recipient; + documentId: number; + recipientId: number; } -export const sendPendingEmail = async ({ document, recipient }: SendPendingEmailOptions) => { +export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => { + const document = await prisma.document.findFirst({ + where: { + id: documentId, + Recipient: { + some: { + id: recipientId, + }, + }, + }, + include: { + Recipient: { + where: { + id: recipientId, + }, + }, + }, + }); + + if (!document) { + throw new Error('Document not found'); + } + + if (document.Recipient.length === 0) { + throw new Error('Document has no recipients'); + } + + const [recipient] = document.Recipient; + const { email, name } = recipient; const assetBaseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; From 525ff215634c02fb9f456568795c2ed917266515 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Thu, 7 Sep 2023 20:52:18 +0000 Subject: [PATCH 16/34] feat: avoid sending pending email to document with 1 recipients --- .../document/complete-document-with-token.ts | 11 +++++++++-- .../lib/server-only/document/send-completed-email.ts | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index ea440beb9..464b7fe4f 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -70,8 +70,15 @@ export const completeDocumentWithToken = async ({ }, }); - // TODO: Send email to documents with two or more recipients - await sendPendingEmail({ documentId, recipientId: recipient.id }); + const numberOfRecipients = await prisma.recipient.count({ + where: { + documentId: document.id, + }, + }); + + if (numberOfRecipients > 1) { + await sendPendingEmail({ documentId, recipientId: recipient.id }); + } const documents = await prisma.document.updateMany({ where: { diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts index b8d50dba4..0a1817964 100644 --- a/packages/lib/server-only/document/send-completed-email.ts +++ b/packages/lib/server-only/document/send-completed-email.ts @@ -48,7 +48,7 @@ export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', }, - subject: 'Everyone has signed!', + subject: 'Signing Complete!', html: render(template), text: render(template, { plainText: true }), }); From 73b0dc315e77a750df232cdf4364495eeb45f071 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Sat, 9 Sep 2023 03:31:17 +0000 Subject: [PATCH 17/34] fix: use ts-pattern --- .../app/(dashboard)/documents/empty-state.tsx | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx index 680f67bf5..a7d25a208 100644 --- a/apps/web/src/app/(dashboard)/documents/empty-state.tsx +++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx @@ -1,41 +1,40 @@ import { ArrowRight, CheckCircle2 } from 'lucide-react'; +import { match } from 'ts-pattern'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; export type EmptyDocumentProps = { status: ExtendedDocumentStatus }; export default function EmptyDocumentState({ status }: EmptyDocumentProps) { - let headerText = 'All done'; - let bodyText = 'All documents signed for now.'; - let extraText = ''; - let showArrow = false; - - switch (status) { - case 'COMPLETED': - headerText = 'Nothing here'; - bodyText = 'There are no signed documents yet.'; - extraText = 'Start by adding a document'; - showArrow = true; - break; - case 'DRAFT': - headerText = 'Nothing here'; - bodyText = 'There are no drafts yet.'; - extraText = 'Start by adding a document'; - showArrow = true; - break; - case 'ALL': - headerText = 'Nothing here'; - bodyText = 'There are no documents yet.'; - extraText = 'Start by adding a document'; - showArrow = true; - break; - default: - break; - } + const { headerText, bodyText, extraText, showArrow } = match(status) + .with(ExtendedDocumentStatus.COMPLETED, () => ({ + headerText: 'Nothing here', + bodyText: 'There are no signed documents yet.', + extraText: 'Start by adding a document', + showArrow: true, + })) + .with(ExtendedDocumentStatus.DRAFT, () => ({ + headerText: 'Nothing here', + bodyText: 'There are no drafts yet.', + extraText: 'Start by adding a document', + showArrow: true, + })) + .with(ExtendedDocumentStatus.ALL, () => ({ + headerText: 'Nothing here', + bodyText: 'There are no documents yet.', + extraText: 'Start by adding a document', + showArrow: true, + })) + .otherwise(() => ({ + headerText: 'All done', + bodyText: 'All documents signed for now.', + extraText: '', + showArrow: false, + })); return (
- +

{headerText}

{bodyText}

From 2bad1b9f558430fd337d443e6229c14a0b906104 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Sat, 9 Sep 2023 03:45:15 +0000 Subject: [PATCH 18/34] fix: tidy messaging --- .../app/(dashboard)/documents/empty-state.tsx | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx index a7d25a208..181f31fa5 100644 --- a/apps/web/src/app/(dashboard)/documents/empty-state.tsx +++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx @@ -1,4 +1,4 @@ -import { ArrowRight, CheckCircle2 } from 'lucide-react'; +import { ArrowRight, Bird, CheckCircle2 } from 'lucide-react'; import { match } from 'ts-pattern'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; @@ -6,43 +6,44 @@ import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-documen export type EmptyDocumentProps = { status: ExtendedDocumentStatus }; export default function EmptyDocumentState({ status }: EmptyDocumentProps) { - const { headerText, bodyText, extraText, showArrow } = match(status) + const { + title, + message, + icon: Icon, + } = match(status) .with(ExtendedDocumentStatus.COMPLETED, () => ({ - headerText: 'Nothing here', - bodyText: 'There are no signed documents yet.', - extraText: 'Start by adding a document', - showArrow: true, + title: 'Nothing to do', + message: + 'There are no completed documents yet. Documents that you have created or received that become completed will appear here later.', + icon: CheckCircle2, })) .with(ExtendedDocumentStatus.DRAFT, () => ({ - headerText: 'Nothing here', - bodyText: 'There are no drafts yet.', - extraText: 'Start by adding a document', - showArrow: true, + title: 'No active drafts', + message: + 'There are no active drafts at then current moment. You can upload a document to start drafting.', + icon: CheckCircle2, })) .with(ExtendedDocumentStatus.ALL, () => ({ - headerText: 'Nothing here', - bodyText: 'There are no documents yet.', - extraText: 'Start by adding a document', - showArrow: true, + title: "We're all empty", + message: + 'You have not yet created or received any documents. To create a document please upload one.', + icon: Bird, })) .otherwise(() => ({ - headerText: 'All done', - bodyText: 'All documents signed for now.', - extraText: '', - showArrow: false, + title: 'Nothing to do', + message: + 'All documents are currently actioned. Any new documents are sent or recieved they will start to appear here.', + icon: CheckCircle2, })); return ( -
- +
+ +
-

{headerText}

-

{bodyText}

- {extraText && ( -

- {extraText} {showArrow && } -

- )} +

{title}

+ +

{message}

); From 6f4c2805833f9ad90051b88796cf0591f8f0f7cc Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Sun, 17 Sep 2023 14:31:44 +0000 Subject: [PATCH 19/34] chore: match file name and method name --- .../lib/server-only/document/complete-document-with-token.ts | 2 +- .../{send-recipient-signed-email.ts => send-pending-email.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/lib/server-only/document/{send-recipient-signed-email.ts => send-pending-email.ts} (100%) diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index 464b7fe4f..e442f3061 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -4,7 +4,7 @@ import { prisma } from '@documenso/prisma'; import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import { sealDocument } from './seal-document'; -import { sendPendingEmail } from './send-recipient-signed-email'; +import { sendPendingEmail } from './send-pending-email'; export type CompleteDocumentWithTokenOptions = { token: string; diff --git a/packages/lib/server-only/document/send-recipient-signed-email.ts b/packages/lib/server-only/document/send-pending-email.ts similarity index 100% rename from packages/lib/server-only/document/send-recipient-signed-email.ts rename to packages/lib/server-only/document/send-pending-email.ts From 776324c8750a409f366363fb27c1c60f65da30c3 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Sun, 17 Sep 2023 14:38:39 +0000 Subject: [PATCH 20/34] fix: fitler only unsigned documents --- .../lib/server-only/document/complete-document-with-token.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index e442f3061..8b1b4d576 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -73,6 +73,9 @@ export const completeDocumentWithToken = async ({ const numberOfRecipients = await prisma.recipient.count({ where: { documentId: document.id, + signingStatus: { + not: SigningStatus.SIGNED, + }, }, }); From a55197fb2d27756ef6a0deadd4cfaafd27b92a4d Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 08:57:50 +0000 Subject: [PATCH 21/34] feat: add prisma schema for document meta --- .../20230920052232_document_meta/migration.sql | 14 ++++++++++++++ packages/prisma/schema.prisma | 10 ++++++++++ 2 files changed, 24 insertions(+) create mode 100644 packages/prisma/migrations/20230920052232_document_meta/migration.sql diff --git a/packages/prisma/migrations/20230920052232_document_meta/migration.sql b/packages/prisma/migrations/20230920052232_document_meta/migration.sql new file mode 100644 index 000000000..00e9db735 --- /dev/null +++ b/packages/prisma/migrations/20230920052232_document_meta/migration.sql @@ -0,0 +1,14 @@ +-- AlterTable +ALTER TABLE "Document" ADD COLUMN "documentMetaId" TEXT; + +-- CreateTable +CREATE TABLE "DocumentMeta" ( + "id" TEXT NOT NULL, + "customEmailSubject" TEXT, + "customEmailBody" TEXT, + + CONSTRAINT "DocumentMeta_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Document" ADD CONSTRAINT "Document_documentMetaId_fkey" FOREIGN KEY ("documentMetaId") REFERENCES "DocumentMeta"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 96b7db0a3..675ac54d7 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -113,6 +113,9 @@ model Document { createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + documentMetaId String? + DocumentMeta DocumentMeta? @relation(fields: [documentMetaId], references: [id], onDelete: Cascade) + @@unique([documentDataId]) } @@ -130,6 +133,13 @@ model DocumentData { Document Document? } +model DocumentMeta { + id String @id @default(cuid()) + customEmailSubject String? + customEmailBody String? + Document Document[] +} + enum ReadStatus { NOT_OPENED OPENED From 6860726e83b4202c0f38042985f70d75ccbeb872 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 09:06:28 +0000 Subject: [PATCH 22/34] feat: send custom email subjects --- .../forms/edit-document/add-subject.action.ts | 3 ++- packages/lib/server-only/document/send-document.tsx | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/forms/edit-document/add-subject.action.ts b/apps/web/src/components/forms/edit-document/add-subject.action.ts index b6ff1c320..7dddbe222 100644 --- a/apps/web/src/components/forms/edit-document/add-subject.action.ts +++ b/apps/web/src/components/forms/edit-document/add-subject.action.ts @@ -8,7 +8,7 @@ export type CompleteDocumentActionInput = TAddSubjectFormSchema & { documentId: number; }; -export const completeDocument = async ({ documentId }: CompleteDocumentActionInput) => { +export const completeDocument = async ({ documentId, email }: CompleteDocumentActionInput) => { 'use server'; const { id: userId } = await getRequiredServerComponentSession(); @@ -16,5 +16,6 @@ export const completeDocument = async ({ documentId }: CompleteDocumentActionInp await sendDocument({ userId, documentId, + email, }); }; diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index 83a88f24a..980868a48 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -5,13 +5,16 @@ import { render } from '@documenso/email/render'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; import { prisma } from '@documenso/prisma'; import { DocumentStatus, SendStatus } from '@documenso/prisma/client'; +import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types'; -export interface SendDocumentOptions { +export type SendDocumentOptions = TAddSubjectFormSchema & { documentId: number; userId: number; -} +}; + +export const sendDocument = async ({ documentId, userId, email }: SendDocumentOptions) => { + const customEmail = email; -export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => { const user = await prisma.user.findFirstOrThrow({ where: { id: userId, @@ -68,7 +71,7 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', }, - subject: 'Please sign this document', + subject: customEmail.subject ? customEmail.subject : 'Please sign this document', html: render(template), text: render(template, { plainText: true }), }); From d5238939ad255355a988c896016c729d43c60a13 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 09:51:04 +0000 Subject: [PATCH 23/34] feat: persist document metadata in database for a specific user --- .../forms/edit-document/add-subject.action.ts | 21 ++++++++++- .../document-meta/create-document-meta.ts | 36 +++++++++++++++++++ .../server-only/document/send-document.tsx | 14 ++++---- .../server-only/document/update-document.ts | 21 +++++++++++ 4 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 packages/lib/server-only/document-meta/create-document-meta.ts create mode 100644 packages/lib/server-only/document/update-document.ts diff --git a/apps/web/src/components/forms/edit-document/add-subject.action.ts b/apps/web/src/components/forms/edit-document/add-subject.action.ts index 7dddbe222..c3b7845f8 100644 --- a/apps/web/src/components/forms/edit-document/add-subject.action.ts +++ b/apps/web/src/components/forms/edit-document/add-subject.action.ts @@ -1,7 +1,9 @@ 'use server'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; +import { createDocumentMeta } from '@documenso/lib/server-only/document-meta/create-document-meta'; import { sendDocument } from '@documenso/lib/server-only/document/send-document'; +import { updateDocument } from '@documenso/lib/server-only/document/update-document'; import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types'; export type CompleteDocumentActionInput = TAddSubjectFormSchema & { @@ -13,9 +15,26 @@ export const completeDocument = async ({ documentId, email }: CompleteDocumentAc const { id: userId } = await getRequiredServerComponentSession(); + const createDocumentMetaResponse = await createDocumentMeta({ + emailBody: email.message, + emailSubject: email.subject, + }); + + if (createDocumentMetaResponse) { + await updateDocument({ + documentId, + data: { + DocumentMeta: { + connect: { + id: createDocumentMetaResponse.id, + }, + }, + }, + }); + } + await sendDocument({ userId, documentId, - email, }); }; diff --git a/packages/lib/server-only/document-meta/create-document-meta.ts b/packages/lib/server-only/document-meta/create-document-meta.ts new file mode 100644 index 000000000..e77db9a87 --- /dev/null +++ b/packages/lib/server-only/document-meta/create-document-meta.ts @@ -0,0 +1,36 @@ +'use server'; + +import { prisma } from '@documenso/prisma'; + +export type CreateDocumentMetaOptions = { + emailSubject: string; + emailBody: string; +}; + +export const createDocumentMeta = async ({ + emailBody, + emailSubject, +}: CreateDocumentMetaOptions) => { + const documentMeta = await prisma.documentMeta.findFirst(); + + if (!documentMeta) { + return await prisma.documentMeta.create({ + data: { + customEmailBody: emailBody, + customEmailSubject: emailSubject, + }, + }); + } + + if (emailBody && emailSubject) { + return await prisma.documentMeta.update({ + where: { + id: documentMeta.id, + }, + data: { + customEmailBody: emailBody, + customEmailSubject: emailSubject, + }, + }); + } +}; diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index 980868a48..3bedc6ee4 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -5,16 +5,13 @@ import { render } from '@documenso/email/render'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; import { prisma } from '@documenso/prisma'; import { DocumentStatus, SendStatus } from '@documenso/prisma/client'; -import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types'; -export type SendDocumentOptions = TAddSubjectFormSchema & { +export type SendDocumentOptions = { documentId: number; userId: number; }; -export const sendDocument = async ({ documentId, userId, email }: SendDocumentOptions) => { - const customEmail = email; - +export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => { const user = await prisma.user.findFirstOrThrow({ where: { id: userId, @@ -28,9 +25,12 @@ export const sendDocument = async ({ documentId, userId, email }: SendDocumentOp }, include: { Recipient: true, + DocumentMeta: true, }, }); + const customEmail = document?.DocumentMeta; + if (!document) { throw new Error('Document not found'); } @@ -71,7 +71,9 @@ export const sendDocument = async ({ documentId, userId, email }: SendDocumentOp name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', }, - subject: customEmail.subject ? customEmail.subject : 'Please sign this document', + subject: customEmail?.customEmailSubject + ? customEmail.customEmailSubject + : 'Please sign this document', html: render(template), text: render(template, { plainText: true }), }); diff --git a/packages/lib/server-only/document/update-document.ts b/packages/lib/server-only/document/update-document.ts new file mode 100644 index 000000000..7793c990a --- /dev/null +++ b/packages/lib/server-only/document/update-document.ts @@ -0,0 +1,21 @@ +'use server'; + +import { Prisma } from '@prisma/client'; + +import { prisma } from '@documenso/prisma'; + +export type UpdateDocumentOptions = { + documentId: number; + data: Prisma.DocumentUpdateInput; +}; + +export const updateDocument = async ({ documentId, data }: UpdateDocumentOptions) => { + return await prisma.document.update({ + where: { + id: documentId, + }, + data: { + ...data, + }, + }); +}; From 677a15327bcf47ad679a749b7004f70b353a04d7 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 09:59:42 +0000 Subject: [PATCH 24/34] feat: send custom email message --- packages/email/templates/document-invite.tsx | 9 +++++++-- packages/lib/server-only/document/send-document.tsx | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/email/templates/document-invite.tsx b/packages/email/templates/document-invite.tsx index 465685649..e0d521c28 100644 --- a/packages/email/templates/document-invite.tsx +++ b/packages/email/templates/document-invite.tsx @@ -20,7 +20,9 @@ import { } from '../template-components/template-document-invite'; import TemplateFooter from '../template-components/template-footer'; -export type DocumentInviteEmailTemplateProps = Partial; +export type DocumentInviteEmailTemplateProps = Partial & { + customBody?: string; +}; export const DocumentInviteEmailTemplate = ({ inviterName = 'Lucas Smith', @@ -28,6 +30,7 @@ export const DocumentInviteEmailTemplate = ({ documentName = 'Open Source Pledge.pdf', signDocumentLink = 'https://documenso.com', assetBaseUrl = 'http://localhost:3002', + customBody, }: DocumentInviteEmailTemplateProps) => { const previewText = `Completed Document`; @@ -78,7 +81,9 @@ export const DocumentInviteEmailTemplate = ({ - {inviterName} has invited you to sign the document "{documentName}". + {customBody + ? customBody + : `${inviterName} has invited you to sign the document "${documentName}".`} diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index 3bedc6ee4..10e75b7ca 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -60,6 +60,7 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) inviterEmail: user.email, assetBaseUrl, signDocumentLink, + customBody: customEmail?.customEmailBody || '', }); await mailer.sendMail({ From d41725591086a7b0d586147c0301204feab1c3fc Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 11:17:26 +0000 Subject: [PATCH 25/34] feat: replace template variables with values Co-authored-by: Mythie --- .../server-only/document/send-document.tsx | 14 +++++++++++-- .../lib/utils/render-custom-email-template.ts | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 packages/lib/utils/render-custom-email-template.ts diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index 10e75b7ca..b3eda2077 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -3,6 +3,7 @@ import { createElement } from 'react'; import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; +import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template'; import { prisma } from '@documenso/prisma'; import { DocumentStatus, SendStatus } from '@documenso/prisma/client'; @@ -47,6 +48,12 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) document.Recipient.map(async (recipient) => { const { email, name } = recipient; + const customEmailTemplate = { + 'signer.name': name, + 'signer.email': email, + 'document.name': document.title, + }; + if (recipient.sendStatus === SendStatus.SENT) { return; } @@ -60,7 +67,10 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) inviterEmail: user.email, assetBaseUrl, signDocumentLink, - customBody: customEmail?.customEmailBody || '', + customBody: renderCustomEmailTemplate( + customEmail?.customEmailBody || '', + customEmailTemplate, + ), }); await mailer.sendMail({ @@ -73,7 +83,7 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', }, subject: customEmail?.customEmailSubject - ? customEmail.customEmailSubject + ? renderCustomEmailTemplate(customEmail.customEmailSubject, customEmailTemplate) : 'Please sign this document', html: render(template), text: render(template, { plainText: true }), diff --git a/packages/lib/utils/render-custom-email-template.ts b/packages/lib/utils/render-custom-email-template.ts new file mode 100644 index 000000000..304298c86 --- /dev/null +++ b/packages/lib/utils/render-custom-email-template.ts @@ -0,0 +1,20 @@ +export const renderCustomEmailTemplate = >( + template: string, + variables: T, +): string => { + let t = template; + + Object.entries(variables).forEach((entry) => { + const [key, value] = entry; + + const placeholder = `{${key}}`; + + const re = new RegExp(placeholder, 'g'); + + if (Object.prototype.hasOwnProperty.call(variables, key)) { + t = t.replace(re, String(value)); + } + }); + + return t; +}; From c9c111cdf27c5e80e8002f95475a10863d826148 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 12:19:52 +0000 Subject: [PATCH 26/34] fix: persist newline in emails --- packages/email/templates/document-invite.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/email/templates/document-invite.tsx b/packages/email/templates/document-invite.tsx index e0d521c28..661a2fd5f 100644 --- a/packages/email/templates/document-invite.tsx +++ b/packages/email/templates/document-invite.tsx @@ -81,9 +81,11 @@ export const DocumentInviteEmailTemplate = ({ - {customBody - ? customBody - : `${inviterName} has invited you to sign the document "${documentName}".`} + {customBody ? ( +
{customBody}
+ ) : ( + `${inviterName} has invited you to sign the document "${documentName}".` + )}
From f2d3c516517c6f5c69bc5db8d49b5e48a47fd1e6 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 12:38:39 +0000 Subject: [PATCH 27/34] fix: avoid creating document meta with empty strings --- .../forms/edit-document/add-subject.action.ts | 25 +++++++++------ .../document-meta/create-document-meta.ts | 31 +++++++++---------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/apps/web/src/components/forms/edit-document/add-subject.action.ts b/apps/web/src/components/forms/edit-document/add-subject.action.ts index c3b7845f8..c0175b751 100644 --- a/apps/web/src/components/forms/edit-document/add-subject.action.ts +++ b/apps/web/src/components/forms/edit-document/add-subject.action.ts @@ -15,23 +15,28 @@ export const completeDocument = async ({ documentId, email }: CompleteDocumentAc const { id: userId } = await getRequiredServerComponentSession(); + if (!email.message && !email.subject) { + return await sendDocument({ + userId, + documentId, + }); + } + const createDocumentMetaResponse = await createDocumentMeta({ emailBody: email.message, emailSubject: email.subject, }); - if (createDocumentMetaResponse) { - await updateDocument({ - documentId, - data: { - DocumentMeta: { - connect: { - id: createDocumentMetaResponse.id, - }, + await updateDocument({ + documentId, + data: { + DocumentMeta: { + connect: { + id: createDocumentMetaResponse.id, }, }, - }); - } + }, + }); await sendDocument({ userId, diff --git a/packages/lib/server-only/document-meta/create-document-meta.ts b/packages/lib/server-only/document-meta/create-document-meta.ts index e77db9a87..68fa8ed06 100644 --- a/packages/lib/server-only/document-meta/create-document-meta.ts +++ b/packages/lib/server-only/document-meta/create-document-meta.ts @@ -11,26 +11,23 @@ export const createDocumentMeta = async ({ emailBody, emailSubject, }: CreateDocumentMetaOptions) => { - const documentMeta = await prisma.documentMeta.findFirst(); + const emailData = { + customEmailBody: emailBody, + customEmailSubject: emailSubject, + }; - if (!documentMeta) { - return await prisma.documentMeta.create({ - data: { - customEmailBody: emailBody, - customEmailSubject: emailSubject, - }, - }); - } + const existingDocumentMeta = await prisma.documentMeta.findFirst({ + where: emailData, + }); - if (emailBody && emailSubject) { + if (existingDocumentMeta) { return await prisma.documentMeta.update({ - where: { - id: documentMeta.id, - }, - data: { - customEmailBody: emailBody, - customEmailSubject: emailSubject, - }, + where: { id: existingDocumentMeta.id }, + data: emailData, + }); + } else { + return await prisma.documentMeta.create({ + data: emailData, }); } }; From e330e906889d00bc939db66c97ec0644ecbc2a08 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Sep 2023 12:54:24 +0000 Subject: [PATCH 28/34] fix: document meta relation with document --- .../migration.sql | 8 ++++++++ packages/prisma/schema.prisma | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 packages/prisma/migrations/20230920124941_fix_documentmeta_relation/migration.sql diff --git a/packages/prisma/migrations/20230920124941_fix_documentmeta_relation/migration.sql b/packages/prisma/migrations/20230920124941_fix_documentmeta_relation/migration.sql new file mode 100644 index 000000000..69b4591c5 --- /dev/null +++ b/packages/prisma/migrations/20230920124941_fix_documentmeta_relation/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[documentMetaId]` on the table `Document` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Document_documentMetaId_key" ON "Document"("documentMetaId"); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 675ac54d7..23fa8d2c7 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -117,6 +117,7 @@ model Document { DocumentMeta DocumentMeta? @relation(fields: [documentMetaId], references: [id], onDelete: Cascade) @@unique([documentDataId]) + @@unique([documentMetaId]) } enum DocumentDataType { @@ -134,10 +135,10 @@ model DocumentData { } model DocumentMeta { - id String @id @default(cuid()) + id String @id @default(cuid()) customEmailSubject String? customEmailBody String? - Document Document[] + Document Document? } enum ReadStatus { From 181af24b78e8ecd940a3fe1ea391b6d9070ce2f7 Mon Sep 17 00:00:00 2001 From: nsylke Date: Wed, 20 Sep 2023 20:46:23 -0500 Subject: [PATCH 29/34] fix: remove disallow property of _next from robots --- apps/marketing/src/app/robots.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/marketing/src/app/robots.ts b/apps/marketing/src/app/robots.ts index de74ea314..cc718ff25 100644 --- a/apps/marketing/src/app/robots.ts +++ b/apps/marketing/src/app/robots.ts @@ -4,11 +4,11 @@ import { getBaseUrl } from '@documenso/lib/universal/get-base-url'; export default function robots(): MetadataRoute.Robots { return { - rules: { - userAgent: '*', - allow: '/*', - disallow: ['/_next/*'], - }, + rules: [ + { + userAgent: '*', + }, + ], sitemap: `${getBaseUrl()}/sitemap.xml`, }; } From f4c77995374d7f2960e6c46a49a061801cf17df8 Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 22 Sep 2023 12:27:54 +0000 Subject: [PATCH 30/34] fix: reverse meta relation and tidy code --- .../forms/edit-document/add-subject.action.ts | 28 +++------- .../template-document-completed.tsx | 22 ++++++-- .../template-document-invite.tsx | 25 ++++++--- .../template-document-pending.tsx | 22 ++++++-- .../document-meta/create-document-meta.ts | 33 ------------ .../document-meta/upsert-document-meta.ts | 30 +++++++++++ .../document/get-document-by-id.ts | 1 + .../server-only/document/send-document.tsx | 13 ++--- .../lib/utils/render-custom-email-template.ts | 18 ++----- .../migration.sql | 52 +++++++++++++++++++ packages/prisma/schema.prisma | 14 +++-- packages/prisma/types/document-with-data.ts | 3 +- .../primitives/document-flow/add-subject.tsx | 9 ++-- 13 files changed, 165 insertions(+), 105 deletions(-) delete mode 100644 packages/lib/server-only/document-meta/create-document-meta.ts create mode 100644 packages/lib/server-only/document-meta/upsert-document-meta.ts create mode 100644 packages/prisma/migrations/20230922121421_fix_document_meta_schema/migration.sql diff --git a/apps/web/src/components/forms/edit-document/add-subject.action.ts b/apps/web/src/components/forms/edit-document/add-subject.action.ts index c0175b751..14ddef867 100644 --- a/apps/web/src/components/forms/edit-document/add-subject.action.ts +++ b/apps/web/src/components/forms/edit-document/add-subject.action.ts @@ -1,9 +1,8 @@ 'use server'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; -import { createDocumentMeta } from '@documenso/lib/server-only/document-meta/create-document-meta'; +import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta'; import { sendDocument } from '@documenso/lib/server-only/document/send-document'; -import { updateDocument } from '@documenso/lib/server-only/document/update-document'; import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types'; export type CompleteDocumentActionInput = TAddSubjectFormSchema & { @@ -15,30 +14,15 @@ export const completeDocument = async ({ documentId, email }: CompleteDocumentAc const { id: userId } = await getRequiredServerComponentSession(); - if (!email.message && !email.subject) { - return await sendDocument({ - userId, + if (email.message || email.subject) { + await upsertDocumentMeta({ documentId, + subject: email.subject, + message: email.message, }); } - const createDocumentMetaResponse = await createDocumentMeta({ - emailBody: email.message, - emailSubject: email.subject, - }); - - await updateDocument({ - documentId, - data: { - DocumentMeta: { - connect: { - id: createDocumentMetaResponse.id, - }, - }, - }, - }); - - await sendDocument({ + return await sendDocument({ userId, documentId, }); diff --git a/packages/email/template-components/template-document-completed.tsx b/packages/email/template-components/template-document-completed.tsx index b64b13cff..91d8fa29d 100644 --- a/packages/email/template-components/template-document-completed.tsx +++ b/packages/email/template-components/template-document-completed.tsx @@ -1,4 +1,4 @@ -import { Button, Img, Section, Tailwind, Text } from '@react-email/components'; +import { Button, Column, Img, Row, Section, Tailwind, Text } from '@react-email/components'; import * as config from '@documenso/tailwind-config'; @@ -29,11 +29,23 @@ export const TemplateDocumentCompleted = ({ }, }} > -
-
- Documenso -
+
+ + + + Documenso + + + + +
+ +
Completed diff --git a/packages/email/template-components/template-document-invite.tsx b/packages/email/template-components/template-document-invite.tsx index bf2fb905e..fcfba406d 100644 --- a/packages/email/template-components/template-document-invite.tsx +++ b/packages/email/template-components/template-document-invite.tsx @@ -1,4 +1,4 @@ -import { Button, Img, Section, Tailwind, Text } from '@react-email/components'; +import { Button, Column, Img, Row, Section, Tailwind, Text } from '@react-email/components'; import * as config from '@documenso/tailwind-config'; @@ -30,13 +30,26 @@ export const TemplateDocumentInvite = ({ }, }} > -
-
- Documenso -
+
+ + + + Documenso + + + + +
+ +
- {inviterName} has invited you to sign "{documentName}" + {inviterName} has invited you to sign +
"{documentName}"
diff --git a/packages/email/template-components/template-document-pending.tsx b/packages/email/template-components/template-document-pending.tsx index 80387b783..f9fc8648a 100644 --- a/packages/email/template-components/template-document-pending.tsx +++ b/packages/email/template-components/template-document-pending.tsx @@ -1,4 +1,4 @@ -import { Img, Section, Tailwind, Text } from '@react-email/components'; +import { Column, Img, Row, Section, Tailwind, Text } from '@react-email/components'; import * as config from '@documenso/tailwind-config'; @@ -25,11 +25,23 @@ export const TemplateDocumentPending = ({ }, }} > -
-
- Documenso -
+
+ + + + Documenso + + + + +
+ +
Waiting for others diff --git a/packages/lib/server-only/document-meta/create-document-meta.ts b/packages/lib/server-only/document-meta/create-document-meta.ts deleted file mode 100644 index 68fa8ed06..000000000 --- a/packages/lib/server-only/document-meta/create-document-meta.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use server'; - -import { prisma } from '@documenso/prisma'; - -export type CreateDocumentMetaOptions = { - emailSubject: string; - emailBody: string; -}; - -export const createDocumentMeta = async ({ - emailBody, - emailSubject, -}: CreateDocumentMetaOptions) => { - const emailData = { - customEmailBody: emailBody, - customEmailSubject: emailSubject, - }; - - const existingDocumentMeta = await prisma.documentMeta.findFirst({ - where: emailData, - }); - - if (existingDocumentMeta) { - return await prisma.documentMeta.update({ - where: { id: existingDocumentMeta.id }, - data: emailData, - }); - } else { - return await prisma.documentMeta.create({ - data: emailData, - }); - } -}; diff --git a/packages/lib/server-only/document-meta/upsert-document-meta.ts b/packages/lib/server-only/document-meta/upsert-document-meta.ts new file mode 100644 index 000000000..e3cce2ea2 --- /dev/null +++ b/packages/lib/server-only/document-meta/upsert-document-meta.ts @@ -0,0 +1,30 @@ +'use server'; + +import { prisma } from '@documenso/prisma'; + +export type CreateDocumentMetaOptions = { + documentId: number; + subject: string; + message: string; +}; + +export const upsertDocumentMeta = async ({ + subject, + message, + documentId, +}: CreateDocumentMetaOptions) => { + return await prisma.documentMeta.upsert({ + where: { + documentId, + }, + create: { + subject, + message, + documentId, + }, + update: { + subject, + message, + }, + }); +}; diff --git a/packages/lib/server-only/document/get-document-by-id.ts b/packages/lib/server-only/document/get-document-by-id.ts index 0fce1af4d..0b599a71c 100644 --- a/packages/lib/server-only/document/get-document-by-id.ts +++ b/packages/lib/server-only/document/get-document-by-id.ts @@ -13,6 +13,7 @@ export const getDocumentById = async ({ id, userId }: GetDocumentByIdOptions) => }, include: { documentData: true, + documentMeta: true, }, }); }; diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index b3eda2077..fcc0f829c 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -26,11 +26,11 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) }, include: { Recipient: true, - DocumentMeta: true, + documentMeta: true, }, }); - const customEmail = document?.DocumentMeta; + const customEmail = document?.documentMeta; if (!document) { throw new Error('Document not found'); @@ -67,10 +67,7 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) inviterEmail: user.email, assetBaseUrl, signDocumentLink, - customBody: renderCustomEmailTemplate( - customEmail?.customEmailBody || '', - customEmailTemplate, - ), + customBody: renderCustomEmailTemplate(customEmail?.message || '', customEmailTemplate), }); await mailer.sendMail({ @@ -82,8 +79,8 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', }, - subject: customEmail?.customEmailSubject - ? renderCustomEmailTemplate(customEmail.customEmailSubject, customEmailTemplate) + subject: customEmail?.subject + ? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate) : 'Please sign this document', html: render(template), text: render(template, { plainText: true }), diff --git a/packages/lib/utils/render-custom-email-template.ts b/packages/lib/utils/render-custom-email-template.ts index 304298c86..e3fdf5c7b 100644 --- a/packages/lib/utils/render-custom-email-template.ts +++ b/packages/lib/utils/render-custom-email-template.ts @@ -2,19 +2,11 @@ export const renderCustomEmailTemplate = >( template: string, variables: T, ): string => { - let t = template; - - Object.entries(variables).forEach((entry) => { - const [key, value] = entry; - - const placeholder = `{${key}}`; - - const re = new RegExp(placeholder, 'g'); - - if (Object.prototype.hasOwnProperty.call(variables, key)) { - t = t.replace(re, String(value)); + return template.replace(/\{(\S+)\}/g, (_, key) => { + if (key in variables) { + return variables[key]; } - }); - return t; + return key; + }); }; diff --git a/packages/prisma/migrations/20230922121421_fix_document_meta_schema/migration.sql b/packages/prisma/migrations/20230922121421_fix_document_meta_schema/migration.sql new file mode 100644 index 000000000..42c20c112 --- /dev/null +++ b/packages/prisma/migrations/20230922121421_fix_document_meta_schema/migration.sql @@ -0,0 +1,52 @@ +/* + Warnings: + + - You are about to drop the column `documentMetaId` on the `Document` table. All the data in the column will be lost. + - You are about to drop the column `customEmailBody` on the `DocumentMeta` table. All the data in the column will be lost. + - You are about to drop the column `customEmailSubject` on the `DocumentMeta` table. All the data in the column will be lost. + - A unique constraint covering the columns `[documentId]` on the table `DocumentMeta` will be added. If there are existing duplicate values, this will fail. + - Added the required column `documentId` to the `DocumentMeta` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "Document" DROP CONSTRAINT "Document_documentMetaId_fkey"; + +-- DropIndex +DROP INDEX "Document_documentMetaId_key"; + +-- AlterTable +ALTER TABLE "DocumentMeta" +ADD COLUMN "documentId" INTEGER, +ADD COLUMN "message" TEXT, +ADD COLUMN "subject" TEXT; + +-- Migrate data +UPDATE "DocumentMeta" SET "documentId" = ( + SELECT "id" FROM "Document" WHERE "Document"."documentMetaId" = "DocumentMeta"."id" +); + +-- Migrate data +UPDATE "DocumentMeta" SET "message" = "customEmailBody"; + +-- Migrate data +UPDATE "DocumentMeta" SET "subject" = "customEmailSubject"; + +-- Prune data +DELETE FROM "DocumentMeta" WHERE "documentId" IS NULL; + +-- AlterTable +ALTER TABLE "Document" DROP COLUMN "documentMetaId"; + +-- AlterTable +ALTER TABLE "DocumentMeta" +DROP COLUMN "customEmailBody", +DROP COLUMN "customEmailSubject"; + +-- AlterColumn +ALTER TABLE "DocumentMeta" ALTER COLUMN "documentId" SET NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "DocumentMeta_documentId_key" ON "DocumentMeta"("documentId"); + +-- AddForeignKey +ALTER TABLE "DocumentMeta" ADD CONSTRAINT "DocumentMeta_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 23fa8d2c7..6576da8e2 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -110,14 +110,11 @@ model Document { Field Field[] documentDataId String documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade) + documentMeta DocumentMeta? createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt - documentMetaId String? - DocumentMeta DocumentMeta? @relation(fields: [documentMetaId], references: [id], onDelete: Cascade) - @@unique([documentDataId]) - @@unique([documentMetaId]) } enum DocumentDataType { @@ -135,10 +132,11 @@ model DocumentData { } model DocumentMeta { - id String @id @default(cuid()) - customEmailSubject String? - customEmailBody String? - Document Document? + id String @id @default(cuid()) + subject String? + message String? + documentId Int @unique + document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) } enum ReadStatus { diff --git a/packages/prisma/types/document-with-data.ts b/packages/prisma/types/document-with-data.ts index d52987552..d8dd8a888 100644 --- a/packages/prisma/types/document-with-data.ts +++ b/packages/prisma/types/document-with-data.ts @@ -1,5 +1,6 @@ -import { Document, DocumentData } from '@documenso/prisma/client'; +import { Document, DocumentData, DocumentMeta } from '@documenso/prisma/client'; export type DocumentWithData = Document & { documentData?: DocumentData | null; + documentMeta?: DocumentMeta | null; }; diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index 312a4eea2..1bf3b2cb4 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -2,7 +2,8 @@ import { useForm } from 'react-hook-form'; -import { Document, DocumentStatus, Field, Recipient } from '@documenso/prisma/client'; +import { DocumentStatus, Field, Recipient } from '@documenso/prisma/client'; +import { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; @@ -21,7 +22,7 @@ export type AddSubjectFormProps = { documentFlow: DocumentFlowStep; recipients: Recipient[]; fields: Field[]; - document: Document; + document: DocumentWithData; numberOfSteps: number; onSubmit: (_data: TAddSubjectFormSchema) => void; }; @@ -41,8 +42,8 @@ export const AddSubjectFormPartial = ({ } = useForm({ defaultValues: { email: { - subject: '', - message: '', + subject: document.documentMeta?.subject ?? '', + message: document.documentMeta?.message ?? '', }, }, }); From 946b2fe1290df6fd29c813436256591c5fc74e61 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 22 Sep 2023 23:15:30 +1000 Subject: [PATCH 31/34] fix: use named export --- apps/web/src/app/(dashboard)/documents/empty-state.tsx | 2 +- apps/web/src/app/(dashboard)/documents/page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx index 181f31fa5..e8ce9073b 100644 --- a/apps/web/src/app/(dashboard)/documents/empty-state.tsx +++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx @@ -5,7 +5,7 @@ import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-documen export type EmptyDocumentProps = { status: ExtendedDocumentStatus }; -export default function EmptyDocumentState({ status }: EmptyDocumentProps) { +export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => { const { title, message, diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx index 8e5a82708..9ae16e44b 100644 --- a/apps/web/src/app/(dashboard)/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/page.tsx @@ -12,7 +12,7 @@ import { PeriodSelectorValue } from '~/components/(dashboard)/period-selector/ty import { DocumentStatus } from '~/components/formatter/document-status'; import { DocumentsDataTable } from './data-table'; -import EmptyDocumentState from './empty-state'; +import { EmptyDocumentState } from './empty-state'; import { UploadDocument } from './upload-document'; export type DocumentsPageProps = { From 0d130b17c8eb1967cd1cb79a196080f37fecd2fc Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 22 Sep 2023 23:22:48 +1000 Subject: [PATCH 32/34] fix: minor updates --- .../server-only/document/complete-document-with-token.ts | 4 ++-- packages/lib/server-only/document/send-completed-email.ts | 6 +++--- packages/lib/server-only/document/send-pending-email.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index 8b1b4d576..2712c56fa 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -70,7 +70,7 @@ export const completeDocumentWithToken = async ({ }, }); - const numberOfRecipients = await prisma.recipient.count({ + const pendingRecipients = await prisma.recipient.count({ where: { documentId: document.id, signingStatus: { @@ -79,7 +79,7 @@ export const completeDocumentWithToken = async ({ }, }); - if (numberOfRecipients > 1) { + if (pendingRecipients > 0) { await sendPendingEmail({ documentId, recipientId: recipient.id }); } diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts index 0a1817964..9d0d2d499 100644 --- a/packages/lib/server-only/document/send-completed-email.ts +++ b/packages/lib/server-only/document/send-completed-email.ts @@ -29,14 +29,14 @@ export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => await Promise.all([ document.Recipient.map(async (recipient) => { - const { email, name } = recipient; + const { email, name, token } = recipient; - const assetBaseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; + const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const template = createElement(DocumentCompletedEmailTemplate, { documentName: document.title, assetBaseUrl, - downloadLink: 'https://documenso.com', + downloadLink: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`, }); await mailer.sendMail({ diff --git a/packages/lib/server-only/document/send-pending-email.ts b/packages/lib/server-only/document/send-pending-email.ts index ece75caec..75861be78 100644 --- a/packages/lib/server-only/document/send-pending-email.ts +++ b/packages/lib/server-only/document/send-pending-email.ts @@ -41,7 +41,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE const { email, name } = recipient; - const assetBaseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; + const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const template = createElement(DocumentPendingEmailTemplate, { documentName: document.title, @@ -57,7 +57,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', }, - subject: 'You are done signing.', + subject: 'Waiting for others to complete signing.', html: render(template), text: render(template, { plainText: true }), }); From 201ba65e1cb96235ec81dbc3cec3d529ab20f520 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 22 Sep 2023 23:23:55 +1000 Subject: [PATCH 33/34] fix: remove unused import --- apps/web/src/app/(dashboard)/documents/empty-state.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx index e8ce9073b..62ba381d4 100644 --- a/apps/web/src/app/(dashboard)/documents/empty-state.tsx +++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx @@ -1,4 +1,4 @@ -import { ArrowRight, Bird, CheckCircle2 } from 'lucide-react'; +import { Bird, CheckCircle2 } from 'lucide-react'; import { match } from 'ts-pattern'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; From 43ce76d92899e3faa47a90b53c79e9fdec252784 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 22 Sep 2023 23:29:14 +1000 Subject: [PATCH 34/34] fix: remove unused import --- apps/web/src/app/(dashboard)/documents/empty-state.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx index 62ba381d4..bdfef3bbe 100644 --- a/apps/web/src/app/(dashboard)/documents/empty-state.tsx +++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx @@ -47,4 +47,4 @@ export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
); -} +};