mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-08-27 13:09: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
|
// Generate QR code
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let isCancelled = false;
|
||||||
|
|
||||||
const generateQR = async () => {
|
const generateQR = async () => {
|
||||||
if (!invoice) return;
|
if (!invoice) {
|
||||||
|
setQrCodeUrl('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = await QRCode.toDataURL(invoice.toUpperCase(), {
|
const url = await QRCode.toDataURL(invoice.toUpperCase(), {
|
||||||
@ -88,13 +93,22 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
light: '#FFFFFF',
|
light: '#FFFFFF',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!isCancelled) {
|
||||||
setQrCodeUrl(url);
|
setQrCodeUrl(url);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (!isCancelled) {
|
||||||
console.error('Failed to generate QR code:', err);
|
console.error('Failed to generate QR code:', err);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
generateQR();
|
generateQR();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isCancelled = true;
|
||||||
|
};
|
||||||
}, [invoice]);
|
}, [invoice]);
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
@ -122,6 +136,12 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
setInvoice(null);
|
setInvoice(null);
|
||||||
setCopied(false);
|
setCopied(false);
|
||||||
setQrCodeUrl('');
|
setQrCodeUrl('');
|
||||||
|
} else {
|
||||||
|
// Clean up state when dialog closes
|
||||||
|
setAmount(100);
|
||||||
|
setInvoice(null);
|
||||||
|
setCopied(false);
|
||||||
|
setQrCodeUrl('');
|
||||||
}
|
}
|
||||||
}, [open, setInvoice]);
|
}, [open, setInvoice]);
|
||||||
|
|
||||||
@ -134,7 +154,7 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
const ZapContent = () => (
|
const ZapContent = () => (
|
||||||
<>
|
<>
|
||||||
{invoice ? (
|
{invoice ? (
|
||||||
<div className="space-y-4 px-4 pb-4">
|
<div className="space-y-4">
|
||||||
{/* Payment amount display */}
|
{/* Payment amount display */}
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl font-bold">{amount} sats</div>
|
<div className="text-2xl font-bold">{amount} sats</div>
|
||||||
@ -212,7 +232,7 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
Open in Lightning Wallet
|
Open in Lightning Wallet
|
||||||
</Button>
|
</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.
|
Scan the QR code or copy the invoice to pay with any Lightning wallet.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -432,9 +452,8 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
<DrawerTitle className="text-lg break-words pt-2">
|
<DrawerTitle className="text-lg break-words pt-2">
|
||||||
Send a Zap
|
Send a Zap
|
||||||
</DrawerTitle>
|
</DrawerTitle>
|
||||||
<DrawerDescription className="text-sm break-words">
|
<DrawerDescription className="text-sm break-words text-center">
|
||||||
Zaps are small Bitcoin payments that support the creator of this item.
|
Zaps are small Bitcoin payments that support the creator of this item. If you enjoyed this, consider sending a zap!
|
||||||
{' '}If you enjoyed this, consider sending a zap!
|
|
||||||
</DrawerDescription>
|
</DrawerDescription>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<div className="overflow-y-auto">
|
<div className="overflow-y-auto">
|
||||||
@ -457,13 +476,12 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
<DialogTitle className="text-lg break-words">
|
<DialogTitle className="text-lg break-words">
|
||||||
{invoice ? 'Lightning Payment' : 'Send a Zap'}
|
{invoice ? 'Lightning Payment' : 'Send a Zap'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm break-words">
|
<DialogDescription className="text-sm text-center break-words">
|
||||||
{invoice ? (
|
{invoice ? (
|
||||||
'Pay with Bitcoin Lightning Network'
|
'Pay with Bitcoin Lightning Network'
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
Zaps are small Bitcoin payments that support the creator of this item.
|
Zaps are small Bitcoin payments that support the creator of this item. If you enjoyed this, consider sending a zap!
|
||||||
{' '}If you enjoyed this, consider sending a zap!
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
@ -66,6 +66,7 @@ export function useNWCInternal() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Test the connection by creating an LN client with timeout
|
// Test the connection by creating an LN client with timeout
|
||||||
|
let timeoutId: NodeJS.Timeout | undefined;
|
||||||
const testPromise = new Promise((resolve, reject) => {
|
const testPromise = new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const client = new LN(parsed.connectionString);
|
const client = new LN(parsed.connectionString);
|
||||||
@ -74,10 +75,17 @@ export function useNWCInternal() {
|
|||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||||
setTimeout(() => reject(new Error('Connection test timeout')), 10000);
|
timeoutId = setTimeout(() => reject(new Error('Connection test timeout')), 10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
await Promise.race([testPromise, timeoutPromise]) as LN;
|
await Promise.race([testPromise, timeoutPromise]) as LN;
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
const connection: NWCConnection = {
|
const connection: NWCConnection = {
|
||||||
connectionString: parsed.connectionString,
|
connectionString: parsed.connectionString,
|
||||||
@ -175,14 +183,22 @@ export function useNWCInternal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Add timeout to prevent hanging
|
// Add timeout to prevent hanging with proper cleanup
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
let timeoutId: NodeJS.Timeout | undefined;
|
||||||
setTimeout(() => reject(new Error('Payment timeout after 15 seconds')), 15000);
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||||
|
timeoutId = setTimeout(() => reject(new Error('Payment timeout after 15 seconds')), 15000);
|
||||||
});
|
});
|
||||||
|
|
||||||
const paymentPromise = client.pay(invoice);
|
const paymentPromise = client.pay(invoice);
|
||||||
|
|
||||||
|
try {
|
||||||
const response = await Promise.race([paymentPromise, timeoutPromise]) as { preimage: string };
|
const response = await Promise.race([paymentPromise, timeoutPromise]) as { preimage: string };
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
return response;
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('NWC payment failed:', error);
|
console.error('NWC payment failed:', error);
|
||||||
|
|
||||||
@ -222,6 +238,7 @@ export function useNWCInternal() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a fresh client for testing
|
// Create a fresh client for testing
|
||||||
|
let timeoutId: NodeJS.Timeout | undefined;
|
||||||
const testPromise = new Promise((resolve, reject) => {
|
const testPromise = new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const client = new LN(connection.connectionString);
|
const client = new LN(connection.connectionString);
|
||||||
@ -231,10 +248,17 @@ export function useNWCInternal() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||||
setTimeout(() => reject(new Error('Connection test timeout')), 5000);
|
timeoutId = setTimeout(() => reject(new Error('Connection test timeout')), 5000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
await Promise.race([testPromise, timeoutPromise]);
|
await Promise.race([testPromise, timeoutPromise]);
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { useAuthor } from '@/hooks/useAuthor';
|
import { useAuthor } from '@/hooks/useAuthor';
|
||||||
import { useAppContext } from '@/hooks/useAppContext';
|
import { useAppContext } from '@/hooks/useAppContext';
|
||||||
@ -32,10 +32,21 @@ export function useZaps(
|
|||||||
const [isZapping, setIsZapping] = useState(false);
|
const [isZapping, setIsZapping] = useState(false);
|
||||||
const [invoice, setInvoice] = useState<string | null>(null);
|
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>({
|
const { data: zapEvents, ...query } = useQuery<NostrEvent[], Error>({
|
||||||
queryKey: ['zaps', actualTarget?.id],
|
queryKey: ['zaps', actualTarget?.id],
|
||||||
staleTime: 30000, // 30 seconds
|
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) => {
|
queryFn: async (c) => {
|
||||||
if (!actualTarget) return [];
|
if (!actualTarget) return [];
|
||||||
|
|
||||||
@ -64,7 +75,7 @@ export function useZaps(
|
|||||||
|
|
||||||
// Process zap events into simple counts and totals
|
// Process zap events into simple counts and totals
|
||||||
const { zapCount, totalSats, zaps } = useMemo(() => {
|
const { zapCount, totalSats, zaps } = useMemo(() => {
|
||||||
if (!zapEvents || !actualTarget) {
|
if (!zapEvents || !Array.isArray(zapEvents) || !actualTarget) {
|
||||||
return { zapCount: 0, totalSats: 0, zaps: [] };
|
return { zapCount: 0, totalSats: 0, zaps: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user