fix: tidy code

This commit is contained in:
Mythie
2024-01-17 17:17:08 +11:00
parent 68953d1253
commit a94b829ee0
13 changed files with 161 additions and 108 deletions

View File

@ -58,6 +58,8 @@ export const EditDocumentForm = ({
const { mutateAsync: addFields } = trpc.field.addFields.useMutation(); const { mutateAsync: addFields } = trpc.field.addFields.useMutation();
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation(); const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation();
const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation(); const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation();
const { mutateAsync: setPasswordForDocument } =
trpc.document.setPasswordForDocument.useMutation();
const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = { const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = {
title: { title: {
@ -178,6 +180,13 @@ export const EditDocumentForm = ({
} }
}; };
const onPasswordSubmit = async (password: string) => {
await setPasswordForDocument({
documentId: document.id,
password,
});
};
const currentDocumentFlow = documentFlow[step]; const currentDocumentFlow = documentFlow[step];
return ( return (
@ -187,7 +196,13 @@ export const EditDocumentForm = ({
gradient gradient
> >
<CardContent className="p-2"> <CardContent className="p-2">
<LazyPDFViewer key={documentData.id} documentData={documentData} document={document} documentMeta={documentMeta} /> <LazyPDFViewer
key={documentData.id}
documentData={documentData}
document={document}
password={documentMeta?.password}
onPasswordSubmit={onPasswordSubmit}
/>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -13,7 +13,6 @@ 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,8 +40,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
redirect('/documents'); redirect('/documents');
} }
const { documentData } = document; const { documentData, documentMeta } = document;
const documentMeta = await getDocumentMetaByDocumentId({ id: document!.id }).catch(() => null);
const [recipients, fields] = await Promise.all([ const [recipients, fields] = await Promise.all([
getRecipientsForDocument({ getRecipientsForDocument({
@ -94,7 +92,12 @@ 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 document={document} key={documentData.id} documentMeta={documentMeta} documentData={documentData} /> <LazyPDFViewer
document={document}
key={documentData.id}
documentMeta={documentMeta}
documentData={documentData}
/>
</div> </div>
)} )}
</div> </div>

View File

@ -101,7 +101,12 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
gradient gradient
> >
<CardContent className="p-2"> <CardContent className="p-2">
<LazyPDFViewer key={documentData.id} documentData={documentData} document={document} documentMeta={documentMeta} /> <LazyPDFViewer
key={documentData.id}
documentData={documentData}
document={document}
password={documentMeta?.password}
/>
</CardContent> </CardContent>
</Card> </Card>

3
package-lock.json generated
View File

@ -19869,7 +19869,8 @@
"react-rnd": "^10.4.1", "react-rnd": "^10.4.1",
"tailwind-merge": "^1.12.0", "tailwind-merge": "^1.12.0",
"tailwindcss-animate": "^1.0.5", "tailwindcss-animate": "^1.0.5",
"ts-pattern": "^5.0.5" "ts-pattern": "^5.0.5",
"zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@documenso/tailwind-config": "*", "@documenso/tailwind-config": "*",

View File

@ -7,7 +7,7 @@ export type CreateDocumentMetaOptions = {
subject?: string; subject?: string;
message?: string; message?: string;
timezone?: string; timezone?: string;
documentPassword?: string; password?: string;
dateFormat?: string; dateFormat?: string;
userId: number; userId: number;
}; };
@ -19,7 +19,7 @@ export const upsertDocumentMeta = async ({
dateFormat, dateFormat,
documentId, documentId,
userId, userId,
documentPassword, password,
}: CreateDocumentMetaOptions) => { }: CreateDocumentMetaOptions) => {
await prisma.document.findFirstOrThrow({ await prisma.document.findFirstOrThrow({
where: { where: {
@ -37,14 +37,14 @@ export const upsertDocumentMeta = async ({
message, message,
dateFormat, dateFormat,
timezone, timezone,
documentPassword, password,
documentId, documentId,
}, },
update: { update: {
subject, subject,
message, message,
dateFormat, dateFormat,
documentPassword, password,
timezone, timezone,
}, },
}); });

View File

@ -26,7 +26,7 @@ export const duplicateDocumentById = async ({ id, userId }: DuplicateDocumentByI
message: true, message: true,
subject: true, subject: true,
dateFormat: true, dateFormat: true,
documentPassword: true, password: true,
timezone: true, timezone: true,
}, },
}, },

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "DocumentMeta" ADD COLUMN "password" TEXT;

View File

@ -162,7 +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? password 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

@ -176,29 +176,27 @@ export const documentRouter = router({
} }
}), }),
setDocumentPassword: authenticatedProcedure setPasswordForDocument: authenticatedProcedure
.input(ZSetPasswordForDocumentMutationSchema) .input(ZSetPasswordForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
try { try {
const { documentId, documentPassword } = input; const { documentId, password } = input;
await upsertDocumentMeta({ await upsertDocumentMeta({
documentId, documentId,
documentPassword, password,
userId: ctx.user.id, userId: ctx.user.id,
}); });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw new TRPCError({ throw new TRPCError({
code: 'BAD_REQUEST', code: 'BAD_REQUEST',
message: 'We were unable to send this document. Please try again later.', message: 'We were unable to set the password for this document. Please try again later.',
}); });
} }
}), }),
sendDocument: authenticatedProcedure sendDocument: authenticatedProcedure
.input(ZSendDocumentMutationSchema) .input(ZSendDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -75,9 +75,13 @@ export const ZSendDocumentMutationSchema = z.object({
export const ZSetPasswordForDocumentMutationSchema = z.object({ export const ZSetPasswordForDocumentMutationSchema = z.object({
documentId: z.number(), documentId: z.number(),
documentPassword: z.string(), password: z.string(),
}); });
export type TSetPasswordForDocumentMutationSchema = z.infer<
typeof ZSetPasswordForDocumentMutationSchema
>;
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

@ -70,6 +70,7 @@
"react-rnd": "^10.4.1", "react-rnd": "^10.4.1",
"tailwind-merge": "^1.12.0", "tailwind-merge": "^1.12.0",
"tailwindcss-animate": "^1.0.5", "tailwindcss-animate": "^1.0.5",
"ts-pattern": "^5.0.5" "ts-pattern": "^5.0.5",
"zod": "^3.22.4"
} }
} }

