feat add documentPassword to documenet meta and improve the ux

Signed-off-by: harkiratsm <multaniharry714@gmail.com>
This commit is contained in:
harkiratsm
2024-01-12 20:54:59 +05:30
parent eeb6a072aa
commit 68953d1253
9 changed files with 75 additions and 13 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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,
}, },
}); });

View File

@ -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,
}, },
}, },

View File

@ -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)

View File

@ -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 }) => {

View File

@ -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),

View File

@ -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) {