fix the protect issue

This commit is contained in:
saqib mir
2026-08-18 11:37:19 +05:30
parent 1217649a96
commit d64d1a18a3
3 changed files with 150 additions and 181 deletions
+2
View File
@@ -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;
+139 -181
View File
@@ -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'}
+9
View File
@@ -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;
}