Files
Stirling-PDF/frontend/src/core/components/shared/EncryptedPdfUnlockModal.tsx
T
Anthony StirlingandGitHub c87da6d5cc Add automatic unlock prompt for encrypted PDFs (#4912)
## Summary
- propagate an `isEncrypted` flag from thumbnail generation into
processed file metadata so uploads know when a password is still present
- add queueing logic inside `FileContext` that detects encrypted
uploads, prompts the user via a new modal, and automatically runs the
Remove Password endpoint to replace the file and preserve history
- introduce a dedicated `EncryptedPdfUnlockModal` component that mirrors
existing styling and messaging for unlocking PDFs

## Testing
- npm run typecheck:core


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6919a0a418bc8328b886ec76a28170b7)
2025-11-20 14:54:41 +00:00

86 lines
2.6 KiB
TypeScript

import { Modal, Stack, Text, Button, PasswordInput, Group } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { type KeyboardEventHandler } from 'react';
import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from '@app/styles/zIndex';
interface EncryptedPdfUnlockModalProps {
opened: boolean;
fileName?: string;
password: string;
errorMessage?: string | null;
isProcessing: boolean;
onPasswordChange: (value: string) => void;
onUnlock: () => void;
onSkip: () => void;
}
const EncryptedPdfUnlockModal = ({
opened,
fileName,
password,
errorMessage,
isProcessing,
onPasswordChange,
onUnlock,
onSkip,
}: EncryptedPdfUnlockModalProps) => {
const { t } = useTranslation();
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
if (event.key === 'Enter' && !isProcessing && password.trim().length > 0) {
onUnlock();
}
};
return (
<Modal
opened={opened}
onClose={onSkip}
title={t('encryptedPdfUnlock.title', 'Remove password to continue')}
centered
size="md"
closeOnClickOutside={!isProcessing}
closeOnEscape={!isProcessing}
zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}
>
<Stack gap="md">
<Text fw={600} ta="center">{fileName}</Text>
<Text c="dimmed" ta="center">
{t(
'encryptedPdfUnlock.description',
'This PDF is password protected. Enter the password so you can continue working with it.'
)}
</Text>
<Stack gap={4}>
<PasswordInput
label={t('encryptedPdfUnlock.password.label', 'PDF password')}
placeholder={t('encryptedPdfUnlock.password.placeholder', 'Enter the PDF password')}
value={password}
onChange={(event) => onPasswordChange(event.currentTarget.value)}
onKeyDown={handleKeyDown}
disabled={isProcessing}
autoFocus
/>
{errorMessage ? (
<Text c="red" size="sm">
{errorMessage}
</Text>
) : null}
</Stack>
<Group justify="space-between">
<Button variant="light" color="var(--mantine-color-gray-8)" onClick={onSkip} disabled={isProcessing}>
{t('encryptedPdfUnlock.skip', 'Skip for now')}
</Button>
<Button onClick={onUnlock} loading={isProcessing} disabled={password.trim().length === 0}>
{t('encryptedPdfUnlock.unlock', 'Unlock & Continue')}
</Button>
</Group>
</Stack>
</Modal>
);
};
export default EncryptedPdfUnlockModal;