From 55bf54564642fb67a6139efcdbae8735f7b23566 Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Sun, 13 Jul 2025 05:59:56 +0000 Subject: [PATCH] cleanup + icons --- src/components/ZapDialog.tsx | 37 +++++++++++++--- src/hooks/useZaps.ts | 83 ++++++++++++------------------------ 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/components/ZapDialog.tsx b/src/components/ZapDialog.tsx index 0b196c1..83b71bb 100644 --- a/src/components/ZapDialog.tsx +++ b/src/components/ZapDialog.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useRef } from 'react'; -import { Zap, Copy } from 'lucide-react'; +import { Zap, Copy, Sparkle, Sparkles, Star, Rocket } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, @@ -18,6 +18,7 @@ import { useAuthor } from '@/hooks/useAuthor'; import { useToast } from '@/hooks/useToast'; import { useZaps } from '@/hooks/useZaps'; import type { WebLNProvider } from 'webln'; +import { requestProvider } from 'webln'; import QRCode from 'qrcode'; import type { Event } from 'nostr-tools'; @@ -27,11 +28,17 @@ interface ZapDialogProps { className?: string; } -const presetAmounts = [1, 50, 100, 250, 1000]; +const presetAmounts = [ + { amount: 1, icon: Sparkle }, + { amount: 50, icon: Sparkles }, + { amount: 100, icon: Zap }, + { amount: 250, icon: Star }, + { amount: 1000, icon: Rocket }, +]; export function ZapDialog({ target, children, className }: ZapDialogProps) { const [open, setOpen] = useState(false); - const [webln, _setWebln] = useState(null); + const [webln, setWebln] = useState(null); const { user } = useCurrentUser(); const { data: author } = useAuthor(target.pubkey); const { toast } = useToast(); @@ -47,6 +54,23 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) { } }, [target]); + // Detect WebLN when dialog opens + useEffect(() => { + const detectWebLN = async () => { + if (open && !webln) { + try { + const provider = await requestProvider(); + setWebln(provider); + } catch (error) { + console.warn('WebLN requestProvider failed:', error); + setWebln(null); + } + } + }; + + detectWebLN(); + }, [open, webln]); + useEffect(() => { if (invoice && qrCodeRef.current) { QRCode.toCanvas(qrCodeRef.current, invoice, { width: 256 }); @@ -81,8 +105,8 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) { return ( - @@ -123,12 +147,13 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) { }} className="grid grid-cols-5 gap-2" > - {presetAmounts.map((presetAmount) => ( + {presetAmounts.map(({ amount: presetAmount, icon: Icon }) => ( + {presetAmount} ))} diff --git a/src/hooks/useZaps.ts b/src/hooks/useZaps.ts index ccaf966..c6abc5f 100644 --- a/src/hooks/useZaps.ts +++ b/src/hooks/useZaps.ts @@ -1,11 +1,12 @@ import { useState } from 'react'; -import { useNostrPublish } from '@/hooks/useNostrPublish'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useAppContext } from '@/hooks/useAppContext'; import { useToast } from '@/hooks/useToast'; -import { nip57, nip19, Event } from 'nostr-tools'; +import { nip57, nip19 } from 'nostr-tools'; +import type { Event } from 'nostr-tools'; import type { WebLNProvider } from 'webln'; +import { LNURL } from '@nostrify/nostrify/ln'; import { useQuery } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; @@ -16,7 +17,6 @@ export function useZaps(target: Event, webln: WebLNProvider | null, onZapSuccess const { toast } = useToast(); const { user } = useCurrentUser(); const { config } = useAppContext(); - const { mutate: publishEvent } = useNostrPublish(); const author = useAuthor(target?.pubkey); const [isZapping, setIsZapping] = useState(false); const [invoice, setInvoice] = useState(null); @@ -86,18 +86,8 @@ export function useZaps(target: Event, webln: WebLNProvider | null, onZapSuccess } try { - const lud16 = author.data?.metadata?.lud16; - if (!lud16) { - toast({ - title: 'Lightning address not found', - description: 'The author does not have a lightning address configured.', - variant: 'destructive', - }); - setIsZapping(false); - return; - } - if (!author.data) { + if (!author.data || !author.data?.metadata) { toast({ title: 'Author not found', description: 'Could not find the author of this item.', @@ -107,67 +97,50 @@ export function useZaps(target: Event, webln: WebLNProvider | null, onZapSuccess return; } - const zapEndpoint = await nip57.getZapEndpoint(author.data.event as Event); - if (!zapEndpoint) { + const { lud06, lud16 } = author.data.metadata; + if (!lud16 && !lud06) { toast({ - title: 'Zap endpoint not found', - description: 'Could not find a zap endpoint for the author.', + title: 'Lightning address not found', + description: 'The author does not have a lightning address (lud16 or lud06) configured.', variant: 'destructive', }); setIsZapping(false); return; } + const lnurl = lud06 ? LNURL.fromString(lud06) : LNURL.fromLightningAddress(lud16!); const zapAmount = amount * 1000; // convert to millisats - const relays = [config.relayUrl]; - const zapRequest = nip57.makeZapRequest({ + const zapRequest = await user.signer.signEvent(nip57.makeZapRequest({ profile: target.pubkey, - event: target.id, + event: target, amount: zapAmount, - relays, + relays: [config.relayUrl], comment: comment, + })); + + const { pr: newInvoice } = await lnurl.getInvoice({ + amount: zapAmount, + nostr: zapRequest, }); - if (naddr) { - const decoded = nip19.decode(naddr).data as nip19.AddressPointer; - zapRequest.tags.push(["a", `${decoded.kind}:${decoded.pubkey}:${decoded.identifier}`]); - zapRequest.tags = zapRequest.tags.filter(t => t[0] !== 'e'); + if (webln) { + await webln.sendPayment(newInvoice); + toast({ + title: 'Zap successful!', + description: `You sent ${amount} sats to the author.`, + }); + onZapSuccess?.(); + } else { + setInvoice(newInvoice); } - - publishEvent(zapRequest, { - onSuccess: async (event) => { - try { - const res = await fetch(`${zapEndpoint}?amount=${zapAmount}&nostr=${encodeURI(JSON.stringify(event))}`); - const { pr: newInvoice } = await res.json(); - - if (webln) { - await webln.sendPayment(newInvoice); - toast({ - title: 'Zap successful!', - description: `You sent ${amount} sats to the author.`, - }); - onZapSuccess?.(); - } else { - setInvoice(newInvoice); - } - } catch (err) { - console.error('Zap error:', err); - toast({ - title: 'Zap failed', - description: (err as Error).message, - variant: 'destructive', - }); - } finally { - setIsZapping(false); - } - }, - }); } catch (err) { + console.error('Zap error:', err); toast({ title: 'Zap failed', description: (err as Error).message, variant: 'destructive', }); + } finally { setIsZapping(false); } };