mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
Merge 6a09ec6091
into 1a3e8e7ecf
This commit is contained in:
commit
ee712f38da
@ -426,6 +426,10 @@
|
|||||||
"title": "Flatten",
|
"title": "Flatten",
|
||||||
"desc": "Remove all interactive elements and forms from a PDF"
|
"desc": "Remove all interactive elements and forms from a PDF"
|
||||||
},
|
},
|
||||||
|
"manageSignatures": {
|
||||||
|
"title": "Sign with Certificate",
|
||||||
|
"desc": "Add digital signatures to PDF documents using certificates"
|
||||||
|
},
|
||||||
"repair": {
|
"repair": {
|
||||||
"title": "Repair",
|
"title": "Repair",
|
||||||
"desc": "Tries to repair a corrupt/broken PDF"
|
"desc": "Tries to repair a corrupt/broken PDF"
|
||||||
@ -2384,5 +2388,146 @@
|
|||||||
"processImages": "Process Images",
|
"processImages": "Process Images",
|
||||||
"processImagesDesc": "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images."
|
"processImagesDesc": "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"manageSignatures": {
|
||||||
|
"title": "Sign with Certificate",
|
||||||
|
"filenamePrefix": "signed",
|
||||||
|
"files": {
|
||||||
|
"placeholder": "Select PDF files to sign with certificates"
|
||||||
|
},
|
||||||
|
"fileStatus": {
|
||||||
|
"stepTitle": "File Status"
|
||||||
|
},
|
||||||
|
"fileNavigation": "File {{current}} of {{total}}",
|
||||||
|
"hasSignatures": "Contains {{count}} signature(s)",
|
||||||
|
"noSignatures": "No signatures detected",
|
||||||
|
"signed": "Signed",
|
||||||
|
"certType": {
|
||||||
|
"stepTitle": "Certificate Type",
|
||||||
|
"tooltip": {
|
||||||
|
"header": {
|
||||||
|
"title": "About Certificate Types"
|
||||||
|
},
|
||||||
|
"what": {
|
||||||
|
"title": "What's a certificate?",
|
||||||
|
"text": "It's a secure ID for your signature that proves you signed. Unless you're required to sign via certificate, we recommend using another secure method like Type, Draw, or Upload."
|
||||||
|
},
|
||||||
|
"which": {
|
||||||
|
"title": "Which option should I use?",
|
||||||
|
"text": "Choose the format that matches your certificate file:",
|
||||||
|
"bullet1": "PKCS#12 (.p12 / .pfx) – one combined file (most common)",
|
||||||
|
"bullet2": "PEM – separate private-key and certificate .pem files",
|
||||||
|
"bullet3": "JKS – Java .jks keystore for dev / CI-CD workflows"
|
||||||
|
},
|
||||||
|
"convert": {
|
||||||
|
"title": "Key not listed?",
|
||||||
|
"text": "Convert your file to a Java keystore (.jks) with keytool, then pick JKS."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"certFiles": {
|
||||||
|
"stepTitle": "Certificate Files"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"stepTitle": "Signature Appearance",
|
||||||
|
"title": "Signature Appearance",
|
||||||
|
"invisible": "Invisible",
|
||||||
|
"visible": "Visible",
|
||||||
|
"options": {
|
||||||
|
"title": "Signature Details"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"header": {
|
||||||
|
"title": "About Signature Appearance"
|
||||||
|
},
|
||||||
|
"invisible": {
|
||||||
|
"title": "Invisible Signatures",
|
||||||
|
"text": "The signature is added to the PDF for security but won't be visible when viewing the document. Perfect for legal requirements without changing the document's appearance.",
|
||||||
|
"bullet1": "Provides security without visual changes",
|
||||||
|
"bullet2": "Meets legal requirements for digital signing",
|
||||||
|
"bullet3": "Doesn't affect document layout or design"
|
||||||
|
},
|
||||||
|
"visible": {
|
||||||
|
"title": "Visible Signatures",
|
||||||
|
"text": "Shows a signature block on the PDF with your name, date, and optional details. Useful when you want readers to clearly see the document is signed.",
|
||||||
|
"bullet1": "Shows signer name and date on the document",
|
||||||
|
"bullet2": "Can include reason and location for signing",
|
||||||
|
"bullet3": "Choose which page to place the signature",
|
||||||
|
"bullet4": "Optional logo can be included"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"title": "Action",
|
||||||
|
"validate": "Check for Signatures",
|
||||||
|
"viewEdit": "View/Edit Signatures",
|
||||||
|
"sign": "Add New Signature"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"title": "Validation Options",
|
||||||
|
"customCert": "Custom Certificate (Optional)",
|
||||||
|
"customCert.desc": "Upload a custom certificate for validation"
|
||||||
|
},
|
||||||
|
"signing": {
|
||||||
|
"title": "Certificate Settings",
|
||||||
|
"certType": "Certificate Type",
|
||||||
|
"choosePrivateKey": "Choose Private Key File",
|
||||||
|
"chooseCertificate": "Choose Certificate File",
|
||||||
|
"chooseP12File": "Choose PKCS12 File",
|
||||||
|
"chooseJksFile": "Choose JKS File",
|
||||||
|
"password": "Certificate Password",
|
||||||
|
"passwordOptional": "Leave empty if no password",
|
||||||
|
"showSignature": "Show visible signature on PDF",
|
||||||
|
"reason": "Reason for Signing",
|
||||||
|
"location": "Location",
|
||||||
|
"name": "Signer Name",
|
||||||
|
"pageNumber": "Page Number",
|
||||||
|
"logoTitle": "Logo",
|
||||||
|
"noLogo": "No Logo",
|
||||||
|
"showLogo": "Show Logo"
|
||||||
|
},
|
||||||
|
"validate": {
|
||||||
|
"submit": "Validate Signatures",
|
||||||
|
"results": "Signature Validation Results"
|
||||||
|
},
|
||||||
|
"sign": {
|
||||||
|
"submit": "Sign PDF",
|
||||||
|
"results": "Signed PDF"
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"title": "Signature Results"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"failed": "An error occurred whilst processing signatures."
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"header": {
|
||||||
|
"title": "About Managing Signatures"
|
||||||
|
},
|
||||||
|
"overview": {
|
||||||
|
"title": "What can this tool do?",
|
||||||
|
"text": "This tool lets you check if your PDFs are digitally signed and add new digital signatures. Digital signatures prove who created or approved a document and show if it has been changed since signing.",
|
||||||
|
"bullet1": "Check existing signatures and their validity",
|
||||||
|
"bullet2": "View detailed information about signers and certificates",
|
||||||
|
"bullet3": "Add new digital signatures to secure your documents",
|
||||||
|
"bullet4": "Multiple files supported with easy navigation"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"title": "Checking Signatures",
|
||||||
|
"text": "When you check signatures, the tool tells you if they're valid, who signed the document, when it was signed, and whether the document has been changed since signing.",
|
||||||
|
"bullet1": "Shows if signatures are valid or invalid",
|
||||||
|
"bullet2": "Displays signer information and signing date",
|
||||||
|
"bullet3": "Checks if the document was modified after signing",
|
||||||
|
"bullet4": "Can use custom certificates for verification"
|
||||||
|
},
|
||||||
|
"signing": {
|
||||||
|
"title": "Adding Signatures",
|
||||||
|
"text": "To sign a PDF, you need a digital certificate (like PEM, PKCS12, or JKS). You can choose to make the signature visible on the document or keep it invisible for security only.",
|
||||||
|
"bullet1": "Supports PEM, PKCS12, and JKS certificate formats",
|
||||||
|
"bullet2": "Option to show or hide signature on the PDF",
|
||||||
|
"bullet3": "Add reason, location, and signer name",
|
||||||
|
"bullet4": "Choose which page to place visible signatures"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import { Stack, Text, TextInput } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters";
|
||||||
|
import FileUploadButton from "../../shared/FileUploadButton";
|
||||||
|
|
||||||
|
interface CertificateFilesSettingsProps {
|
||||||
|
parameters: ManageSignaturesParameters;
|
||||||
|
onParameterChange: (key: keyof ManageSignaturesParameters, value: any) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CertificateFilesSettings = ({ parameters, onParameterChange, disabled = false }: CertificateFilesSettingsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Certificate Files based on type */}
|
||||||
|
{parameters.certType === 'PEM' && (
|
||||||
|
<Stack gap="sm">
|
||||||
|
<FileUploadButton
|
||||||
|
file={parameters.privateKeyFile}
|
||||||
|
onChange={(file) => onParameterChange('privateKeyFile', file || undefined)}
|
||||||
|
accept=".pem,.der"
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={t('manageSignatures.signing.choosePrivateKey', 'Choose Private Key File')}
|
||||||
|
/>
|
||||||
|
{parameters.privateKeyFile && (
|
||||||
|
<FileUploadButton
|
||||||
|
file={parameters.certFile}
|
||||||
|
onChange={(file) => onParameterChange('certFile', file || undefined)}
|
||||||
|
accept=".pem,.der"
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={t('manageSignatures.signing.chooseCertificate', 'Choose Certificate File')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{parameters.certType === 'PKCS12' && (
|
||||||
|
<FileUploadButton
|
||||||
|
file={parameters.p12File}
|
||||||
|
onChange={(file) => onParameterChange('p12File', file || undefined)}
|
||||||
|
accept=".p12,.pfx"
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={t('manageSignatures.signing.chooseP12File', 'Choose PKCS12 File')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{parameters.certType === 'JKS' && (
|
||||||
|
<FileUploadButton
|
||||||
|
file={parameters.jksFile}
|
||||||
|
onChange={(file) => onParameterChange('jksFile', file || undefined)}
|
||||||
|
accept=".jks,.keystore"
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={t('manageSignatures.signing.chooseJksFile', 'Choose JKS File')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Password - only show when files are uploaded */}
|
||||||
|
{parameters.certType && (
|
||||||
|
(parameters.certType === 'PEM' && parameters.privateKeyFile && parameters.certFile) ||
|
||||||
|
(parameters.certType === 'PKCS12' && parameters.p12File) ||
|
||||||
|
(parameters.certType === 'JKS' && parameters.jksFile)
|
||||||
|
) && (
|
||||||
|
<TextInput
|
||||||
|
label={t('manageSignatures.signing.password', 'Certificate Password')}
|
||||||
|
placeholder={t('manageSignatures.signing.passwordOptional', 'Leave empty if no password')}
|
||||||
|
type="password"
|
||||||
|
value={parameters.password}
|
||||||
|
onChange={(event) => onParameterChange('password', event.currentTarget.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CertificateFilesSettings;
|
@ -0,0 +1,60 @@
|
|||||||
|
import { Stack, Button } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters";
|
||||||
|
|
||||||
|
interface CertificateTypeSettingsProps {
|
||||||
|
parameters: ManageSignaturesParameters;
|
||||||
|
onParameterChange: (key: keyof ManageSignaturesParameters, value: any) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = false }: CertificateTypeSettingsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Certificate Type Selection */}
|
||||||
|
<Stack gap="sm">
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||||
|
<div style={{ display: 'flex', gap: '4px' }}>
|
||||||
|
<Button
|
||||||
|
variant={parameters.certType === 'PKCS12' ? 'filled' : 'outline'}
|
||||||
|
color={parameters.certType === 'PKCS12' ? 'blue' : 'var(--text-muted)'}
|
||||||
|
onClick={() => onParameterChange('certType', 'PKCS12')}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
|
||||||
|
PKCS#12
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={parameters.certType === 'PEM' ? 'filled' : 'outline'}
|
||||||
|
color={parameters.certType === 'PEM' ? 'blue' : 'var(--text-muted)'}
|
||||||
|
onClick={() => onParameterChange('certType', 'PEM')}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
|
||||||
|
PEM
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant={parameters.certType === 'JKS' ? 'filled' : 'outline'}
|
||||||
|
color={parameters.certType === 'JKS' ? 'blue' : 'var(--text-muted)'}
|
||||||
|
onClick={() => onParameterChange('certType', 'JKS')}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ width: '100%', height: 'auto', minHeight: '40px', fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
|
||||||
|
JKS
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CertificateTypeSettings;
|
@ -0,0 +1,113 @@
|
|||||||
|
import { Stack, Text, Button, TextInput, NumberInput } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters";
|
||||||
|
|
||||||
|
interface SignatureAppearanceSettingsProps {
|
||||||
|
parameters: ManageSignaturesParameters;
|
||||||
|
onParameterChange: (key: keyof ManageSignaturesParameters, value: any) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignatureAppearanceSettings = ({ parameters, onParameterChange, disabled = false }: SignatureAppearanceSettingsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Signature Visibility */}
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{t('manageSignatures.appearance.title', 'Signature Appearance')}
|
||||||
|
</Text>
|
||||||
|
<div style={{ display: 'flex', gap: '4px' }}>
|
||||||
|
<Button
|
||||||
|
variant={!parameters.showSignature ? 'filled' : 'outline'}
|
||||||
|
color={!parameters.showSignature ? 'blue' : 'var(--text-muted)'}
|
||||||
|
onClick={() => onParameterChange('showSignature', false)}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
|
||||||
|
{t('manageSignatures.appearance.invisible', 'Invisible')}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={parameters.showSignature ? 'filled' : 'outline'}
|
||||||
|
color={parameters.showSignature ? 'blue' : 'var(--text-muted)'}
|
||||||
|
onClick={() => onParameterChange('showSignature', true)}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
|
||||||
|
{t('manageSignatures.appearance.visible', 'Visible')}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Visible Signature Options */}
|
||||||
|
{parameters.showSignature && (
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{t('manageSignatures.appearance.options.title', 'Signature Details')}
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
label={t('manageSignatures.signing.reason', 'Reason for Signing')}
|
||||||
|
value={parameters.reason}
|
||||||
|
onChange={(event) => onParameterChange('reason', event.currentTarget.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={t('manageSignatures.signing.location', 'Location')}
|
||||||
|
value={parameters.location}
|
||||||
|
onChange={(event) => onParameterChange('location', event.currentTarget.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={t('manageSignatures.signing.name', 'Signer Name')}
|
||||||
|
value={parameters.name}
|
||||||
|
onChange={(event) => onParameterChange('name', event.currentTarget.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label={t('manageSignatures.signing.pageNumber', 'Page Number')}
|
||||||
|
value={parameters.pageNumber}
|
||||||
|
onChange={(value) => onParameterChange('pageNumber', value || 1)}
|
||||||
|
min={1}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{t('manageSignatures.signing.logoTitle', 'Logo')}
|
||||||
|
</Text>
|
||||||
|
<div style={{ display: 'flex', gap: '4px' }}>
|
||||||
|
<Button
|
||||||
|
variant={!parameters.showLogo ? 'filled' : 'outline'}
|
||||||
|
color={!parameters.showLogo ? 'blue' : 'var(--text-muted)'}
|
||||||
|
onClick={() => onParameterChange('showLogo', false)}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
|
||||||
|
{t('manageSignatures.signing.noLogo', 'No Logo')}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={parameters.showLogo ? 'filled' : 'outline'}
|
||||||
|
color={parameters.showLogo ? 'blue' : 'var(--text-muted)'}
|
||||||
|
onClick={() => onParameterChange('showLogo', true)}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
|
||||||
|
{t('manageSignatures.signing.showLogo', 'Show Logo')}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignatureAppearanceSettings;
|
31
frontend/src/components/tooltips/useCertificateTypeTips.ts
Normal file
31
frontend/src/components/tooltips/useCertificateTypeTips.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TooltipContent } from '../../types/tips';
|
||||||
|
|
||||||
|
export const useCertificateTypeTips = (): TooltipContent => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
title: t("manageSignatures.certType.tooltip.header.title", "About Certificate Types")
|
||||||
|
},
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.certType.tooltip.what.title", "What's a certificate?"),
|
||||||
|
description: t("manageSignatures.certType.tooltip.what.text", "It's a secure ID for your signature that proves you signed. Unless you're required to sign via certificate, we recommend using another secure method like Type, Draw, or Upload.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.certType.tooltip.which.title", "Which option should I use?"),
|
||||||
|
description: t("manageSignatures.certType.tooltip.which.text", "Choose the format that matches your certificate file:"),
|
||||||
|
bullets: [
|
||||||
|
t("manageSignatures.certType.tooltip.which.bullet1", "PKCS#12 (.p12 / .pfx) – one combined file (most common)"),
|
||||||
|
t("manageSignatures.certType.tooltip.which.bullet2", "PEM – separate private-key and certificate .pem files"),
|
||||||
|
t("manageSignatures.certType.tooltip.which.bullet3", "JKS – Java .jks keystore for dev / CI-CD workflows")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.certType.tooltip.convert.title", "Key not listed?"),
|
||||||
|
description: t("manageSignatures.certType.tooltip.convert.text", "Convert your file to a Java keystore (.jks) with keytool, then pick JKS.")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TooltipContent } from '../../types/tips';
|
||||||
|
|
||||||
|
export const useManageSignaturesTooltips = (): TooltipContent => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
title: t("manageSignatures.tooltip.header.title", "About Managing Signatures")
|
||||||
|
},
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.tooltip.overview.title", "What can this tool do?"),
|
||||||
|
description: t("manageSignatures.tooltip.overview.text", "This tool lets you check if your PDFs are digitally signed and add new digital signatures. Digital signatures prove who created or approved a document and show if it has been changed since signing."),
|
||||||
|
bullets: [
|
||||||
|
t("manageSignatures.tooltip.overview.bullet1", "Check existing signatures and their validity"),
|
||||||
|
t("manageSignatures.tooltip.overview.bullet2", "View detailed information about signers and certificates"),
|
||||||
|
t("manageSignatures.tooltip.overview.bullet3", "Add new digital signatures to secure your documents"),
|
||||||
|
t("manageSignatures.tooltip.overview.bullet4", "Multiple files supported with easy navigation")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.tooltip.validation.title", "Checking Signatures"),
|
||||||
|
description: t("manageSignatures.tooltip.validation.text", "When you check signatures, the tool tells you if they're valid, who signed the document, when it was signed, and whether the document has been changed since signing."),
|
||||||
|
bullets: [
|
||||||
|
t("manageSignatures.tooltip.validation.bullet1", "Shows if signatures are valid or invalid"),
|
||||||
|
t("manageSignatures.tooltip.validation.bullet2", "Displays signer information and signing date"),
|
||||||
|
t("manageSignatures.tooltip.validation.bullet3", "Checks if the document was modified after signing"),
|
||||||
|
t("manageSignatures.tooltip.validation.bullet4", "Can use custom certificates for verification")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.tooltip.signing.title", "Adding Signatures"),
|
||||||
|
description: t("manageSignatures.tooltip.signing.text", "To sign a PDF, you need a digital certificate (like PEM, PKCS12, or JKS). You can choose to make the signature visible on the document or keep it invisible for security only."),
|
||||||
|
bullets: [
|
||||||
|
t("manageSignatures.tooltip.signing.bullet1", "Supports PEM, PKCS12, and JKS certificate formats"),
|
||||||
|
t("manageSignatures.tooltip.signing.bullet2", "Option to show or hide signature on the PDF"),
|
||||||
|
t("manageSignatures.tooltip.signing.bullet3", "Add reason, location, and signer name"),
|
||||||
|
t("manageSignatures.tooltip.signing.bullet4", "Choose which page to place visible signatures")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TooltipContent } from '../../types/tips';
|
||||||
|
|
||||||
|
export const useSignatureAppearanceTips = (): TooltipContent => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
title: t("manageSignatures.appearance.tooltip.header.title", "About Signature Appearance")
|
||||||
|
},
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.appearance.tooltip.invisible.title", "Invisible Signatures"),
|
||||||
|
description: t("manageSignatures.appearance.tooltip.invisible.text", "The signature is added to the PDF for security but won't be visible when viewing the document. Perfect for legal requirements without changing the document's appearance."),
|
||||||
|
bullets: [
|
||||||
|
t("manageSignatures.appearance.tooltip.invisible.bullet1", "Provides security without visual changes"),
|
||||||
|
t("manageSignatures.appearance.tooltip.invisible.bullet2", "Meets legal requirements for digital signing"),
|
||||||
|
t("manageSignatures.appearance.tooltip.invisible.bullet3", "Doesn't affect document layout or design")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.appearance.tooltip.visible.title", "Visible Signatures"),
|
||||||
|
description: t("manageSignatures.appearance.tooltip.visible.text", "Shows a signature block on the PDF with your name, date, and optional details. Useful when you want readers to clearly see the document is signed."),
|
||||||
|
bullets: [
|
||||||
|
t("manageSignatures.appearance.tooltip.visible.bullet1", "Shows signer name and date on the document"),
|
||||||
|
t("manageSignatures.appearance.tooltip.visible.bullet2", "Can include reason and location for signing"),
|
||||||
|
t("manageSignatures.appearance.tooltip.visible.bullet3", "Choose which page to place the signature"),
|
||||||
|
t("manageSignatures.appearance.tooltip.visible.bullet4", "Optional logo can be included")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
@ -15,6 +15,7 @@ import Repair from "../tools/Repair";
|
|||||||
import SingleLargePage from "../tools/SingleLargePage";
|
import SingleLargePage from "../tools/SingleLargePage";
|
||||||
import UnlockPdfForms from "../tools/UnlockPdfForms";
|
import UnlockPdfForms from "../tools/UnlockPdfForms";
|
||||||
import RemoveCertificateSign from "../tools/RemoveCertificateSign";
|
import RemoveCertificateSign from "../tools/RemoveCertificateSign";
|
||||||
|
import ManageSignatures from "../tools/ManageSignatures";
|
||||||
import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation";
|
import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation";
|
||||||
import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation";
|
import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation";
|
||||||
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
||||||
@ -28,6 +29,7 @@ import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation";
|
|||||||
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
|
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
|
||||||
import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
|
import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
|
||||||
import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
|
import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
|
||||||
|
import { manageSignaturesOperationConfig } from "../hooks/tools/manageSignatures/useManageSignaturesOperation";
|
||||||
import CompressSettings from "../components/tools/compress/CompressSettings";
|
import CompressSettings from "../components/tools/compress/CompressSettings";
|
||||||
import SplitSettings from "../components/tools/split/SplitSettings";
|
import SplitSettings from "../components/tools/split/SplitSettings";
|
||||||
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
||||||
@ -39,6 +41,7 @@ import AddWatermarkSingleStepSettings from "../components/tools/addWatermark/Add
|
|||||||
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
||||||
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
||||||
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
|
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
|
||||||
|
import CertificateTypeSettings from "../components/tools/manageSignatures/CertificateTypeSettings";
|
||||||
import { ToolId } from "../types/toolId";
|
import { ToolId } from "../types/toolId";
|
||||||
|
|
||||||
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
||||||
@ -134,11 +137,15 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
|
|
||||||
certSign: {
|
certSign: {
|
||||||
icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.certSign.title", "Sign with Certificate"),
|
name: t("home.certSign.title", "Certificate Sign"),
|
||||||
component: null,
|
component: ManageSignatures,
|
||||||
description: t("home.certSign.desc", "Signs a PDF with a Certificate/Key (PEM/P12)"),
|
description: t("home.certSign.desc", "Sign PDF documents using digital certificates"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.SIGNING,
|
subcategoryId: SubcategoryId.SIGNING,
|
||||||
|
maxFiles: -1,
|
||||||
|
endpoints: ["cert-sign"],
|
||||||
|
operationConfig: manageSignaturesOperationConfig,
|
||||||
|
settingsComponent: CertificateTypeSettings,
|
||||||
},
|
},
|
||||||
sign: {
|
sign: {
|
||||||
icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -240,14 +247,6 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
},
|
},
|
||||||
// Verification
|
// Verification
|
||||||
|
|
||||||
"get-all-info-on-pdf": {
|
|
||||||
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
|
|
||||||
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
|
|
||||||
component: null,
|
|
||||||
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
|
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
|
||||||
subcategoryId: SubcategoryId.VERIFICATION,
|
|
||||||
},
|
|
||||||
"validate-pdf-signature": {
|
"validate-pdf-signature": {
|
||||||
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.validateSignature.title", "Validate PDF Signature"),
|
name: t("home.validateSignature.title", "Validate PDF Signature"),
|
||||||
@ -256,6 +255,14 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.VERIFICATION,
|
subcategoryId: SubcategoryId.VERIFICATION,
|
||||||
},
|
},
|
||||||
|
"get-all-info-on-pdf": {
|
||||||
|
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
|
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
|
||||||
|
component: null,
|
||||||
|
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
|
||||||
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
|
subcategoryId: SubcategoryId.VERIFICATION,
|
||||||
|
},
|
||||||
|
|
||||||
// Document Review
|
// Document Review
|
||||||
|
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ToolType, useToolOperation } from '../shared/useToolOperation';
|
||||||
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
|
import { ManageSignaturesParameters, defaultParameters } from './useManageSignaturesParameters';
|
||||||
|
|
||||||
|
// Build form data for signing
|
||||||
|
export const buildManageSignaturesFormData = (parameters: ManageSignaturesParameters, file: File): FormData => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fileInput', file);
|
||||||
|
formData.append('certType', parameters.certType);
|
||||||
|
formData.append('password', parameters.password);
|
||||||
|
|
||||||
|
// Add certificate files based on type
|
||||||
|
switch (parameters.certType) {
|
||||||
|
case 'PEM':
|
||||||
|
if (parameters.privateKeyFile) {
|
||||||
|
formData.append('privateKeyFile', parameters.privateKeyFile);
|
||||||
|
}
|
||||||
|
if (parameters.certFile) {
|
||||||
|
formData.append('certFile', parameters.certFile);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'PKCS12':
|
||||||
|
if (parameters.p12File) {
|
||||||
|
formData.append('p12File', parameters.p12File);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'JKS':
|
||||||
|
if (parameters.jksFile) {
|
||||||
|
formData.append('jksFile', parameters.jksFile);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add signature appearance options if enabled
|
||||||
|
if (parameters.showSignature) {
|
||||||
|
formData.append('showSignature', 'true');
|
||||||
|
formData.append('reason', parameters.reason);
|
||||||
|
formData.append('location', parameters.location);
|
||||||
|
formData.append('name', parameters.name);
|
||||||
|
formData.append('pageNumber', parameters.pageNumber.toString());
|
||||||
|
formData.append('showLogo', parameters.showLogo.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static configuration object
|
||||||
|
export const manageSignaturesOperationConfig = {
|
||||||
|
toolType: ToolType.singleFile,
|
||||||
|
buildFormData: buildManageSignaturesFormData,
|
||||||
|
operationType: 'manageSignatures',
|
||||||
|
endpoint: '/api/v1/security/cert-sign',
|
||||||
|
filePrefix: 'signed_', // Will be overridden in hook with translation
|
||||||
|
multiFileEndpoint: false,
|
||||||
|
defaultParameters,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const useManageSignaturesOperation = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return useToolOperation<ManageSignaturesParameters>({
|
||||||
|
...manageSignaturesOperationConfig,
|
||||||
|
filePrefix: t('manageSignatures.filenamePrefix', 'signed') + '_',
|
||||||
|
getErrorMessage: createStandardErrorHandler(t('manageSignatures.error.failed', 'An error occurred while processing signatures.'))
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,58 @@
|
|||||||
|
import { BaseParameters } from '../../../types/parameters';
|
||||||
|
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
|
||||||
|
|
||||||
|
export interface ManageSignaturesParameters extends BaseParameters {
|
||||||
|
// Certificate signing options
|
||||||
|
certType: '' | 'PEM' | 'PKCS12' | 'JKS';
|
||||||
|
privateKeyFile?: File;
|
||||||
|
certFile?: File;
|
||||||
|
p12File?: File;
|
||||||
|
jksFile?: File;
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
// Signature appearance options
|
||||||
|
showSignature: boolean;
|
||||||
|
reason: string;
|
||||||
|
location: string;
|
||||||
|
name: string;
|
||||||
|
pageNumber: number;
|
||||||
|
showLogo: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultParameters: ManageSignaturesParameters = {
|
||||||
|
certType: '',
|
||||||
|
password: '',
|
||||||
|
showSignature: false,
|
||||||
|
reason: '',
|
||||||
|
location: '',
|
||||||
|
name: '',
|
||||||
|
pageNumber: 1,
|
||||||
|
showLogo: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ManageSignaturesParametersHook = BaseParametersHook<ManageSignaturesParameters>;
|
||||||
|
|
||||||
|
export const useManageSignaturesParameters = (): ManageSignaturesParametersHook => {
|
||||||
|
return useBaseParameters({
|
||||||
|
defaultParameters,
|
||||||
|
endpointName: 'manage-signatures',
|
||||||
|
validateFn: (params) => {
|
||||||
|
// Requires certificate type
|
||||||
|
if (!params.certType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for required files based on cert type
|
||||||
|
switch (params.certType) {
|
||||||
|
case 'PEM':
|
||||||
|
return !!(params.privateKeyFile && params.certFile);
|
||||||
|
case 'PKCS12':
|
||||||
|
return !!params.p12File;
|
||||||
|
case 'JKS':
|
||||||
|
return !!params.jksFile;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
140
frontend/src/services/signatureDetectionService.ts
Normal file
140
frontend/src/services/signatureDetectionService.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* Service for detecting signatures in PDF files using PDF.js
|
||||||
|
* This provides a quick client-side check to determine if a PDF contains signatures
|
||||||
|
* without needing to make API calls
|
||||||
|
*/
|
||||||
|
|
||||||
|
// PDF.js types (simplified)
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
pdfjsLib?: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignatureDetectionResult {
|
||||||
|
hasSignatures: boolean;
|
||||||
|
signatureCount?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileSignatureStatus {
|
||||||
|
file: File;
|
||||||
|
result: SignatureDetectionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect signatures in a single PDF file using PDF.js
|
||||||
|
*/
|
||||||
|
const detectSignaturesInFile = async (file: File): Promise<SignatureDetectionResult> => {
|
||||||
|
try {
|
||||||
|
// Ensure PDF.js is available
|
||||||
|
if (!window.pdfjsLib) {
|
||||||
|
return {
|
||||||
|
hasSignatures: false,
|
||||||
|
error: 'PDF.js not available'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert file to ArrayBuffer
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
|
||||||
|
// Load the PDF document
|
||||||
|
const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||||||
|
|
||||||
|
let totalSignatures = 0;
|
||||||
|
|
||||||
|
// Check each page for signature annotations
|
||||||
|
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
||||||
|
const page = await pdf.getPage(pageNum);
|
||||||
|
const annotations = await page.getAnnotations();
|
||||||
|
|
||||||
|
// Count signature annotations (Type: /Sig)
|
||||||
|
const signatureAnnotations = annotations.filter((annotation: any) =>
|
||||||
|
annotation.subtype === 'Widget' &&
|
||||||
|
annotation.fieldType === 'Sig'
|
||||||
|
);
|
||||||
|
|
||||||
|
totalSignatures += signatureAnnotations.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for document-level signatures in AcroForm
|
||||||
|
const metadata = await pdf.getMetadata();
|
||||||
|
if (metadata?.info?.Signature || metadata?.metadata?.has('dc:signature')) {
|
||||||
|
totalSignatures = Math.max(totalSignatures, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up PDF.js document
|
||||||
|
pdf.destroy();
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasSignatures: totalSignatures > 0,
|
||||||
|
signatureCount: totalSignatures
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('PDF signature detection failed:', error);
|
||||||
|
return {
|
||||||
|
hasSignatures: false,
|
||||||
|
signatureCount: 0,
|
||||||
|
error: error instanceof Error ? error.message : 'Detection failed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if PDF files contain signatures using PDF.js client-side processing
|
||||||
|
*/
|
||||||
|
export const detectSignaturesInFiles = async (files: File[]): Promise<FileSignatureStatus[]> => {
|
||||||
|
const results: FileSignatureStatus[] = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const result = await detectSignaturesInFile(file);
|
||||||
|
results.push({ file, result });
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for managing signature detection state
|
||||||
|
*/
|
||||||
|
export const useSignatureDetection = () => {
|
||||||
|
const [detectionResults, setDetectionResults] = React.useState<FileSignatureStatus[]>([]);
|
||||||
|
const [isDetecting, setIsDetecting] = React.useState(false);
|
||||||
|
|
||||||
|
const detectSignatures = async (files: File[]) => {
|
||||||
|
if (files.length === 0) {
|
||||||
|
setDetectionResults([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsDetecting(true);
|
||||||
|
try {
|
||||||
|
const results = await detectSignaturesInFiles(files);
|
||||||
|
setDetectionResults(results);
|
||||||
|
} finally {
|
||||||
|
setIsDetecting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileSignatureStatus = (file: File): SignatureDetectionResult | null => {
|
||||||
|
const result = detectionResults.find(r => r.file === file);
|
||||||
|
return result ? result.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasAnySignatures = detectionResults.some(r => r.result.hasSignatures);
|
||||||
|
const totalSignatures = detectionResults.reduce((sum, r) => sum + (r.result.signatureCount || 0), 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
detectionResults,
|
||||||
|
isDetecting,
|
||||||
|
detectSignatures,
|
||||||
|
getFileSignatureStatus,
|
||||||
|
hasAnySignatures,
|
||||||
|
totalSignatures,
|
||||||
|
reset: () => setDetectionResults([])
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import React for the hook
|
||||||
|
import React from 'react';
|
108
frontend/src/tools/ManageSignatures.tsx
Normal file
108
frontend/src/tools/ManageSignatures.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
|
import CertificateTypeSettings from "../components/tools/manageSignatures/CertificateTypeSettings";
|
||||||
|
import CertificateFilesSettings from "../components/tools/manageSignatures/CertificateFilesSettings";
|
||||||
|
import SignatureAppearanceSettings from "../components/tools/manageSignatures/SignatureAppearanceSettings";
|
||||||
|
import { useManageSignaturesParameters } from "../hooks/tools/manageSignatures/useManageSignaturesParameters";
|
||||||
|
import { useManageSignaturesOperation } from "../hooks/tools/manageSignatures/useManageSignaturesOperation";
|
||||||
|
import { useCertificateTypeTips } from "../components/tooltips/useCertificateTypeTips";
|
||||||
|
import { useSignatureAppearanceTips } from "../components/tooltips/useSignatureAppearanceTips";
|
||||||
|
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
||||||
|
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||||
|
|
||||||
|
const ManageSignatures = (props: BaseToolProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const base = useBaseTool(
|
||||||
|
'manageSignatures',
|
||||||
|
useManageSignaturesParameters,
|
||||||
|
useManageSignaturesOperation,
|
||||||
|
props
|
||||||
|
);
|
||||||
|
|
||||||
|
const certTypeTips = useCertificateTypeTips();
|
||||||
|
const appearanceTips = useSignatureAppearanceTips();
|
||||||
|
|
||||||
|
// Check if certificate files are configured for appearance step
|
||||||
|
const areCertFilesConfigured = () => {
|
||||||
|
const params = base.params.parameters;
|
||||||
|
switch (params.certType) {
|
||||||
|
case 'PEM':
|
||||||
|
return !!(params.privateKeyFile && params.certFile);
|
||||||
|
case 'PKCS12':
|
||||||
|
return !!params.p12File;
|
||||||
|
case 'JKS':
|
||||||
|
return !!params.jksFile;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return createToolFlow({
|
||||||
|
forceStepNumbers: true,
|
||||||
|
files: {
|
||||||
|
selectedFiles: base.selectedFiles,
|
||||||
|
isCollapsed: base.hasResults,
|
||||||
|
placeholder: t("manageSignatures.files.placeholder", "Select PDF files to sign with certificates"),
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.certType.stepTitle", "Certificate Type"),
|
||||||
|
isCollapsed: base.settingsCollapsed,
|
||||||
|
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
||||||
|
tooltip: certTypeTips,
|
||||||
|
content: (
|
||||||
|
<CertificateTypeSettings
|
||||||
|
parameters={base.params.parameters}
|
||||||
|
onParameterChange={base.params.updateParameter}
|
||||||
|
disabled={base.endpointLoading}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.certFiles.stepTitle", "Certificate Files"),
|
||||||
|
isCollapsed: base.settingsCollapsed,
|
||||||
|
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
||||||
|
content: (
|
||||||
|
<CertificateFilesSettings
|
||||||
|
parameters={base.params.parameters}
|
||||||
|
onParameterChange={base.params.updateParameter}
|
||||||
|
disabled={base.endpointLoading}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("manageSignatures.appearance.stepTitle", "Signature Appearance"),
|
||||||
|
isCollapsed: base.settingsCollapsed || !areCertFilesConfigured(),
|
||||||
|
onCollapsedClick: (base.settingsCollapsed || !areCertFilesConfigured()) ? base.handleSettingsReset : undefined,
|
||||||
|
tooltip: appearanceTips,
|
||||||
|
content: (
|
||||||
|
<SignatureAppearanceSettings
|
||||||
|
parameters={base.params.parameters}
|
||||||
|
onParameterChange={base.params.updateParameter}
|
||||||
|
disabled={base.endpointLoading}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
executeButton: {
|
||||||
|
text: t("manageSignatures.sign.submit", "Sign PDF"),
|
||||||
|
isVisible: !base.hasResults,
|
||||||
|
loadingText: t("loading"),
|
||||||
|
onClick: base.handleExecute,
|
||||||
|
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
|
||||||
|
},
|
||||||
|
review: {
|
||||||
|
isVisible: base.hasResults,
|
||||||
|
operation: base.operation,
|
||||||
|
title: t("manageSignatures.sign.results", "Signed PDF"),
|
||||||
|
onFileClick: base.handleThumbnailClick,
|
||||||
|
onUndo: base.handleUndo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static method to get the operation hook for automation
|
||||||
|
ManageSignatures.tool = () => useManageSignaturesOperation;
|
||||||
|
|
||||||
|
export default ManageSignatures as ToolComponent;
|
@ -12,8 +12,8 @@ const TOOL_IDS = [
|
|||||||
'flatten', 'remove-certificate-sign',
|
'flatten', 'remove-certificate-sign',
|
||||||
'unlock-pdf-forms', 'compress', 'extract-page', 'reorganize-pages', 'extract-images',
|
'unlock-pdf-forms', 'compress', 'extract-page', 'reorganize-pages', 'extract-images',
|
||||||
'add-stamp', 'add-attachments', 'change-metadata', 'overlay-pdfs',
|
'add-stamp', 'add-attachments', 'change-metadata', 'overlay-pdfs',
|
||||||
'manage-certificates', 'get-all-info-on-pdf', 'validate-pdf-signature', 'read', 'automate', 'replace-and-invert-color',
|
'manage-certificates', 'get-all-info-on-pdf', 'read', 'automate', 'replace-and-invert-color',
|
||||||
'show-javascript', 'dev-api', 'dev-folder-scanning', 'dev-sso-guide', 'dev-airgapped'
|
'show-javascript', 'dev-api', 'dev-folder-scanning', 'dev-sso-guide', 'dev-airgapped', 'validate-pdf-signature'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Tool identity - what PDF operation we're performing (type-safe)
|
// Tool identity - what PDF operation we're performing (type-safe)
|
||||||
|
Loading…
Reference in New Issue
Block a user