import QrScannerModal from '@/components/QrScannerModal' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { useNostr } from '@/providers/NostrProvider' import { BunkerSigner } from '@/providers/NostrProvider/bunker.signer' import { ArrowLeft, Loader2, QrCode, Server, Copy, Check, ScanLine } from 'lucide-react' import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import QRCode from 'qrcode' // Default relay for bunker connections - can be configured const DEFAULT_BUNKER_RELAY = 'wss://relay.nsec.app' export default function BunkerLogin({ back, onLoginSuccess }: { back: () => void onLoginSuccess: () => void }) { const { t } = useTranslation() const { bunkerLoginWithSigner, bunkerLogin } = useNostr() const [mode, setMode] = useState<'choose' | 'scan' | 'paste'>('choose') const [bunkerUrl, setBunkerUrl] = useState('') const [relayUrl, setRelayUrl] = useState(DEFAULT_BUNKER_RELAY) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [connectUrl, setConnectUrl] = useState(null) const [qrDataUrl, setQrDataUrl] = useState(null) const [copied, setCopied] = useState(false) const [showScanner, setShowScanner] = useState(false) // Generate QR code when in scan mode useEffect(() => { if (mode !== 'scan') return let cancelled = false const startConnection = async () => { setLoading(true) setError(null) try { const { connectUrl, signer: signerPromise } = await BunkerSigner.awaitSignerConnection( relayUrl, undefined, 120000 // 2 minute timeout ) if (cancelled) return setConnectUrl(connectUrl) // Generate QR code const qr = await QRCode.toDataURL(connectUrl, { width: 256, margin: 2, color: { dark: '#000000', light: '#ffffff' } }) setQrDataUrl(qr) setLoading(false) // Wait for signer to connect const signer = await signerPromise if (cancelled) { signer.disconnect() return } // Get the user's pubkey from the signer const pubkey = await signer.getPublicKey() // Complete login await bunkerLoginWithSigner(signer, pubkey) onLoginSuccess() } catch (err) { if (!cancelled) { setError((err as Error).message) setLoading(false) } } } startConnection() return () => { cancelled = true } }, [mode, relayUrl, bunkerLoginWithSigner, onLoginSuccess]) const handleScan = (result: string) => { setBunkerUrl(result) setError(null) } const handlePasteSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!bunkerUrl.trim()) { setError(t('Please enter a bunker URL')) return } if (!bunkerUrl.startsWith('bunker://')) { setError(t('Invalid bunker URL format. Must start with bunker://')) return } setLoading(true) setError(null) try { // Use the existing bunkerLogin flow for bunker:// URLs await bunkerLogin(bunkerUrl.trim()) onLoginSuccess() } catch (err) { setError((err as Error).message) } finally { setLoading(false) } } const copyToClipboard = async () => { if (connectUrl) { await navigator.clipboard.writeText(connectUrl) setCopied(true) setTimeout(() => setCopied(false), 2000) } } if (mode === 'choose') { return (
e.stopPropagation()}>
{t('Login with Bunker')}

{t('What is a bunker?')}

{t( 'A bunker (NIP-46) is a remote signing service that keeps your private key secure while allowing you to sign Nostr events. Your key never leaves the bunker.' )}

) } if (mode === 'scan') { return (
e.stopPropagation()}>
{t('Scan with Signer')}
setRelayUrl(e.target.value)} disabled={loading || !!qrDataUrl} className="font-mono text-sm" />
{loading && !qrDataUrl && (
)} {qrDataUrl && (
Bunker QR Code
{copied ? ( ) : ( )}

{t('Scan this QR code with Amber or your NIP-46 signer')}

{t('Waiting for connection...')}
{connectUrl && (
)}
)} {error &&
{error}
}
) } // Paste mode return ( <> {showScanner && ( setShowScanner(false)} /> )}
e.stopPropagation()}>
{t('Paste Bunker URL')}
setBunkerUrl(e.target.value)} disabled={loading} className="font-mono text-sm" />

{t( 'Enter the bunker connection URL. This is typically provided by your signing device or service.' )}

{error &&
{error}
}
) }