fix: remove server actions (#684)

This commit is contained in:
Lucas Smith
2023-12-02 09:38:24 +11:00
committed by GitHub
parent 335684d0b7
commit 39c01f4e8d
44 changed files with 2711 additions and 3956 deletions

View File

@ -22,7 +22,7 @@ const config = {
experimental: { experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'), outputFileTracingRoot: path.join(__dirname, '../../'),
serverActions: { serverActions: {
bodySizeLimit: '50mb' bodySizeLimit: '50mb',
}, },
}, },
reactStrictMode: true, reactStrictMode: true,

View File

@ -24,8 +24,8 @@
"lucide-react": "^0.279.0", "lucide-react": "^0.279.0",
"luxon": "^3.4.0", "luxon": "^3.4.0",
"micro": "^10.0.1", "micro": "^10.0.1",
"next": "14.0.0", "next": "14.0.3",
"next-auth": "4.24.3", "next-auth": "4.24.5",
"next-contentlayer": "^0.3.4", "next-contentlayer": "^0.3.4",
"next-plausible": "^3.10.1", "next-plausible": "^3.10.1",
"perfect-freehand": "^1.2.0", "perfect-freehand": "^1.2.0",
@ -44,5 +44,13 @@
"@types/node": "20.1.0", "@types/node": "20.1.0",
"@types/react": "18.2.18", "@types/react": "18.2.18",
"@types/react-dom": "18.2.7" "@types/react-dom": "18.2.7"
},
"overrides": {
"next-auth": {
"next": "$next"
},
"next-contentlayer": {
"next": "$next"
}
} }
} }

View File

@ -23,7 +23,7 @@ const config = {
experimental: { experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'), outputFileTracingRoot: path.join(__dirname, '../../'),
serverActions: { serverActions: {
bodySizeLimit: '50mb' bodySizeLimit: '50mb',
}, },
}, },
reactStrictMode: true, reactStrictMode: true,

View File

@ -27,8 +27,8 @@
"lucide-react": "^0.279.0", "lucide-react": "^0.279.0",
"luxon": "^3.4.0", "luxon": "^3.4.0",
"micro": "^10.0.1", "micro": "^10.0.1",
"next": "14.0.0", "next": "14.0.3",
"next-auth": "4.24.3", "next-auth": "4.24.5",
"next-plausible": "^3.10.1", "next-plausible": "^3.10.1",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"perfect-freehand": "^1.2.0", "perfect-freehand": "^1.2.0",
@ -53,5 +53,13 @@
"@types/node": "20.1.0", "@types/node": "20.1.0",
"@types/react": "18.2.18", "@types/react": "18.2.18",
"@types/react-dom": "18.2.7" "@types/react-dom": "18.2.7"
},
"overrides": {
"next-auth": {
"next": "$next"
},
"next-contentlayer": {
"next": "$next"
}
} }
} }

View File

