small styling fixes + fix potential memory leaks

This commit is contained in:
Chad Curtis 2025-07-14 06:27:29 +00:00
parent a51094e157
commit a92f80b6e3
3 changed files with 79 additions and 26 deletions

View File

@ -76,8 +76,13 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
// Generate QR code
useEffect(() => {
let isCancelled = false;
const generateQR = async () => {
if (!invoice) return;
if (!invoice) {
setQrCodeUrl('');
return;
}
try {
const url = await QRCode.toDataURL(invoice.toUpperCase(), {
@ -88,13 +93,22 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
light: '#FFFFFF',
},
});
setQrCodeUrl(url);
if (!isCancelled) {
setQrCodeUrl(url);
}
} catch (err) {
console.error('Failed to generate QR code:', err);
if (!isCancelled) {
console.error('Failed to generate QR code:', err);
}
}
};
generateQR();
return () => {
isCancelled = true;
};
}, [invoice]);
const handleCopy = async () => {
@ -122,6 +136,12 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
setInvoice(null);
setCopied(false);
setQrCodeUrl('');
} else {
// Clean up state when dialog closes
setAmount(100);
setInvoice(null);
setCopied(false);
setQrCodeUrl('');
}
}, [open, setInvoice]);
@ -134,7 +154,7 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
const ZapContent = () => (
<>
{invoice ? (
<div className="space-y-4 px-4 pb-4">
<div className="space-y-4">
{/* Payment amount display */}
<div className="text-center">
<div className="text-2xl font-bold">{amount} sats</div>
@ -212,7 +232,7 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
Open in Lightning Wallet
</Button>
<div className="text-[.65rem] text-muted-foreground text-center px-2">
<div className="text-[.65rem] text-muted-foreground text-center">
Scan the QR code or copy the invoice to pay with any Lightning wallet.
</div>
</div>
@ -432,9 +452,8 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
<DrawerTitle className="text-lg break-words pt-2">
Send a Zap
</DrawerTitle>
<DrawerDescription className="text-sm break-words">
Zaps are small Bitcoin payments that support the creator of this item.
{' '}If you enjoyed this, consider sending a zap!
<DrawerDescription className="text-sm break-words text-center">
Zaps are small Bitcoin payments that support the creator of this item. If you enjoyed this, consider sending a zap!
</DrawerDescription>
</DrawerHeader>
<div className="overflow-y-auto">
@ -457,13 +476,12 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
<DialogTitle className="text-lg break-words">
{invoice ? 'Lightning Payment' : 'Send a Zap'}
</DialogTitle>
<DialogDescription className="text-sm break-words">
<DialogDescription className="text-sm text-center break-words">
{invoice ? (
'Pay with Bitcoin Lightning Network'
) : (
<>
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!
</>
)}
</DialogDescription>

View File

@ -66,6 +66,7 @@ export function useNWCInternal() {
try {
// Test the connection by creating an LN client with timeout
let timeoutId: NodeJS.Timeout | undefined;
const testPromise = new Promise((resolve, reject) => {
try {
const client = new LN(parsed.connectionString);
@ -74,10 +75,17 @@ export function useNWCInternal() {
reject(error);
}
});
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Connection test timeout')), 10000);
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => reject(new Error('Connection test timeout')), 10000);
});
await Promise.race([testPromise, timeoutPromise]) as LN;
try {
await Promise.race([testPromise, timeoutPromise]) as LN;
if (timeoutId) clearTimeout(timeoutId);
} catch (error) {
if (timeoutId) clearTimeout(timeoutId);
throw error;
}
const connection: NWCConnection = {
connectionString: parsed.connectionString,
@ -149,7 +157,7 @@ export function useNWCInternal() {
setActiveConnection(connections[0].connectionString);
return connections[0];
}
if (!activeConnection) return null;
const found = connections.find(c => c.connectionString === activeConnection);
@ -175,14 +183,22 @@ export function useNWCInternal() {
}
try {
// Add timeout to prevent hanging
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Payment timeout after 15 seconds')), 15000);
// Add timeout to prevent hanging with proper cleanup
let timeoutId: NodeJS.Timeout | undefined;
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => reject(new Error('Payment timeout after 15 seconds')), 15000);
});
const paymentPromise = client.pay(invoice);
const response = await Promise.race([paymentPromise, timeoutPromise]) as { preimage: string };
return response;
try {
const response = await Promise.race([paymentPromise, timeoutPromise]) as { preimage: string };
if (timeoutId) clearTimeout(timeoutId);
return response;
} catch (error) {
if (timeoutId) clearTimeout(timeoutId);
throw error;
}
} catch (error) {
console.error('NWC payment failed:', error);
@ -222,6 +238,7 @@ export function useNWCInternal() {
try {
// Create a fresh client for testing
let timeoutId: NodeJS.Timeout | undefined;
const testPromise = new Promise((resolve, reject) => {
try {
const client = new LN(connection.connectionString);
@ -231,10 +248,17 @@ export function useNWCInternal() {
}
});
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Connection test timeout')), 5000);
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => reject(new Error('Connection test timeout')), 5000);
});
await Promise.race([testPromise, timeoutPromise]);
try {
await Promise.race([testPromise, timeoutPromise]);
if (timeoutId) clearTimeout(timeoutId);
} catch (error) {
if (timeoutId) clearTimeout(timeoutId);
throw error;
}
return true;
} catch (error) {

View File

@ -1,4 +1,4 @@
import { useState, useMemo } from 'react';
import { useState, useMemo, useEffect } from 'react';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useAuthor } from '@/hooks/useAuthor';
import { useAppContext } from '@/hooks/useAppContext';
@ -32,10 +32,21 @@ export function useZaps(
const [isZapping, setIsZapping] = useState(false);
const [invoice, setInvoice] = useState<string | null>(null);
// Cleanup state when component unmounts
useEffect(() => {
return () => {
setIsZapping(false);
setInvoice(null);
};
}, []);
const { data: zapEvents, ...query } = useQuery<NostrEvent[], Error>({
queryKey: ['zaps', actualTarget?.id],
staleTime: 30000, // 30 seconds
refetchInterval: 60000, // Refetch every minute to catch new zaps
refetchInterval: (query) => {
// Only refetch if the query is currently being observed (component is mounted)
return query.getObserversCount() > 0 ? 60000 : false;
},
queryFn: async (c) => {
if (!actualTarget) return [];
@ -64,7 +75,7 @@ export function useZaps(
// Process zap events into simple counts and totals
const { zapCount, totalSats, zaps } = useMemo(() => {
if (!zapEvents || !actualTarget) {
if (!zapEvents || !Array.isArray(zapEvents) || !actualTarget) {
return { zapCount: 0, totalSats: 0, zaps: [] };
}