bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
143 lines (142 loc) • 11.5 kB
JavaScript
"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..." }) }));
}