@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation';
import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client'; import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client';
import { DocumentStatus } from '@documenso/prisma/client'; import { DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card'; import { Card, CardContent } from '@documenso/ui/primitives/card';
import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields'; import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields';
@ -25,11 +26,6 @@ import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/ty
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { addFields } from '~/components/forms/edit-document/add-fields.action';
import { addSigners } from '~/components/forms/edit-document/add-signers.action';
import { completeDocument } from '~/components/forms/edit-document/add-subject.action';
import { addTitle } from '~/components/forms/edit-document/add-title.action';
export type EditDocumentFormProps = { export type EditDocumentFormProps = {
className?: string; className?: string;
user: User; user: User;
@ -56,6 +52,11 @@ export const EditDocumentForm = ({
document.status === DocumentStatus.DRAFT ? 'title' : 'signers', document.status === DocumentStatus.DRAFT ? 'title' : 'signers',
); );
const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation();
const { mutateAsync: addFields } = trpc.field.addFields.useMutation();
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation();
const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation();
const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = { const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = {
title: { title: {
title: 'Add Title', title: 'Add Title',
@ -154,7 +155,7 @@ export const EditDocumentForm = ({
const { subject, message } = data.email; const { subject, message } = data.email;
try { try {
await completeDocument({ await sendDocument({
documentId: document.id, documentId: document.id,
email: { email: {
subject, subject,

View File

@ -7,9 +7,9 @@ import { useRouter } from 'next/navigation';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields'; import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
import { Document, Field, Recipient } from '@documenso/prisma/client'; import type { Document, Field, Recipient } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
@ -34,6 +34,9 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext(); const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext();
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
const { mutateAsync: completeDocumentWithToken } =
trpc.recipient.completeDocumentWithToken.useMutation();
const { const {
handleSubmit, handleSubmit,
formState: { isSubmitting }, formState: { isSubmitting },

View File

@ -1,30 +0,0 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
export type AddFieldsActionInput = TAddFieldsFormSchema & {
documentId: number;
};
export const addFields = async ({ documentId, fields }: AddFieldsActionInput) => {
'use server';
const { user } = await getRequiredServerComponentSession();
await setFieldsForDocument({
userId: user.id,
documentId,
fields: fields.map((field) => ({
id: field.nativeId,
signerEmail: field.signerEmail,
type: field.type,
pageNumber: field.pageNumber,
pageX: field.pageX,
pageY: field.pageY,
pageWidth: field.pageWidth,
pageHeight: field.pageHeight,
})),
});
};

View File

@ -1,25 +0,0 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import type { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
export type AddSignersActionInput = TAddSignersFormSchema & {
documentId: number;
};
export const addSigners = async ({ documentId, signers }: AddSignersActionInput) => {
'use server';
const { user } = await getRequiredServerComponentSession();
await setRecipientsForDocument({
userId: user.id,
documentId,
recipients: signers.map((signer) => ({
id: signer.nativeId,
email: signer.email,
name: signer.name,
})),
});
};

View File

@ -1,29 +0,0 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import type { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
export type CompleteDocumentActionInput = TAddSubjectFormSchema & {
documentId: number;
};
export const completeDocument = async ({ documentId, email }: CompleteDocumentActionInput) => {
'use server';
const { user } = await getRequiredServerComponentSession();
if (email.message || email.subject) {
await upsertDocumentMeta({
documentId,
subject: email.subject,
message: email.message,
});
}
return await sendDocument({
userId: user.id,
documentId,
});
};

View File

@ -1,21 +0,0 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { updateTitle } from '@documenso/lib/server-only/document/update-title';
import type { TAddTitleFormSchema } from '@documenso/ui/primitives/document-flow/add-title.types';
export type AddTitleActionInput = TAddTitleFormSchema & {
documentId: number;
};
export const addTitle = async ({ documentId, title }: AddTitleActionInput) => {
'use server';
const { user } = await getRequiredServerComponentSession();
await updateTitle({
documentId,
userId: user.id,
title: title,
});
};

5970
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,8 +47,13 @@
"apps/*", "apps/*",
"packages/*" "packages/*"
], ],
"dependencies": { "dependencies": {},
"recharts": "^2.7.2", "overrides": {
"react-hotkeys-hook": "^4.4.1" "next-auth": {
"next": "14.0.3"
},
"next-contentlayer": {
"next": "14.0.3"
}
} }
} }

View File

@ -17,8 +17,8 @@
"@documenso/prisma": "*", "@documenso/prisma": "*",
"luxon": "^3.4.0", "luxon": "^3.4.0",
"micro": "^10.0.1", "micro": "^10.0.1",
"next": "14.0.0", "next": "14.0.3",
"next-auth": "4.24.3", "next-auth": "4.24.5",
"react": "18.2.0", "react": "18.2.0",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",
"zod": "^3.22.4" "zod": "^3.22.4"

View File

@ -0,0 +1,17 @@
export * from '@react-email/body';
export * from '@react-email/button';
export * from '@react-email/column';
export * from '@react-email/container';
export * from '@react-email/font';
export * from '@react-email/head';
export * from '@react-email/heading';
export * from '@react-email/hr';
export * from '@react-email/html';
export * from '@react-email/img';
export * from '@react-email/link';
export * from '@react-email/preview';
export * from '@react-email/render';
export * from '@react-email/row';
export * from '@react-email/section';
export * from '@react-email/tailwind';
export * from '@react-email/text';

View File

@ -18,8 +18,23 @@
}, },
"dependencies": { "dependencies": {
"@documenso/nodemailer-resend": "2.0.0", "@documenso/nodemailer-resend": "2.0.0",
"@react-email/components": "^0.0.11", "@react-email/body": "0.0.4",
"@react-email/tailwind": "0.0.9", "@react-email/button": "0.0.11",
"@react-email/column": "0.0.8",
"@react-email/container": "0.0.10",
"@react-email/font": "0.0.4",
"@react-email/head": "0.0.6",
"@react-email/heading": "0.0.9",
"@react-email/hr": "0.0.6",
"@react-email/html": "0.0.6",
"@react-email/img": "0.0.6",
"@react-email/link": "0.0.6",
"@react-email/preview": "0.0.7",
"@react-email/render": "0.0.9",
"@react-email/row": "0.0.6",
"@react-email/section": "0.0.10",
"@react-email/tailwind": "0.0.13-canary.1",
"@react-email/text": "0.0.6",
"nodemailer": "^6.9.3", "nodemailer": "^6.9.3",
"react-email": "^1.9.5", "react-email": "^1.9.5",
"resend": "^2.0.0" "resend": "^2.0.0"
@ -29,8 +44,5 @@
"@documenso/tsconfig": "*", "@documenso/tsconfig": "*",
"@types/nodemailer": "^6.4.8", "@types/nodemailer": "^6.4.8",
"tsup": "^7.1.0" "tsup": "^7.1.0"
},
"overrides": {
"@react-email/tailwind": "0.0.9"
} }
} }

View File

@ -1 +1 @@
export { render } from '@react-email/components'; export { render, renderAsync } from '@react-email/render';

View File

@ -1,7 +1,4 @@
import { Button, Section, Tailwind, Text } from '@react-email/components'; import { Button, Section, Text } from '../components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
export type TemplateConfirmationEmailProps = { export type TemplateConfirmationEmailProps = {
@ -14,15 +11,7 @@ export const TemplateConfirmationEmail = ({
assetBaseUrl, assetBaseUrl,
}: TemplateConfirmationEmailProps) => { }: TemplateConfirmationEmailProps) => {
return ( return (
<Tailwind <>
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section className="flex-row items-center justify-center"> <Section className="flex-row items-center justify-center">
@ -47,6 +36,6 @@ export const TemplateConfirmationEmail = ({
</Text> </Text>
</Section> </Section>
</Section> </Section>
</Tailwind> </>
); );
}; };

View File

@ -1,7 +1,4 @@
import { Button, Column, Img, Section, Tailwind, Text } from '@react-email/components'; import { Button, Column, Img, Section, Text } from '../components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
export interface TemplateDocumentCompletedProps { export interface TemplateDocumentCompletedProps {
@ -20,15 +17,7 @@ export const TemplateDocumentCompleted = ({
}; };
return ( return (
<Tailwind <>
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section> <Section>
@ -72,7 +61,7 @@ export const TemplateDocumentCompleted = ({
</Button> </Button>
</Section> </Section>
</Section> </Section>
</Tailwind> </>
); );
}; };

View File

@ -1,4 +1,4 @@
import { Column, Img, Row, Section } from '@react-email/components'; import { Column, Img, Row, Section } from '../components';
export interface TemplateDocumentImageProps { export interface TemplateDocumentImageProps {
assetBaseUrl: string; assetBaseUrl: string;

View File

@ -1,7 +1,4 @@
import { Button, Section, Tailwind, Text } from '@react-email/components'; import { Button, Section, Text } from '../components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
export interface TemplateDocumentInviteProps { export interface TemplateDocumentInviteProps {
@ -19,15 +16,7 @@ export const TemplateDocumentInvite = ({
assetBaseUrl, assetBaseUrl,
}: TemplateDocumentInviteProps) => { }: TemplateDocumentInviteProps) => {
return ( return (
<Tailwind <>
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section> <Section>
@ -49,7 +38,7 @@ export const TemplateDocumentInvite = ({
</Button> </Button>
</Section> </Section>
</Section> </Section>
</Tailwind> </>
); );
}; };

View File

@ -1,7 +1,4 @@
import { Column, Img, Section, Tailwind, Text } from '@react-email/components'; import { Column, Img, Section, Text } from '../components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
export interface TemplateDocumentPendingProps { export interface TemplateDocumentPendingProps {
@ -18,15 +15,7 @@ export const TemplateDocumentPending = ({
}; };
return ( return (
<Tailwind <>
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section> <Section>
@ -52,7 +41,7 @@ export const TemplateDocumentPending = ({
We'll notify you as soon as it's ready. We'll notify you as soon as it's ready.
</Text> </Text>
</Section> </Section>
</Tailwind> </>
); );
}; };

View File

@ -1,7 +1,4 @@
import { Button, Column, Img, Link, Section, Tailwind, Text } from '@react-email/components'; import { Button, Column, Img, Link, Section, Text } from '../components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
export interface TemplateDocumentSelfSignedProps { export interface TemplateDocumentSelfSignedProps {
@ -20,15 +17,7 @@ export const TemplateDocumentSelfSigned = ({
}; };
return ( return (
<Tailwind <>
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section className="flex-row items-center justify-center"> <Section className="flex-row items-center justify-center">
@ -84,7 +73,7 @@ export const TemplateDocumentSelfSigned = ({
</Button> </Button>
</Section> </Section>
</Section> </Section>
</Tailwind> </>
); );
}; };

View File

@ -1,4 +1,4 @@
import { Link, Section, Text } from '@react-email/components'; import { Link, Section, Text } from '../components';
export type TemplateFooterProps = { export type TemplateFooterProps = {
isDocument?: boolean; isDocument?: boolean;

View File

@ -1,7 +1,4 @@
import { Button, Section, Tailwind, Text } from '@react-email/components'; import { Button, Section, Text } from '../components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
export type TemplateForgotPasswordProps = { export type TemplateForgotPasswordProps = {
@ -14,15 +11,7 @@ export const TemplateForgotPassword = ({
assetBaseUrl, assetBaseUrl,
}: TemplateForgotPasswordProps) => { }: TemplateForgotPasswordProps) => {
return ( return (
<Tailwind <>
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section className="flex-row items-center justify-center"> <Section className="flex-row items-center justify-center">
@ -43,7 +32,7 @@ export const TemplateForgotPassword = ({
</Button> </Button>
</Section> </Section>
</Section> </Section>
</Tailwind> </>
); );
}; };

View File

@ -1,7 +1,4 @@
import { Button, Section, Tailwind, Text } from '@react-email/components'; import { Button, Section, Text } from '../components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
export interface TemplateResetPasswordProps { export interface TemplateResetPasswordProps {
@ -12,15 +9,7 @@ export interface TemplateResetPasswordProps {
export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordProps) => { export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordProps) => {
return ( return (
<Tailwind <>
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section className="flex-row items-center justify-center"> <Section className="flex-row items-center justify-center">
@ -41,7 +30,7 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro
</Button> </Button>
</Section> </Section>
</Section> </Section>
</Tailwind> </>
); );
}; };

View File

@ -1,20 +1,8 @@
import {
Body,
Container,
Head,
Html,
Img,
Preview,
Section,
Tailwind,
} from '@react-email/components';
import config from '@documenso/tailwind-config'; import config from '@documenso/tailwind-config';
import { import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
TemplateConfirmationEmail, import type { TemplateConfirmationEmailProps } from '../template-components/template-confirmation-email';
TemplateConfirmationEmailProps, import { TemplateConfirmationEmail } from '../template-components/template-confirmation-email';
} from '../template-components/template-confirmation-email';
import { TemplateFooter } from '../template-components/template-footer'; import { TemplateFooter } from '../template-components/template-footer';
export const ConfirmEmailTemplate = ({ export const ConfirmEmailTemplate = ({

View File

@ -1,20 +1,8 @@
import {
Body,
Container,
Head,
Html,
Img,
Preview,
Section,
Tailwind,
} from '@react-email/components';
import config from '@documenso/tailwind-config'; import config from '@documenso/tailwind-config';
import { import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
TemplateDocumentCompleted, import type { TemplateDocumentCompletedProps } from '../template-components/template-document-completed';
TemplateDocumentCompletedProps, import { TemplateDocumentCompleted } from '../template-components/template-document-completed';
} from '../template-components/template-document-completed';
import { TemplateFooter } from '../template-components/template-footer'; import { TemplateFooter } from '../template-components/template-footer';
export type DocumentCompletedEmailTemplateProps = Partial<TemplateDocumentCompletedProps>; export type DocumentCompletedEmailTemplateProps = Partial<TemplateDocumentCompletedProps>;

View File

@ -1,3 +1,5 @@
import config from '@documenso/tailwind-config';
import { import {
Body, Body,
Container, Container,
@ -10,14 +12,9 @@ import {
Section, Section,
Tailwind, Tailwind,
Text, Text,
} from '@react-email/components'; } from '../components';
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
import config from '@documenso/tailwind-config'; import { TemplateDocumentInvite } from '../template-components/template-document-invite';
import {
TemplateDocumentInvite,
TemplateDocumentInviteProps,
} from '../template-components/template-document-invite';
import { TemplateFooter } from '../template-components/template-footer'; import { TemplateFooter } from '../template-components/template-footer';
export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps> & { export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps> & {

View File

@ -1,20 +1,8 @@
import {
Body,
Container,
Head,
Html,
Img,
Preview,
Section,
Tailwind,
} from '@react-email/components';
import config from '@documenso/tailwind-config'; import config from '@documenso/tailwind-config';
import { import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
TemplateDocumentPending, import type { TemplateDocumentPendingProps } from '../template-components/template-document-pending';
TemplateDocumentPendingProps, import { TemplateDocumentPending } from '../template-components/template-document-pending';
} from '../template-components/template-document-pending';
import { TemplateFooter } from '../template-components/template-footer'; import { TemplateFooter } from '../template-components/template-footer';
export type DocumentPendingEmailTemplateProps = Partial<TemplateDocumentPendingProps>; export type DocumentPendingEmailTemplateProps = Partial<TemplateDocumentPendingProps>;

View File

@ -1,20 +1,8 @@
import {
Body,
Container,
Head,
Html,
Img,
Preview,
Section,
Tailwind,
} from '@react-email/components';
import config from '@documenso/tailwind-config'; import config from '@documenso/tailwind-config';
import { import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
TemplateDocumentSelfSigned, import type { TemplateDocumentSelfSignedProps } from '../template-components/template-document-self-signed';
TemplateDocumentSelfSignedProps, import { TemplateDocumentSelfSigned } from '../template-components/template-document-self-signed';
} from '../template-components/template-document-self-signed';
import { TemplateFooter } from '../template-components/template-footer'; import { TemplateFooter } from '../template-components/template-footer';
export type DocumentSelfSignedTemplateProps = TemplateDocumentSelfSignedProps; export type DocumentSelfSignedTemplateProps = TemplateDocumentSelfSignedProps;

View File

@ -1,21 +1,9 @@
import {
Body,
Container,
Head,
Html,
Img,
Preview,
Section,
Tailwind,
} from '@react-email/components';
import config from '@documenso/tailwind-config'; import config from '@documenso/tailwind-config';
import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from '../components';
import { TemplateFooter } from '../template-components/template-footer'; import { TemplateFooter } from '../template-components/template-footer';
import { import type { TemplateForgotPasswordProps } from '../template-components/template-forgot-password';
TemplateForgotPassword, import { TemplateForgotPassword } from '../template-components/template-forgot-password';
TemplateForgotPasswordProps,
} from '../template-components/template-forgot-password';
export type ForgotPasswordTemplateProps = Partial<TemplateForgotPasswordProps>; export type ForgotPasswordTemplateProps = Partial<TemplateForgotPasswordProps>;

View File

@ -1,3 +1,5 @@
import config from '@documenso/tailwind-config';
import { import {
Body, Body,
Container, Container,
@ -10,15 +12,10 @@ import {
Section, Section,
Tailwind, Tailwind,
Text, Text,
} from '@react-email/components'; } from '../components';
import config from '@documenso/tailwind-config';
import { TemplateFooter } from '../template-components/template-footer'; import { TemplateFooter } from '../template-components/template-footer';
import { import type { TemplateResetPasswordProps } from '../template-components/template-reset-password';
TemplateResetPassword, import { TemplateResetPassword } from '../template-components/template-reset-password';
TemplateResetPasswordProps,
} from '../template-components/template-reset-password';
export type ResetPasswordTemplateProps = Partial<TemplateResetPasswordProps>; export type ResetPasswordTemplateProps = Partial<TemplateResetPasswordProps>;

View File

@ -34,8 +34,8 @@
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"luxon": "^3.4.0", "luxon": "^3.4.0",
"nanoid": "^4.0.2", "nanoid": "^4.0.2",
"next": "14.0.0", "next": "14.0.3",
"next-auth": "4.24.3", "next-auth": "4.24.5",
"oslo": "^0.17.0", "oslo": "^0.17.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"react": "18.2.0", "react": "18.2.0",

View File

@ -54,6 +54,7 @@ export const signFieldWithToken = async ({
field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE; field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
let customText = !isSignatureField ? value : undefined; let customText = !isSignatureField ? value : undefined;
const signatureImageAsBase64 = isSignatureField && isBase64 ? value : undefined; const signatureImageAsBase64 = isSignatureField && isBase64 ? value : undefined;
const typedSignature = isSignatureField && !isBase64 ? value : undefined; const typedSignature = isSignatureField && !isBase64 ? value : undefined;
@ -61,29 +62,48 @@ export const signFieldWithToken = async ({
customText = DateTime.now().toFormat('yyyy-MM-dd hh:mm a'); customText = DateTime.now().toFormat('yyyy-MM-dd hh:mm a');
} }
await prisma.field.update({ if (isSignatureField && !signatureImageAsBase64 && !typedSignature) {
where: { throw new Error('Signature field must have a signature');
id: field.id, }
},
data: { return await prisma.$transaction(async (tx) => {
customText, const updatedField = await tx.field.update({
inserted: true, where: {
Signature: isSignatureField id: field.id,
? { },
upsert: { data: {
create: { customText,
recipientId: field.recipientId, inserted: true,
signatureImageAsBase64, },
typedSignature, });
},
update: { if (isSignatureField) {
recipientId: field.recipientId, if (!field.recipientId) {
signatureImageAsBase64, throw new Error('Field has no recipientId');
typedSignature, }
},
}, const signature = await tx.signature.upsert({
} where: {
: undefined, fieldId: field.id,
}, },
create: {
fieldId: field.id,
recipientId: field.recipientId,
signatureImageAsBase64: signatureImageAsBase64,
typedSignature: typedSignature,
},
update: {
signatureImageAsBase64: signatureImageAsBase64,
typedSignature: typedSignature,
},
});
// Dirty but I don't want to deal with type information
Object.assign(updatedField, {
Signature: signature,
});
}
return updatedField;
}); });
}; };

View File

@ -9,7 +9,7 @@
"dependencies": { "dependencies": {
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"tailwindcss": "^3.2.7", "tailwindcss": "3.3.2",
"tailwindcss-animate": "^1.0.5" "tailwindcss-animate": "^1.0.5"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,7 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { getServerLimits } from '@documenso/ee/server-only/limits/server'; import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { createDocument } from '@documenso/lib/server-only/document/create-document'; import { createDocument } from '@documenso/lib/server-only/document/create-document';
import { deleteDraftDocument } from '@documenso/lib/server-only/document/delete-draft-document'; import { deleteDraftDocument } from '@documenso/lib/server-only/document/delete-draft-document';
import { duplicateDocumentById } from '@documenso/lib/server-only/document/duplicate-document-by-id'; import { duplicateDocumentById } from '@documenso/lib/server-only/document/duplicate-document-by-id';
@ -119,7 +120,7 @@ export const documentRouter = router({
.input(ZSetTitleForDocumentMutationSchema) .input(ZSetTitleForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { documentId, title } = input; const { documentId, title } = input;
const userId = ctx.user.id; const userId = ctx.user.id;
return await updateTitle({ return await updateTitle({
@ -176,7 +177,15 @@ export const documentRouter = router({
.input(ZSendDocumentMutationSchema) .input(ZSendDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
try { try {
const { documentId } = input; const { documentId, email } = input;
if (email.message || email.subject) {
await upsertDocumentMeta({
documentId,
subject: email.subject,
message: email.message,
});
}
return await sendDocument({ return await sendDocument({
userId: ctx.user.id, userId: ctx.user.id,

View File

@ -65,6 +65,10 @@ export type TSetFieldsForDocumentMutationSchema = z.infer<
export const ZSendDocumentMutationSchema = z.object({ export const ZSendDocumentMutationSchema = z.object({
documentId: z.number(), documentId: z.number(),
email: z.object({
subject: z.string(),
message: z.string(),
}),
}); });
export const ZResendDocumentMutationSchema = z.object({ export const ZResendDocumentMutationSchema = z.object({

View File

@ -1,15 +1,47 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { removeSignedFieldWithToken } from '@documenso/lib/server-only/field/remove-signed-field-with-token'; import { removeSignedFieldWithToken } from '@documenso/lib/server-only/field/remove-signed-field-with-token';
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token'; import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token';
import { procedure, router } from '../trpc'; import { authenticatedProcedure, procedure, router } from '../trpc';
import { import {
ZAddFieldsMutationSchema,
ZRemovedSignedFieldWithTokenMutationSchema, ZRemovedSignedFieldWithTokenMutationSchema,
ZSignFieldWithTokenMutationSchema, ZSignFieldWithTokenMutationSchema,
} from './schema'; } from './schema';
export const fieldRouter = router({ export const fieldRouter = router({
addFields: authenticatedProcedure
.input(ZAddFieldsMutationSchema)
.mutation(async ({ input, ctx }) => {
try {
const { documentId, fields } = input;
return await setFieldsForDocument({
documentId,
userId: ctx.user.id,
fields: fields.map((field) => ({
id: field.nativeId,
signerEmail: field.signerEmail,
type: field.type,
pageNumber: field.pageNumber,
pageX: field.pageX,
pageY: field.pageY,
pageWidth: field.pageWidth,
pageHeight: field.pageHeight,
})),
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to sign this field. Please try again later.',
});
}
}),
signFieldWithToken: procedure signFieldWithToken: procedure
.input(ZSignFieldWithTokenMutationSchema) .input(ZSignFieldWithTokenMutationSchema)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {

View File

@ -1,5 +1,26 @@
import { z } from 'zod'; import { z } from 'zod';
import { FieldType } from '@documenso/prisma/client';
export const ZAddFieldsMutationSchema = z.object({
documentId: z.number(),
fields: z.array(
z.object({
formId: z.string().min(1),
nativeId: z.number().optional(),
type: z.nativeEnum(FieldType),
signerEmail: z.string().min(1),
pageNumber: z.number().min(1),
pageX: z.number().min(0),
pageY: z.number().min(0),
pageWidth: z.number().min(0),
pageHeight: z.number().min(0),
}),
),
});
export type TAddFieldsMutationSchema = z.infer<typeof ZAddFieldsMutationSchema>;
export const ZSignFieldWithTokenMutationSchema = z.object({ export const ZSignFieldWithTokenMutationSchema = z.object({
token: z.string(), token: z.string(),
fieldId: z.number(), fieldId: z.number(),

View File

@ -0,0 +1,54 @@
import { TRPCError } from '@trpc/server';
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import { authenticatedProcedure, procedure, router } from '../trpc';
import { ZAddSignersMutationSchema, ZCompleteDocumentWithTokenMutationSchema } from './schema';
export const recipientRouter = router({
addSigners: authenticatedProcedure
.input(ZAddSignersMutationSchema)
.mutation(async ({ input, ctx }) => {
try {
const { documentId, signers } = input;
return await setRecipientsForDocument({
userId: ctx.user.id,
documentId,
recipients: signers.map((signer) => ({
id: signer.nativeId,
email: signer.email,
name: signer.name,
})),
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to sign this field. Please try again later.',
});
}
}),
completeDocumentWithToken: procedure
.input(ZCompleteDocumentWithTokenMutationSchema)
.mutation(async ({ input }) => {
try {
const { token, documentId } = input;
return await completeDocumentWithToken({
token,
documentId,
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to sign this field. Please try again later.',
});
}
}),
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
export const ZAddSignersMutationSchema = z
.object({
documentId: z.number(),
signers: z.array(
z.object({
nativeId: z.number().optional(),
email: z.string().email().min(1),
name: z.string(),
}),
),
})
.refine(
(schema) => {
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
return new Set(emails).size === emails.length;
},
// Dirty hack to handle errors when .root is populated for an array type
{ message: 'Signers must have unique emails', path: ['signers__root'] },
);
export type TAddSignersMutationSchema = z.infer<typeof ZAddSignersMutationSchema>;
export const ZCompleteDocumentWithTokenMutationSchema = z.object({
token: z.string(),
documentId: z.number(),
});
export type TCompleteDocumentWithTokenMutationSchema = z.infer<
typeof ZCompleteDocumentWithTokenMutationSchema
>;

View File

@ -3,6 +3,7 @@ import { authRouter } from './auth-router/router';
import { documentRouter } from './document-router/router'; import { documentRouter } from './document-router/router';
import { fieldRouter } from './field-router/router'; import { fieldRouter } from './field-router/router';
import { profileRouter } from './profile-router/router'; import { profileRouter } from './profile-router/router';
import { recipientRouter } from './recipient-router/router';
import { shareLinkRouter } from './share-link-router/router'; import { shareLinkRouter } from './share-link-router/router';
import { singleplayerRouter } from './singleplayer-router/router'; import { singleplayerRouter } from './singleplayer-router/router';
import { router } from './trpc'; import { router } from './trpc';
@ -13,6 +14,7 @@ export const appRouter = router({
profile: profileRouter, profile: profileRouter,
document: documentRouter, document: documentRouter,
field: fieldRouter, field: fieldRouter,
recipient: recipientRouter,
admin: adminRouter, admin: adminRouter,
shareLink: shareLinkRouter, shareLink: shareLinkRouter,
singleplayer: singleplayerRouter, singleplayer: singleplayerRouter,

View File

@ -3,7 +3,7 @@ import { createElement } from 'react';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { renderAsync } from '@documenso/email/render';
import { DocumentSelfSignedEmailTemplate } from '@documenso/email/templates/document-self-signed'; import { DocumentSelfSignedEmailTemplate } from '@documenso/email/templates/document-self-signed';
import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/constants/email'; import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/constants/email';
import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in-pdf'; import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in-pdf';
@ -36,6 +36,7 @@ export const singleplayerRouter = router({
}); });
const doc = await PDFDocument.load(document); const doc = await PDFDocument.load(document);
const createdAt = new Date(); const createdAt = new Date();
const isBase64 = signer.signature.startsWith('data:image/png;base64,'); const isBase64 = signer.signature.startsWith('data:image/png;base64,');
@ -149,6 +150,11 @@ export const singleplayerRouter = router({
assetBaseUrl: process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000', assetBaseUrl: process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000',
}); });
const [html, text] = await Promise.all([
renderAsync(template),
renderAsync(template, { plainText: true }),
]);
// Send email to signer. // Send email to signer.
await mailer.sendMail({ await mailer.sendMail({
to: { to: {
@ -160,8 +166,8 @@ export const singleplayerRouter = router({
address: FROM_ADDRESS, address: FROM_ADDRESS,
}, },
subject: 'Document signed', subject: 'Document signed',
html: render(template), html,
text: render(template, { plainText: true }), text,
attachments: [{ content: signedPdfBuffer, filename: documentName }], attachments: [{ content: signedPdfBuffer, filename: documentName }],
}); });

View File

@ -62,7 +62,7 @@
"framer-motion": "^10.12.8", "framer-motion": "^10.12.8",
"lucide-react": "^0.279.0", "lucide-react": "^0.279.0",
"luxon": "^3.4.2", "luxon": "^3.4.2",
"next": "14.0.0", "next": "14.0.3",
"pdfjs-dist": "3.6.172", "pdfjs-dist": "3.6.172",
"react-day-picker": "^8.7.1", "react-day-picker": "^8.7.1",
"react-hook-form": "^7.45.4", "react-hook-form": "^7.45.4",