UNPKG

bigblocks

Version:

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

106 lines (105 loc) 7.69 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useCallback, useEffect, useState } from "react"; import { useAuthMessages } from "../../lib/auth-messages.js"; import { CONTAINER_WIDTHS } from "../../lib/layout-constants.js"; import { ErrorDisplay } from "../ui-components/ErrorDisplay.js"; import { LoadingButton } from "../ui-components/LoadingButton.js"; import { QRCodeRenderer } from "../ui-components/QRCodeRenderer.js"; import { Alert, AlertDescription } from "../ui/alert.js"; import { Button } from "../ui/button.js"; import { Card, CardContent, CardHeader, CardTitle } from "../ui/card.js"; export function DeviceLinkQR({ onLinkGenerated, onError }) { const messages = useAuthMessages(); const [qrCodeUrl, setQrCodeUrl] = useState(null); const [linkUrl, setLinkUrl] = useState(null); const [token, setToken] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [timeRemaining, setTimeRemaining] = useState(0); const [copied, setCopied] = useState(false); const formatTime = useCallback((seconds) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, "0")}`; }, []); const generateQRCode = useCallback(async () => { setLoading(true); setError(null); let intervalId = null; try { // For now, simulate the API call since backend isn't implemented // In a real implementation, this would call your backend const mockResponse = { url: `${window.location.origin}/device-link/mock-token-${Date.now()}`, token: `token-${Date.now().toString().slice(-8)}`, expiresIn: 600, // 10 minutes }; setLinkUrl(mockResponse.url); setToken(mockResponse.token); setTimeRemaining(mockResponse.expiresIn); onLinkGenerated?.(mockResponse.url, mockResponse.token); // Generate QR code (using a simple text representation for now) // In a real implementation, you'd use a QR code library const qrText = `QR Code would be generated here for: ${mockResponse.url}`; setQrCodeUrl(qrText); // Start countdown intervalId = setInterval(() => { setTimeRemaining((prev) => { if (prev <= 1) { if (intervalId) clearInterval(intervalId); setQrCodeUrl(null); setLinkUrl(null); setToken(null); return 0; } return prev - 1; }); }, 1000); } catch (err) { const errorMessage = err instanceof Error ? err.message : messages.errorDeviceLinkFailed; setError(errorMessage); onError?.(errorMessage); } finally { setLoading(false); } // Return cleanup function return () => { if (intervalId) clearInterval(intervalId); }; }, [onLinkGenerated, onError, messages.errorDeviceLinkFailed]); const copyToClipboard = useCallback(async () => { if (!linkUrl) return; try { await navigator.clipboard.writeText(linkUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Failed to copy to clipboard:", err); } }, [linkUrl]); const resetQRCode = useCallback(() => { setQrCodeUrl(null); setLinkUrl(null); setToken(null); setTimeRemaining(0); setError(null); }, []); // Cleanup on unmount useEffect(() => { return () => { setTimeRemaining(0); }; }, []); return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-center", children: messages.deviceLinkTitle }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: messages.deviceLinkSubtitle })] }), _jsx(CardContent, { children: _jsxs("div", { className: "flex flex-col gap-6", children: [error && _jsx(ErrorDisplay, { error: error }), !qrCodeUrl ? (_jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("p", { className: "text-sm text-muted-foreground text-center", children: "Generate a QR code to quickly sign in on another device" }), _jsx(LoadingButton, { onClick: generateQRCode, loading: loading, size: "lg", variant: "default", children: messages.deviceLinkGenerateButton })] })) : (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsx("div", { className: "flex flex-col gap-2", children: messages.deviceLinkInstructions.map((instruction, index) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-xs font-bold text-primary", children: [index + 1, "."] }), _jsx("span", { className: "text-sm text-muted-foreground", children: instruction })] }, instruction))) }), _jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center gap-3 p-6", children: [linkUrl ? (_jsx(QRCodeRenderer, { data: linkUrl, size: 200, qrStyle: "dots", themeAware: true, bgColor: "var(--background)", fgColor: "var(--primary)" })) : (_jsx("div", { className: "flex items-center justify-center border-2 border-dashed border-muted-foreground rounded-lg", style: { width: CONTAINER_WIDTHS.POPOVER_SMALL, height: CONTAINER_WIDTHS.POPOVER_SMALL, }, children: _jsx("span", { className: "text-xs text-muted-foreground text-center", children: "Generating..." }) })), timeRemaining > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm text-primary", children: messages.deviceLinkExpires }), _jsx("code", { className: "px-2 py-1 bg-primary/10 text-primary rounded text-xs", children: formatTime(timeRemaining) })] }))] }) }), linkUrl && (_jsx(Card, { className: "bg-muted/50", children: _jsxs(CardContent, { className: "flex flex-col gap-3 p-4", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Or visit manually:" }), _jsx("code", { className: "text-xs bg-background p-2 rounded border break-all", children: linkUrl }), _jsxs("div", { className: "flex justify-between items-center", children: [token && (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["Token: ", token.substring(0, 8), "..."] })), _jsx(Button, { size: "sm", variant: "ghost", onClick: copyToClipboard, className: copied ? "text-green-600" : "", children: copied ? messages.deviceLinkCopied : messages.deviceLinkCopyButton })] })] }) })), _jsxs("div", { className: "flex gap-3 justify-center", children: [_jsx(Button, { variant: "ghost", onClick: resetQRCode, children: messages.cancel }), _jsx(Button, { variant: "ghost", onClick: generateQRCode, disabled: loading, children: messages.deviceLinkNewCode })] }), _jsx(Alert, { className: "bg-amber-50 border-amber-200", children: _jsx(AlertDescription, { className: "text-amber-900", children: "The QR code contains a one-time link that expires in 10 minutes. You'll need to enter your encryption password on the new device." }) })] }))] }) })] })); }