From 8183e3cc5a3e754afd6f2e35dbebc65a757ab758 Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Mon, 14 Jul 2025 01:16:31 +0000 Subject: [PATCH] cleanup + shakespeare-like manual zap modal --- src/components/WalletModal.tsx | 43 +--------- src/components/ZapDialog.tsx | 148 ++++++++++++++++++++++++++++----- src/hooks/useZaps.ts | 27 ------ 3 files changed, 129 insertions(+), 89 deletions(-) diff --git a/src/components/WalletModal.tsx b/src/components/WalletModal.tsx index be28642..ecc46b3 100644 --- a/src/components/WalletModal.tsx +++ b/src/components/WalletModal.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Wallet, Plus, Trash2, Zap, Globe, Settings, CheckCircle } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { @@ -46,17 +46,6 @@ export function WalletModal({ children, className }: WalletModalProps) { const hasNWC = connections.length > 0 && connections.some(c => c.isConnected); const { toast } = useToast(); - // Debug logging for wallet modal status - useEffect(() => { - console.debug('WalletModal status:', { - hasWebLN, - hasNWC, - connectionsCount: connections.length, - connectionsDetails: connections.map(c => ({ alias: c.alias, isConnected: c.isConnected })), - isDetecting - }); - }, [hasWebLN, hasNWC, connections, isDetecting]); - const handleAddConnection = async () => { if (!connectionUri.trim()) { toast({ @@ -67,30 +56,13 @@ export function WalletModal({ children, className }: WalletModalProps) { return; } - console.debug('WalletModal: Before adding connection', { - currentConnections: connections.length, - hasNWC - }); - setIsConnecting(true); try { const success = await addConnection(connectionUri.trim(), alias.trim() || undefined); if (success) { - console.debug('WalletModal: Connection added successfully', { - newConnections: connections.length, - hasNWC - }); setConnectionUri(''); setAlias(''); setAddDialogOpen(false); - - // Force a small delay to check state after React updates - setTimeout(() => { - console.debug('WalletModal: Post-add state check', { - connectionsLength: connections.length, - hasNWC - }); - }, 100); } } finally { setIsConnecting(false); @@ -98,20 +70,7 @@ export function WalletModal({ children, className }: WalletModalProps) { }; const handleRemoveConnection = (connectionString: string) => { - console.debug('WalletModal: Before removing connection', { - currentConnections: connections.length, - hasNWC - }); - removeConnection(connectionString); - - // Force a small delay to check state after React updates - setTimeout(() => { - console.debug('WalletModal: Post-remove state check', { - connectionsLength: connections.length, - hasNWC - }); - }, 100); }; const handleSetActive = (connectionString: string) => { diff --git a/src/components/ZapDialog.tsx b/src/components/ZapDialog.tsx index 7a33eef..8ef5261 100644 --- a/src/components/ZapDialog.tsx +++ b/src/components/ZapDialog.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useRef } from 'react'; -import { Zap, Copy, Sparkle, Sparkles, Star, Rocket } from 'lucide-react'; +import { Zap, Copy, Check, ExternalLink, Sparkle, Sparkles, Star, Rocket } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, @@ -11,15 +11,18 @@ import { DialogTrigger, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; +import { Card, CardContent } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useToast } from '@/hooks/useToast'; import { useZaps } from '@/hooks/useZaps'; import { useWallet } from '@/hooks/useWallet'; -import QRCode from 'qrcode'; import type { Event } from 'nostr-tools'; +import QRCode from 'qrcode'; interface ZapDialogProps { target: Event; @@ -44,8 +47,9 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) { const { zap, isZapping, invoice, setInvoice } = useZaps(target, webln, activeNWC, () => setOpen(false)); const [amount, setAmount] = useState(100); const [comment, setComment] = useState(''); + const [copied, setCopied] = useState(false); + const [qrCodeUrl, setQrCodeUrl] = useState(''); const inputRef = useRef(null); - const qrCodeRef = useRef(null); useEffect(() => { if (target) { @@ -60,18 +64,45 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) { } }, [open, hasWebLN, detectWebLN]); + // Generate QR code useEffect(() => { - if (invoice && qrCodeRef.current) { - QRCode.toCanvas(qrCodeRef.current, invoice, { width: 256 }); - } + const generateQR = async () => { + if (!invoice) return; + + try { + const url = await QRCode.toDataURL(invoice.toUpperCase(), { + width: 256, + margin: 2, + color: { + dark: '#000000', + light: '#FFFFFF', + }, + }); + setQrCodeUrl(url); + } catch (err) { + console.error('Failed to generate QR code:', err); + } + }; + + generateQR(); }, [invoice]); - const handleCopy = () => { + const handleCopy = async () => { if (invoice) { - navigator.clipboard.writeText(invoice); + await navigator.clipboard.writeText(invoice); + setCopied(true); toast({ - title: 'Copied to clipboard!', + title: 'Invoice copied', + description: 'Lightning invoice copied to clipboard', }); + setTimeout(() => setCopied(false), 2000); + } + }; + + const openInWallet = () => { + if (invoice) { + const lightningUrl = `lightning:${invoice}`; + window.open(lightningUrl, '_blank'); } }; @@ -79,6 +110,8 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) { if (open) { setAmount(100); setInvoice(null); + setCopied(false); + setQrCodeUrl(''); } }, [open, setInvoice]); @@ -100,26 +133,101 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) { - {invoice ? 'Manual Zap' : 'Send a Zap'} - + {invoice ? 'Lightning Payment' : 'Send a Zap'} + {invoice ? ( -
Scan the QR code with a lightning-enabled wallet or copy the invoice below.
+ 'Scan the QR code or copy the invoice to pay with any Lightning wallet' ) : ( <> -
Zaps are small Bitcoin payments that support the creator of this item.
-
If you enjoyed this, consider sending a zap!
+ Zaps are small Bitcoin payments that support the creator of this item. + {' '}If you enjoyed this, consider sending a zap! )}
{invoice ? ( -
- -
- - +
+
+ + {/* Payment buttons */} +
+ {hasWebLN && ( + + )} + + + +
+ Scan the QR code or copy the invoice to pay with any Lightning wallet +
) : ( diff --git a/src/hooks/useZaps.ts b/src/hooks/useZaps.ts index c1f4a4d..36840df 100644 --- a/src/hooks/useZaps.ts +++ b/src/hooks/useZaps.ts @@ -12,32 +12,6 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; import type { NostrEvent } from '@nostrify/nostrify'; -// NWC utility functions -function parseNWCUri(uri: string): NWCConnection | null { - try { - const url = new URL(uri); - if (url.protocol !== 'nostr+walletconnect:') { - return null; - } - - const walletPubkey = url.pathname.replace('//', ''); - const secret = url.searchParams.get('secret'); - const relayParam = url.searchParams.getAll('relay'); - - if (!walletPubkey || !secret || relayParam.length === 0) { - return null; - } - - return { - connectionString: uri, - alias: 'Parsed NWC', - isConnected: false, - }; - } catch { - return null; - } -} - export function useZaps( target: Event | Event[], webln: WebLNProvider | null, @@ -327,6 +301,5 @@ export function useZaps( isZapping, invoice, setInvoice, - parseNWCUri, }; }