bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
15 lines • 11.4 kB
JSON
{
"name": "bap-identity-components-bapkeyrotationmanager",
"type": "registry:component",
"dependencies": [],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/bap-identity/components/BapKeyRotationManager.tsx",
"type": "registry:component",
"content": "import {\n\tCheckCircledIcon,\n\tClockIcon,\n\tExclamationTriangleIcon,\n\tLockClosedIcon,\n\tUpdateIcon,\n} from \"@radix-ui/react-icons\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { CONTAINER_WIDTHS } from \"../../../lib/layout-constants.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { MasterKeyBadge, WarningCard } from \"../../ui-components/index.js\";\nimport { Badge } from \"../../ui/badge.js\";\nimport { Button } from \"../../ui/button.js\";\nimport { Card, CardContent } from \"../../ui/card.js\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogTitle,\n\tDialogTrigger,\n} from \"../../ui/dialog.js\";\nimport { Progress } from \"../../ui/progress.js\";\nimport { useBapKeyRotation } from \"../hooks/useBapKeyRotation.js\";\nimport type { BapKeyRotationManagerProps } from \"../types/identity.js\";\n\nexport function BapKeyRotationManager({\n\tbapInstance: _bapInstance,\n\tonRotationComplete: _onRotationComplete,\n\tshowRotationHistory = true,\n\tautoBackupAfterRotation = true,\n\tonBroadcast,\n\tvariant = \"surface\",\n\tsize = \"3\",\n}: BapKeyRotationManagerProps) {\n\tconst [showConfirmDialog, setShowConfirmDialog] = useState(false);\n\tconst [rotationProgress, setRotationProgress] = useState(0);\n\n\tconst {\n\t\trotateKey,\n\t\tisLoading: isRotating,\n\t\trotationHistory,\n\t\tcurrentKeyInfo,\n\t\terror,\n\t\tcanRotateKeys,\n\t\tmasterKeyError,\n\t} = useBapKeyRotation();\n\n\tconst handleRotation = useCallback(() => {\n\t\tsetShowConfirmDialog(false);\n\t\tsetRotationProgress(10);\n\n\t\tsetRotationProgress(30);\n\t\trotateKey({\n\t\t\tautoBackup: autoBackupAfterRotation,\n\t\t\tcustomBroadcast: onBroadcast,\n\t\t});\n\t}, [rotateKey, autoBackupAfterRotation, onBroadcast]);\n\n\tconst truncateAddress = (address: string) => {\n\t\treturn `${address.substring(0, 8)}...${address.slice(-6)}`;\n\t};\n\n\tconst formatDate = (date: Date) => {\n\t\treturn new Intl.RelativeTimeFormat(\"en\", { numeric: \"auto\" }).format(\n\t\t\tMath.round((date.getTime() - Date.now()) / (1000 * 60 * 60 * 24)),\n\t\t\t\"day\",\n\t\t);\n\t};\n\n\tconst sortedHistory = useMemo(() => {\n\t\treturn [...rotationHistory].sort(\n\t\t\t(a, b) => b.timestamp.getTime() - a.timestamp.getTime(),\n\t\t);\n\t}, [rotationHistory]);\n\n\t// Check if user has master key access (supports both legacy xprv and Type 42 masterKey)\n\tif (!canRotateKeys) {\n\t\treturn (\n\t\t\t<Card className={cn(\"p-6\", size === \"3\" && \"p-8\")}>\n\t\t\t\t<WarningCard\n\t\t\t\t\ttitle=\"Key Rotation Unavailable\"\n\t\t\t\t\tmessage={\n\t\t\t\t\t\tmasterKeyError ||\n\t\t\t\t\t\t\"Key rotation requires master backup access. Please sign in with your master backup (either traditional or Type 42) to use this feature.\"\n\t\t\t\t\t}\n\t\t\t\t\ticon={<LockClosedIcon />}\n\t\t\t\t/>\n\t\t\t</Card>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Card\n\t\t\tclassName={cn(\n\t\t\t\tsize === \"1\" ? \"text-sm\" : size === \"3\" ? \"text-base\" : \"text-sm\",\n\t\t\t)}\n\t\t>\n\t\t\t<CardContent className=\"p-6\">\n\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t{/* Current Key Info */}\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<div className=\"flex justify-between items-center mb-3\">\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<h2 className=\"text-xl font-bold\">\n\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<LockClosedIcon width=\"20\" height=\"20\" />\n\t\t\t\t\t\t\t\t\t\tKey Rotation Manager\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t<MasterKeyBadge tooltip=\"This feature requires signing in with a master backup (traditional or Type 42)\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\tonClick={() => setShowConfirmDialog(true)}\n\t\t\t\t\t\t\t\tdisabled={isRotating}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<UpdateIcon width=\"16\" height=\"16\" className=\"mr-2\" />\n\t\t\t\t\t\t\t\t{isRotating ? \"Rotating...\" : \"Rotate Key\"}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div className=\"space-y-3\">\n\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tCurrent Address\n\t\t\t\t\t\t\t\t</span>\n\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<Badge variant=\"default\" className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\tActive\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-mono\">\n\t\t\t\t\t\t\t\t\t\t{truncateAddress(currentKeyInfo.address)}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tIdentity Path\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-mono\">{currentKeyInfo.path}</span>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tLast Rotation\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t{sortedHistory.length > 0\n\t\t\t\t\t\t\t\t\t\t? formatDate(sortedHistory[0]?.timestamp ?? new Date())\n\t\t\t\t\t\t\t\t\t\t: \"Never rotated\"}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Rotation Progress */}\n\t\t\t\t\t{rotationProgress > 0 && (\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<p className=\"text-sm mb-2\">Rotating key...</p>\n\t\t\t\t\t\t\t<Progress value={rotationProgress} />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Error Display */}\n\t\t\t\t\t{error && (\n\t\t\t\t\t\t<WarningCard title=\"Rotation Failed\" message={error.message} />\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Rotation History */}\n\t\t\t\t\t{showRotationHistory && sortedHistory.length > 0 && (\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<h3 className=\"text-lg font-bold mb-3\">Rotation History</h3>\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t{sortedHistory.map((event) => (\n\t\t\t\t\t\t\t\t\t<Card key={event.id} className=\"p-3\">\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\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<Badge\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tevent.status === \"confirmed\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? \"default\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: event.status === \"pending\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? \"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: \"destructive\"\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\tclassName=\"text-xs\"\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\t{event.status === \"confirmed\" && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CheckCircledIcon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twidth=\"12\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\theight=\"12\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"mr-1\"\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)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{event.status === \"pending\" && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<ClockIcon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twidth=\"12\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\theight=\"12\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"mr-1\"\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)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{event.status}\n\t\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\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatDate(event.timestamp)}\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\t<span className=\"text-xs font-mono\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{truncateAddress(event.fromAddress)} →{\" \"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{truncateAddress(event.toAddress)}\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</div>\n\t\t\t\t\t\t\t\t\t\t\t{event.blockHeight && (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tBlock #{event.blockHeight}\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)}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Confirmation Dialog */}\n\t\t\t\t<Dialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>\n\t\t\t\t\t<DialogContent className={cn(\"max-w-md\")}>\n\t\t\t\t\t\t<DialogTitle>Confirm Key Rotation</DialogTitle>\n\n\t\t\t\t\t\t<div className=\"flex flex-col gap-4 mt-4\">\n\t\t\t\t\t\t\t<WarningCard\n\t\t\t\t\t\t\t\ttitle=\"Important Security Action\"\n\t\t\t\t\t\t\t\tmessage=\"Key rotation will change your signing address while maintaining your identity. This action cannot be undone.\"\n\t\t\t\t\t\t\t\ticon={<ExclamationTriangleIcon />}\n\t\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t\t<div className=\"space-y-3\">\n\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tCurrent Address\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-mono\">\n\t\t\t\t\t\t\t\t\t\t{truncateAddress(currentKeyInfo.address)}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tEstimated Fee\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">~500 satoshis</span>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t{autoBackupAfterRotation && (\n\t\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\tAuto-backup\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<Badge variant=\"default\" className=\"text-xs\">\n\t\t\t\t\t\t\t\t\t\t\tEnabled\n\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div className=\"flex gap-3 mt-4 justify-end\">\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\tonClick={() => setShowConfirmDialog(false)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\tonClick={handleRotation}\n\t\t\t\t\t\t\t\tdisabled={isRotating}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<UpdateIcon width=\"16\" height=\"16\" className=\"mr-2\" />\n\t\t\t\t\t\t\t\t{isRotating ? \"Confirming...\" : \"Confirm Rotation\"}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</DialogContent>\n\t\t\t\t</Dialog>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/bapkeyrotationmanager.tsx"
}
]
}