Compare commits

..

7 Commits

Author SHA1 Message Date
flo merian 31829f99cf Add images to blog post 2023-09-20 12:35:29 +02:00
flo merian e2237ee67b Remove .png file 2023-09-20 12:30:25 +02:00
flo merian 89fe8842f2 Remove .png file 2023-09-20 12:30:08 +02:00
flo merian db6ef10829 Remove .png file 2023-09-20 12:29:55 +02:00
flo merian 9742898cb6 Add blog post assets (optimized) 2023-09-20 12:28:35 +02:00
flo merian 0bf1310ae0 Add blog post assets 2023-09-20 12:25:46 +02:00
flo merian b6ed07b46b Add blog post 2023-09-20 12:15:36 +02:00
30 changed files with 163 additions and 574 deletions
+101
View File
@@ -0,0 +1,101 @@
---
title: A week of announcements
description: An overview of what's new at Documenso.
authorName: 'Flo Merian'
authorImage: '/blog/blog-author-flo.jpeg'
authorRole: 'Go-to-market'
date: 2023-09-20
tags:
- Announcement
- Community
---
We just spent the week announcing something new every day. This blog post gives an overview of what's new at Documenso.
## A week of announcements
### Day 1: Documenso Design System
<figure>
<MdxNextImage
src="/blog/blog-fig-launch-week-documenso-design-system.webp"
width="2000"
height="1126"
alt="Documenso Design System"
/>
<figcaption className="text-center">Documenso Design System</figcaption>
</figure>
We open-sourced Documenso's design system.
Documenso isn't just an open-source alternative to DocuSign. It's a beautiful document signing experience.
We welcome every developer AND designer to contribute to building the product.
[This Figma file]() serves as an open source guide for the design of the product across website, desktop & mobile apps, and brand.
It includes design tokens, primitives and components, screens, and brand assets.
Go duplicate and remix it!
### Day 2: Malfunction Mania
Documenso 1.0 just hit the testing environment.
We want to ensure the best possible release. That's why we started this week a public testing phase, the most significant community project yet, where we invite you to try out the new version, report and fix bugs and give feedback before release.
We call it "Malfunction Mania."
Early contributors love it so far:
"I love the refresh. The UI is much cleaner and the possibility to select the signature spot, date, name, email, and change the size, is a life-saver..."
Documenso's co-founder and CEO Timur shared the details in [this announcement]().
### Day 3: Documenso Shop
Supporting and contributing to open-source projects like Documenso is cool for sure.
Do you know what's *even* cooler tho? To wear swag from open-source projects.
We announced the [Documenso Shop](https://documen.so/shop), a place where you can buy some merch. Suit up!
### Day 4: Early Adopter Plan
<figure>
<MdxNextImage
src="/blog/blog-fig-launch-week-early-adopter-plan.webp"
width="2000"
height="1126"
alt="Early Adopter Plan"
/>
<figcaption className="text-center">The Early Adopter Plan. $30/month, forever. Available for the first 100 new signups.</figcaption>
</figure>
As we ramp up development speed, we introduced the [Early Adopter Plan](https://documen.so/pricing), a special, fixed $30/mo offer for 100 early adopters who want to get deep hands-on feedback.
Timur published a blog post [here]() to explain how we plan to build the core version of [documenso.com](http://documenso.com/).
### Day 5: Upcoming Launches
<figure>
<MdxNextImage
src="/blog/blog-fig-launch-week-upcoming-launches.webp"
width="2000"
height="1126"
alt="Documenso Upcoming Launches"
/>
</figure>
Say hello! hey! yo! to the public roadmap.
Go to the repository to find it and preview what's next at Documenso, the features we're working on and upcoming launches.
Make sure to [star the repo on GitHub](https://documen.so/github) and watch it to get the latest updates.
## Wrapping up
So this week was Documenso's first Launch Week. We hope you enjoyed it as much as we did.
Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

-65
View File
@@ -1,65 +0,0 @@
'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 (
<div className={cn('relative max-w-[100vw] overflow-hidden')}>
<div className="absolute -inset-24 -z-10">
<motion.div
className="flex h-full w-full items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 0.8, transition: { duration: 0.5, delay: 0.5 } }}
>
<Image
src={backgroundPattern}
alt="background pattern"
className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover md:scale-100 lg:scale-[100%]"
priority
/>
</motion.div>
</div>
<div className="container mx-auto flex h-full min-h-screen items-center px-6 py-32">
<div>
<p className="text-muted-foreground font-semibold">404 Page not found</p>
<h1 className="mt-3 text-2xl font-bold md:text-3xl">Oops! Something went wrong.</h1>
<p className="text-muted-foreground mt-4 text-sm">
The page you are looking for was moved, removed, renamed or might never have existed.
</p>
<div className="mt-6 flex gap-x-2.5 gap-y-4 md:items-center">
<Button
variant="ghost"
className="w-32"
onClick={() => {
void router.back();
}}
>
<ChevronLeft className="mr-2 h-4 w-4" />
Go Back
</Button>
<Button className="w-32" asChild>
<Link href="/">Home</Link>
</Button>
</div>
</div>
</div>
</div>
);
}
+5 -5
View File
@@ -4,11 +4,11 @@ import { getBaseUrl } from '@documenso/lib/universal/get-base-url';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
},
],
rules: {
userAgent: '*',
allow: '/*',
disallow: ['/_next/*'],
},
sitemap: `${getBaseUrl()}/sitemap.xml`,
};
}
-26
View File
@@ -1,26 +0,0 @@
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 (
<NotFoundPartial>
{session && (
<Button className="w-32" asChild>
<Link href="/documents">Documents</Link>
</Button>
)}
{!session && (
<Button className="w-32" asChild>
<Link href="/signin">Sign In</Link>
</Button>
)}
</NotFoundPartial>
);
}
@@ -10,7 +10,6 @@ import {
User as LucideUser,
Monitor,
Moon,
Palette,
Sun,
UserCog,
} from 'lucide-react';
@@ -27,13 +26,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
@@ -44,8 +37,8 @@ export type ProfileDropdownProps = {
};
export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
const { getFlag } = useFeatureFlags();
const { theme, setTheme } = useTheme();
const { getFlag } = useFeatureFlags();
const isUserAdmin = isAdmin(user);
const isBillingEnabled = getFlag('app_billing');
@@ -105,30 +98,28 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Palette className="mr-2 h-4 w-4" />
Themes
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
<DropdownMenuRadioItem value="light">
<Sun className="mr-2 h-4 w-4" /> Light
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark">
<Moon className="mr-2 h-4 w-4" />
Dark
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system">
<Monitor className="mr-2 h-4 w-4" />
System
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
{theme === 'light' ? null : (
<DropdownMenuItem onClick={() => setTheme('light')}>
<Sun className="mr-2 h-4 w-4" />
Light Mode
</DropdownMenuItem>
)}
{theme === 'dark' ? null : (
<DropdownMenuItem onClick={() => setTheme('dark')}>
<Moon className="mr-2 h-4 w-4" />
Dark Mode
</DropdownMenuItem>
)}
{theme === 'system' ? null : (
<DropdownMenuItem onClick={() => setTheme('system')}>
<Monitor className="mr-2 h-4 w-4" />
System Theme
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link href="https://github.com/documenso/documenso" className="cursor-pointer">
<Github className="mr-2 h-4 w-4" />
@@ -1,7 +1,6 @@
'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';
@@ -9,20 +8,12 @@ export type CompleteDocumentActionInput = TAddSubjectFormSchema & {
documentId: number;
};
export const completeDocument = async ({ documentId, email }: CompleteDocumentActionInput) => {
export const completeDocument = async ({ documentId }: CompleteDocumentActionInput) => {
'use server';
const { id: userId } = await getRequiredServerComponentSession();
if (email.message || email.subject) {
await upsertDocumentMeta({
documentId,
subject: email.subject,
message: email.message,
});
}
return await sendDocument({
await sendDocument({
userId,
documentId,
});
+7
View File
@@ -0,0 +1,7 @@
'use client';
import { motion } from 'framer-motion';
export * from 'framer-motion';
export const MotionDiv = motion.div;
@@ -1,66 +0,0 @@
'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 (
<div className={cn('relative max-w-[100vw] overflow-hidden')}>
<div className="absolute -inset-24 -z-10">
<motion.div
className="flex h-full w-full items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 0.8, transition: { duration: 0.5, delay: 0.5 } }}
>
<Image
src={backgroundPattern}
alt="background pattern"
className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover md:scale-100 lg:scale-[100%]"
priority
/>
</motion.div>
</div>
<div className="container mx-auto flex h-full min-h-screen items-center px-6 py-32">
<div>
<p className="text-muted-foreground font-semibold">404 Page not found</p>
<h1 className="mt-3 text-2xl font-bold md:text-3xl">Oops! Something went wrong.</h1>
<p className="text-muted-foreground mt-4 text-sm">
The page you are looking for was moved, removed, renamed or might never have existed.
</p>
<div className="mt-6 flex gap-x-2.5 gap-y-4 md:items-center">
<Button
variant="ghost"
className="w-32"
onClick={() => {
void router.back();
}}
>
<ChevronLeft className="mr-2 h-4 w-4" />
Go Back
</Button>
{children}
</div>
</div>
</div>
</div>
);
}
@@ -1,4 +1,4 @@
import { Button, Column, Img, Row, Section, Tailwind, Text } from '@react-email/components';
import { Button, Img, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config';
@@ -29,23 +29,11 @@ export const TemplateDocumentCompleted = ({
},
}}
>
<Section>
<Row className="table-fixed">
<Column />
<Section className="flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img className="h-42" src={getAssetUrl('/static/document.png')} alt="Documenso" />
</div>
<Column>
<Img
className="h-42 mx-auto"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</Column>
<Column />
</Row>
</Section>
<Section>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-[#7AC455]">
<Img src={getAssetUrl('/static/completed.png')} className="-mb-0.5 mr-2 inline h-7 w-7" />
Completed
@@ -1,4 +1,4 @@
import { Button, Column, Img, Row, Section, Tailwind, Text } from '@react-email/components';
import { Button, Img, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config';
@@ -30,26 +30,13 @@ export const TemplateDocumentInvite = ({
},
}}
>
<Section className="mt-4">
<Row className="table-fixed">
<Column />
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img className="h-42" src={getAssetUrl('/static/document.png')} alt="Documenso" />
</div>
<Column>
<Img
className="h-42 mx-auto"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</Column>
<Column />
</Row>
</Section>
<Section>
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
{inviterName} has invited you to sign
<br />"{documentName}"
{inviterName} has invited you to sign "{documentName}"
</Text>
<Text className="my-1 text-center text-base text-slate-400">
@@ -1,4 +1,4 @@
import { Column, Img, Row, Section, Tailwind, Text } from '@react-email/components';
import { Img, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config';
@@ -25,23 +25,11 @@ export const TemplateDocumentPending = ({
},
}}
>
<Section>
<Row className="table-fixed">
<Column />
<Section className="flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img className="h-42" src={getAssetUrl('/static/document.png')} alt="Documenso" />
</div>
<Column>
<Img
className="h-42 mx-auto"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</Column>
<Column />
</Row>
</Section>
<Section>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-blue-500">
<Img src={getAssetUrl('/static/clock.png')} className="-mb-0.5 mr-2 inline h-7 w-7" />
Waiting for others
+2 -9
View File
@@ -20,9 +20,7 @@ import {
} from '../template-components/template-document-invite';
import TemplateFooter from '../template-components/template-footer';
export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps> & {
customBody?: string;
};
export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps>;
export const DocumentInviteEmailTemplate = ({
inviterName = 'Lucas Smith',
@@ -30,7 +28,6 @@ export const DocumentInviteEmailTemplate = ({
documentName = 'Open Source Pledge.pdf',
signDocumentLink = 'https://documenso.com',
assetBaseUrl = 'http://localhost:3002',
customBody,
}: DocumentInviteEmailTemplateProps) => {
const previewText = `Completed Document`;
@@ -81,11 +78,7 @@ export const DocumentInviteEmailTemplate = ({
</Text>
<Text className="mt-2 text-base text-slate-400">
{customBody ? (
<pre className="font-sans text-base text-slate-400">{customBody}</pre>
) : (
`${inviterName} has invited you to sign the document "${documentName}".`
)}
{inviterName} has invited you to sign the document "{documentName}".
</Text>
</Section>
</Container>
@@ -1,30 +0,0 @@
'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,
},
});
};
@@ -4,7 +4,6 @@ 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;
@@ -70,19 +69,6 @@ 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,
@@ -13,7 +13,6 @@ export const getDocumentById = async ({ id, userId }: GetDocumentByIdOptions) =>
},
include: {
documentData: true,
documentMeta: true,
},
});
};
@@ -9,7 +9,6 @@ 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;
@@ -87,6 +86,4 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
data: newData,
},
});
await sendCompletedEmail({ documentId });
};
@@ -1,57 +0,0 @@
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 }),
});
}),
]);
};
@@ -3,14 +3,13 @@ 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 type SendDocumentOptions = {
export interface SendDocumentOptions {
documentId: number;
userId: number;
};
}
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({
@@ -26,12 +25,9 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
},
include: {
Recipient: true,
documentMeta: true,
},
});
const customEmail = document?.documentMeta;
if (!document) {
throw new Error('Document not found');
}
@@ -48,12 +44,6 @@ 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;
}
@@ -67,7 +57,6 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
inviterEmail: user.email,
assetBaseUrl,
signDocumentLink,
customBody: renderCustomEmailTemplate(customEmail?.message || '', customEmailTemplate),
});
await mailer.sendMail({
@@ -79,9 +68,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: customEmail?.subject
? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate)
: 'Please sign this document',
subject: 'Please sign this document',
html: render(template),
text: render(template, { plainText: true }),
});
@@ -1,64 +0,0 @@
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 }),
});
};
@@ -1,21 +0,0 @@
'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,
},
});
};
@@ -1,12 +0,0 @@
export const renderCustomEmailTemplate = <T extends Record<string, string>>(
template: string,
variables: T,
): string => {
return template.replace(/\{(\S+)\}/g, (_, key) => {
if (key in variables) {
return variables[key];
}
return key;
});
};
@@ -1,14 +0,0 @@
-- 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;
@@ -1,8 +0,0 @@
/*
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");
@@ -1,52 +0,0 @@
/*
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;
-9
View File
@@ -110,7 +110,6 @@ 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
@@ -131,14 +130,6 @@ 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
+1 -2
View File
@@ -1,6 +1,5 @@
import { Document, DocumentData, DocumentMeta } from '@documenso/prisma/client';
import { Document, DocumentData } from '@documenso/prisma/client';
export type DocumentWithData = Document & {
documentData?: DocumentData | null;
documentMeta?: DocumentMeta | null;
};
@@ -2,8 +2,7 @@
import { useForm } from 'react-hook-form';
import { DocumentStatus, Field, Recipient } from '@documenso/prisma/client';
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { Document, DocumentStatus, Field, Recipient } from '@documenso/prisma/client';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
@@ -22,7 +21,7 @@ export type AddSubjectFormProps = {
documentFlow: DocumentFlowStep;
recipients: Recipient[];
fields: Field[];
document: DocumentWithData;
document: Document;
numberOfSteps: number;
onSubmit: (_data: TAddSubjectFormSchema) => void;
};
@@ -42,8 +41,8 @@ export const AddSubjectFormPartial = ({
} = useForm<TAddSubjectFormSchema>({
defaultValues: {
email: {
subject: document.documentMeta?.subject ?? '',
message: document.documentMeta?.message ?? '',
subject: '',
message: '',
},
},
});