mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
feat add documentPassword to documenet meta and improve the ux
Signed-off-by: harkiratsm <multaniharry714@gmail.com>
This commit is contained in:
@ -4,7 +4,7 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
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 { 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 { trpc } from '@documenso/trpc/react';
|
||||||
@ -29,6 +29,7 @@ export type EditDocumentFormProps = {
|
|||||||
user: User;
|
user: User;
|
||||||
document: DocumentWithData;
|
document: DocumentWithData;
|
||||||
recipients: Recipient[];
|
recipients: Recipient[];
|
||||||
|
documentMeta: DocumentMeta | null;
|
||||||
fields: Field[];
|
fields: Field[];
|
||||||
documentData: DocumentData;
|
documentData: DocumentData;
|
||||||
};
|
};
|
||||||
@ -41,6 +42,7 @@ export const EditDocumentForm = ({
|
|||||||
document,
|
document,
|
||||||
recipients,
|
recipients,
|
||||||
fields,
|
fields,
|
||||||
|
documentMeta,
|
||||||
user: _user,
|
user: _user,
|
||||||
documentData,
|
documentData,
|
||||||
}: EditDocumentFormProps) => {
|
}: EditDocumentFormProps) => {
|
||||||
@ -185,7 +187,7 @@ export const EditDocumentForm = ({
|
|||||||
gradient
|
gradient
|
||||||
>
|
>
|
||||||
<CardContent className="p-2">
|
<CardContent className="p-2">
|
||||||
<LazyPDFViewer key={documentData.id} documentData={documentData} />
|
<LazyPDFViewer key={documentData.id} documentData={documentData} document={document} documentMeta={documentMeta} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
|||||||
import { EditDocumentForm } from '~/app/(dashboard)/documents/[id]/edit-document';
|
import { EditDocumentForm } from '~/app/(dashboard)/documents/[id]/edit-document';
|
||||||
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
|
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
|
||||||
import { DocumentStatus } from '~/components/formatter/document-status';
|
import { DocumentStatus } from '~/components/formatter/document-status';
|
||||||
|
import { getDocumentMetaByDocumentId } from '@documenso/lib/server-only/document/get-document-meta-by-document-id';
|
||||||
|
|
||||||
export type DocumentPageProps = {
|
export type DocumentPageProps = {
|
||||||
params: {
|
params: {
|
||||||
@ -41,6 +42,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { documentData } = document;
|
const { documentData } = document;
|
||||||
|
const documentMeta = await getDocumentMetaByDocumentId({ id: document!.id }).catch(() => null);
|
||||||
|
|
||||||
const [recipients, fields] = await Promise.all([
|
const [recipients, fields] = await Promise.all([
|
||||||
getRecipientsForDocument({
|
getRecipientsForDocument({
|
||||||
@ -83,6 +85,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
|
|||||||
className="mt-8"
|
className="mt-8"
|
||||||
document={document}
|
document={document}
|
||||||
user={user}
|
user={user}
|
||||||
|
documentMeta={documentMeta}
|
||||||
recipients={recipients}
|
recipients={recipients}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
documentData={documentData}
|
documentData={documentData}
|
||||||
@ -91,7 +94,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
|
|||||||
|
|
||||||
{document.status === InternalDocumentStatus.COMPLETED && (
|
{document.status === InternalDocumentStatus.COMPLETED && (
|
||||||
<div className="mx-auto mt-12 max-w-2xl">
|
<div className="mx-auto mt-12 max-w-2xl">
|
||||||
<LazyPDFViewer key={documentData.id} documentData={documentData} />
|
<LazyPDFViewer document={document} key={documentData.id} documentMeta={documentMeta} documentData={documentData} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
gradient
|
gradient
|
||||||
>
|
>
|
||||||
<CardContent className="p-2">
|
<CardContent className="p-2">
|
||||||
<LazyPDFViewer key={documentData.id} documentData={documentData} />
|
<LazyPDFViewer key={documentData.id} documentData={documentData} document={document} documentMeta={documentMeta} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,11 @@ import { prisma } from '@documenso/prisma';
|
|||||||
|
|
||||||
export type CreateDocumentMetaOptions = {
|
export type CreateDocumentMetaOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
subject: string;
|
subject?: string;
|
||||||
message: string;
|
message?: string;
|
||||||
timezone: string;
|
timezone?: string;
|
||||||
dateFormat: string;
|
documentPassword?: string;
|
||||||
|
dateFormat?: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ export const upsertDocumentMeta = async ({
|
|||||||
dateFormat,
|
dateFormat,
|
||||||
documentId,
|
documentId,
|
||||||
userId,
|
userId,
|
||||||
|
documentPassword,
|
||||||
}: CreateDocumentMetaOptions) => {
|
}: CreateDocumentMetaOptions) => {
|
||||||
await prisma.document.findFirstOrThrow({
|
await prisma.document.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
@ -35,12 +37,14 @@ export const upsertDocumentMeta = async ({
|
|||||||
message,
|
message,
|
||||||
dateFormat,
|
dateFormat,
|
||||||
timezone,
|
timezone,
|
||||||
|
documentPassword,
|
||||||
documentId,
|
documentId,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
dateFormat,
|
dateFormat,
|
||||||
|
documentPassword,
|
||||||
timezone,
|
timezone,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export const duplicateDocumentById = async ({ id, userId }: DuplicateDocumentByI
|
|||||||
message: true,
|
message: true,
|
||||||
subject: true,
|
subject: true,
|
||||||
dateFormat: true,
|
dateFormat: true,
|
||||||
|
documentPassword: true,
|
||||||
timezone: true,
|
timezone: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -162,6 +162,7 @@ model DocumentMeta {
|
|||||||
subject String?
|
subject String?
|
||||||
message String?
|
message String?
|
||||||
timezone String? @db.Text @default("Etc/UTC")
|
timezone String? @db.Text @default("Etc/UTC")
|
||||||
|
documentPassword String?
|
||||||
dateFormat String? @db.Text @default("yyyy-MM-dd hh:mm a")
|
dateFormat String? @db.Text @default("yyyy-MM-dd hh:mm a")
|
||||||
documentId Int @unique
|
documentId Int @unique
|
||||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import {
|
|||||||
ZSearchDocumentsMutationSchema,
|
ZSearchDocumentsMutationSchema,
|
||||||
ZSendDocumentMutationSchema,
|
ZSendDocumentMutationSchema,
|
||||||
ZSetFieldsForDocumentMutationSchema,
|
ZSetFieldsForDocumentMutationSchema,
|
||||||
|
ZSetPasswordForDocumentMutationSchema,
|
||||||
ZSetRecipientsForDocumentMutationSchema,
|
ZSetRecipientsForDocumentMutationSchema,
|
||||||
ZSetTitleForDocumentMutationSchema,
|
ZSetTitleForDocumentMutationSchema,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
@ -175,6 +176,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
|
sendDocument: authenticatedProcedure
|
||||||
.input(ZSendDocumentMutationSchema)
|
.input(ZSendDocumentMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
|||||||
@ -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({
|
export const ZResendDocumentMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
recipients: z.array(z.number()).min(1),
|
recipients: z.array(z.number()).min(1),
|
||||||
|
|||||||
@ -10,11 +10,13 @@ import 'react-pdf/dist/esm/Page/TextLayer.css';
|
|||||||
|
|
||||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
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 { cn } from '../lib/utils';
|
||||||
import { PasswordDialog } from './document-password-dialog';
|
import { PasswordDialog } from './document-password-dialog';
|
||||||
import { useToast } from './use-toast';
|
import { useToast } from './use-toast';
|
||||||
|
import { trpc } from '@documenso/trpc/react';
|
||||||
|
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||||
|
|
||||||
export type LoadedPDFDocument = PDFDocumentProxy;
|
export type LoadedPDFDocument = PDFDocumentProxy;
|
||||||
|
|
||||||
@ -44,6 +46,8 @@ const PDFLoader = () => (
|
|||||||
export type PDFViewerProps = {
|
export type PDFViewerProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
documentData: DocumentData;
|
documentData: DocumentData;
|
||||||
|
document?: DocumentWithData;
|
||||||
|
documentMeta?: DocumentMeta | null;
|
||||||
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
|
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
|
||||||
onPageClick?: OnPDFViewerPageClick;
|
onPageClick?: OnPDFViewerPageClick;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
@ -52,6 +56,8 @@ export type PDFViewerProps = {
|
|||||||
export const PDFViewer = ({
|
export const PDFViewer = ({
|
||||||
className,
|
className,
|
||||||
documentData,
|
documentData,
|
||||||
|
document,
|
||||||
|
documentMeta,
|
||||||
onDocumentLoad,
|
onDocumentLoad,
|
||||||
onPageClick,
|
onPageClick,
|
||||||
...props
|
...props
|
||||||
@ -62,7 +68,7 @@ export const PDFViewer = ({
|
|||||||
|
|
||||||
const [isDocumentBytesLoading, setIsDocumentBytesLoading] = useState(false);
|
const [isDocumentBytesLoading, setIsDocumentBytesLoading] = useState(false);
|
||||||
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
||||||
const [password, setPassword] = useState<string | null>(null);
|
const [password, setPassword] = useState<string | null>(documentMeta?.documentPassword || null);
|
||||||
const passwordCallbackRef = useRef<((password: string | null) => void) | null>(null);
|
const passwordCallbackRef = useRef<((password: string | null) => void) | null>(null);
|
||||||
const [isPasswordError, setIsPasswordError] = useState(false);
|
const [isPasswordError, setIsPasswordError] = useState(false);
|
||||||
const [documentBytes, setDocumentBytes] = useState<Uint8Array | null>(null);
|
const [documentBytes, setDocumentBytes] = useState<Uint8Array | null>(null);
|
||||||
@ -76,6 +82,9 @@ export const PDFViewer = ({
|
|||||||
[documentData.data, documentData.type],
|
[documentData.data, documentData.type],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {mutateAsync: addDocumentPassword } = trpc.document.setDocumentPassword.useMutation();
|
||||||
|
|
||||||
|
|
||||||
const isLoading = isDocumentBytesLoading || !documentBytes;
|
const isLoading = isDocumentBytesLoading || !documentBytes;
|
||||||
|
|
||||||
const onDocumentLoaded = (doc: LoadedPDFDocument) => {
|
const onDocumentLoaded = (doc: LoadedPDFDocument) => {
|
||||||
@ -83,12 +92,20 @@ export const PDFViewer = ({
|
|||||||
onDocumentLoad?.(doc);
|
onDocumentLoad?.(doc);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPasswordSubmit = () => {
|
const onPasswordSubmit = async() => {
|
||||||
setIsPasswordModalOpen(false);
|
setIsPasswordModalOpen(false);
|
||||||
if (passwordCallbackRef.current) {
|
try{
|
||||||
passwordCallbackRef.current(password);
|
await addDocumentPassword({
|
||||||
|
documentId: document?.id ?? 0,
|
||||||
|
documentPassword: password!,
|
||||||
|
});
|
||||||
|
passwordCallbackRef.current?.(password);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding document password:', error);
|
||||||
|
} finally {
|
||||||
passwordCallbackRef.current = null;
|
passwordCallbackRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDocumentPageClick = (
|
const onDocumentPageClick = (
|
||||||
@ -189,6 +206,11 @@ export const PDFViewer = ({
|
|||||||
'h-[80vh] max-h-[60rem]': numPages === 0,
|
'h-[80vh] max-h-[60rem]': numPages === 0,
|
||||||
})}
|
})}
|
||||||
onPassword={(callback, reason) => {
|
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);
|
setIsPasswordModalOpen(true);
|
||||||
passwordCallbackRef.current = callback;
|
passwordCallbackRef.current = callback;
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
|
|||||||
Reference in New Issue
Block a user