diff --git a/apps/marketing/src/app/not-found.tsx b/apps/marketing/src/app/not-found.tsx
new file mode 100644
index 000000000..9f87cdc88
--- /dev/null
+++ b/apps/marketing/src/app/not-found.tsx
@@ -0,0 +1,65 @@
+'use client';
+
+import Image from 'next/image';
+import Link from 'next/link';
+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 (
+
+
+
+
+
+
+
+
+
+
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/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`,
};
}
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..bdfef3bbe
--- /dev/null
+++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx
@@ -0,0 +1,50 @@
+import { Bird, CheckCircle2 } from 'lucide-react';
+import { match } from 'ts-pattern';
+
+import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
+
+export type EmptyDocumentProps = { status: ExtendedDocumentStatus };
+
+export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
+ const {
+ title,
+ message,
+ icon: Icon,
+ } = match(status)
+ .with(ExtendedDocumentStatus.COMPLETED, () => ({
+ 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, () => ({
+ 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, () => ({
+ 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(() => ({
+ 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 (
+
+
+
+
+
{title}
+
+
{message}
+
+
+ );
+};
diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx
index 32ec4f814..9ae16e44b 100644
--- a/apps/web/src/app/(dashboard)/documents/page.tsx
+++ b/apps/web/src/app/(dashboard)/documents/page.tsx
@@ -12,6 +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 { UploadDocument } from './upload-document';
export type DocumentsPageProps = {
@@ -96,7 +97,8 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
-
+ {results.count > 0 && }
+ {results.count === 0 && }
);
diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx
new file mode 100644
index 000000000..c8dc15086
--- /dev/null
+++ b/apps/web/src/app/not-found.tsx
@@ -0,0 +1,26 @@
+import Link from 'next/link';
+
+import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { Button } from '@documenso/ui/primitives/button';
+
+import NotFoundPartial from '~/components/partials/not-found';
+
+export default async function NotFound() {
+ const session = await getServerComponentSession();
+
+ return (
+
+ {session && (
+
+ )}
+
+ {!session && (
+
+ )}
+
+ );
+}
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..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,6 +1,7 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
@@ -8,12 +9,20 @@ 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();
- await sendDocument({
+ if (email.message || email.subject) {
+ await upsertDocumentMeta({
+ documentId,
+ subject: email.subject,
+ message: email.message,
+ });
+ }
+
+ return await sendDocument({
userId,
documentId,
});
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 (
+
+
+
+
+
+
+
+
+
+
404 Page not found
+
+
Oops! Something went wrong.
+
+
+ The page you are looking for was moved, removed, renamed or might never have existed.
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
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 = ({
},
}}
>
-
-
-
})
-
+
+
+
+
+
+
+
+
+
+
+
+
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 = ({
},
}}
>
-
-
-
})
-
+
+
+
+
+
+
+
+
+
+
+
+
- {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 = ({
},
}}
>
-
-
-
})
-
+
+
+
+
+
+
+
+
+
+
+
+
Waiting for others
diff --git a/packages/email/templates/document-invite.tsx b/packages/email/templates/document-invite.tsx
index 465685649..661a2fd5f 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,11 @@ 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-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/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts
index a013d5e69..2712c56fa 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-pending-email';
export type CompleteDocumentWithTokenOptions = {
token: string;
@@ -69,6 +70,19 @@ export const completeDocumentWithToken = async ({
},
});
+ const pendingRecipients = await prisma.recipient.count({
+ where: {
+ documentId: document.id,
+ signingStatus: {
+ not: SigningStatus.SIGNED,
+ },
+ },
+ });
+
+ if (pendingRecipients > 0) {
+ await sendPendingEmail({ documentId, recipientId: recipient.id });
+ }
+
const documents = await prisma.document.updateMany({
where: {
id: document.id,
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/seal-document.ts b/packages/lib/server-only/document/seal-document.ts
index 883d13e6f..d551e4adf 100644
--- a/packages/lib/server-only/document/seal-document.ts
+++ b/packages/lib/server-only/document/seal-document.ts
@@ -9,6 +9,7 @@ import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
import { getFile } from '../../universal/upload/get-file';
import { putFile } from '../../universal/upload/put-file';
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
+import { sendCompletedEmail } from './send-completed-email';
export type SealDocumentOptions = {
documentId: number;
@@ -86,4 +87,6 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
data: newData,
},
});
+
+ 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..9d0d2d499
--- /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, token } = recipient;
+
+ const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
+
+ const template = createElement(DocumentCompletedEmailTemplate, {
+ documentName: document.title,
+ assetBaseUrl,
+ downloadLink: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`,
+ });
+
+ 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: 'Signing Complete!',
+ html: render(template),
+ text: render(template, { plainText: true }),
+ });
+ }),
+ ]);
+};
diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx
index 83a88f24a..fcc0f829c 100644
--- a/packages/lib/server-only/document/send-document.tsx
+++ b/packages/lib/server-only/document/send-document.tsx
@@ -3,13 +3,14 @@ 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';
-export interface SendDocumentOptions {
+export type SendDocumentOptions = {
documentId: number;
userId: number;
-}
+};
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({
@@ -25,9 +26,12 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
},
include: {
Recipient: true,
+ documentMeta: true,
},
});
+ const customEmail = document?.documentMeta;
+
if (!document) {
throw new Error('Document not found');
}
@@ -44,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;
}
@@ -57,6 +67,7 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
inviterEmail: user.email,
assetBaseUrl,
signDocumentLink,
+ customBody: renderCustomEmailTemplate(customEmail?.message || '', customEmailTemplate),
});
await mailer.sendMail({
@@ -68,7 +79,9 @@ 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
+ ? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate)
+ : 'Please sign this document',
html: render(template),
text: render(template, { plainText: true }),
});
diff --git a/packages/lib/server-only/document/send-pending-email.ts b/packages/lib/server-only/document/send-pending-email.ts
new file mode 100644
index 000000000..75861be78
--- /dev/null
+++ b/packages/lib/server-only/document/send-pending-email.ts
@@ -0,0 +1,64 @@
+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 { prisma } from '@documenso/prisma';
+
+export interface SendPendingEmailOptions {
+ documentId: number;
+ recipientId: number;
+}
+
+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_WEBAPP_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: 'Waiting for others to complete signing.',
+ 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,
+ },
+ });
+};
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);
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..e3fdf5c7b
--- /dev/null
+++ b/packages/lib/utils/render-custom-email-template.ts
@@ -0,0 +1,12 @@
+export const renderCustomEmailTemplate = >(
+ template: string,
+ variables: T,
+): string => {
+ return template.replace(/\{(\S+)\}/g, (_, key) => {
+ if (key in variables) {
+ return variables[key];
+ }
+
+ return key;
+ });
+};
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/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/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 87321e20f..c4f034ba2 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -110,9 +110,10 @@ model Document {
Field Field[]
ShareLink DocumentShareLink[]
documentDataId String
- documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade)
- createdAt DateTime @default(now())
- updatedAt DateTime @default(now()) @updatedAt
+ documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade)
+ documentMeta DocumentMeta?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @default(now()) @updatedAt
@@unique([documentDataId])
}
@@ -131,6 +132,14 @@ model DocumentData {
Document Document?
}
+model DocumentMeta {
+ id String @id @default(cuid())
+ subject String?
+ message String?
+ documentId Int @unique
+ document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
+}
+
enum ReadStatus {
NOT_OPENED
OPENED
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 ?? '',
},
},
});