diff --git a/src/app/components/ChannelMessageZapButton.svelte b/src/app/components/ChannelMessageZapButton.svelte
index 59272fb..c74b25c 100644
--- a/src/app/components/ChannelMessageZapButton.svelte
+++ b/src/app/components/ChannelMessageZapButton.svelte
@@ -1,28 +1,10 @@
-
+
diff --git a/src/app/components/GoalActions.svelte b/src/app/components/GoalActions.svelte
new file mode 100644
index 0000000..802dca6
--- /dev/null
+++ b/src/app/components/GoalActions.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ {#if showActivity}
+
+ {/if}
+
+
+
diff --git a/src/app/components/GoalCreate.svelte b/src/app/components/GoalCreate.svelte
new file mode 100644
index 0000000..b1ae664
--- /dev/null
+++ b/src/app/components/GoalCreate.svelte
@@ -0,0 +1,146 @@
+
+
+
diff --git a/src/app/components/GoalItem.svelte b/src/app/components/GoalItem.svelte
new file mode 100644
index 0000000..7e398fe
--- /dev/null
+++ b/src/app/components/GoalItem.svelte
@@ -0,0 +1,36 @@
+
+
+
+ {event.content}
+
+
+
+
diff --git a/src/app/components/GoalSummary.svelte b/src/app/components/GoalSummary.svelte
new file mode 100644
index 0000000..47bc63c
--- /dev/null
+++ b/src/app/components/GoalSummary.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+
{zapAmount} sats
+
funded of {goalAmount} sats
+
+
+
{contributorsCount}
+
{contributorsCount === 1 ? "contributor" : "contributors"}
+
+
+
{daysOld}
+
{daysOld === 1 ? "day" : "days"} old
+
+
+
+
+
+ Contribute to this goal
+
+
diff --git a/src/app/components/MenuSpace.svelte b/src/app/components/MenuSpace.svelte
index bb18af2..f955629 100644
--- a/src/app/components/MenuSpace.svelte
+++ b/src/app/components/MenuSpace.svelte
@@ -34,6 +34,7 @@
const relay = deriveRelay(url)
const chatPath = makeSpacePath(url, "chat")
+ const goalsPath = makeSpacePath(url, "goals")
const threadsPath = makeSpacePath(url, "threads")
const calendarPath = makeSpacePath(url, "calendar")
const userRooms = deriveUserRooms(url)
@@ -130,6 +131,12 @@
Home
+
+ Goals
+
+ import {deriveZapperForPubkey} from "@welshman/app"
+ import Button from "@lib/components/Button.svelte"
+ import Zap from "@app/components/Zap.svelte"
+ import InfoZapperError from "@app/components/InfoZapperError.svelte"
+ import WalletConnect from "@app/components/WalletConnect.svelte"
+ import {pushModal} from "@app/modal"
+ import {wallet} from "@app/state"
+
+ const {url, event, children, ...props} = $props()
+
+ const zapper = deriveZapperForPubkey(event.pubkey)
+
+ const onClick = () => {
+ if (!$zapper?.allowsNostr) {
+ pushModal(InfoZapperError, {url, pubkey: event.pubkey, eventId: event.id})
+ } else if ($wallet) {
+ pushModal(Zap, {url, pubkey: event.pubkey, eventId: event.id})
+ } else {
+ pushModal(WalletConnect)
+ }
+ }
+
+
+
diff --git a/src/app/routes.ts b/src/app/routes.ts
index 01827c9..6ac0596 100644
--- a/src/app/routes.ts
+++ b/src/app/routes.ts
@@ -12,6 +12,7 @@ import {
DIRECT_MESSAGE_FILE,
MESSAGE,
THREAD,
+ ZAP_GOAL,
EVENT_TIME,
} from "@welshman/util"
import {makeChatId, entityLink, decodeRelay, encodeRelay, userRoomsByUrl, ROOM} from "@app/state"
@@ -37,6 +38,8 @@ export const makeRoomPath = (url: string, room: string) => `/spaces/${encodeRela
export const makeSpaceChatPath = (url: string) => makeRoomPath(url, "chat")
+export const makeGoalPath = (url: string, eventId?: string) => makeSpacePath(url, "goals", eventId)
+
export const makeThreadPath = (url: string, eventId?: string) =>
makeSpacePath(url, "threads", eventId)
@@ -87,6 +90,10 @@ export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
if (urls.length > 0) {
const url = urls[0]
+ if (event.kind === ZAP_GOAL) {
+ return makeGoalPath(url, event.id)
+ }
+
if (event.kind === THREAD) {
return makeThreadPath(url, event.id)
}
@@ -103,6 +110,10 @@ export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
const id = event.tags.find(nthEq(0, "E"))?.[1]
if (id && kind) {
+ if (parseInt(kind) === ZAP_GOAL) {
+ return makeGoalPath(url, id)
+ }
+
if (parseInt(kind) === THREAD) {
return makeThreadPath(url, id)
}
diff --git a/src/assets/icons/Star Fall Minimalistic 2.svg b/src/assets/icons/Star Fall Minimalistic 2.svg
new file mode 100644
index 0000000..e61b180
--- /dev/null
+++ b/src/assets/icons/Star Fall Minimalistic 2.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/lib/components/Icon.svelte b/src/lib/components/Icon.svelte
index 518986a..704a872 100644
--- a/src/lib/components/Icon.svelte
+++ b/src/lib/components/Icon.svelte
@@ -86,6 +86,7 @@
import SquareShareLine from "@assets/icons/Square Share Line.svg?dataurl"
import SortVertical from "@assets/icons/Sort Vertical.svg?dataurl"
import Star from "@assets/icons/Star.svg?dataurl"
+ import StarFallMinimalistic2 from "@assets/icons/Star Fall Minimalistic 2.svg?dataurl"
import TrashBin2 from "@assets/icons/Trash Bin 2.svg?dataurl"
import UFO3 from "@assets/icons/UFO 3.svg?dataurl"
import UserHeart from "@assets/icons/User Heart.svg?dataurl"
@@ -190,6 +191,7 @@
"square-share-line": SquareShareLine,
"sort-vertical": SortVertical,
star: Star,
+ "star-fall-minimalistic-2": StarFallMinimalistic2,
"user-heart": UserHeart,
"user-circle": UserCircle,
"user-rounded": UserRounded,
diff --git a/src/routes/spaces/[relay]/goals/+page.svelte b/src/routes/spaces/[relay]/goals/+page.svelte
new file mode 100644
index 0000000..e6f173e
--- /dev/null
+++ b/src/routes/spaces/[relay]/goals/+page.svelte
@@ -0,0 +1,122 @@
+
+
+
+ {#snippet icon()}
+
+
+
+ {/snippet}
+ {#snippet title()}
+ Fundraising Goals
+ {/snippet}
+ {#snippet action()}
+
+
+
+
+ {/snippet}
+
+
+
+ {#each events as event (event.id)}
+
+
+
+ {/each}
+
+
+ {#if loading}
+ Looking for goals...
+ {:else if events.length === 0}
+ No goals found.
+ {:else}
+ That's all!
+ {/if}
+
+
+
diff --git a/src/routes/spaces/[relay]/goals/[id]/+page.svelte b/src/routes/spaces/[relay]/goals/[id]/+page.svelte
new file mode 100644
index 0000000..bb0ef56
--- /dev/null
+++ b/src/routes/spaces/[relay]/goals/[id]/+page.svelte
@@ -0,0 +1,123 @@
+
+
+
+ {#snippet icon()}
+
+
+
+ {/snippet}
+ {#snippet title()}
+ {$event.content}
+ {/snippet}
+ {#snippet action()}
+
+
+
+ {/snippet}
+
+
+
+ {#if $event}
+
+
+
+
+
+
+
+
+ {#if !showAll && $replies.length > 4}
+
+
+
+ {/if}
+ {#each sortBy(e => e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
+
+
+
+
+
+
+ {/each}
+
+ {#if showReply}
+
+ {:else}
+
+
+
+ {/if}
+ {:else}
+ {#await sleep(5000)}
+ Loading funding goal...
+ {:then}
+ Failed to load funding goal.
+ {/await}
+ {/if}
+