mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-08-26 20:49:22 +00:00
small styling fixes + fix potential memory leaks
This commit is contained in:
parent
a51094e157
commit
a92f80b6e3
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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: [] };
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user