fix the protect issue
This commit is contained in:
@@ -857,6 +857,7 @@ function App() {
|
||||
setProtectModalState(null);
|
||||
setIsInspectorOpen(true);
|
||||
setInspectorTab('properties');
|
||||
setExportModalOpen(true);
|
||||
} catch (e: any) {
|
||||
console.error('Protection failed', e);
|
||||
throw e;
|
||||
@@ -875,6 +876,7 @@ function App() {
|
||||
setUnlockModalState(null);
|
||||
setIsInspectorOpen(true);
|
||||
setInspectorTab('properties');
|
||||
setExportModalOpen(true);
|
||||
} catch (e: any) {
|
||||
console.error('Unlock failed', e);
|
||||
throw e;
|
||||
|
||||
@@ -10,9 +10,9 @@ interface ProtectModalProps {
|
||||
state: ProtectModalState | null;
|
||||
onSubmit: (payload: {
|
||||
userPassword: string;
|
||||
ownerPassword?: string;
|
||||
confirmPassword: string;
|
||||
permissions: {
|
||||
ownerPassword?: string;
|
||||
permissions?: {
|
||||
canPrint: boolean;
|
||||
canPrintHighRes: boolean;
|
||||
canCopy: boolean;
|
||||
@@ -26,43 +26,43 @@ interface ProtectModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const getPasswordStrength = (pass: string): { label: 'Weak' | 'Medium' | 'Strong'; score: number; color: string } | null => {
|
||||
if (!pass) return null;
|
||||
let score = 0;
|
||||
if (pass.length >= 6) score += 1;
|
||||
if (pass.length >= 10) score += 1;
|
||||
if (/[A-Z]/.test(pass)) score += 1;
|
||||
if (/[0-9]/.test(pass)) score += 1;
|
||||
if (/[^A-Za-z0-9]/.test(pass)) score += 1;
|
||||
|
||||
if (score <= 2 || pass.length < 6) {
|
||||
return { label: 'Weak', score: 1, color: '#ef4444' };
|
||||
} else if (score === 3 || score === 4) {
|
||||
return { label: 'Medium', score: 2, color: '#f59e0b' };
|
||||
} else {
|
||||
return { label: 'Strong', score: 3, color: '#10b981' };
|
||||
}
|
||||
};
|
||||
|
||||
export const ProtectModal: React.FC<ProtectModalProps> = ({ state, onSubmit, onClose }) => {
|
||||
const [userPassword, setUserPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [useOwnerPassword, setUseOwnerPassword] = useState(false);
|
||||
const [ownerPassword, setOwnerPassword] = useState('');
|
||||
const [showUserPassword, setShowUserPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [canPrint, setCanPrint] = useState(true);
|
||||
const [canPrintHighRes, setCanPrintHighRes] = useState(true);
|
||||
const [canCopy, setCanCopy] = useState(true);
|
||||
const [canModify, setCanModify] = useState(true);
|
||||
const [canAnnotate, setCanAnnotate] = useState(true);
|
||||
const [canFillForms, setCanFillForms] = useState(true);
|
||||
const [canExtractForAccessibility, setCanExtractForAccessibility] = useState(true);
|
||||
const [canAssemble, setCanAssemble] = useState(true);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!state) return;
|
||||
setUserPassword('');
|
||||
setConfirmPassword('');
|
||||
setUseOwnerPassword(false);
|
||||
setOwnerPassword('');
|
||||
setShowUserPassword(false);
|
||||
setShowConfirmPassword(false);
|
||||
setError(null);
|
||||
setIsSubmitting(false);
|
||||
|
||||
setCanPrint(true);
|
||||
setCanPrintHighRes(true);
|
||||
setCanCopy(true);
|
||||
setCanModify(true);
|
||||
setCanAnnotate(true);
|
||||
setCanFillForms(true);
|
||||
setCanExtractForAccessibility(true);
|
||||
setCanAssemble(true);
|
||||
|
||||
const t = setTimeout(() => inputRef.current?.focus(), 30);
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||
document.addEventListener('keydown', onKey);
|
||||
@@ -71,12 +71,9 @@ export const ProtectModal: React.FC<ProtectModalProps> = ({ state, onSubmit, onC
|
||||
|
||||
if (!state) return null;
|
||||
|
||||
const handlePrintToggle = (val: boolean) => {
|
||||
setCanPrint(val);
|
||||
if (!val) {
|
||||
setCanPrintHighRes(false);
|
||||
}
|
||||
};
|
||||
const strength = getPasswordStrength(userPassword);
|
||||
const showMismatch = confirmPassword.length > 0 && userPassword !== confirmPassword;
|
||||
const showMatch = confirmPassword.length > 0 && userPassword === confirmPassword;
|
||||
|
||||
const handleFormSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -88,10 +85,6 @@ export const ProtectModal: React.FC<ProtectModalProps> = ({ state, onSubmit, onC
|
||||
setError('User password and confirmation password do not match');
|
||||
return;
|
||||
}
|
||||
if (useOwnerPassword && !ownerPassword) {
|
||||
setError('Owner password cannot be empty if separate owner password is checked');
|
||||
return;
|
||||
}
|
||||
|
||||
setError(null);
|
||||
setIsSubmitting(true);
|
||||
@@ -100,16 +93,15 @@ export const ProtectModal: React.FC<ProtectModalProps> = ({ state, onSubmit, onC
|
||||
await onSubmit({
|
||||
userPassword,
|
||||
confirmPassword,
|
||||
ownerPassword: useOwnerPassword ? ownerPassword : undefined,
|
||||
permissions: {
|
||||
canPrint,
|
||||
canPrintHighRes: canPrint ? canPrintHighRes : false,
|
||||
canCopy,
|
||||
canModify,
|
||||
canAnnotate,
|
||||
canFillForms,
|
||||
canExtractForAccessibility,
|
||||
canAssemble,
|
||||
canPrint: true,
|
||||
canPrintHighRes: true,
|
||||
canCopy: true,
|
||||
canModify: true,
|
||||
canAnnotate: true,
|
||||
canFillForms: true,
|
||||
canExtractForAccessibility: true,
|
||||
canAssemble: true,
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
@@ -124,7 +116,7 @@ export const ProtectModal: React.FC<ProtectModalProps> = ({ state, onSubmit, onC
|
||||
<div className="absolute inset-0 bg-[#0f172a]/30 backdrop-blur-[2px]" style={{ animation: 'toastIn 0.2s ease-out' }} />
|
||||
|
||||
<div
|
||||
style={{ width: 500, animation: 'slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
||||
style={{ width: 440, animation: 'slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
||||
className="relative flex max-h-[90vh] flex-col overflow-hidden rounded-[16px] bg-[#ffffff] shadow-[0_24px_48px_rgba(16,24,40,0.18)] ring-1 ring-[#ebedf0]"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
@@ -142,160 +134,126 @@ export const ProtectModal: React.FC<ProtectModalProps> = ({ state, onSubmit, onC
|
||||
<p className="truncate text-[12px] text-[#98a1ad]" title={state.filename}>{state.filename}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="rounded-full bg-[#eef4ff] px-2.5 py-1 text-[10px] font-extrabold uppercase text-[#2563eb]">
|
||||
AES-256
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content Body */}
|
||||
<form onSubmit={handleFormSubmit} className="custom-scrollbar flex-1 overflow-y-auto p-6 flex flex-col gap-5">
|
||||
<form onSubmit={handleFormSubmit} className="custom-scrollbar flex-1 overflow-y-auto p-6 flex flex-col gap-4">
|
||||
{error && (
|
||||
<div className="rounded-[8px] border border-[#fca5a5] bg-[#fdecec] p-3 text-[12.5px] font-medium text-[#dc2626]">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Passwords Section */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3 className="text-[12px] font-bold uppercase tracking-wider text-[#98a1ad]">Passwords</h3>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<label className="block text-[12.5px] font-semibold text-[#344054] mb-1">User Password</label>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="password"
|
||||
value={userPassword}
|
||||
onChange={(e) => setUserPassword(e.target.value)}
|
||||
placeholder="Required to open PDF"
|
||||
autoComplete="off"
|
||||
className="w-full rounded-[8px] border border-[#d6dae0] bg-white px-3 py-2 text-[13px] outline-none focus:border-[#2563eb] focus:ring-2 focus:ring-[#eef4ff]"
|
||||
/>
|
||||
<label className="block text-[12.5px] font-semibold text-[#344054] mb-1">Set Password</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type={showUserPassword ? 'text' : 'password'}
|
||||
value={userPassword}
|
||||
onChange={(e) => setUserPassword(e.target.value)}
|
||||
placeholder="Required to open PDF"
|
||||
autoComplete="new-password"
|
||||
data-lpignore="true"
|
||||
className="w-full rounded-[8px] border border-[#d6dae0] bg-white pl-3 pr-10 py-2 text-[13px] outline-none focus:border-[#2563eb] focus:ring-2 focus:ring-[#eef4ff]"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowUserPassword(!showUserPassword)}
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[#98a1ad] hover:text-[#475467] p-1"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{showUserPassword ? (
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24M1 1l22 22" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Password Strength Indicator */}
|
||||
{userPassword && strength && (
|
||||
<div className="mt-2 flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between text-[11px] font-semibold">
|
||||
<span className="text-[#64748b]">Strength:</span>
|
||||
<span style={{ color: strength.color }}>{strength.label}</span>
|
||||
</div>
|
||||
<div className="flex gap-1 h-1.5 w-full">
|
||||
<div
|
||||
className="h-full flex-1 rounded-full transition-all duration-300"
|
||||
style={{ backgroundColor: strength.score >= 1 ? strength.color : '#e2e8f0' }}
|
||||
/>
|
||||
<div
|
||||
className="h-full flex-1 rounded-full transition-all duration-300"
|
||||
style={{ backgroundColor: strength.score >= 2 ? strength.color : '#e2e8f0' }}
|
||||
/>
|
||||
<div
|
||||
className="h-full flex-1 rounded-full transition-all duration-300"
|
||||
style={{ backgroundColor: strength.score >= 3 ? strength.color : '#e2e8f0' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[12.5px] font-semibold text-[#344054] mb-1">Confirm Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="Re-enter user password"
|
||||
autoComplete="off"
|
||||
className="w-full rounded-[8px] border border-[#d6dae0] bg-white px-3 py-2 text-[13px] outline-none focus:border-[#2563eb] focus:ring-2 focus:ring-[#eef4ff]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2 cursor-pointer pt-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useOwnerPassword}
|
||||
onChange={(e) => setUseOwnerPassword(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="text-[12.5px] font-medium text-[#475467]">Use separate owner password</span>
|
||||
</label>
|
||||
|
||||
{useOwnerPassword && (
|
||||
<div>
|
||||
<label className="block text-[12.5px] font-semibold text-[#344054] mb-1">Owner Password</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="password"
|
||||
value={ownerPassword}
|
||||
onChange={(e) => setOwnerPassword(e.target.value)}
|
||||
placeholder="Required for permissions modification"
|
||||
autoComplete="off"
|
||||
className="w-full rounded-[8px] border border-[#d6dae0] bg-white px-3 py-2 text-[13px] outline-none focus:border-[#2563eb] focus:ring-2 focus:ring-[#eef4ff]"
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="Re-enter password"
|
||||
autoComplete="new-password"
|
||||
data-lpignore="true"
|
||||
className="w-full rounded-[8px] border border-[#d6dae0] bg-white pl-3 pr-10 py-2 text-[13px] outline-none focus:border-[#2563eb] focus:ring-2 focus:ring-[#eef4ff]"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[#98a1ad] hover:text-[#475467] p-1"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{showConfirmPassword ? (
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24M1 1l22 22" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-[#ebedf0]" />
|
||||
|
||||
{/* Permissions Section */}
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<h3 className="text-[12px] font-bold uppercase tracking-wider text-[#98a1ad]">Permissions</h3>
|
||||
|
||||
<div className="grid grid-cols-1 gap-2.5">
|
||||
<label className="flex items-center gap-2.5 cursor-pointer text-[13px] text-[#344054]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canPrint}
|
||||
onChange={(e) => handlePrintToggle(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="font-medium">Allow printing</span>
|
||||
</label>
|
||||
|
||||
<label className={`flex items-center gap-2.5 text-[13px] pl-6 ${canPrint ? 'cursor-pointer text-[#344054]' : 'cursor-not-allowed text-[#98a1ad]'}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canPrintHighRes}
|
||||
disabled={!canPrint}
|
||||
onChange={(e) => setCanPrintHighRes(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb] disabled:opacity-50"
|
||||
/>
|
||||
<span className="font-medium">High-quality printing</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2.5 cursor-pointer text-[13px] text-[#344054]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canCopy}
|
||||
onChange={(e) => setCanCopy(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="font-medium">Copy text and graphics</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2.5 cursor-pointer text-[13px] text-[#344054]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canModify}
|
||||
onChange={(e) => setCanModify(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="font-medium">Modify document content</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2.5 cursor-pointer text-[13px] text-[#344054]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canAnnotate}
|
||||
onChange={(e) => setCanAnnotate(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="font-medium">Annotate & comment</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2.5 cursor-pointer text-[13px] text-[#344054]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canFillForms}
|
||||
onChange={(e) => setCanFillForms(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="font-medium">Fill form fields</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2.5 cursor-pointer text-[13px] text-[#344054]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canExtractForAccessibility}
|
||||
onChange={(e) => setCanExtractForAccessibility(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="font-medium">Accessibility content extraction</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2.5 cursor-pointer text-[13px] text-[#344054]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={canAssemble}
|
||||
onChange={(e) => setCanAssemble(e.target.checked)}
|
||||
className="rounded border-[#d6dae0] text-[#2563eb] focus:ring-[#2563eb]"
|
||||
/>
|
||||
<span className="font-medium">Document assembly (page reordering/deletion)</span>
|
||||
</label>
|
||||
{/* Password Match Status */}
|
||||
{confirmPassword.length > 0 && (
|
||||
<div className="mt-1.5 flex items-center gap-1 text-[11.5px] font-medium">
|
||||
{showMatch ? (
|
||||
<span className="text-[#10b981] flex items-center gap-1">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
Passwords match
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[#ef4444] flex items-center gap-1">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
Passwords do not match
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -313,7 +271,7 @@ export const ProtectModal: React.FC<ProtectModalProps> = ({ state, onSubmit, onC
|
||||
<CustomButton
|
||||
variant="primary"
|
||||
onClick={handleFormSubmit}
|
||||
disabled={!userPassword || isSubmitting}
|
||||
disabled={!userPassword || showMismatch || isSubmitting}
|
||||
className="rounded-[8px] bg-[#2563eb] px-5 font-semibold text-white shadow-sm transition-colors hover:bg-[#1d4ed8] disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? 'Encrypting…' : 'Protect PDF'}
|
||||
|
||||
@@ -96,3 +96,12 @@ body {
|
||||
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||
@keyframes slideUp { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes toastIn { from { opacity: 0; transform: translateY(10px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
||||
|
||||
/* Hide browser default password reveal icons (Edge / IE) */
|
||||
input::-ms-reveal,
|
||||
input::-ms-clear {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user