UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

143 lines (142 loc) 11.5 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useState } from "react"; import { useBitcoinAuth } from "../../hooks/useBitcoinAuth.js"; import { isBapMasterBackupLegacy } from "../../lib/types.js"; import { cn } from "../../lib/utils.js"; import { BackupDownload } from "../backup-recovery/BackupDownload.js"; import { FileImport } from "../backup-recovery/FileImport.js"; import { MnemonicDisplay } from "../backup-recovery/MnemonicDisplay.js"; import { useBapProfileSync } from "../bap-identity/hooks/useBapProfileSync.js"; import { ErrorDisplay } from "../ui-components/ErrorDisplay.js"; import { LoadingButton } from "../ui-components/LoadingButton.js"; import { PasswordInput } from "../ui-components/PasswordInput.js"; import { Button } from "../ui/button.js"; import { Separator } from "../ui/separator.js"; export function LoginForm({ mode = "signin", onSuccess, onError, className = "", style, cardProps: _cardProps = {}, buttonProps: _buttonProps = {}, // Profile sync options enableProfileSync = false, maxDiscoveryAttempts = 50, }) { const { user, currentStep, hasLocalBackup, isLoading, error, signIn, signUp, generateBackup, importBackup, setMode, setStep, reset: _reset, config, hasUnencryptedBackupInSession, hasEncryptedBackupStored, } = useBitcoinAuth(); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [backup, setBackup] = useState(null); const [backupDownloaded, setBackupDownloaded] = useState(false); const [showSuccess, setShowSuccess] = useState(false); const [needsEncryption, setNeedsEncryption] = useState(false); // Profile synchronization - only initialize if enabled const profileSync = useBapProfileSync({ autoSync: false, // Manual trigger only maxDiscoveryAttempts, }); // Set initial mode useEffect(() => { setMode(mode === "restore" ? "signin" : mode); if (mode === "signin" && hasLocalBackup) { setStep("password"); } else if (mode === "signup") { setStep("signup"); } }, [mode, hasLocalBackup, setMode, setStep]); // Handle errors useEffect(() => { if (error && onError) { onError(error); } }, [error, onError]); // Check if we need encryption when step changes to password useEffect(() => { if (currentStep === "password") { hasUnencryptedBackupInSession().then(setNeedsEncryption); } }, [currentStep, hasUnencryptedBackupInSession]); const handleSignIn = async (e) => { e.preventDefault(); const result = await signIn(password); if (result.success) { setShowSuccess(true); // Optional: Trigger profile sync after successful sign-in if (enableProfileSync && !profileSync.isScanning) { setTimeout(() => { profileSync.syncProfiles(); }, 1000); } // Show success for a moment before calling onSuccess setTimeout(() => { if (user) { onSuccess?.(user); } }, 500); } }; const handleSignUp = async (e) => { e.preventDefault(); if (currentStep === "signup") { // Generate backup and move to password step const newBackup = generateBackup(); setBackup(newBackup); setStep("password"); } else if (currentStep === "password") { // Move to confirm password setStep("confirm-password"); } else if (currentStep === "confirm-password") { if (password !== confirmPassword) { // Show error return; } // Show backup setStep("backup-display"); } else if (currentStep === "backup-display") { // Complete signup const result = await signUp(password); if (result.success && user) { // Optional: Trigger profile sync after successful sign-up if (enableProfileSync && !profileSync.isScanning) { setTimeout(() => { profileSync.syncProfiles(); }, 1000); } onSuccess?.(user); } } }; const handleFileImport = async (file) => { const result = await importBackup(file); if (result.success) { // After successful import, we always have an unencrypted backup in session // that needs to be encrypted with a password setNeedsEncryption(true); setStep("password"); } }; // Initial Sign In Screen if (currentStep === "initial" || currentStep === "signin") { return (_jsxs("div", { className: cn("space-y-6", className), style: style, children: [_jsxs("div", { className: "text-center space-y-2", children: [_jsx("h2", { className: "text-2xl font-semibold", children: "Welcome Back" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Sign in to your Bitcoin identity" })] }), hasLocalBackup ? (_jsx("form", { onSubmit: handleSignIn, children: _jsxs("div", { className: "space-y-4", children: [_jsx(PasswordInput, { label: "Backup Password", value: password, onChange: setPassword, placeholder: "Enter your password", disabled: isLoading, autoFocus: true }), error?.message && _jsx(ErrorDisplay, { error: error.message }), _jsx(LoadingButton, { type: "submit", loading: isLoading || showSuccess, disabled: !password || showSuccess, className: "w-full", variant: showSuccess ? "default" : "default", children: showSuccess ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("svg", { width: "16", height: "16", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-label": "Success", children: [_jsx("title", { children: "Success" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" })] }), "Success!"] })) : ("Sign In") })] }) })) : (_jsxs("div", { className: "space-y-4", children: [_jsx("p", { className: "text-center text-sm text-muted-foreground", children: "No local backup found. Import your backup or create a new identity." }), _jsx(FileImport, { onFileSelect: handleFileImport, accept: ".json,.txt", disabled: isLoading, allowedBackupTypes: config.backupTypes?.enabled, unsupportedBackupMessage: config.backupTypes?.errorMessages?.unsupported }), _jsx(Button, { variant: "secondary", onClick: () => { setMode("signup"); setStep("signup"); }, className: "w-full", children: "Create New Identity" })] }))] })); } // Password Entry for Imported Backup if (currentStep === "password" && mode === "signin") { return (_jsx("form", { onSubmit: handleSignIn, children: _jsxs("div", { className: cn("space-y-6", className), style: style, children: [_jsxs("div", { className: "text-center space-y-2", children: [_jsx("h2", { className: "text-2xl font-semibold", children: "Enter Password" }), _jsx("p", { className: "text-sm text-muted-foreground", children: needsEncryption ? "Create a password to encrypt your imported backup" : "Enter password to decrypt your backup" })] }), _jsxs("div", { className: "space-y-4", children: [_jsx(PasswordInput, { label: "Backup Password", value: password, onChange: setPassword, placeholder: "Enter your password", disabled: isLoading, autoFocus: true }), error?.message && _jsx(ErrorDisplay, { error: error.message }), _jsx(LoadingButton, { type: "submit", loading: isLoading, disabled: !password, className: "w-full", children: needsEncryption ? "Encrypt & Sign In" : "Decrypt & Sign In" })] })] }) })); } // Signup Flow - Password Entry if (currentStep === "password" && mode === "signup") { return (_jsx("form", { onSubmit: handleSignUp, children: _jsxs("div", { className: cn("space-y-6", className), style: style, children: [_jsxs("div", { className: "text-center space-y-2", children: [_jsx("h2", { className: "text-2xl font-semibold", children: "Set Password" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Secure your Bitcoin identity" })] }), _jsxs("div", { className: "space-y-4", children: [_jsx(PasswordInput, { label: "Password", value: password, onChange: setPassword, placeholder: "Create a strong password", disabled: isLoading, autoFocus: true, showHint: true }), error?.message && _jsx(ErrorDisplay, { error: error.message }), _jsx(LoadingButton, { type: "submit", loading: isLoading, disabled: !password || password.length < 8, className: "w-full", children: "Continue" })] })] }) })); } // Signup Flow - Confirm Password if (currentStep === "confirm-password") { return (_jsx("form", { onSubmit: handleSignUp, children: _jsxs("div", { className: cn("space-y-6", className), style: style, children: [_jsxs("div", { className: "text-center space-y-2", children: [_jsx("h2", { className: "text-2xl font-semibold", children: "Confirm Password" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Re-enter your password" })] }), _jsxs("div", { className: "space-y-4", children: [_jsx(PasswordInput, { label: "Confirm Password", value: confirmPassword, onChange: setConfirmPassword, placeholder: "Re-enter your password", disabled: isLoading, autoFocus: true, hasError: confirmPassword.length > 0 && password !== confirmPassword }), confirmPassword.length > 0 && password !== confirmPassword && (_jsx(ErrorDisplay, { error: "Passwords do not match" })), _jsx(LoadingButton, { type: "submit", loading: isLoading, disabled: !confirmPassword || password !== confirmPassword, className: "w-full", children: "Continue" })] })] }) })); } // Signup Flow - Display Backup if (currentStep === "backup-display" && backup) { return (_jsxs("div", { className: cn("space-y-6", className), style: style, children: [_jsxs("div", { className: "text-center space-y-2", children: [_jsx("h2", { className: "text-2xl font-semibold", children: "Save Your Backup" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "Download and securely store your backup. You'll need this to recover your identity." })] }), isBapMasterBackupLegacy(backup) && (_jsx(MnemonicDisplay, { mnemonic: backup.mnemonic })), !isBapMasterBackupLegacy(backup) && (_jsx("p", { className: "text-sm text-muted-foreground text-center", children: "Type 42 backup format does not use mnemonic phrases. Your backup is ready for download." })), _jsx(Separator, {}), _jsx(BackupDownload, { backup: backup, onDownloaded: () => setBackupDownloaded(true) }), _jsx(LoadingButton, { onClick: handleSignUp, loading: isLoading, disabled: !backupDownloaded, className: "w-full", children: "Complete Setup" }), !backupDownloaded && (_jsx("p", { className: "text-xs text-muted-foreground text-center", children: "Please download your backup before continuing" }))] })); } // Default fallback return (_jsx("div", { className: cn("flex flex-col items-center gap-4", className), style: style, children: _jsx("p", { className: "text-muted-foreground", children: "Loading..." }) })); }