mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
fix: remove server actions (#684)
This commit is contained in:
@ -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,
|
||||||
|
|||||||
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -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,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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
5970
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
17
packages/email/components.ts
Normal file
17
packages/email/components.ts
Normal 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';
|
||||||
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { render } from '@react-email/components';
|
export { render, renderAsync } from '@react-email/render';
|
||||||
|
|||||||
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 = ({
|
||||||
|
|||||||
@ -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>;
|
||||||
|
|||||||
@ -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> & {
|
||||||
|
|||||||
@ -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>;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>;
|
||||||
|
|
||||||
|
|||||||
@ -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>;
|
||||||
|
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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';
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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 }) => {
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
54
packages/trpc/server/recipient-router/router.ts
Normal file
54
packages/trpc/server/recipient-router/router.ts
Normal 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.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
33
packages/trpc/server/recipient-router/schema.ts
Normal file
33
packages/trpc/server/recipient-router/schema.ts
Normal 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
|
||||||
|
>;
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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 }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user