You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

130 lines
3.6 KiB

<script lang="ts">
import { nostrClient } from '../../services/nostr/nostr-client.js';
import { onMount } from 'svelte';
import type { NostrEvent } from '../../types/nostr.js';
interface Props {
eventId: string; // The event that was zapped
pubkey?: string; // Optional: filter by zapped pubkey
}
let { eventId, pubkey }: Props = $props();
let zapReceipts = $state<NostrEvent[]>([]);
let totalAmount = $state<number>(0);
let loading = $state(true);
onMount(async () => {
await nostrClient.initialize();
loadZapReceipts();
});
async function loadZapReceipts() {
loading = true;
const timeout = 30000; // 30 seconds
try {
const config = nostrClient.getConfig();
const threshold = config.zapThreshold;
// Create a timeout promise
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('Zap loading timeout')), timeout);
});
// Fetch zap receipts (kind 9735) for this event
const filters: any[] = [
{
kinds: [9735],
'#e': [eventId]
}
];
if (pubkey) {
filters[0]['#p'] = [pubkey];
}
// Race between loading and timeout
const receipts = await Promise.race([
nostrClient.fetchEvents(
filters,
[...config.defaultRelays],
{ useCache: true, cacheResults: true, onUpdate: (updated) => {
processReceipts(updated);
}}
),
timeoutPromise
]);
processReceipts(receipts);
} catch (error) {
console.error('Error loading zap receipts:', error);
// On timeout or error, show empty state
zapReceipts = [];
totalAmount = 0;
} finally {
loading = false;
}
}
function processReceipts(receipts: NostrEvent[]) {
const config = nostrClient.getConfig();
const threshold = config.zapThreshold;
// Filter by threshold and extract amounts
const validReceipts = receipts.filter((receipt) => {
const amountTag = receipt.tags.find((t) => t[0] === 'amount');
if (amountTag && amountTag[1]) {
const amount = parseInt(amountTag[1], 10);
return !isNaN(amount) && amount >= threshold;
}
return false;
});
zapReceipts = validReceipts;
// Calculate total
totalAmount = validReceipts.reduce((sum, receipt) => {
const amountTag = receipt.tags.find((t) => t[0] === 'amount');
if (amountTag && amountTag[1]) {
const amount = parseInt(amountTag[1], 10);
return sum + (isNaN(amount) ? 0 : amount);
}
return sum;
}, 0);
}
function getAmount(receipt: NostrEvent): number {
const amountTag = receipt.tags.find((t) => t[0] === 'amount');
if (amountTag && amountTag[1]) {
const amount = parseInt(amountTag[1], 10);
return isNaN(amount) ? 0 : amount;
}
return 0;
}
</script>
<div class="zap-receipts">
{#if loading}
<span class="text-sm text-fog-text-light dark:text-fog-dark-text-light">Loading zaps...</span>
{:else if zapReceipts.length > 0}
<div class="flex items-center gap-2">
<span class="text-lg"></span>
<span class="text-sm font-semibold">{totalAmount.toLocaleString()} sats</span>
<span class="text-xs text-fog-text-light dark:text-fog-dark-text-light">
({zapReceipts.length} {zapReceipts.length === 1 ? 'zap' : 'zaps'})
</span>
</div>
{:else}
<span class="text-xs text-fog-text-light dark:text-fog-dark-text-light">No zaps</span>
{/if}
</div>
<style>
.zap-receipts {
display: flex;
align-items: center;
display: inline-flex;
align-items: center;
}
</style>