View File

@ -1,55 +1,95 @@
import React from 'react'; import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Button } from './button'; import { Button } from './button';
import { import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog';
Dialog, import { Form, FormControl, FormField, FormItem, FormMessage } from './form/form';
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from './dialog';
import { Input } from './input'; import { Input } from './input';
const ZPasswordDialogFormSchema = z.object({
password: z.string(),
});
type TPasswordDialogFormSchema = z.infer<typeof ZPasswordDialogFormSchema>;
type PasswordDialogProps = { type PasswordDialogProps = {
open: boolean; open: boolean;
onOpenChange: (_open: boolean) => void; onOpenChange: (_open: boolean) => void;
setPassword: (_password: string) => void; defaultPassword?: string;
onPasswordSubmit: () => void; onPasswordSubmit?: (password: string) => void;
isError?: boolean; isError?: boolean;
}; };
export const PasswordDialog = ({ export const PasswordDialog = ({
open, open,
onOpenChange, onOpenChange,
defaultPassword,
onPasswordSubmit, onPasswordSubmit,
isError, isError,
setPassword,
}: PasswordDialogProps) => { }: PasswordDialogProps) => {
const form = useForm<TPasswordDialogFormSchema>({
defaultValues: {
password: defaultPassword ?? '',
},
resolver: zodResolver(ZPasswordDialogFormSchema),
});
const onFormSubmit = ({ password }: TPasswordDialogFormSchema) => {
onPasswordSubmit?.(password);
};
useEffect(() => {
if (isError) {
form.setError('password', {
type: 'manual',
message: 'The password you have entered is incorrect. Please try again.',
});
}
}, [form, isError]);
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open}>
<DialogContent className="max-w-md"> <DialogContent className="w-full max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>Password Required</DialogTitle> <DialogTitle>Password Required</DialogTitle>
<DialogDescription className="text-muted-foreground"> <DialogDescription className="text-muted-foreground">
This document is password protected. Please enter the password to view the document. This document is password protected. Please enter the password to view the document.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter className="flex w-full items-center justify-center gap-4">
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex flex-wrap items-start justify-between gap-4">
<FormField
name="password"
control={form.control}
render={({ field }) => (
<FormItem className="relative flex-1">
<FormControl>
<Input <Input
type="password" type="password"
className="bg-background mt-1.5" className="bg-background"
placeholder="Enter password" placeholder="Enter password"
onChange={(e) => setPassword(e.target.value)}
autoComplete="off" autoComplete="off"
{...field}
/> />
<Button onClick={onPasswordSubmit}>Submit</Button> </FormControl>
</DialogFooter>
{isError && ( <FormMessage />
<span className="text-xs text-red-500"> </FormItem>
The password you entered is incorrect. Please try again.
</span>
)} )}
/>
<div>
<Button>Submit</Button>
</div>
</fieldset>
</form>
</Form>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@ -7,16 +7,16 @@ import { type PDFDocumentProxy, PasswordResponses } from 'pdfjs-dist';
import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf'; import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import 'react-pdf/dist/esm/Page/TextLayer.css'; import 'react-pdf/dist/esm/Page/TextLayer.css';
import { match } from 'ts-pattern';
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, DocumentMeta } from '@documenso/prisma/client'; import type { DocumentData } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
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;
@ -47,7 +47,8 @@ export type PDFViewerProps = {
className?: string; className?: string;
documentData: DocumentData; documentData: DocumentData;
document?: DocumentWithData; document?: DocumentWithData;
documentMeta?: DocumentMeta | null; password?: string | null;
onPasswordSubmit?: (password: string) => void | Promise<void>;
onDocumentLoad?: (_doc: LoadedPDFDocument) => void; onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
onPageClick?: OnPDFViewerPageClick; onPageClick?: OnPDFViewerPageClick;
[key: string]: unknown; [key: string]: unknown;
@ -56,8 +57,8 @@ export type PDFViewerProps = {
export const PDFViewer = ({ export const PDFViewer = ({
className, className,
documentData, documentData,
document, password: defaultPassword,
documentMeta, onPasswordSubmit,
onDocumentLoad, onDocumentLoad,
onPageClick, onPageClick,
...props ...props
@ -66,10 +67,10 @@ export const PDFViewer = ({
const $el = useRef<HTMLDivElement>(null); const $el = useRef<HTMLDivElement>(null);
const passwordCallbackRef = useRef<((password: string | null) => void) | null>(null);
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>(documentMeta?.documentPassword || 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);
@ -82,9 +83,6 @@ 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) => {
@ -92,22 +90,6 @@ export const PDFViewer = ({
onDocumentLoad?.(doc); onDocumentLoad?.(doc);
}; };
const onPasswordSubmit = async() => {
setIsPasswordModalOpen(false);
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 = ( const onDocumentPageClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>, event: React.MouseEvent<HTMLDivElement, MouseEvent>,
pageNumber: number, pageNumber: number,
@ -206,23 +188,19 @@ 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 the document already has a password, we don't need to ask for it again.
if(password && reason !== PasswordResponses.INCORRECT_PASSWORD){ if (defaultPassword && reason !== PasswordResponses.INCORRECT_PASSWORD) {
callback(password); callback(defaultPassword);
return; return;
} }
setIsPasswordModalOpen(true); setIsPasswordModalOpen(true);
passwordCallbackRef.current = callback; passwordCallbackRef.current = callback;
switch (reason) {
case PasswordResponses.NEED_PASSWORD: match(reason)
setIsPasswordError(false); .with(PasswordResponses.NEED_PASSWORD, () => setIsPasswordError(false))
break; .with(PasswordResponses.INCORRECT_PASSWORD, () => setIsPasswordError(true));
case PasswordResponses.INCORRECT_PASSWORD:
setIsPasswordError(true);
break;
default:
break;
}
}} }}
onLoadSuccess={(d) => onDocumentLoaded(d)} onLoadSuccess={(d) => onDocumentLoaded(d)}
// Uploading a invalid document causes an error which doesn't appear to be handled by the `error` prop. // Uploading a invalid document causes an error which doesn't appear to be handled by the `error` prop.
@ -270,12 +248,18 @@ export const PDFViewer = ({
</div> </div>
))} ))}
</PDFDocument> </PDFDocument>
<PasswordDialog <PasswordDialog
open={isPasswordModalOpen} open={isPasswordModalOpen}
onOpenChange={setIsPasswordModalOpen} onOpenChange={setIsPasswordModalOpen}
onPasswordSubmit={onPasswordSubmit} onPasswordSubmit={(password) => {
passwordCallbackRef.current?.(password);
setIsPasswordModalOpen(false);
void onPasswordSubmit?.(password);
}}
isError={isPasswordError} isError={isPasswordError}
setPassword={setPassword}
/> />
</> </>
)} )}