bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
15 lines • 19.6 kB
JSON
{
"name": "bap-identity-components-bapfilesigner",
"type": "registry:component",
"dependencies": [],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/bap-identity/components/BapFileSigner.tsx",
"type": "registry:component",
"content": "import {\n\tCheckCircledIcon,\n\tCrossCircledIcon,\n\tDownloadIcon,\n\tFileIcon,\n\tFileTextIcon,\n\tUploadIcon,\n} from \"@radix-ui/react-icons\";\nimport { useCallback, useState } from \"react\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { DropZone, ErrorDisplay } from \"../../ui-components/index.js\";\nimport { Badge } from \"../../ui/badge.js\";\nimport { Button } from \"../../ui/button.js\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"../../ui/card.js\";\nimport { Progress } from \"../../ui/progress.js\";\nimport {\n\tTable,\n\tTableBody,\n\tTableCell,\n\tTableHead,\n\tTableHeader,\n\tTableRow,\n} from \"../../ui/table.js\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../../ui/tabs.js\";\nimport { useBapSigning } from \"../hooks/useBapSigning.js\";\nimport type {\n\tBapFileSignerProps,\n\tSignedFile,\n\tSigningMode,\n} from \"../types/signing.js\";\n\nconst DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\nconst DEFAULT_ACCEPTED_TYPES = [\n\t\".pdf\",\n\t\".txt\",\n\t\".json\",\n\t\".md\",\n\t\".doc\",\n\t\".docx\",\n];\n\nexport function BapFileSigner({\n\tbapInstance: _bapInstance,\n\tmode = \"both\",\n\tonFilesSigned,\n\tonFileVerified,\n\tblockchainAnchor = false,\n\tmaxFileSize = DEFAULT_MAX_FILE_SIZE,\n\tacceptedTypes = DEFAULT_ACCEPTED_TYPES,\n\tvariant = \"surface\",\n\tsize = \"3\",\n}: BapFileSignerProps) {\n\tconst [files, setFiles] = useState<SignedFile[]>([]);\n\tconst [currentMode, setCurrentMode] = useState<SigningMode>(\n\t\tmode === \"both\" ? \"sign\" : mode,\n\t);\n\tconst [processingFileId, setProcessingFileId] = useState<string | null>(null);\n\tconst [dropError, setDropError] = useState<string | null>(null);\n\n\tconst { signFileAsync, verifyFileAsync, isLoading, error } = useBapSigning();\n\n\t// Process individual file (declared first to be used in handleFiles)\n\tconst processFile = useCallback(\n\t\tasync (signedFile: SignedFile) => {\n\t\t\tconst fileId = `${signedFile.file.name}-${signedFile.timestamp.getTime()}`;\n\t\t\tsetProcessingFileId(fileId);\n\n\t\t\ttry {\n\t\t\t\tif (currentMode === \"sign\") {\n\t\t\t\t\tconst result = await signFileAsync({\n\t\t\t\t\t\tfile: signedFile.file,\n\t\t\t\t\t\toptions: { blockchainAnchor },\n\t\t\t\t\t});\n\n\t\t\t\t\tif (result.success && result.signature && result.address) {\n\t\t\t\t\t\tconst updatedFile: SignedFile = {\n\t\t\t\t\t\t\t...signedFile,\n\t\t\t\t\t\t\tsignature: result.signature,\n\t\t\t\t\t\t\taddress: result.address,\n\t\t\t\t\t\t\tstatus: \"signed\",\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\t\tprev.map((f) =>\n\t\t\t\t\t\t\t\tf.file.name === signedFile.file.name &&\n\t\t\t\t\t\t\t\tf.timestamp === signedFile.timestamp\n\t\t\t\t\t\t\t\t\t? updatedFile\n\t\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tonFilesSigned?.([updatedFile]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow new Error(result.error?.message || \"Signing failed\");\n\t\t\t\t\t}\n\t\t\t\t} else if (currentMode === \"verify\") {\n\t\t\t\t\t// For verification, we need the signature - it should be in the filename or metadata\n\t\t\t\t\tconst result = await verifyFileAsync({\n\t\t\t\t\t\tfile: signedFile.file,\n\t\t\t\t\t\tsignature: signedFile.signature,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (result.valid) {\n\t\t\t\t\t\tconst updatedFile: SignedFile = {\n\t\t\t\t\t\t\t...signedFile,\n\t\t\t\t\t\t\taddress: result.address || \"\",\n\t\t\t\t\t\t\tidentityKey: result.identityKey || \"\",\n\t\t\t\t\t\t\tstatus: \"verified\",\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\t\tprev.map((f) =>\n\t\t\t\t\t\t\t\tf.file.name === signedFile.file.name &&\n\t\t\t\t\t\t\t\tf.timestamp === signedFile.timestamp\n\t\t\t\t\t\t\t\t\t? updatedFile\n\t\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tonFileVerified?.(result);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst updatedFile: SignedFile = {\n\t\t\t\t\t\t\t...signedFile,\n\t\t\t\t\t\t\tstatus: \"invalid\",\n\t\t\t\t\t\t\terror: result.error,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\t\tprev.map((f) =>\n\t\t\t\t\t\t\t\tf.file.name === signedFile.file.name &&\n\t\t\t\t\t\t\t\tf.timestamp === signedFile.timestamp\n\t\t\t\t\t\t\t\t\t? updatedFile\n\t\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconst updatedFile: SignedFile = {\n\t\t\t\t\t...signedFile,\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: err instanceof Error ? err.message : \"Processing failed\",\n\t\t\t\t};\n\n\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\tprev.map((f) =>\n\t\t\t\t\t\tf.file.name === signedFile.file.name &&\n\t\t\t\t\t\tf.timestamp === signedFile.timestamp\n\t\t\t\t\t\t\t? updatedFile\n\t\t\t\t\t\t\t: f,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tsetProcessingFileId(null);\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tcurrentMode,\n\t\t\tsignFileAsync,\n\t\t\tverifyFileAsync,\n\t\t\tblockchainAnchor,\n\t\t\tonFilesSigned,\n\t\t\tonFileVerified,\n\t\t],\n\t);\n\n\t// Process files (declared second to use processFile)\n\tconst handleFiles = useCallback(\n\t\t(newFiles: File[]) => {\n\t\t\tsetDropError(null); // Clear any previous errors\n\n\t\t\tconst validFiles = newFiles.filter((file) => {\n\t\t\t\t// Check file size\n\t\t\t\tif (file.size > maxFileSize) {\n\t\t\t\t\tconsole.error(`File ${file.name} exceeds size limit`);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Check file type\n\t\t\t\tconst extension = `.${file.name.split(\".\").pop()?.toLowerCase()}`;\n\t\t\t\tif (acceptedTypes.length > 0 && !acceptedTypes.includes(extension)) {\n\t\t\t\t\tconsole.error(`File type ${extension} not accepted`);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\t\tconst signedFiles: SignedFile[] = validFiles.map((file) => ({\n\t\t\t\tfile,\n\t\t\t\tsignature: \"\",\n\t\t\t\taddress: \"\",\n\t\t\t\tidentityKey: \"\",\n\t\t\t\ttimestamp: new Date(),\n\t\t\t\tstatus: \"pending\",\n\t\t\t}));\n\n\t\t\tsetFiles((prev) => [...prev, ...signedFiles]);\n\n\t\t\t// Auto-process in sign mode\n\t\t\tif (currentMode === \"sign\") {\n\t\t\t\tfor (const sf of signedFiles) {\n\t\t\t\t\tprocessFile(sf);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[maxFileSize, acceptedTypes, currentMode, processFile],\n\t);\n\n\t// Download signed file\n\tconst downloadSignedFile = useCallback(\n\t\t(signedFile: SignedFile) => {\n\t\t\tif (signedFile.status !== \"signed\") return;\n\n\t\t\t// Create signature metadata\n\t\t\tconst metadata = {\n\t\t\t\tfilename: signedFile.file.name,\n\t\t\t\tsignature: signedFile.signature,\n\t\t\t\taddress: signedFile.address,\n\t\t\t\tidentityKey: signedFile.identityKey,\n\t\t\t\ttimestamp: signedFile.timestamp.toISOString(),\n\t\t\t\tanchored: blockchainAnchor,\n\t\t\t};\n\n\t\t\t// Create a blob with the signature\n\t\t\tconst blob = new Blob([JSON.stringify(metadata, null, 2)], {\n\t\t\t\ttype: \"application/json\",\n\t\t\t});\n\t\t\tconst url = URL.createObjectURL(blob);\n\t\t\tconst a = document.createElement(\"a\");\n\t\t\ta.href = url;\n\t\t\ta.download = `${signedFile.file.name}.sig.json`;\n\t\t\tdocument.body.appendChild(a);\n\t\t\ta.click();\n\t\t\tdocument.body.removeChild(a);\n\t\t\tURL.revokeObjectURL(url);\n\t\t},\n\t\t[blockchainAnchor],\n\t);\n\n\t// Remove file from list\n\tconst removeFile = useCallback((signedFile: SignedFile) => {\n\t\tsetFiles((prev) =>\n\t\t\tprev.filter(\n\t\t\t\t(f) =>\n\t\t\t\t\t!(\n\t\t\t\t\t\tf.file.name === signedFile.file.name &&\n\t\t\t\t\t\tf.timestamp === signedFile.timestamp\n\t\t\t\t\t),\n\t\t\t),\n\t\t);\n\t}, []);\n\n\t// Get status color\n\tconst getStatusColor = (status: SignedFile[\"status\"]) => {\n\t\tswitch (status) {\n\t\t\tcase \"signed\":\n\t\t\tcase \"verified\":\n\t\t\t\treturn \"default\";\n\t\t\tcase \"invalid\":\n\t\t\tcase \"error\":\n\t\t\t\treturn \"destructive\";\n\t\t\tcase \"pending\":\n\t\t\t\treturn \"secondary\";\n\t\t\tdefault:\n\t\t\t\treturn \"secondary\";\n\t\t}\n\t};\n\n\t// Get status icon\n\tconst getStatusIcon = (status: SignedFile[\"status\"]) => {\n\t\tswitch (status) {\n\t\t\tcase \"signed\":\n\t\t\tcase \"verified\":\n\t\t\t\treturn <CheckCircledIcon />;\n\t\t\tcase \"invalid\":\n\t\t\tcase \"error\":\n\t\t\t\treturn <CrossCircledIcon />;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t};\n\n\tconst formatFileSize = (bytes: number) => {\n\t\tif (bytes < 1024) return `${bytes} B`;\n\t\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n\t\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n\t};\n\n\treturn (\n\t\t<Card className={cn(size === \"1\" || size === \"2\" ? \"p-4\" : \"p-6\")}>\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t{/* Mode selector for 'both' mode */}\n\t\t\t\t{mode === \"both\" && (\n\t\t\t\t\t<Tabs\n\t\t\t\t\t\tvalue={currentMode}\n\t\t\t\t\t\tonValueChange={(v) => setCurrentMode(v as SigningMode)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<TabsList>\n\t\t\t\t\t\t\t<TabsTrigger value=\"sign\">Sign Files</TabsTrigger>\n\t\t\t\t\t\t\t<TabsTrigger value=\"verify\">Verify Files</TabsTrigger>\n\t\t\t\t\t\t</TabsList>\n\t\t\t\t\t</Tabs>\n\t\t\t\t)}\n\n\t\t\t\t{/* Header */}\n\t\t\t\t<div>\n\t\t\t\t\t<h3 className=\"text-lg font-bold flex items-center gap-2\">\n\t\t\t\t\t\t<FileTextIcon className=\"h-5 w-5\" />\n\t\t\t\t\t\t{currentMode === \"sign\" ? \"File Signer\" : \"File Verifier\"}\n\t\t\t\t\t</h3>\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t{currentMode === \"sign\"\n\t\t\t\t\t\t\t? \"Sign files with your BAP identity\"\n\t\t\t\t\t\t\t: \"Verify file signatures\"}\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Drop zone */}\n\t\t\t\t<DropZone\n\t\t\t\t\tmultiple\n\t\t\t\t\taccept={\n\t\t\t\t\t\tcurrentMode === \"verify\"\n\t\t\t\t\t\t\t? [...acceptedTypes, \".sig.json\"].join(\",\")\n\t\t\t\t\t\t\t: acceptedTypes.join(\",\")\n\t\t\t\t\t}\n\t\t\t\t\tmaxSize={maxFileSize}\n\t\t\t\t\ticon={<UploadIcon />}\n\t\t\t\t\ttitle={\n\t\t\t\t\t\tcurrentMode === \"sign\"\n\t\t\t\t\t\t\t? \"Sign Files\"\n\t\t\t\t\t\t\t: currentMode === \"verify\"\n\t\t\t\t\t\t\t\t? \"Verify Files\"\n\t\t\t\t\t\t\t\t: \"Upload Files\"\n\t\t\t\t\t}\n\t\t\t\t\tdescription={\n\t\t\t\t\t\tcurrentMode === \"verify\"\n\t\t\t\t\t\t\t? \"Drop .sig.json files or original files to verify\"\n\t\t\t\t\t\t\t: \"Drag & drop files or click to browse\"\n\t\t\t\t\t}\n\t\t\t\t\tdragActiveText=\"Drop files here\"\n\t\t\t\t\tdragActiveDescription=\"Release to upload\"\n\t\t\t\t\theight={150}\n\t\t\t\t\tonDrop={(fileList) => handleFiles(Array.from(fileList))}\n\t\t\t\t\tonError={(error) => {\n\t\t\t\t\t\tconsole.error(\"File upload error:\", error);\n\t\t\t\t\t\tsetDropError(error);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t{/* Error display */}\n\t\t\t\t{error && <ErrorDisplay error={error.message} />}\n\t\t\t\t{dropError && <ErrorDisplay error={dropError} />}\n\n\t\t\t\t{/* Verification help text */}\n\t\t\t\t{currentMode === \"verify\" && files.length === 0 && (\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground text-center\">\n\t\t\t\t\t\tTo verify files, either:\n\t\t\t\t\t\t<br />• Upload .sig.json signature files generated when signing\n\t\t\t\t\t\t<br />• Upload original files and enter signatures manually\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\n\t\t\t\t{/* File list */}\n\t\t\t\t{files.length > 0 && (\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<Table>\n\t\t\t\t\t\t\t<TableHeader>\n\t\t\t\t\t\t\t\t<TableRow>\n\t\t\t\t\t\t\t\t\t<TableHead>File</TableHead>\n\t\t\t\t\t\t\t\t\t<TableHead>Size</TableHead>\n\t\t\t\t\t\t\t\t\t<TableHead>Status</TableHead>\n\t\t\t\t\t\t\t\t\t<TableHead>Actions</TableHead>\n\t\t\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t\t\t</TableHeader>\n\t\t\t\t\t\t\t<TableBody>\n\t\t\t\t\t\t\t\t{files.map((file, index) => {\n\t\t\t\t\t\t\t\t\tconst fileId = `${file.file.name}-${file.timestamp.getTime()}`;\n\t\t\t\t\t\t\t\t\tconst isProcessing = processingFileId === fileId;\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<TableRow key={fileId}>\n\t\t\t\t\t\t\t\t\t\t\t<TableCell>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<FileIcon />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{file.file.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t\t\t\t\t\t<TableCell>\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(file.file.size)}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t\t\t\t\t\t<TableCell>\n\t\t\t\t\t\t\t\t\t\t\t\t<Badge variant={getStatusColor(file.status)}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{getStatusIcon(file.status)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{file.status}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t\t{file.error && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-destructive block mt-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{file.error}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t\t\t\t\t\t<TableCell>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{file.status === \"pending\" &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcurrentMode === \"sign\" && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => processFile(file)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdisabled={isProcessing || isLoading}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tSign\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{file.status === \"pending\" &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcurrentMode === \"verify\" && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// For verification, we need to prompt for signature\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// For now, check if it's a .sig.json file\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (file.file.name.endsWith(\".sig.json\")) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Read the signature from the file\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treader.onload = async (e) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst sigData = JSON.parse(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te.target?.result as string,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst updatedFile = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t...file,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsignature: sigData.signature,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tprev.map((f) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tf.file.name === file.file.name &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tf.timestamp === file.timestamp\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? updatedFile\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tprocessFile(updatedFile);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} catch (_err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetDropError(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Invalid signature file format\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treader.readAsText(file.file);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// For regular files, we need a signature input\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst signature = prompt(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Enter the signature for this file:\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (signature) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst updatedFile = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t...file,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsignature,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tprev.map((f) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tf.file.name === file.file.name &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tf.timestamp === file.timestamp\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? updatedFile\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tprocessFile(updatedFile);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdisabled={isProcessing || isLoading}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tVerify\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{file.status === \"signed\" && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => downloadSignedFile(file)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<DownloadIcon />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tSignature\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolor=\"red\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => removeFile(file)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdisabled={isProcessing}\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</TableBody>\n\t\t\t\t\t\t</Table>\n\n\t\t\t\t\t\t{/* Batch actions */}\n\t\t\t\t\t\t{currentMode === \"sign\" &&\n\t\t\t\t\t\t\tfiles.some((f) => f.status === \"pending\") && (\n\t\t\t\t\t\t\t\t<div className=\"flex justify-end mt-3\">\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\t\t\tfiles\n\t\t\t\t\t\t\t\t\t\t\t\t.filter((f) => f.status === \"pending\")\n\t\t\t\t\t\t\t\t\t\t\t\t.forEach(processFile)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdisabled={isLoading}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\tSign All Pending\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t{/* Progress indicator */}\n\t\t\t\t{isLoading && (\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<span className=\"text-sm mb-2 block\">Processing files...</span>\n\t\t\t\t\t\t<Progress />\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</Card>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/bapfilesigner.tsx"
}
]
}