feat: improve the ux for password protected documents
Signed-off-by: harkiratsm <multaniharry714@gmail.com>
This commit is contained in:
51
packages/ui/primitives/document-password-dialog.tsx
Normal file
51
packages/ui/primitives/document-password-dialog.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
} from './dialog';
|
||||||
|
|
||||||
|
import { Input } from './input';
|
||||||
|
import { Button } from './button';
|
||||||
|
|
||||||
|
type PasswordDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (_open: boolean) => void;
|
||||||
|
setPassword: (_password: string) => void;
|
||||||
|
handleSubmit: () => void;
|
||||||
|
isError?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordDialog = ({ open, onOpenChange, handleSubmit, isError, setPassword }: PasswordDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Password Required</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{isError ? (
|
||||||
|
<p className="text-red-500">Incorrect password. Please try again.</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
This document is password protected. Please enter the password to view the document.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter className="flex w-full items-center justify-center gap-4">
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
className="bg-background mt-1.5"
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button onClick={handleSubmit}>Submit</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
import { PasswordResponses, type PDFDocumentProxy } 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';
|
||||||
@@ -14,6 +14,7 @@ import type { DocumentData } from '@documenso/prisma/client';
|
|||||||
|
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { useToast } from './use-toast';
|
import { useToast } from './use-toast';
|
||||||
|
import { PasswordDialog } from './document-password-dialog';
|
||||||
|
|
||||||
export type LoadedPDFDocument = PDFDocumentProxy;
|
export type LoadedPDFDocument = PDFDocumentProxy;
|
||||||
|
|
||||||
@@ -60,6 +61,10 @@ export const PDFViewer = ({
|
|||||||
const $el = useRef<HTMLDivElement>(null);
|
const $el = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [isDocumentBytesLoading, setIsDocumentBytesLoading] = useState(false);
|
const [isDocumentBytesLoading, setIsDocumentBytesLoading] = useState(false);
|
||||||
|
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
||||||
|
const [password, setPassword] = useState<string | null>(null);
|
||||||
|
const passwordCallbackRef = useRef<((password: string | null) => void) | null>(null);
|
||||||
|
const [isPasswordError, setIsPasswordError] = useState(false);
|
||||||
const [documentBytes, setDocumentBytes] = useState<Uint8Array | null>(null);
|
const [documentBytes, setDocumentBytes] = useState<Uint8Array | null>(null);
|
||||||
|
|
||||||
const [width, setWidth] = useState(0);
|
const [width, setWidth] = useState(0);
|
||||||
@@ -78,6 +83,14 @@ export const PDFViewer = ({
|
|||||||
onDocumentLoad?.(doc);
|
onDocumentLoad?.(doc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePasswordSubmit = () => {
|
||||||
|
setIsPasswordModalOpen(false);
|
||||||
|
if (passwordCallbackRef.current) {
|
||||||
|
passwordCallbackRef.current(password);
|
||||||
|
passwordCallbackRef.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onDocumentPageClick = (
|
const onDocumentPageClick = (
|
||||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||||
pageNumber: number,
|
pageNumber: number,
|
||||||
@@ -169,11 +182,26 @@ export const PDFViewer = ({
|
|||||||
<PDFLoader />
|
<PDFLoader />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<PDFDocument
|
<PDFDocument
|
||||||
file={documentBytes.buffer}
|
file={documentBytes.buffer}
|
||||||
className={cn('w-full overflow-hidden rounded', {
|
className={cn('w-full overflow-hidden rounded', {
|
||||||
'h-[80vh] max-h-[60rem]': numPages === 0,
|
'h-[80vh] max-h-[60rem]': numPages === 0,
|
||||||
})}
|
})}
|
||||||
|
onPassword={(callback, reason) => {
|
||||||
|
setIsPasswordModalOpen(true);
|
||||||
|
passwordCallbackRef.current = callback;
|
||||||
|
switch (reason) {
|
||||||
|
case PasswordResponses.NEED_PASSWORD:
|
||||||
|
setIsPasswordError(false);
|
||||||
|
break;
|
||||||
|
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.
|
||||||
// Therefore we add some additional custom error handling.
|
// Therefore we add some additional custom error handling.
|
||||||
@@ -220,6 +248,14 @@ export const PDFViewer = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</PDFDocument>
|
</PDFDocument>
|
||||||
|
<PasswordDialog
|
||||||
|
open={isPasswordModalOpen}
|
||||||
|
onOpenChange={setIsPasswordModalOpen}
|
||||||
|
handleSubmit={handlePasswordSubmit}
|
||||||
|
isError={isPasswordError}
|
||||||
|
setPassword={setPassword}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user