diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
index a5dc9e23e..613146b99 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
@@ -4,7 +4,7 @@ import { useState } from 'react';
import { useRouter } from 'next/navigation';
-import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client';
+import type { DocumentData, DocumentMeta, Field, Recipient, User } from '@documenso/prisma/client';
import { DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { trpc } from '@documenso/trpc/react';
@@ -29,6 +29,7 @@ export type EditDocumentFormProps = {
user: User;
document: DocumentWithData;
recipients: Recipient[];
+ documentMeta: DocumentMeta | null;
fields: Field[];
documentData: DocumentData;
};
@@ -41,6 +42,7 @@ export const EditDocumentForm = ({
document,
recipients,
fields,
+ documentMeta,
user: _user,
documentData,
}: EditDocumentFormProps) => {
@@ -185,7 +187,7 @@ export const EditDocumentForm = ({
gradient
>
-
+
diff --git a/apps/web/src/app/(dashboard)/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/documents/[id]/page.tsx
index b26b6308c..b19e1cf4b 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/page.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/page.tsx
@@ -13,6 +13,7 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { EditDocumentForm } from '~/app/(dashboard)/documents/[id]/edit-document';
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
import { DocumentStatus } from '~/components/formatter/document-status';
+import { getDocumentMetaByDocumentId } from '@documenso/lib/server-only/document/get-document-meta-by-document-id';
export type DocumentPageProps = {
params: {
@@ -41,6 +42,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
}
const { documentData } = document;
+ const documentMeta = await getDocumentMetaByDocumentId({ id: document!.id }).catch(() => null);
const [recipients, fields] = await Promise.all([
getRecipientsForDocument({
@@ -83,6 +85,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
className="mt-8"
document={document}
user={user}
+ documentMeta={documentMeta}
recipients={recipients}
fields={fields}
documentData={documentData}
@@ -91,7 +94,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
{document.status === InternalDocumentStatus.COMPLETED && (
-
+
)}
diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx
index efd0b266c..80d88ce40 100644
--- a/apps/web/src/app/(signing)/sign/[token]/page.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx
@@ -101,7 +101,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
gradient
>
-
+
diff --git a/packages/lib/server-only/document-meta/upsert-document-meta.ts b/packages/lib/server-only/document-meta/upsert-document-meta.ts
index 34c33e7cd..3c12bcb35 100644
--- a/packages/lib/server-only/document-meta/upsert-document-meta.ts
+++ b/packages/lib/server-only/document-meta/upsert-document-meta.ts
@@ -4,10 +4,11 @@ import { prisma } from '@documenso/prisma';
export type CreateDocumentMetaOptions = {
documentId: number;
- subject: string;
- message: string;
- timezone: string;
- dateFormat: string;
+ subject?: string;
+ message?: string;
+ timezone?: string;
+ documentPassword?: string;
+ dateFormat?: string;
userId: number;
};
@@ -18,6 +19,7 @@ export const upsertDocumentMeta = async ({
dateFormat,
documentId,
userId,
+ documentPassword,
}: CreateDocumentMetaOptions) => {
await prisma.document.findFirstOrThrow({
where: {
@@ -35,12 +37,14 @@ export const upsertDocumentMeta = async ({
message,
dateFormat,
timezone,
+ documentPassword,
documentId,
},
update: {
subject,
message,
dateFormat,
+ documentPassword,
timezone,
},
});
diff --git a/packages/lib/server-only/document/duplicate-document-by-id.ts b/packages/lib/server-only/document/duplicate-document-by-id.ts
index 5986b4cfe..6cdc5bc49 100644
--- a/packages/lib/server-only/document/duplicate-document-by-id.ts
+++ b/packages/lib/server-only/document/duplicate-document-by-id.ts
@@ -26,6 +26,7 @@ export const duplicateDocumentById = async ({ id, userId }: DuplicateDocumentByI
message: true,
subject: true,
dateFormat: true,
+ documentPassword: true,
timezone: true,
},
},
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index f0bfc6fda..59a92f296 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -162,6 +162,7 @@ model DocumentMeta {
subject String?
message String?
timezone String? @db.Text @default("Etc/UTC")
+ documentPassword String?
dateFormat String? @db.Text @default("yyyy-MM-dd hh:mm a")
documentId Int @unique
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts
index b4a1b60e3..717f8bed2 100644
--- a/packages/trpc/server/document-router/router.ts
+++ b/packages/trpc/server/document-router/router.ts
@@ -24,6 +24,7 @@ import {
ZSearchDocumentsMutationSchema,
ZSendDocumentMutationSchema,
ZSetFieldsForDocumentMutationSchema,
+ ZSetPasswordForDocumentMutationSchema,
ZSetRecipientsForDocumentMutationSchema,
ZSetTitleForDocumentMutationSchema,
} from './schema';
@@ -174,6 +175,29 @@ export const documentRouter = router({
});
}
}),
+
+ setDocumentPassword: authenticatedProcedure
+ .input(ZSetPasswordForDocumentMutationSchema)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const { documentId, documentPassword } = input;
+ await upsertDocumentMeta({
+ documentId,
+ documentPassword,
+ userId: ctx.user.id,
+ });
+
+ } catch (err) {
+ console.error(err);
+
+ throw new TRPCError({
+ code: 'BAD_REQUEST',
+ message: 'We were unable to send this document. Please try again later.',
+ });
+ }
+ }),
+
+
sendDocument: authenticatedProcedure
.input(ZSendDocumentMutationSchema)
diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts
index 4559f65f3..baccc6b85 100644
--- a/packages/trpc/server/document-router/schema.ts
+++ b/packages/trpc/server/document-router/schema.ts
@@ -73,6 +73,11 @@ export const ZSendDocumentMutationSchema = z.object({
}),
});
+export const ZSetPasswordForDocumentMutationSchema = z.object({
+ documentId: z.number(),
+ documentPassword: z.string(),
+});
+
export const ZResendDocumentMutationSchema = z.object({
documentId: z.number(),
recipients: z.array(z.number()).min(1),
diff --git a/packages/ui/primitives/pdf-viewer.tsx b/packages/ui/primitives/pdf-viewer.tsx
index be2d0cc4a..b109dca24 100644
--- a/packages/ui/primitives/pdf-viewer.tsx
+++ b/packages/ui/primitives/pdf-viewer.tsx
@@ -10,11 +10,13 @@ import 'react-pdf/dist/esm/Page/TextLayer.css';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { getFile } from '@documenso/lib/universal/upload/get-file';
-import type { DocumentData } from '@documenso/prisma/client';
+import type { DocumentData, DocumentMeta } from '@documenso/prisma/client';
import { cn } from '../lib/utils';
import { PasswordDialog } from './document-password-dialog';
import { useToast } from './use-toast';
+import { trpc } from '@documenso/trpc/react';
+import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
export type LoadedPDFDocument = PDFDocumentProxy;
@@ -44,6 +46,8 @@ const PDFLoader = () => (
export type PDFViewerProps = {
className?: string;
documentData: DocumentData;
+ document?: DocumentWithData;
+ documentMeta?: DocumentMeta | null;
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
onPageClick?: OnPDFViewerPageClick;
[key: string]: unknown;
@@ -52,6 +56,8 @@ export type PDFViewerProps = {
export const PDFViewer = ({
className,
documentData,
+ document,
+ documentMeta,
onDocumentLoad,
onPageClick,
...props
@@ -62,7 +68,7 @@ export const PDFViewer = ({
const [isDocumentBytesLoading, setIsDocumentBytesLoading] = useState(false);
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
- const [password, setPassword] = useState(null);
+ const [password, setPassword] = useState(documentMeta?.documentPassword || null);
const passwordCallbackRef = useRef<((password: string | null) => void) | null>(null);
const [isPasswordError, setIsPasswordError] = useState(false);
const [documentBytes, setDocumentBytes] = useState(null);
@@ -76,6 +82,9 @@ export const PDFViewer = ({
[documentData.data, documentData.type],
);
+ const {mutateAsync: addDocumentPassword } = trpc.document.setDocumentPassword.useMutation();
+
+
const isLoading = isDocumentBytesLoading || !documentBytes;
const onDocumentLoaded = (doc: LoadedPDFDocument) => {
@@ -83,12 +92,20 @@ export const PDFViewer = ({
onDocumentLoad?.(doc);
};
- const onPasswordSubmit = () => {
+ const onPasswordSubmit = async() => {
setIsPasswordModalOpen(false);
- if (passwordCallbackRef.current) {
- passwordCallbackRef.current(password);
+ try{
+ await addDocumentPassword({
+ documentId: document?.id ?? 0,
+ documentPassword: password!,
+ });
+ passwordCallbackRef.current?.(password);
+ } catch (error) {
+ console.error('Error adding document password:', error);
+ } finally {
passwordCallbackRef.current = null;
}
+
};
const onDocumentPageClick = (
@@ -189,6 +206,11 @@ export const PDFViewer = ({
'h-[80vh] max-h-[60rem]': numPages === 0,
})}
onPassword={(callback, reason) => {
+ // If the documentMeta already has a password, we don't need to ask for it again.
+ if(password && reason !== PasswordResponses.INCORRECT_PASSWORD){
+ callback(password);
+ return;
+ }
setIsPasswordModalOpen(true);
passwordCallbackRef.current = callback;
switch (reason) {