9df8cee501
Adopts the rewritten welshman API: the removed @welshman/util helpers (Profile/List/Room/Handler/Encryptable) are now Reader/Builder classes in @welshman/domain, and @welshman/app dropped its global singletons for an App instance + app.use(Plugin) registry. - src/app/welshman.ts is now the app bootstrap + session-state module (one shared App instance, multi-account sessions/login, app-wide reactive views) rather than a compat shim re-exporting the old globals. - Rewrote ~100 callers to use app.use(Plugin) directly (thunks, profiles, relays, rooms, zaps, tags, wot, feeds, sync); thunk helpers are now thunk methods. - Added @welshman/domain dependency. - Resolved residual gaps (storage hydration via plugin.onItem/wrapManager/Plaintext, relay-list mutators, search-relay list, outbox #d filter). Best-effort: no toolchain/linking available, so this is not build- or type-checked. Remaining judgment calls are flagged with TODO(welshman-migration). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsMjvv7krpZeHK1Njeneru
59 lines
2.2 KiB
Svelte
59 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
import {now, DAY, uniq, sum} from "@welshman/lib"
|
|
import type {Zap, TrustedEvent} from "@welshman/util"
|
|
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
|
|
import {derived} from "svelte/store"
|
|
import {deriveEvents} from "@welshman/store"
|
|
import {app, repository} from "@app/welshman"
|
|
import {Zappers} from "@welshman/app"
|
|
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
|
import Icon from "@lib/components/Icon.svelte"
|
|
import ZapButton from "@app/components/ZapButton.svelte"
|
|
|
|
type Props = {
|
|
url?: string
|
|
event: TrustedEvent
|
|
class?: string
|
|
}
|
|
|
|
const {url, event, ...props}: Props = $props()
|
|
|
|
// Validated zaps for this goal. `validZapReceipts` is a reactive Projection
|
|
// (resolves recipient zappers from loaded profiles); we re-derive it whenever
|
|
// the set of ZAP_RESPONSE events in the repository changes.
|
|
const zapReceipts = deriveEvents({repository, filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}]})
|
|
const zaps = derived(
|
|
zapReceipts,
|
|
($receipts: TrustedEvent[], set) =>
|
|
app.use(Zappers).validZapReceipts($receipts, event).$.subscribe(set),
|
|
[] as Zap[],
|
|
)
|
|
|
|
const goalAmount = parseInt(getTagValue("amount", event.tags) || "0")
|
|
const zapAmount = $derived(fromMsats(sum($zaps.map(zap => zap.invoiceAmount))))
|
|
const contributorsCount = $derived(uniq($zaps.map(zap => zap.request.pubkey)).length)
|
|
const daysOld = Math.ceil((now() - event.created_at) / DAY)
|
|
</script>
|
|
|
|
<div class="flex flex-col gap-8 {props.class}">
|
|
<div class="flex gap-8">
|
|
<div>
|
|
<p class="text-xl text-primary">{zapAmount} sats</p>
|
|
<p class="text-sm opacity-75">funded of {goalAmount} sats</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xl">{contributorsCount}</p>
|
|
<p class="text-sm opacity-75">{contributorsCount === 1 ? "contributor" : "contributors"}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xl">{daysOld}</p>
|
|
<p class="text-sm opacity-75">{daysOld === 1 ? "day" : "days"} old</p>
|
|
</div>
|
|
</div>
|
|
<progress class="progress progress-primary" value={zapAmount} max={goalAmount}></progress>
|
|
<ZapButton {url} {event} class="btn btn-primary lg:m-auto lg:px-20">
|
|
<Icon icon={Bolt} />
|
|
Contribute to this goal
|
|
</ZapButton>
|
|
</div>
|