bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
147 lines (146 loc) • 7.84 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* HandCash Connector Component
*
* Provides a complete UI for connecting to HandCash with:
* - OAuth flow management
* - Callback handling
* - Error handling
* - Loading states
*/
import { PersonIcon } from "@radix-ui/react-icons";
import React from "react";
import { cn } from "../../lib/utils.js";
import { HandCashIcon } from "../ui-components/HandCashIcon.js";
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js";
import { Button } from "../ui/button.js";
import { Card, CardContent } from "../ui/card.js";
import { OAUTH_BRAND_COLORS } from "./OAuthProviders.js";
export function HandCashConnector({ config, onSuccess, onError, className = "", }) {
const [state, setState] = React.useState({
isLoading: false,
isConnected: false,
profile: null,
authToken: null,
error: null,
});
// Lazy load the HandCash integration
const [handCashModule, setHandCashModule] = React.useState(null);
const loadHandCashModule = React.useCallback(async () => {
try {
const module = await import("../../lib/handcash-provider.js");
setHandCashModule(module);
}
catch {
setState((prev) => ({
...prev,
error: "Failed to load HandCash integration",
}));
}
}, []);
const handleOAuthCallback = React.useCallback(async () => {
// Check URL for HandCash callback
const urlParams = new globalThis.URLSearchParams(window.location.search);
const authToken = urlParams.get("authToken");
if (authToken && handCashModule) {
setState((prev) => ({ ...prev, isLoading: true }));
try {
const provider = new handCashModule.HandCashOAuthProvider(config);
const success = await provider.handleCallback(authToken);
if (success) {
const providerState = provider.getState();
setState((prev) => ({
...prev,
isConnected: true,
profile: providerState.profile,
authToken: providerState.authToken,
isLoading: false,
error: null,
}));
// Clean up URL
window.history.replaceState({}, document.title, window.location.pathname);
onSuccess?.({
authToken: providerState.authToken ?? "",
profile: providerState.profile ?? {},
// These would come from your backend after processing
idKey: providerState.profile?.id,
encryptedBackup: undefined,
});
}
else {
const errorMsg = provider.getState().error || "Authentication failed";
setState((prev) => ({
...prev,
isLoading: false,
error: errorMsg,
}));
onError?.(errorMsg);
}
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : "Unknown error occurred";
setState((prev) => ({
...prev,
isLoading: false,
error: errorMsg,
}));
onError?.(errorMsg);
}
}
}, [handCashModule, config, onSuccess, onError]);
React.useEffect(() => {
// Load HandCash module
loadHandCashModule();
// Check if this is a callback from HandCash
handleOAuthCallback();
}, [handleOAuthCallback, loadHandCashModule]);
const handleConnect = async () => {
if (!handCashModule) {
setState((prev) => ({ ...prev, error: "HandCash module not loaded" }));
return;
}
setState((prev) => ({ ...prev, isLoading: true, error: null }));
try {
const provider = new handCashModule.HandCashOAuthProvider(config);
await provider.startOAuth();
// Page will redirect, so this won't continue
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : "Failed to start OAuth flow";
setState((prev) => ({
...prev,
isLoading: false,
error: errorMsg,
}));
onError?.(errorMsg);
}
};
const handleDisconnect = async () => {
if (!handCashModule)
return;
try {
const provider = new handCashModule.HandCashOAuthProvider(config);
await provider.disconnect();
setState({
isLoading: false,
isConnected: false,
profile: null,
authToken: null,
error: null,
});
}
catch (error) {
console.error("Error disconnecting:", error);
}
};
// Loading state
if (state.isLoading) {
return (_jsxs("div", { className: cn("flex flex-col items-center gap-4", className), children: [_jsx("div", { className: "animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full" }), _jsx("p", { className: "text-muted-foreground", children: "Connecting to HandCash..." })] }));
}
// Connected state
if (state.isConnected && state.profile) {
return (_jsx("div", { className: cn("flex flex-col items-center gap-4", className), children: _jsx(Card, { className: "w-full", children: _jsxs(CardContent, { className: "flex flex-col items-center gap-3 p-6", children: [_jsxs(Avatar, { className: "h-20 w-20", children: [_jsx(AvatarImage, { src: state.profile.avatarUrl }), _jsx(AvatarFallback, { className: "bg-primary", children: _jsx(PersonIcon, { className: "h-6 w-6" }) })] }), _jsx("h2", { className: "text-2xl font-bold", children: "Connected to HandCash" }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("p", { className: "text-base text-muted-foreground", children: state.profile.displayName }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["@", state.profile.handle] })] }), _jsx(Button, { onClick: handleDisconnect, size: "sm", variant: "secondary", className: "mt-2", children: "Disconnect" })] }) }) }));
}
// Ready to connect state
return (_jsx("div", { className: cn("flex flex-col items-center gap-4", className), children: _jsx(Card, { className: "w-full", children: _jsxs(CardContent, { className: "flex flex-col items-center gap-4 p-6", children: [_jsx("div", { className: "w-12 h-12 rounded-full flex items-center justify-center", style: { backgroundColor: OAUTH_BRAND_COLORS.handcash }, children: _jsx(HandCashIcon, { size: 24, color: "white" }) }), _jsx("h2", { className: "text-2xl font-bold", children: "Connect with HandCash" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "Connect your HandCash wallet to authenticate with your Bitcoin identity." }), state.error && (_jsx(Card, { className: "w-full border-destructive", children: _jsx(CardContent, { className: "p-3", children: _jsx("p", { className: "text-sm text-destructive", children: state.error }) }) })), _jsx(Button, { onClick: handleConnect, disabled: state.isLoading || !handCashModule, size: "lg", className: "w-full", children: !handCashModule ? "Loading..." : "Connect HandCash Wallet" }), _jsx("p", { className: "text-xs text-muted-foreground text-center", children: "You'll be redirected to HandCash to authorize the connection" })] }) }) }));
}