This commit is contained in:
Chad Curtis 2025-07-13 20:12:42 +00:00
parent 8c34dbc8d5
commit 7eecec7506
5 changed files with 18 additions and 161 deletions

View File

@ -10,7 +10,6 @@ interface ZapButtonProps {
target: Event;
className?: string;
showCount?: boolean;
// New: option to pass pre-fetched zap data (for batch mode)
zapData?: { count: number; totalSats: number; isLoading?: boolean };
}

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import { Zap, Copy, Sparkle, Sparkles, Star, Rocket, Wallet, Globe } from 'lucide-react';
import { Zap, Copy, Sparkle, Sparkles, Star, Rocket } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -40,24 +40,7 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
const { user } = useCurrentUser();
const { data: author } = useAuthor(target.pubkey);
const { toast } = useToast();
const { webln, activeNWC, hasWebLN, hasNWC, detectWebLN } = useWallet();
// Debug logging
useEffect(() => {
console.debug('ZapDialog wallet status:', { hasWebLN, hasNWC, activeNWC: !!activeNWC });
}, [hasWebLN, hasNWC, activeNWC]);
// Additional debug logging when dialog opens
useEffect(() => {
if (open) {
console.debug('ZapDialog opened with wallet status:', {
hasWebLN,
hasNWC,
activeNWC: activeNWC ? { alias: activeNWC.alias, isConnected: activeNWC.isConnected } : null
});
}
}, [open, hasWebLN, hasNWC, activeNWC]);
const { webln, activeNWC, hasWebLN, detectWebLN } = useWallet();
const { zap, isZapping, invoice, setInvoice } = useZaps(target, webln, activeNWC, () => setOpen(false));
const [amount, setAmount] = useState<number | string>(100);
const [comment, setComment] = useState<string>('');
@ -104,7 +87,7 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
zap(finalAmount, comment);
};
if (!user || user.pubkey === target.pubkey || !author?.metadata?.lud16) {
if (!user || user.pubkey === target.pubkey || !author?.metadata?.lud06 && !author?.metadata?.lud16) {
return null;
}
@ -141,28 +124,6 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
</div>
) : (
<>
{/* Payment Method Indicator */}
<div className="flex items-center justify-center py-2 px-1">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{hasNWC ? (
<>
<Wallet className="h-4 w-4 text-green-600" />
<span>Wallet Connected</span>
</>
) : hasWebLN ? (
<>
<Globe className="h-4 w-4 text-blue-600" />
<span>WebLN Available</span>
</>
) : (
<>
<Copy className="h-4 w-4" />
<span>Manual Payment</span>
</>
)}
</div>
</div>
<div className="grid gap-4 py-4">
<ToggleGroup
type="single"

View File

@ -25,8 +25,6 @@ export function useNWCInternal() {
const [activeConnection, setActiveConnection] = useLocalStorage<string | null>('nwc-active-connection', null);
const [connectionInfo, setConnectionInfo] = useState<Record<string, NWCInfo>>({});
// Use connections directly - no filtering needed
// Parse and validate NWC URI
const parseNWCUri = (uri: string): { connectionString: string } | null => {
try {
@ -70,8 +68,6 @@ export function useNWCInternal() {
}
try {
console.debug('Testing NWC connection:', { uri: uri.substring(0, 50) + '...' });
// Test the connection by creating an LN client with timeout
const testPromise = new Promise((resolve, reject) => {
try {
@ -81,18 +77,15 @@ export function useNWCInternal() {
reject(error);
}
});
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Connection test timeout')), 10000);
});
const _client = await Promise.race([testPromise, timeoutPromise]) as LN;
await Promise.race([testPromise, timeoutPromise]) as LN;
const connection: NWCConnection = {
connectionString: parsed.connectionString,
alias: alias || 'NWC Wallet',
isConnected: true,
// Don't store the client, create fresh ones for each payment
};
// Store basic connection info
@ -107,33 +100,9 @@ export function useNWCInternal() {
const newConnections = [...connections, connection];
setConnections(newConnections);
console.debug('NWC connection added:', {
alias: connection.alias,
totalConnections: newConnections.length,
connectionString: parsed.connectionString.substring(0, 50) + '...',
isConnected: connection.isConnected
});
// Set as active if it's the first connection or no active connection is set
if (connections.length === 0 || !activeConnection) {
console.debug('Setting as active connection:', {
alias: connection.alias,
connectionString: parsed.connectionString.substring(0, 50) + '...',
previousActiveConnection: activeConnection
});
if (connections.length === 0 || !activeConnection)
setActiveConnection(parsed.connectionString);
console.debug('Active connection set to:', parsed.connectionString.substring(0, 50) + '...');
}
console.debug('NWC connection successful');
// Force a small delay to ensure state updates are processed
setTimeout(() => {
console.debug('Post-connection state check:', {
connectionsLength: connections.length + 1, // +1 because we just added one
newConnectionAlias: connection.alias
});
}, 100);
toast({
title: 'Wallet connected',
@ -159,14 +128,9 @@ export function useNWCInternal() {
const filtered = connections.filter(c => c.connectionString !== connectionString);
setConnections(filtered);
console.debug('NWC connection removed:', {
remainingConnections: filtered.length
});
if (activeConnection === connectionString) {
const newActive = filtered.length > 0 ? filtered[0].connectionString : null;
setActiveConnection(newActive);
console.debug('Active connection changed:', { newActive });
}
setConnectionInfo(prev => {
@ -183,15 +147,8 @@ export function useNWCInternal() {
// Get active connection
const getActiveConnection = useCallback((): NWCConnection | null => {
console.debug('getActiveConnection called:', {
activeConnection,
connectionsLength: connections.length,
connections: connections.map(c => ({ alias: c.alias, connectionString: c.connectionString.substring(0, 50) + '...' }))
});
// If no active connection is set but we have connections, set the first one as active
if (!activeConnection && connections.length > 0) {
console.debug('Setting first connection as active:', connections[0].alias);
setActiveConnection(connections[0].connectionString);
return connections[0];
}
@ -202,7 +159,6 @@ export function useNWCInternal() {
}
const found = connections.find(c => c.connectionString === activeConnection);
console.debug('Found active connection:', found ? found.alias : 'null');
return found || null;
}, [activeConnection, connections, setActiveConnection]);
@ -218,7 +174,6 @@ export function useNWCInternal() {
// Always create a fresh client for each payment to avoid stale connections
let client: LN;
try {
console.debug('Creating fresh NWC client for payment...');
client = new LN(connection.connectionString);
} catch (error) {
console.error('Failed to create NWC client:', error);
@ -226,20 +181,13 @@ export function useNWCInternal() {
}
try {
console.debug('Sending payment via NWC SDK:', {
invoice: invoice.substring(0, 50) + '...',
connectionAlias: connection.alias
});
// Add timeout to prevent hanging
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Payment timeout after 30 seconds')), 30000);
setTimeout(() => reject(new Error('Payment timeout after 15 seconds')), 15);
});
const paymentPromise = client.pay(invoice);
const response = await Promise.race([paymentPromise, timeoutPromise]) as { preimage: string };
console.debug('Payment successful:', { preimage: response.preimage });
return response;
} catch (error) {
console.error('NWC payment failed:', error);
@ -279,8 +227,6 @@ export function useNWCInternal() {
}
try {
console.debug('Testing NWC connection...', { alias: connection.alias });
// Create a fresh client for testing
const testPromise = new Promise((resolve, reject) => {
try {
@ -294,10 +240,8 @@ export function useNWCInternal() {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Connection test timeout')), 5000);
});
await Promise.race([testPromise, timeoutPromise]);
console.debug('NWC connection test successful');
return true;
} catch (error) {
console.error('NWC connection test failed:', error);
@ -305,8 +249,6 @@ export function useNWCInternal() {
}
}, []);
return {
connections,
activeConnection,

View File

@ -44,7 +44,7 @@ export function useWallet() {
}
}, [webln, isDetecting]);
// Only auto-detect once on mount, don't spam detection
// Only auto-detect once on mount
useEffect(() => {
if (!hasAttemptedDetection) {
detectWebLN();

View File

@ -24,7 +24,6 @@ function parseNWCUri(uri: string): NWCConnection | null {
const walletPubkey = url.pathname.replace('//', '');
const secret = url.searchParams.get('secret');
const relayParam = url.searchParams.getAll('relay');
const _lud16 = url.searchParams.get('lud16') || undefined;
if (!walletPubkey || !secret || relayParam.length === 0) {
return null;
@ -194,7 +193,7 @@ export function useZaps(
}
try {
if (!author.data || !author.data?.metadata) {
if (!author.data || !author.data?.metadata || !author.data?.event ) {
toast({
title: 'Author not found',
description: 'Could not find the author of this item.',
@ -204,8 +203,8 @@ export function useZaps(
return;
}
const { lud16 } = author.data.metadata;
if (!lud16) {
const { lud06, lud16 } = author.data.metadata;
if (!lud06 && !lud16) {
toast({
title: 'Lightning address not found',
description: 'The author does not have a lightning address configured.',
@ -216,7 +215,7 @@ export function useZaps(
}
// Get zap endpoint using the old reliable method
const zapEndpoint = await nip57.getZapEndpoint(author.data.event as Event);
const zapEndpoint = await nip57.getZapEndpoint(author.data.event);
if (!zapEndpoint) {
toast({
title: 'Zap endpoint not found',
@ -227,30 +226,20 @@ export function useZaps(
return;
}
// Create zap request
const zapAmount = amount * 1000; // convert to millisats
const relays = [config.relayUrl];
// Create zap request (unsigned, like the old implementation)
const zapRequest = nip57.makeZapRequest({
profile: primaryTarget.pubkey,
event: primaryTarget.id,
event: primaryTarget,
amount: zapAmount,
relays,
comment: comment,
relays: [config.relayUrl],
comment
});
// Handle addressable events
if (primaryTarget.kind >= 30000 && primaryTarget.kind < 40000) {
const identifier = primaryTarget.tags.find((t) => t[0] === 'd')?.[1] || '';
zapRequest.tags.push(["a", `${primaryTarget.kind}:${primaryTarget.pubkey}:${identifier}`]);
zapRequest.tags = zapRequest.tags.filter(t => t[0] !== 'e');
}
// Sign and publish the zap request
publishEvent(zapRequest, {
onSuccess: async (event) => {
try {
// Use the old fetch method - more reliable than LNURL validation
const res = await fetch(`${zapEndpoint}?amount=${zapAmount}&nostr=${encodeURI(JSON.stringify(event))}`);
const responseData = await res.json();
@ -266,38 +255,10 @@ export function useZaps(
// Get the current active NWC connection dynamically
const currentNWCConnection = getActiveConnection();
console.debug('Zap payment - detailed state check:', {
// Raw state
connectionsLength: connections.length,
activeConnectionString: activeConnection ? activeConnection.substring(0, 50) + '...' : null,
// Connection details
connections: connections.map(c => ({
alias: c.alias,
isConnected: c.isConnected,
connectionString: c.connectionString.substring(0, 50) + '...'
})),
// getActiveConnection result
currentNWCConnection: currentNWCConnection ? {
alias: currentNWCConnection.alias,
isConnected: currentNWCConnection.isConnected,
connectionString: currentNWCConnection.connectionString.substring(0, 50) + '...'
} : null
});
// Try NWC first if available and properly connected
if (currentNWCConnection && currentNWCConnection.connectionString && currentNWCConnection.isConnected) {
try {
console.debug('Attempting NWC payment...', {
amount,
alias: currentNWCConnection.alias,
invoiceLength: newInvoice.length
});
const response = await sendPayment(currentNWCConnection, newInvoice);
console.debug('NWC payment successful:', { preimage: response.preimage });
await sendPayment(currentNWCConnection, newInvoice);
// Clear states immediately on success
setIsZapping(false);
@ -322,10 +283,7 @@ export function useZaps(
variant: 'destructive',
});
}
}
// Fallback to WebLN or manual payment
if (webln) {
} else if (webln) { // Try WebLN next
await webln.sendPayment(newInvoice);
// Clear states immediately on success
@ -339,7 +297,7 @@ export function useZaps(
// Close dialog last to ensure clean state
onZapSuccess?.();
} else {
} else { // Default - show QR code and manual Lightning URI
setInvoice(newInvoice);
setIsZapping(false);
}
@ -376,7 +334,6 @@ export function useZaps(
};
return {
// Legacy single-target API (for backward compatibility)
zaps,
...query,
zap,
@ -384,8 +341,6 @@ export function useZaps(
invoice,
setInvoice,
parseNWCUri,
// New batch API
zapData,
isBatchMode,