bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
17 lines • 5.75 kB
JSON
{
"name": "backup-recovery-memberexport",
"type": "registry:component",
"dependencies": [
"@radix-ui/react-icons"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/backup-recovery/MemberExport.tsx",
"type": "registry:component",
"content": "import { ExclamationTriangleIcon, MobileIcon } from \"@radix-ui/react-icons\";\nimport React, { useState } from \"react\";\nimport { LoadingButton } from \"../ui-components/LoadingButton.js\";\nimport { QRCodeRenderer } from \"../ui-components/QRCodeRenderer.js\";\nimport { Alert, AlertDescription } from \"../ui/alert.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Card, CardContent } from \"../ui/card.js\";\n\nexport interface MemberExportProps {\n\tprofileName: string;\n\tonGenerateExport: () => Promise<{\n\t\tqrData: string;\n\t\ttoken: string;\n\t\texpiresAt: Date;\n\t}>;\n\tbaseUrl?: string;\n}\n\nexport function MemberExport({\n\tprofileName,\n\tonGenerateExport,\n}: MemberExportProps) {\n\tconst [qrData, setQrData] = useState<string | null>(null);\n\tconst [expiresAt, setExpiresAt] = useState<Date | null>(null);\n\tconst [loading, setLoading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\n\tconst handleGenerateExport = async () => {\n\t\tsetLoading(true);\n\t\tsetError(null);\n\t\ttry {\n\t\t\tconst result = await onGenerateExport();\n\t\t\tsetQrData(result.qrData);\n\t\t\tsetExpiresAt(result.expiresAt);\n\t\t} catch (err) {\n\t\t\tsetError(\n\t\t\t\terr instanceof Error ? err.message : \"Failed to generate export QR\",\n\t\t\t);\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t};\n\n\tconst handleReset = React.useCallback(() => {\n\t\tsetQrData(null);\n\t\tsetExpiresAt(null);\n\t\tsetError(null);\n\t}, []);\n\n\tconst getTimeRemaining = React.useCallback(() => {\n\t\tif (!expiresAt) return null;\n\t\tconst now = new Date();\n\t\tconst diff = expiresAt.getTime() - now.getTime();\n\t\tif (diff <= 0) return \"Expired\";\n\t\tconst minutes = Math.floor(diff / 60000);\n\t\tconst seconds = Math.floor((diff % 60000) / 1000);\n\t\treturn `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n\t}, [expiresAt]);\n\n\tReact.useEffect(() => {\n\t\tif (!expiresAt) return;\n\n\t\tconst interval = setInterval(() => {\n\t\t\tconst remaining = getTimeRemaining();\n\t\t\tif (remaining === \"Expired\") {\n\t\t\t\thandleReset();\n\t\t\t\tclearInterval(interval);\n\t\t\t}\n\t\t}, 1000);\n\n\t\treturn () => clearInterval(interval);\n\t}, [expiresAt, getTimeRemaining, handleReset]);\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<div className=\"flex flex-col items-center gap-1\">\n\t\t\t\t<h2 className=\"text-xl font-semibold\">Export to Mobile</h2>\n\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\tExport \"{profileName}\" profile to use on your mobile device\n\t\t\t\t</p>\n\t\t\t</div>\n\n\t\t\t{error && (\n\t\t\t\t<Alert variant=\"destructive\">\n\t\t\t\t\t<AlertDescription>{error}</AlertDescription>\n\t\t\t\t</Alert>\n\t\t\t)}\n\n\t\t\t{!qrData ? (\n\t\t\t\t<LoadingButton\n\t\t\t\t\tonClick={handleGenerateExport}\n\t\t\t\t\tloading={loading}\n\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\tvariant=\"default\"\n\t\t\t\t>\n\t\t\t\t\t<MobileIcon className=\"h-4 w-4 mr-2\" />\n\t\t\t\t\tExport to Mobile\n\t\t\t\t</LoadingButton>\n\t\t\t) : (\n\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t<Card className=\"bg-muted/50 border\">\n\t\t\t\t\t\t<CardContent className=\"p-4\">\n\t\t\t\t\t\t\t<QRCodeRenderer\n\t\t\t\t\t\t\t\tdata={qrData}\n\t\t\t\t\t\t\t\tsize={256}\n\t\t\t\t\t\t\t\tqrStyle=\"dots\"\n\t\t\t\t\t\t\t\tthemeAware={true}\n\t\t\t\t\t\t\t\tbgColor=\"var(--background)\"\n\t\t\t\t\t\t\t\tfgColor=\"var(--primary)\"\n\t\t\t\t\t\t\t\tlogoImage=\"https://cdn.jsdelivr.net/gh/radix-ui/icons@main/packages/radix-icons/icons/mobile.svg\"\n\t\t\t\t\t\t\t\tlogoWidth={32}\n\t\t\t\t\t\t\t\tlogoHeight={32}\n\t\t\t\t\t\t\t\tlogoOpacity={0.7}\n\t\t\t\t\t\t\t\tstyle={{ width: \"100%\", height: \"auto\" }}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t</Card>\n\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">Expires in:</span>\n\t\t\t\t\t\t\t<span className=\"text-sm font-mono\">{getTimeRemaining()}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={handleReset}\n\t\t\t\t\t\t\tsize=\"default\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<Alert className=\"bg-amber-50 border-amber-200\">\n\t\t\t\t\t\t<ExclamationTriangleIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t<AlertDescription className=\"text-amber-900\">\n\t\t\t\t\t\t\t<span className=\"font-semibold\">Security Notice:</span> This QR\n\t\t\t\t\t\t\tcode contains a link to download your member backup. You'll need\n\t\t\t\t\t\t\tto enter your password on the mobile device to decrypt it.\n\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t</Alert>\n\n\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t1. Scan this QR code on your mobile device\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t2. Enter your password when prompted\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t3. The member backup will be downloaded\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/memberexport.tsx"
}
]
}