diff --git a/apps/browser-extension/src/components/LightningTab.tsx b/apps/browser-extension/src/components/LightningTab.tsx index 2f6e630b..4fc09fe7 100644 --- a/apps/browser-extension/src/components/LightningTab.tsx +++ b/apps/browser-extension/src/components/LightningTab.tsx @@ -3,7 +3,9 @@ import { Autocomplete, Box, Button, + Checkbox, CircularProgress, + FormControlLabel, Tab, Tabs, TextField, @@ -52,6 +54,7 @@ const LightningTab: React.FC = () => { // Payments sub-tab const [payments, setPayments] = useState([]); const [loadingPayments, setLoadingPayments] = useState(false); + const [statusFilter, setStatusFilter] = useState({ settled: true, pending: true, failed: true, expired: true }); // Publish state const [isPublished, setIsPublished] = useState(false); @@ -311,21 +314,35 @@ const LightningTab: React.FC = () => { {activeTab === "payments" && ( - + + {(['settled', 'pending', 'failed', 'expired'] as const).map(s => ( + setStatusFilter(f => ({ ...f, [s]: e.target.checked }))} />} /> + ))} {loadingPayments && } {!loadingPayments && payments.length === 0 && ( No payments found. )} {!loadingPayments && payments.length > 0 && ( - + + + + + + + + Date - Amount + Amount (sats) + Fee + Status Memo @@ -333,11 +350,21 @@ const LightningTab: React.FC = () => { {payments.map((p, i) => { const d = p.time ? new Date(p.time) : null; const date = d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}` : "—"; + const displayStatus: 'settled' | 'pending' | 'failed' | 'expired' = p.status === 'success' ? 'settled' + : p.status === 'failed' ? 'failed' + : (p.expiry && new Date(p.expiry) < new Date()) ? 'expired' + : 'pending'; + if (!statusFilter[displayStatus]) return null; + const statusColor = displayStatus === 'settled' ? 'inherit' + : displayStatus === 'failed' ? 'error.main' + : 'text.secondary'; return ( {date} - {p.amount} sats{p.fee > 0 ? ` (fee: ${p.fee})` : ""} - {p.memo || "—"}{p.pending ? " [pending]" : ""} + {p.amount} + {p.fee > 0 ? p.fee : ""} + {displayStatus} + {p.memo || "—"} ); })} diff --git a/apps/gatekeeper-client/src/KeymasterUI.jsx b/apps/gatekeeper-client/src/KeymasterUI.jsx index f864dc21..3286efd4 100644 --- a/apps/gatekeeper-client/src/KeymasterUI.jsx +++ b/apps/gatekeeper-client/src/KeymasterUI.jsx @@ -300,6 +300,7 @@ function KeymasterUI({ keymaster, title, challengeDID, onWalletUpload, hasLightn const [zapResult, setZapResult] = useState(null); const [lightningPayments, setLightningPayments] = useState([]); const [loadingPayments, setLoadingPayments] = useState(false); + const [lightningStatusFilter, setLightningStatusFilter] = useState({ settled: true, pending: true, failed: true, expired: true }); const [isPublished, setIsPublished] = useState(false); const [loadingPublishToggle, setLoadingPublishToggle] = useState(false); @@ -6283,21 +6284,36 @@ function KeymasterUI({ keymaster, title, challengeDID, onWalletUpload, hasLightn {lightningTab === 'payments' && - -

+ + + {['settled', 'pending', 'failed', 'expired'].map(s => ( + setLightningStatusFilter(f => ({ ...f, [s]: e.target.checked }))} />} /> + ))} + {loadingPayments && Loading...} {!loadingPayments && lightningPayments.length === 0 && No payments found. } {!loadingPayments && lightningPayments.length > 0 && - +
+ + + + + + + Date - Amount + Amount (sats) + Fee + Status Memo @@ -6305,11 +6321,21 @@ function KeymasterUI({ keymaster, title, challengeDID, onWalletUpload, hasLightn {lightningPayments.map((p, i) => { const d = p.time ? new Date(p.time) : null; const date = d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}` : '—'; + const displayStatus = p.status === 'success' ? 'settled' + : p.status === 'failed' ? 'failed' + : (p.expiry && new Date(p.expiry) < new Date()) ? 'expired' + : 'pending'; + if (!lightningStatusFilter[displayStatus]) return null; + const statusColor = displayStatus === 'settled' ? 'inherit' + : displayStatus === 'failed' ? 'error.main' + : 'text.secondary'; return ( {date} - {p.amount} sats{p.fee > 0 ? ` (fee: ${p.fee})` : ''} - {p.memo || '—'}{p.pending ? ' [pending]' : ''} + {p.amount} + {p.fee > 0 ? p.fee : ''} + {displayStatus} + {p.memo || '—'} ); })} diff --git a/apps/keymaster-client/src/KeymasterUI.jsx b/apps/keymaster-client/src/KeymasterUI.jsx index f864dc21..3286efd4 100644 --- a/apps/keymaster-client/src/KeymasterUI.jsx +++ b/apps/keymaster-client/src/KeymasterUI.jsx @@ -300,6 +300,7 @@ function KeymasterUI({ keymaster, title, challengeDID, onWalletUpload, hasLightn const [zapResult, setZapResult] = useState(null); const [lightningPayments, setLightningPayments] = useState([]); const [loadingPayments, setLoadingPayments] = useState(false); + const [lightningStatusFilter, setLightningStatusFilter] = useState({ settled: true, pending: true, failed: true, expired: true }); const [isPublished, setIsPublished] = useState(false); const [loadingPublishToggle, setLoadingPublishToggle] = useState(false); @@ -6283,21 +6284,36 @@ function KeymasterUI({ keymaster, title, challengeDID, onWalletUpload, hasLightn {lightningTab === 'payments' && - -

+ + + {['settled', 'pending', 'failed', 'expired'].map(s => ( + setLightningStatusFilter(f => ({ ...f, [s]: e.target.checked }))} />} /> + ))} + {loadingPayments && Loading...} {!loadingPayments && lightningPayments.length === 0 && No payments found. } {!loadingPayments && lightningPayments.length > 0 && -

+
+ + + + + + + Date - Amount + Amount (sats) + Fee + Status Memo @@ -6305,11 +6321,21 @@ function KeymasterUI({ keymaster, title, challengeDID, onWalletUpload, hasLightn {lightningPayments.map((p, i) => { const d = p.time ? new Date(p.time) : null; const date = d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}` : '—'; + const displayStatus = p.status === 'success' ? 'settled' + : p.status === 'failed' ? 'failed' + : (p.expiry && new Date(p.expiry) < new Date()) ? 'expired' + : 'pending'; + if (!lightningStatusFilter[displayStatus]) return null; + const statusColor = displayStatus === 'settled' ? 'inherit' + : displayStatus === 'failed' ? 'error.main' + : 'text.secondary'; return ( {date} - {p.amount} sats{p.fee > 0 ? ` (fee: ${p.fee})` : ''} - {p.memo || '—'}{p.pending ? ' [pending]' : ''} + {p.amount} + {p.fee > 0 ? p.fee : ''} + {displayStatus} + {p.memo || '—'} ); })} diff --git a/apps/react-wallet/src/components/LightningTab.tsx b/apps/react-wallet/src/components/LightningTab.tsx index 2f6e630b..4fc09fe7 100644 --- a/apps/react-wallet/src/components/LightningTab.tsx +++ b/apps/react-wallet/src/components/LightningTab.tsx @@ -3,7 +3,9 @@ import { Autocomplete, Box, Button, + Checkbox, CircularProgress, + FormControlLabel, Tab, Tabs, TextField, @@ -52,6 +54,7 @@ const LightningTab: React.FC = () => { // Payments sub-tab const [payments, setPayments] = useState([]); const [loadingPayments, setLoadingPayments] = useState(false); + const [statusFilter, setStatusFilter] = useState({ settled: true, pending: true, failed: true, expired: true }); // Publish state const [isPublished, setIsPublished] = useState(false); @@ -311,21 +314,35 @@ const LightningTab: React.FC = () => { {activeTab === "payments" && ( - + + {(['settled', 'pending', 'failed', 'expired'] as const).map(s => ( + setStatusFilter(f => ({ ...f, [s]: e.target.checked }))} />} /> + ))} {loadingPayments && } {!loadingPayments && payments.length === 0 && ( No payments found. )} {!loadingPayments && payments.length > 0 && ( - + + + + + + + + - + + + @@ -333,11 +350,21 @@ const LightningTab: React.FC = () => { {payments.map((p, i) => { const d = p.time ? new Date(p.time) : null; const date = d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}` : "—"; + const displayStatus: 'settled' | 'pending' | 'failed' | 'expired' = p.status === 'success' ? 'settled' + : p.status === 'failed' ? 'failed' + : (p.expiry && new Date(p.expiry) < new Date()) ? 'expired' + : 'pending'; + if (!statusFilter[displayStatus]) return null; + const statusColor = displayStatus === 'settled' ? 'inherit' + : displayStatus === 'failed' ? 'error.main' + : 'text.secondary'; return ( - - + + + + ); })} diff --git a/packages/gatekeeper/src/types.ts b/packages/gatekeeper/src/types.ts index 17901d35..0863c9e8 100644 --- a/packages/gatekeeper/src/types.ts +++ b/packages/gatekeeper/src/types.ts @@ -198,11 +198,13 @@ export interface LightningPaymentStatus { export interface LightningPaymentRecord { paymentHash: string; - amount: number; + amount: number; // positive = incoming, negative = outgoing fee: number; memo: string; time: string; pending: boolean; + status: 'success' | 'pending' | 'failed'; + expiry?: string; // ISO timestamp, invoices only } export interface DrawbridgeInterface extends GatekeeperInterface { diff --git a/services/drawbridge/server/src/lnbits.ts b/services/drawbridge/server/src/lnbits.ts index ec8f715c..726e90a7 100644 --- a/services/drawbridge/server/src/lnbits.ts +++ b/services/drawbridge/server/src/lnbits.ts @@ -96,21 +96,27 @@ export async function payInvoice( export async function getPayments( url: string, adminKey: string -): Promise> { +): Promise> { try { const response = await axios.get(`${url}/api/v1/payments`, { headers: { 'X-Api-Key': adminKey }, }); return (response.data || []) - .filter((p: any) => p.status === 'success') - .map((p: any) => ({ - paymentHash: p.payment_hash || p.checking_id || '', - amount: Math.floor((p.amount || 0) / 1000), - fee: Math.floor(Math.abs(p.fee || 0) / 1000), - memo: p.memo || '', - time: p.time || '', - pending: false, - })); + .map((p: any) => { + const status = p.status === 'success' ? 'success' + : p.status === 'failed' ? 'failed' + : 'pending'; + return { + paymentHash: p.payment_hash || p.checking_id || '', + amount: Math.floor((p.amount || 0) / 1000), + fee: Math.floor(Math.abs(p.fee || 0) / 1000), + memo: p.memo || '', + time: p.time || '', + pending: status === 'pending', + status, + expiry: p.expiry ?? undefined, + }; + }); } catch (error: any) { throwLnbitsError(error); }
DateAmountAmount (sats)FeeStatus Memo
{date}{p.amount} sats{p.fee > 0 ? ` (fee: ${p.fee})` : ""}{p.memo || "—"}{p.pending ? " [pending]" : ""}{p.amount}{p.fee > 0 ? p.fee : ""}{displayStatus}{p.memo || "—"}