bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
15 lines • 10.4 kB
JSON
{
"name": "backup-recovery-cloudbackupmanager",
"type": "registry:component",
"dependencies": [],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/backup-recovery/CloudBackupManager.tsx",
"type": "registry:component",
"content": "\"use client\";\n\nimport {\n\tCheckCircledIcon,\n\tExclamationTriangleIcon,\n\tInfoCircledIcon,\n\tUpdateIcon,\n\tUploadIcon,\n} from \"@radix-ui/react-icons\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useBitcoinAuth } from \"../../hooks/useBitcoinAuth.js\";\nimport { cn } from \"../../lib/utils.js\";\nimport { LoadingButton } from \"../ui-components/LoadingButton.js\";\nimport { Alert, AlertDescription } from \"../ui/alert.js\";\nimport { Badge } from \"../ui/badge.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Card, CardContent } from \"../ui/card.js\";\n\nexport interface CloudBackupStatus {\n\thasCloudBackup: boolean;\n\tisOutdated?: boolean;\n\tlastUpdated?: number;\n\tprovider?: string;\n}\n\nexport interface CloudBackupManagerProps {\n\tcheckBackupStatus?: () => Promise<CloudBackupStatus>;\n\tupdateCloudBackup?: () => Promise<void>;\n\tonStatusChange?: (status: CloudBackupStatus) => void;\n\tclassName?: string;\n}\n\n/**\n * CloudBackupManager - Manage encrypted backups stored with OAuth providers\n *\n * Features:\n * - Check cloud backup status\n * - Update outdated backups\n * - Show last backup time\n * - OAuth provider indication\n * - Automatic status refresh\n */\nexport function CloudBackupManager({\n\tcheckBackupStatus: checkBackupStatusProp,\n\tupdateCloudBackup: updateCloudBackupProp,\n\tonStatusChange,\n\tclassName = \"\",\n}: CloudBackupManagerProps) {\n\tuseBitcoinAuth();\n\tconst [status, setStatus] = useState<CloudBackupStatus>({\n\t\thasCloudBackup: false,\n\t});\n\tconst [isChecking, setIsChecking] = useState(true);\n\tconst [isUpdating, setIsUpdating] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\n\t// Default implementation if not provided\n\tconst checkBackupStatus = useMemo(() => {\n\t\treturn (\n\t\t\tcheckBackupStatusProp ||\n\t\t\t(async () => {\n\t\t\t\t// Check if user has any OAuth providers linked\n\t\t\t\t// For now, simulate based on localStorage\n\t\t\t\tconst linkedProvider = localStorage.getItem(\"oauthProvider\");\n\t\t\t\tif (!linkedProvider) {\n\t\t\t\t\treturn { hasCloudBackup: false };\n\t\t\t\t}\n\n\t\t\t\t// In a real implementation, this would check the backend\n\t\t\t\t// For now, we'll simulate based on localStorage\n\t\t\t\tconst lastBackupTime = localStorage.getItem(\"cloudBackupLastUpdated\");\n\t\t\t\tconst localBackupTime = localStorage.getItem(\"localBackupLastUpdated\");\n\n\t\t\t\treturn {\n\t\t\t\t\thasCloudBackup: !!lastBackupTime,\n\t\t\t\t\tisOutdated: localBackupTime\n\t\t\t\t\t\t? Number.parseInt(localBackupTime) >\n\t\t\t\t\t\t\tNumber.parseInt(lastBackupTime || \"0\")\n\t\t\t\t\t\t: false,\n\t\t\t\t\tlastUpdated: lastBackupTime\n\t\t\t\t\t\t? Number.parseInt(lastBackupTime)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\tprovider: linkedProvider || \"Unknown\",\n\t\t\t\t};\n\t\t\t})\n\t\t);\n\t}, [checkBackupStatusProp]);\n\n\t// Check status function (declared first to be used in useEffect)\n\tconst checkStatus = useCallback(async () => {\n\t\tsetIsChecking(true);\n\t\tsetError(null);\n\t\ttry {\n\t\t\tconst newStatus = await checkBackupStatus();\n\t\t\tsetStatus(newStatus);\n\t\t\tonStatusChange?.(newStatus);\n\t\t} catch (err) {\n\t\t\tsetError(\n\t\t\t\terr instanceof Error ? err.message : \"Failed to check backup status\",\n\t\t\t);\n\t\t} finally {\n\t\t\tsetIsChecking(false);\n\t\t}\n\t}, [checkBackupStatus, onStatusChange]);\n\n\tconst updateCloudBackup =\n\t\tupdateCloudBackupProp ||\n\t\t(async () => {\n\t\t\t// In a real implementation, this would update the backup on the server\n\t\t\t// For now, we'll simulate with a delay\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 1500));\n\t\t\tlocalStorage.setItem(\"cloudBackupLastUpdated\", Date.now().toString());\n\t\t});\n\n\tuseEffect(() => {\n\t\tcheckStatus();\n\t}, [checkStatus]);\n\n\tconst handleUpdate = async () => {\n\t\tsetIsUpdating(true);\n\t\tsetError(null);\n\t\ttry {\n\t\t\tawait updateCloudBackup();\n\t\t\tawait checkStatus();\n\t\t} catch (err) {\n\t\t\tsetError(\n\t\t\t\terr instanceof Error ? err.message : \"Failed to update cloud backup\",\n\t\t\t);\n\t\t} finally {\n\t\t\tsetIsUpdating(false);\n\t\t}\n\t};\n\n\tconst formatLastUpdated = (timestamp: number) => {\n\t\tconst date = new Date(timestamp);\n\t\tconst now = new Date();\n\t\tconst diffMs = now.getTime() - date.getTime();\n\t\tconst diffMins = Math.floor(diffMs / 60000);\n\t\tconst diffHours = Math.floor(diffMs / 3600000);\n\t\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\t\tif (diffMins < 1) return \"Just now\";\n\t\tif (diffMins < 60)\n\t\t\treturn `${diffMins} minute${diffMins > 1 ? \"s\" : \"\"} ago`;\n\t\tif (diffHours < 24)\n\t\t\treturn `${diffHours} hour${diffHours > 1 ? \"s\" : \"\"} ago`;\n\t\tif (diffDays < 7) return `${diffDays} day${diffDays > 1 ? \"s\" : \"\"} ago`;\n\t\treturn date.toLocaleDateString();\n\t};\n\n\tif (isChecking) {\n\t\treturn (\n\t\t\t<Card className={className}>\n\t\t\t\t<CardContent className=\"flex items-center gap-3 p-6\">\n\t\t\t\t\t<UpdateIcon className=\"animate-spin\" />\n\t\t\t\t\t<p className=\"text-base\">Checking backup status...</p>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Card className={className}>\n\t\t\t<CardContent className=\"flex flex-col gap-4 p-6\">\n\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<UploadIcon className=\"w-5 h-5\" />\n\t\t\t\t\t\t<h3 className=\"text-lg font-bold\">Cloud Backup</h3>\n\t\t\t\t\t</div>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tonClick={checkStatus}\n\t\t\t\t\t\tdisabled={isChecking}\n\t\t\t\t\t>\n\t\t\t\t\t\t<UpdateIcon className=\"w-4 h-4 mr-1\" />\n\t\t\t\t\t\tRefresh\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\n\t\t\t\t{error && (\n\t\t\t\t\t<Alert variant=\"destructive\">\n\t\t\t\t\t\t<ExclamationTriangleIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t<AlertDescription>{error}</AlertDescription>\n\t\t\t\t\t</Alert>\n\t\t\t\t)}\n\n\t\t\t\t{!status.hasCloudBackup ? (\n\t\t\t\t\t<Alert>\n\t\t\t\t\t\t<InfoCircledIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t<AlertDescription>\n\t\t\t\t\t\t\tNo cloud backup found. Link an OAuth provider (Google, GitHub, or\n\t\t\t\t\t\t\tX) in Settings to enable cloud backup restoration from other\n\t\t\t\t\t\t\tdevices.\n\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t</Alert>\n\t\t\t\t) : (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<dl className=\"space-y-2\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t<dt className=\"text-sm text-muted-foreground min-w-[100px]\">\n\t\t\t\t\t\t\t\t\tStatus\n\t\t\t\t\t\t\t\t</dt>\n\t\t\t\t\t\t\t\t<dd>\n\t\t\t\t\t\t\t\t\t<Badge\n\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\tstatus.isOutdated\n\t\t\t\t\t\t\t\t\t\t\t\t? \"bg-orange-100 text-orange-700\"\n\t\t\t\t\t\t\t\t\t\t\t\t: \"bg-green-100 text-green-700\",\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{status.isOutdated ? \"Outdated\" : \"Up to date\"}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t<dt className=\"text-sm text-muted-foreground min-w-[100px]\">\n\t\t\t\t\t\t\t\t\tProvider\n\t\t\t\t\t\t\t\t</dt>\n\t\t\t\t\t\t\t\t<dd className=\"text-sm\">{status.provider || \"Unknown\"}</dd>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{status.lastUpdated && (\n\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t<dt className=\"text-sm text-muted-foreground min-w-[100px]\">\n\t\t\t\t\t\t\t\t\t\tLast Updated\n\t\t\t\t\t\t\t\t\t</dt>\n\t\t\t\t\t\t\t\t\t<dd className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t{formatLastUpdated(status.lastUpdated)}\n\t\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</dl>\n\n\t\t\t\t\t\t{status.isOutdated && (\n\t\t\t\t\t\t\t<Alert className=\"bg-purple-50 border-purple-200\">\n\t\t\t\t\t\t\t\t<ExclamationTriangleIcon className=\"h-4 w-4 text-purple-600\" />\n\t\t\t\t\t\t\t\t<AlertDescription className=\"text-purple-700\">\n\t\t\t\t\t\t\t\t\tYour local backup is newer than the cloud backup. Update it to\n\t\t\t\t\t\t\t\t\tensure you can access your latest identity from other devices.\n\t\t\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t\t\t</Alert>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t<div className=\"flex gap-3 justify-center\">\n\t\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\t\tloading={isUpdating}\n\t\t\t\t\t\t\t\tdisabled={!status.isOutdated}\n\t\t\t\t\t\t\t\tonClick={handleUpdate}\n\t\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<UploadIcon className=\"w-4 h-4 mr-1\" />\n\t\t\t\t\t\t\t\t{isUpdating\n\t\t\t\t\t\t\t\t\t? \"Updating...\"\n\t\t\t\t\t\t\t\t\t: status.isOutdated\n\t\t\t\t\t\t\t\t\t\t? \"Update Cloud Backup\"\n\t\t\t\t\t\t\t\t\t\t: \"Already Up to Date\"}\n\t\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2 mb-2\">\n\t\t\t\t\t\t\t\t<CheckCircledIcon className=\"text-green-600\" />\n\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Backup Features</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t• Encrypted with your password\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t• Stored securely with OAuth provider\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t• Accessible from any device\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t• Automatic version tracking\n\t\t\t\t\t\t\t\t</p>\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)}\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/cloudbackupmanager.tsx"
}
]
}