forked from Kieran/zap.stream
feat: allow to configure a stream goal
This commit is contained in:
parent
3da6f5439b
commit
9fd59817d9
@ -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({
|
||||
<TopZappers zaps={zaps} />
|
||||
</div>
|
||||
{goal && <Goal ev={goal} />}
|
||||
{login?.pubkey === host && <NewGoalDialog link={link} />}
|
||||
</div>
|
||||
)}
|
||||
<div className="messages">
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
<select defaultValue={goal} onChange={ev => onGoalSelect(ev.target.value)}>
|
||||
<option value="">{formatMessage({ defaultMessage: "Select a goal..." })}</option>
|
||||
{goals?.map(goal => (
|
||||
<option key={goal.id} value={goal.id}>
|
||||
{goal.content}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
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<string[]>([]);
|
||||
const [contentWarning, setContentWarning] = useState(false);
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const [goal, setGoal] = useState<string | undefined>();
|
||||
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) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{login?.pubkey && (
|
||||
<>
|
||||
<div>
|
||||
<p>
|
||||
<FormattedMessage defaultMessage="Goal" />
|
||||
</p>
|
||||
<div className="paper">
|
||||
<GoalSelector goal={goal} pubkey={login?.pubkey} onGoalSelect={setGoal} />
|
||||
</div>
|
||||
</div>
|
||||
<NewGoalDialog />
|
||||
</>
|
||||
)}
|
||||
{(options?.canSetContentWarning ?? true) && (
|
||||
<div className="flex g12 content-warning">
|
||||
<div>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user