bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
15 lines • 13.9 kB
JSON
{
"name": "authentication-loginform",
"type": "registry:component",
"dependencies": [],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/authentication/LoginForm.tsx",
"type": "registry:component",
"content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useBitcoinAuth } from \"../../hooks/useBitcoinAuth.js\";\nimport { isBapMasterBackupLegacy } from \"../../lib/types.js\";\nimport type { BapMasterBackup, LoginFormProps } from \"../../lib/types.js\";\nimport { cn } from \"../../lib/utils.js\";\nimport { BackupDownload } from \"../backup-recovery/BackupDownload.js\";\nimport { FileImport } from \"../backup-recovery/FileImport.js\";\nimport { MnemonicDisplay } from \"../backup-recovery/MnemonicDisplay.js\";\nimport { useBapProfileSync } from \"../bap-identity/hooks/useBapProfileSync.js\";\nimport { ErrorDisplay } from \"../ui-components/ErrorDisplay.js\";\nimport { LoadingButton } from \"../ui-components/LoadingButton.js\";\nimport { PasswordInput } from \"../ui-components/PasswordInput.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Separator } from \"../ui/separator.js\";\n\nexport function LoginForm({\n\tmode = \"signin\",\n\tonSuccess,\n\tonError,\n\tclassName = \"\",\n\tstyle,\n\tcardProps: _cardProps = {},\n\tbuttonProps: _buttonProps = {},\n\t// Profile sync options\n\tenableProfileSync = false,\n\tmaxDiscoveryAttempts = 50,\n}: LoginFormProps) {\n\tconst {\n\t\tuser,\n\t\tcurrentStep,\n\t\thasLocalBackup,\n\t\tisLoading,\n\t\terror,\n\t\tsignIn,\n\t\tsignUp,\n\t\tgenerateBackup,\n\t\timportBackup,\n\t\tsetMode,\n\t\tsetStep,\n\t\treset: _reset,\n\t\tconfig,\n\t\thasUnencryptedBackupInSession,\n\t\thasEncryptedBackupStored,\n\t} = useBitcoinAuth();\n\n\tconst [password, setPassword] = useState(\"\");\n\tconst [confirmPassword, setConfirmPassword] = useState(\"\");\n\tconst [backup, setBackup] = useState<BapMasterBackup | null>(null);\n\tconst [backupDownloaded, setBackupDownloaded] = useState(false);\n\tconst [showSuccess, setShowSuccess] = useState(false);\n\tconst [needsEncryption, setNeedsEncryption] = useState(false);\n\n\t// Profile synchronization - only initialize if enabled\n\tconst profileSync = useBapProfileSync({\n\t\tautoSync: false, // Manual trigger only\n\t\tmaxDiscoveryAttempts,\n\t});\n\n\t// Set initial mode\n\tuseEffect(() => {\n\t\tsetMode(mode === \"restore\" ? \"signin\" : mode);\n\t\tif (mode === \"signin\" && hasLocalBackup) {\n\t\t\tsetStep(\"password\");\n\t\t} else if (mode === \"signup\") {\n\t\t\tsetStep(\"signup\");\n\t\t}\n\t}, [mode, hasLocalBackup, setMode, setStep]);\n\n\t// Handle errors\n\tuseEffect(() => {\n\t\tif (error && onError) {\n\t\t\tonError(error);\n\t\t}\n\t}, [error, onError]);\n\n\t// Check if we need encryption when step changes to password\n\tuseEffect(() => {\n\t\tif (currentStep === \"password\") {\n\t\t\thasUnencryptedBackupInSession().then(setNeedsEncryption);\n\t\t}\n\t}, [currentStep, hasUnencryptedBackupInSession]);\n\n\tconst handleSignIn = async (e: React.FormEvent) => {\n\t\te.preventDefault();\n\t\tconst result = await signIn(password);\n\t\tif (result.success) {\n\t\t\tsetShowSuccess(true);\n\n\t\t\t// Optional: Trigger profile sync after successful sign-in\n\t\t\tif (enableProfileSync && !profileSync.isScanning) {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tprofileSync.syncProfiles();\n\t\t\t\t}, 1000);\n\t\t\t}\n\n\t\t\t// Show success for a moment before calling onSuccess\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (user) {\n\t\t\t\t\tonSuccess?.(user);\n\t\t\t\t}\n\t\t\t}, 500);\n\t\t}\n\t};\n\n\tconst handleSignUp = async (e: React.FormEvent) => {\n\t\te.preventDefault();\n\n\t\tif (currentStep === \"signup\") {\n\t\t\t// Generate backup and move to password step\n\t\t\tconst newBackup = generateBackup();\n\t\t\tsetBackup(newBackup);\n\t\t\tsetStep(\"password\");\n\t\t} else if (currentStep === \"password\") {\n\t\t\t// Move to confirm password\n\t\t\tsetStep(\"confirm-password\");\n\t\t} else if (currentStep === \"confirm-password\") {\n\t\t\tif (password !== confirmPassword) {\n\t\t\t\t// Show error\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Show backup\n\t\t\tsetStep(\"backup-display\");\n\t\t} else if (currentStep === \"backup-display\") {\n\t\t\t// Complete signup\n\t\t\tconst result = await signUp(password);\n\t\t\tif (result.success && user) {\n\t\t\t\t// Optional: Trigger profile sync after successful sign-up\n\t\t\t\tif (enableProfileSync && !profileSync.isScanning) {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tprofileSync.syncProfiles();\n\t\t\t\t\t}, 1000);\n\t\t\t\t}\n\n\t\t\t\tonSuccess?.(user);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst handleFileImport = async (file: File) => {\n\t\tconst result = await importBackup(file);\n\t\tif (result.success) {\n\t\t\t// After successful import, we always have an unencrypted backup in session\n\t\t\t// that needs to be encrypted with a password\n\t\t\tsetNeedsEncryption(true);\n\t\t\tsetStep(\"password\");\n\t\t}\n\t};\n\n\t// Initial Sign In Screen\n\tif (currentStep === \"initial\" || currentStep === \"signin\") {\n\t\treturn (\n\t\t\t<div className={cn(\"space-y-6\", className)} style={style}>\n\t\t\t\t<div className=\"text-center space-y-2\">\n\t\t\t\t\t<h2 className=\"text-2xl font-semibold\">Welcome Back</h2>\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\tSign in to your Bitcoin identity\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\n\t\t\t\t{hasLocalBackup ? (\n\t\t\t\t\t<form onSubmit={handleSignIn}>\n\t\t\t\t\t\t<div className=\"space-y-4\">\n\t\t\t\t\t\t\t<PasswordInput\n\t\t\t\t\t\t\t\tlabel=\"Backup Password\"\n\t\t\t\t\t\t\t\tvalue={password}\n\t\t\t\t\t\t\t\tonChange={setPassword}\n\t\t\t\t\t\t\t\tplaceholder=\"Enter your password\"\n\t\t\t\t\t\t\t\tdisabled={isLoading}\n\t\t\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t\t{error?.message && <ErrorDisplay error={error.message} />}\n\n\t\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\tloading={isLoading || showSuccess}\n\t\t\t\t\t\t\t\tdisabled={!password || showSuccess}\n\t\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\t\tvariant={showSuccess ? \"default\" : \"default\"}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{showSuccess ? (\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<svg\n\t\t\t\t\t\t\t\t\t\t\twidth=\"16\"\n\t\t\t\t\t\t\t\t\t\t\theight=\"16\"\n\t\t\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\t\t\t\taria-label=\"Success\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<title>Success</title>\n\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\t\t\t\t\tstrokeWidth={2}\n\t\t\t\t\t\t\t\t\t\t\t\td=\"M5 13l4 4L19 7\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\tSuccess!\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\t\t\"Sign In\"\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</form>\n\t\t\t\t) : (\n\t\t\t\t\t<div className=\"space-y-4\">\n\t\t\t\t\t\t<p className=\"text-center text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tNo local backup found. Import your backup or create a new\n\t\t\t\t\t\t\tidentity.\n\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t<FileImport\n\t\t\t\t\t\t\tonFileSelect={handleFileImport}\n\t\t\t\t\t\t\taccept=\".json,.txt\"\n\t\t\t\t\t\t\tdisabled={isLoading}\n\t\t\t\t\t\t\tallowedBackupTypes={config.backupTypes?.enabled}\n\t\t\t\t\t\t\tunsupportedBackupMessage={\n\t\t\t\t\t\t\t\tconfig.backupTypes?.errorMessages?.unsupported\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsetMode(\"signup\");\n\t\t\t\t\t\t\t\tsetStep(\"signup\");\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCreate New Identity\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t);\n\t}\n\n\t// Password Entry for Imported Backup\n\tif (currentStep === \"password\" && mode === \"signin\") {\n\t\treturn (\n\t\t\t<form onSubmit={handleSignIn}>\n\t\t\t\t<div className={cn(\"space-y-6\", className)} style={style}>\n\t\t\t\t\t<div className=\"text-center space-y-2\">\n\t\t\t\t\t\t<h2 className=\"text-2xl font-semibold\">Enter Password</h2>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t{needsEncryption\n\t\t\t\t\t\t\t\t? \"Create a password to encrypt your imported backup\"\n\t\t\t\t\t\t\t\t: \"Enter password to decrypt your backup\"}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"space-y-4\">\n\t\t\t\t\t\t<PasswordInput\n\t\t\t\t\t\t\tlabel=\"Backup Password\"\n\t\t\t\t\t\t\tvalue={password}\n\t\t\t\t\t\t\tonChange={setPassword}\n\t\t\t\t\t\t\tplaceholder=\"Enter your password\"\n\t\t\t\t\t\t\tdisabled={isLoading}\n\t\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t{error?.message && <ErrorDisplay error={error.message} />}\n\n\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\tloading={isLoading}\n\t\t\t\t\t\t\tdisabled={!password}\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{needsEncryption ? \"Encrypt & Sign In\" : \"Decrypt & Sign In\"}\n\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</form>\n\t\t);\n\t}\n\n\t// Signup Flow - Password Entry\n\tif (currentStep === \"password\" && mode === \"signup\") {\n\t\treturn (\n\t\t\t<form onSubmit={handleSignUp}>\n\t\t\t\t<div className={cn(\"space-y-6\", className)} style={style}>\n\t\t\t\t\t<div className=\"text-center space-y-2\">\n\t\t\t\t\t\t<h2 className=\"text-2xl font-semibold\">Set Password</h2>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tSecure your Bitcoin identity\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"space-y-4\">\n\t\t\t\t\t\t<PasswordInput\n\t\t\t\t\t\t\tlabel=\"Password\"\n\t\t\t\t\t\t\tvalue={password}\n\t\t\t\t\t\t\tonChange={setPassword}\n\t\t\t\t\t\t\tplaceholder=\"Create a strong password\"\n\t\t\t\t\t\t\tdisabled={isLoading}\n\t\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\t\tshowHint\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t{error?.message && <ErrorDisplay error={error.message} />}\n\n\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\tloading={isLoading}\n\t\t\t\t\t\t\tdisabled={!password || password.length < 8}\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tContinue\n\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</form>\n\t\t);\n\t}\n\n\t// Signup Flow - Confirm Password\n\tif (currentStep === \"confirm-password\") {\n\t\treturn (\n\t\t\t<form onSubmit={handleSignUp}>\n\t\t\t\t<div className={cn(\"space-y-6\", className)} style={style}>\n\t\t\t\t\t<div className=\"text-center space-y-2\">\n\t\t\t\t\t\t<h2 className=\"text-2xl font-semibold\">Confirm Password</h2>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tRe-enter your password\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"space-y-4\">\n\t\t\t\t\t\t<PasswordInput\n\t\t\t\t\t\t\tlabel=\"Confirm Password\"\n\t\t\t\t\t\t\tvalue={confirmPassword}\n\t\t\t\t\t\t\tonChange={setConfirmPassword}\n\t\t\t\t\t\t\tplaceholder=\"Re-enter your password\"\n\t\t\t\t\t\t\tdisabled={isLoading}\n\t\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\t\thasError={\n\t\t\t\t\t\t\t\tconfirmPassword.length > 0 && password !== confirmPassword\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t{confirmPassword.length > 0 && password !== confirmPassword && (\n\t\t\t\t\t\t\t<ErrorDisplay error=\"Passwords do not match\" />\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\tloading={isLoading}\n\t\t\t\t\t\t\tdisabled={!confirmPassword || password !== confirmPassword}\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tContinue\n\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</form>\n\t\t);\n\t}\n\n\t// Signup Flow - Display Backup\n\tif (currentStep === \"backup-display\" && backup) {\n\t\treturn (\n\t\t\t<div className={cn(\"space-y-6\", className)} style={style}>\n\t\t\t\t<div className=\"text-center space-y-2\">\n\t\t\t\t\t<h2 className=\"text-2xl font-semibold\">Save Your Backup</h2>\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground text-center\">\n\t\t\t\t\t\tDownload and securely store your backup. You'll need this to recover\n\t\t\t\t\t\tyour identity.\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\n\t\t\t\t{isBapMasterBackupLegacy(backup) && (\n\t\t\t\t\t<MnemonicDisplay mnemonic={backup.mnemonic} />\n\t\t\t\t)}\n\t\t\t\t{!isBapMasterBackupLegacy(backup) && (\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground text-center\">\n\t\t\t\t\t\tType 42 backup format does not use mnemonic phrases. Your backup is\n\t\t\t\t\t\tready for download.\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\n\t\t\t\t<Separator />\n\n\t\t\t\t<BackupDownload\n\t\t\t\t\tbackup={backup}\n\t\t\t\t\tonDownloaded={() => setBackupDownloaded(true)}\n\t\t\t\t/>\n\n\t\t\t\t<LoadingButton\n\t\t\t\t\tonClick={handleSignUp}\n\t\t\t\t\tloading={isLoading}\n\t\t\t\t\tdisabled={!backupDownloaded}\n\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t>\n\t\t\t\t\tComplete Setup\n\t\t\t\t</LoadingButton>\n\n\t\t\t\t{!backupDownloaded && (\n\t\t\t\t\t<p className=\"text-xs text-muted-foreground text-center\">\n\t\t\t\t\t\tPlease download your backup before continuing\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t);\n\t}\n\n\t// Default fallback\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"flex flex-col items-center gap-4\", className)}\n\t\t\tstyle={style}\n\t\t>\n\t\t\t<p className=\"text-muted-foreground\">Loading...</p>\n\t\t</div>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/loginform.tsx"
}
]
}