UNPKG

bigblocks

Version:

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

185 lines (184 loc) 13.7 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useCallback, useMemo, useState } from "react"; import { useBitcoinAuth } from "../../hooks/useBitcoinAuth.js"; import { ErrorDisplay } from "../ui-components/ErrorDisplay.js"; import { LoadingButton } from "../ui-components/LoadingButton.js"; import { Modal } from "../ui-components/Modal.js"; import { PasswordInput } from "../ui-components/PasswordInput.js"; import { Button } from "../ui/button.js"; import { OAuthProviders, } from "../wallet-integrations/OAuthProviders.js"; export function OAuthRestoreFlow({ providers = [ { id: "google", name: "Google", enabled: true, icon: (_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", children: [_jsx("title", { children: "Google" }), _jsx("path", { fill: "#4285F4", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), _jsx("path", { fill: "#34A853", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), _jsx("path", { fill: "#FBBC05", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), _jsx("path", { fill: "#EA4335", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })] })), }, { id: "github", name: "GitHub", enabled: true, icon: (_jsxs("svg", { width: "20", height: "20", fill: "currentColor", viewBox: "0 0 24 24", children: [_jsx("title", { children: "GitHub" }), _jsx("path", { d: "M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" })] })), }, { id: "twitter", name: "X (Twitter)", enabled: true, icon: (_jsxs("svg", { width: "20", height: "20", fill: "currentColor", viewBox: "0 0 24 24", children: [_jsx("title", { children: "X (Twitter)" }), _jsx("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" })] })), }, ], signupCallbackUrl = "/signup/oauth", signinCallbackUrl = "/signin/oauth-restore", showProviderSelection = true, showPasswordEntry = true, allowProviderSwitch = true, isModal = false, isOpen = true, onClose, onProviderSelected, onPasswordValidated, onRestoreSuccess, onRestoreError, onFlowComplete, className = "", title = "Restore from Cloud Backup", subtitle = "Sign in with your linked account to restore your wallet", mockOAuthResponse, }) { const [currentStep, setCurrentStep] = useState("provider"); const [selectedProvider, setSelectedProvider] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [encryptedBackup, setEncryptedBackup] = useState(""); const auth = useBitcoinAuth(); const { isLoading } = auth; // Handle methods that might not be available in all contexts (for backward compatibility) const restoreFromBackup = useMemo(() => { const authWithMethods = auth; return (authWithMethods.restoreFromBackup || (async () => ({ success: false, error: "restoreFromBackup not available in this context", }))); }, [auth]); const validatePassword = useMemo(() => { const authWithMethods = auth; return authWithMethods.validatePassword || (async () => false); }, [auth]); // Handle OAuth provider selection const handleProviderClick = useCallback(async (provider) => { try { setError(""); setSelectedProvider(provider); onProviderSelected?.(provider); // If we have a mock response (for testing), use it if (mockOAuthResponse && mockOAuthResponse.provider === provider) { setEncryptedBackup(mockOAuthResponse.encryptedBackup); setCurrentStep("password"); return; } // Start OAuth flow setCurrentStep("processing"); // Redirect to OAuth provider const callbackUrl = currentStep === "provider" ? signinCallbackUrl : signupCallbackUrl; const authUrl = `/api/auth/signin/${provider}?callbackUrl=${encodeURIComponent(callbackUrl)}`; window.location.href = authUrl; } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to start OAuth flow"; setError(errorMessage); setCurrentStep("error"); onRestoreError?.(errorMessage); } }, [ currentStep, signinCallbackUrl, signupCallbackUrl, mockOAuthResponse, onProviderSelected, onRestoreError, ]); // Handle password validation and restore const handlePasswordSubmit = useCallback(async () => { if (!password.trim() || !encryptedBackup) { setError("Password is required"); return; } try { setError(""); setCurrentStep("processing"); // Validate password can decrypt the backup const isValid = await validatePassword(encryptedBackup, password); if (!isValid) { throw new Error("Invalid password. Please check your password and try again."); } onPasswordValidated?.(password); // Restore from backup const result = await restoreFromBackup(encryptedBackup, password); if (result.success && "idKey" in result && result.idKey) { setCurrentStep("success"); onRestoreSuccess?.(result.idKey); // Auto-close after success setTimeout(() => { onFlowComplete?.(); onClose?.(); }, 2000); } else { throw new Error(result.error || "Failed to restore backup"); } } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to restore backup"; setError(errorMessage); setCurrentStep("error"); onRestoreError?.(errorMessage); } }, [ password, encryptedBackup, validatePassword, restoreFromBackup, onPasswordValidated, onRestoreSuccess, onRestoreError, onFlowComplete, onClose, ]); // Handle provider switch const handleProviderSwitch = useCallback(() => { setCurrentStep("provider"); setSelectedProvider(""); setPassword(""); setError(""); setEncryptedBackup(""); }, []); // Handle retry const handleRetry = useCallback(() => { if (selectedProvider && allowProviderSwitch) { setCurrentStep("password"); } else { setCurrentStep("provider"); setSelectedProvider(""); } setPassword(""); setError(""); }, [selectedProvider, allowProviderSwitch]); const renderContent = () => { switch (currentStep) { case "provider": if (!showProviderSelection) { return (_jsx("div", { className: "flex justify-center", children: _jsx("p", { className: "text-muted-foreground text-center", children: "Provider selection disabled" }) })); } return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("h2", { className: "text-xl font-semibold text-center", children: title }), _jsx("p", { className: "text-muted-foreground text-center text-sm", children: subtitle })] }), _jsx(OAuthProviders, { providers: providers, action: "signin", onProviderClick: handleProviderClick, loading: isLoading })] })); case "password": if (!showPasswordEntry) { return (_jsx("div", { className: "flex justify-center", children: _jsx("p", { className: "text-muted-foreground text-center", children: "Password entry disabled" }) })); } return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("h2", { className: "text-xl font-semibold text-center", children: "Enter Your Password" }), _jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Enter the password you used to encrypt your wallet backup" }), selectedProvider && allowProviderSwitch && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: handleProviderSwitch, className: "mt-2 text-primary", children: ["Switch Provider (", selectedProvider, ")"] }))] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("div", { onKeyDown: (e) => e.key === "Enter" && !isLoading && password.trim() && handlePasswordSubmit(), children: _jsx(PasswordInput, { value: password, onChange: setPassword, placeholder: "Enter your wallet password", disabled: isLoading, autoFocus: true }) }), error && _jsx(ErrorDisplay, { error: error }), _jsx(LoadingButton, { onClick: handlePasswordSubmit, loading: isLoading, disabled: !password.trim(), className: "w-full", children: "Restore Wallet" })] })] })); case "processing": return (_jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsxs("svg", { className: "animate-spin h-10 w-10 text-primary", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-label": "Loading spinner", children: [_jsx("title", { children: "Loading spinner" }), _jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), _jsx("h2", { className: "text-xl font-semibold text-center", children: selectedProvider ? "Restoring Wallet..." : "Connecting to Provider..." }), _jsx("p", { className: "text-muted-foreground text-center text-sm", children: selectedProvider ? "Decrypting and restoring your wallet backup" : "Please complete the sign-in process in the popup window" })] })); case "success": return (_jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-green-500 flex items-center justify-center", children: _jsxs("svg", { width: "24", height: "24", fill: "none", stroke: "white", viewBox: "0 0 24 24", "aria-label": "Success", children: [_jsx("title", { children: "Success" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" })] }) }), _jsx("h2", { className: "text-xl font-semibold text-center", children: "Wallet Restored!" }), _jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Your wallet has been successfully restored from your cloud backup." })] })); case "error": return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-destructive flex items-center justify-center", children: _jsxs("svg", { width: "24", height: "24", fill: "none", stroke: "white", viewBox: "0 0 24 24", "aria-label": "Error", children: [_jsx("title", { children: "Error" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" })] }) }), _jsx("h2", { className: "text-xl font-semibold text-center", children: "Restore Failed" })] }), _jsx(ErrorDisplay, { error: error }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(LoadingButton, { onClick: handleRetry, loading: false, className: "w-full", variant: "secondary", children: "Try Again" }), onClose && (_jsx(Button, { variant: "ghost", size: "lg", onClick: onClose, className: "w-full", children: "Cancel" }))] })] })); default: return null; } }; if (isModal) { return (_jsx(Modal, { isOpen: isOpen, onClose: onClose || (() => { }), className: "max-w-md", children: _jsx("div", { className: `p-6 ${className || ""}`, children: renderContent() }) })); } return (_jsx("div", { className: `w-full h-full flex items-center justify-center ${className || ""}`, children: _jsx("div", { className: "w-full max-w-md p-4", children: renderContent() }) })); }