diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx
index 1caf24e..61ce96d 100644
--- a/src/element/live-chat.tsx
+++ b/src/element/live-chat.tsx
@@ -12,7 +12,6 @@ import { Profile } from "element/profile";
import { ChatMessage } from "element/chat-message";
import { Goal } from "element/goal";
import { Badge } from "element/badge";
-import { NewGoalDialog } from "element/new-goal";
import { WriteMessage } from "element/write-message";
import useEmoji, { packId } from "hooks/emoji";
import { useLiveChatFeed } from "hooks/live-chat";
@@ -117,7 +116,6 @@ export function LiveChat({
{goal && }
- {login?.pubkey === host && }
)}
diff --git a/src/element/new-goal.tsx b/src/element/new-goal.tsx
index cd950e6..48264bb 100644
--- a/src/element/new-goal.tsx
+++ b/src/element/new-goal.tsx
@@ -2,19 +2,15 @@ import "./new-goal.css";
import * as Dialog from "@radix-ui/react-dialog";
import AsyncButton from "./async-button";
-import { NostrLink } from "@snort/system";
import { Icon } from "element/icon";
import { useState } from "react";
import { System } from "index";
import { GOAL } from "const";
import { useLogin } from "hooks/login";
import { FormattedMessage } from "react-intl";
+import { defaultRelays } from "const";
-interface NewGoalDialogProps {
- link: NostrLink;
-}
-
-export function NewGoalDialog({ link }: NewGoalDialogProps) {
+export function NewGoalDialog() {
const [open, setOpen] = useState(false);
const login = useLogin();
@@ -26,12 +22,9 @@ export function NewGoalDialog({ link }: NewGoalDialogProps) {
if (pub) {
const evNew = await pub.generic(eb => {
eb.kind(GOAL)
- .tag(["a", `${link.kind}:${link.author}:${link.id}`])
.tag(["amount", String(Number(goalAmount) * 1000)])
+ .tag(["relays", ...Object.keys(defaultRelays)])
.content(goalName);
- if (link.relays?.length) {
- eb.tag(["relays", ...link.relays]);
- }
return eb;
});
console.debug(evNew);
diff --git a/src/element/stream-editor.tsx b/src/element/stream-editor.tsx
index 9270594..3710300 100644
--- a/src/element/stream-editor.tsx
+++ b/src/element/stream-editor.tsx
@@ -3,12 +3,14 @@ import { useEffect, useState, useCallback } from "react";
import { NostrEvent } from "@snort/system";
import { unixNow } from "@snort/shared";
import { TagsInput } from "react-tag-input-component";
+import { FormattedMessage, useIntl } from "react-intl";
import AsyncButton from "./async-button";
import { StreamState } from "../index";
import { findTag } from "../utils";
import { useLogin } from "hooks/login";
-import { FormattedMessage, useIntl } from "react-intl";
+import { NewGoalDialog } from "element/new-goal";
+import { useGoals } from "hooks/goals";
export interface StreamEditorProps {
ev?: NostrEvent;
@@ -24,6 +26,27 @@ export interface StreamEditorProps {
};
}
+interface GoalSelectorProps {
+ goal?: string;
+ pubkey: string;
+ onGoalSelect: (g: string) => void;
+}
+
+function GoalSelector({ goal, pubkey, onGoalSelect }: GoalSelectorProps) {
+ const goals = useGoals(pubkey, true);
+ const { formatMessage } = useIntl();
+ return (
+
+ );
+}
+
export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
const [title, setTitle] = useState("");
const [summary, setSummary] = useState("");
@@ -34,6 +57,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
const [tags, setTags] = useState([]);
const [contentWarning, setContentWarning] = useState(false);
const [isValid, setIsValid] = useState(false);
+ const [goal, setGoal] = useState();
const login = useLogin();
const { formatMessage } = useIntl();
@@ -46,6 +70,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
setStart(findTag(ev, "starts"));
setTags(ev?.tags.filter(a => a[0] === "t").map(a => a[1]) ?? []);
setContentWarning(findTag(ev, "content-warning") !== undefined);
+ setGoal(findTag(ev, "goal"));
}, [ev?.id]);
const validate = useCallback(() => {
@@ -90,6 +115,9 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
if (contentWarning) {
eb.tag(["content-warning", "nsfw"]);
}
+ if (goal && goal.length > 0) {
+ eb.tag(["goal", goal]);
+ }
return eb;
});
console.debug(evNew);
@@ -201,6 +229,19 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
)}
+ {login?.pubkey && (
+ <>
+
+
+ >
+ )}
{(options?.canSetContentWarning ?? true) && (
diff --git a/src/hooks/goals.ts b/src/hooks/goals.ts
index 76d067e..d97bcdd 100644
--- a/src/hooks/goals.ts
+++ b/src/hooks/goals.ts
@@ -1,22 +1,31 @@
import { useMemo } from "react";
-import { RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system";
+import { RequestBuilder, FlatNoteStore, ReplaceableNoteStore } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
-import { unwrap } from "@snort/shared";
import { GOAL } from "const";
-export function useZapGoal(host: string, link?: NostrLink, leaveOpen = false) {
+export function useZapGoal(id?: string) {
const sub = useMemo(() => {
- if (!link) return null;
- const b = new RequestBuilder(`goals:${host.slice(0, 12)}`);
- b.withOptions({ leaveOpen });
- b.withFilter()
- .kinds([GOAL])
- .authors([host])
- .tag("a", [`${link.kind}:${unwrap(link.author)}:${link.id}`]);
+ if (!id) return null;
+ const b = new RequestBuilder(`goal:${id.slice(0, 12)}`);
+ b.withFilter().kinds([GOAL]).ids([id]);
return b;
- }, [link, leaveOpen]);
+ }, [id]);
const { data } = useRequestBuilder(ReplaceableNoteStore, sub);
return data;
}
+
+export function useGoals(pubkey?: string, leaveOpen = false) {
+ const sub = useMemo(() => {
+ if (!pubkey) return null;
+ const b = new RequestBuilder(`goals:${pubkey.slice(0, 12)}`);
+ b.withOptions({ leaveOpen });
+ b.withFilter().kinds([GOAL]).authors([pubkey]);
+ return b;
+ }, [pubkey, leaveOpen]);
+
+ const { data } = useRequestBuilder(FlatNoteStore, sub);
+
+ return data;
+}
diff --git a/src/index.css b/src/index.css
index 1411e7e..dcd7ab0 100644
--- a/src/index.css
+++ b/src/index.css
@@ -190,6 +190,16 @@ input[type="number"] {
font-weight: 500;
}
+select {
+ font-family: inherit;
+ border: unset;
+ background-color: unset;
+ color: inherit;
+ width: 100%;
+ font-size: 16px;
+ font-weight: 500;
+}
+
input[type="checkbox"] {
-webkit-appearance: none;
-moz-appearance: none;
diff --git a/src/pages/stream-page.tsx b/src/pages/stream-page.tsx
index a0016df..3973d46 100644
--- a/src/pages/stream-page.tsx
+++ b/src/pages/stream-page.tsx
@@ -114,7 +114,7 @@ export function StreamPage({ link, evPreload }: { evPreload?: NostrEvent; link:
const ev = useCurrentStreamFeed(link, true, evPreload);
const host = getHost(ev);
const evLink = ev ? eventToLink(ev) : undefined;
- const goal = useZapGoal(host, evLink, true);
+ const goal = useZapGoal(findTag(ev, "goal"));
const title = findTag(ev, "title");
const summary = findTag(ev, "summary");
diff --git a/src/providers/zsz.ts b/src/providers/zsz.ts
index 77f06a4..fe11c72 100644
--- a/src/providers/zsz.ts
+++ b/src/providers/zsz.ts
@@ -59,12 +59,14 @@ export class Nip103StreamProvider implements StreamProvider {
const image = findTag(ev, "image");
const tags = ev?.tags.filter(a => a[0] === "t").map(a => a[1]);
const contentWarning = findTag(ev, "content-warning");
+ const goal = findTag(ev, "goal");
await this.#getJson("PATCH", "event", {
title,
summary,
image,
tags,
content_warning: contentWarning,
+ goal,
});
}