diff --git a/src/element/goal.tsx b/src/element/goal.tsx
index feb21d0..a241fc6 100644
--- a/src/element/goal.tsx
+++ b/src/element/goal.tsx
@@ -6,19 +6,20 @@ import Confetti from "react-confetti";
import { type NostrEvent } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
-import { findTag } from "utils";
+import { eventToLink, findTag } from "utils";
import { formatSats } from "number";
import usePreviousValue from "hooks/usePreviousValue";
import { SendZapsDialog } from "element/send-zap";
-import { useZaps } from "hooks/goals";
import { getName } from "element/profile";
import { Icon } from "./icon";
import { FormattedMessage } from "react-intl";
+import { useZaps } from "hooks/zaps";
export function Goal({ ev }: { ev: NostrEvent }) {
const profile = useUserProfile(ev.pubkey);
const zapTarget = profile?.lud16 ?? profile?.lud06;
- const zaps = useZaps(ev, true);
+ const link = eventToLink(ev);
+ const zaps = useZaps(link, true);
const goalAmount = useMemo(() => {
const amount = findTag(ev, "amount");
return amount ? Number(amount) / 1000 : null;
diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx
index 2721654..1caf24e 100644
--- a/src/element/live-chat.tsx
+++ b/src/element/live-chat.tsx
@@ -1,7 +1,8 @@
import "./live-chat.css";
+import { FormattedMessage } from "react-intl";
import { EventKind, NostrPrefix, NostrLink, ParsedZap, NostrEvent, parseZap, encodeTLV } from "@snort/system";
-import { unixNow, unwrap } from "@snort/shared";
-import { useEffect, useMemo } from "react";
+import { unixNow } from "@snort/shared";
+import { useMemo } from "react";
import uniqBy from "lodash.uniqby";
import { Icon } from "element/icon";
@@ -18,13 +19,12 @@ import { useLiveChatFeed } from "hooks/live-chat";
import { useMutedPubkeys } from "hooks/lists";
import { useBadges } from "hooks/badges";
import { useLogin } from "hooks/login";
-import useTopZappers from "hooks/top-zappers";
import { useAddress } from "hooks/event";
import { formatSats } from "number";
import { WEEK, LIVE_STREAM_CHAT } from "const";
import { findTag, getTagValues, getHost } from "utils";
import { System } from "index";
-import { FormattedMessage } from "react-intl";
+import { TopZappers } from "element/top-zappers";
export interface LiveChatOptions {
canWrite?: boolean;
@@ -49,28 +49,6 @@ function BadgeAward({ ev }: { ev: NostrEvent }) {
);
}
-function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
- const zappers = useTopZappers(zaps);
-
- return (
- <>
- {zappers.map(({ pubkey, total }) => {
- return (
-
- {pubkey === "anon" ? (
-
Anon
- ) : (
-
- )}
-
-
{formatSats(total)}
-
- );
- })}
- >
- );
-}
-
export function LiveChat({
link,
ev,
@@ -87,11 +65,6 @@ export function LiveChat({
const host = getHost(ev);
const feed = useLiveChatFeed(link, goal ? [goal.id] : undefined);
const login = useLogin();
- useEffect(() => {
- const pubkeys = [...new Set(feed.zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))];
- System.ProfileLoader.TrackMetadata(pubkeys);
- return () => System.ProfileLoader.UntrackMetadata(pubkeys);
- }, [feed.zaps]);
const started = useMemo(() => {
const starts = findTag(ev, "starts");
return starts ? Number(starts) : unixNow() - WEEK;
@@ -111,7 +84,6 @@ export function LiveChat({
const events = useMemo(() => {
return [...feed.messages, ...feed.zaps, ...awards].sort((a, b) => b.created_at - a.created_at);
}, [feed.messages, feed.zaps, awards]);
- const streamer = getHost(ev);
const naddr = useMemo(() => {
if (ev) {
return encodeTLV(NostrPrefix.Address, findTag(ev, "d") ?? "", undefined, ev.kind, ev.pubkey);
@@ -145,7 +117,7 @@ export function LiveChat({
{goal && }
- {login?.pubkey === streamer && }
+ {login?.pubkey === host && }
)}
@@ -159,7 +131,7 @@ export function LiveChat({
b.id === a.id && b.receiver === streamer);
+ const zap = zaps.find(b => b.id === a.id && b.receiver === host);
if (zap) {
return ;
}
diff --git a/src/element/top-zappers.tsx b/src/element/top-zappers.tsx
new file mode 100644
index 0000000..6003648
--- /dev/null
+++ b/src/element/top-zappers.tsx
@@ -0,0 +1,27 @@
+import { ParsedZap } from "@snort/system";
+import useTopZappers from "hooks/top-zappers";
+import { formatSats } from "number";
+import { Icon } from "./icon";
+import { Profile } from "./profile";
+
+export function TopZappers({ zaps, limit }: { zaps: ParsedZap[], limit?: number }) {
+ const zappers = useTopZappers(zaps);
+
+ return (
+ <>
+ {zappers.slice(0, limit ?? 10).map(({ pubkey, total }) => {
+ return (
+
+ {pubkey === "anon" ? (
+
Anon
+ ) : (
+
+ )}
+
+
{formatSats(total)}
+
+ );
+ })}
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/hooks/goals.ts b/src/hooks/goals.ts
index 6fe1b12..76d067e 100644
--- a/src/hooks/goals.ts
+++ b/src/hooks/goals.ts
@@ -1,30 +1,8 @@
import { useMemo } from "react";
-import {
- EventKind,
- NostrEvent,
- RequestBuilder,
- NoteCollection,
- ReplaceableNoteStore,
- NostrLink,
- parseZap,
-} from "@snort/system";
+import { RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { unwrap } from "@snort/shared";
import { GOAL } from "const";
-import { System } from "index";
-
-export function useZaps(goal: NostrEvent, leaveOpen = false) {
- const sub = useMemo(() => {
- const b = new RequestBuilder(`goal-zaps:${goal.id.slice(0, 12)}`);
- b.withOptions({ leaveOpen });
- b.withFilter().kinds([EventKind.ZapReceipt]).tag("e", [goal.id]).since(goal.created_at);
- return b;
- }, [goal, leaveOpen]);
-
- const { data } = useRequestBuilder(NoteCollection, sub);
-
- return data?.map(ev => parseZap(ev, System.ProfileLoader.Cache)).filter(z => z && z.valid) ?? [];
-}
export function useZapGoal(host: string, link?: NostrLink, leaveOpen = false) {
const sub = useMemo(() => {
diff --git a/src/hooks/live-chat.tsx b/src/hooks/live-chat.tsx
index 9e7b790..0a33353 100644
--- a/src/hooks/live-chat.tsx
+++ b/src/hooks/live-chat.tsx
@@ -1,10 +1,12 @@
import { NostrLink, RequestBuilder, EventKind, NoteCollection } from "@snort/system";
-import { useRequestBuilder } from "@snort/system-react";
-import { unixNow } from "@snort/shared";
-import { useMemo } from "react";
+import { SnortContext, useRequestBuilder } from "@snort/system-react";
+import { unixNow, unwrap } from "@snort/shared";
+import { useContext, useEffect, useMemo } from "react";
import { LIVE_STREAM_CHAT, WEEK } from "const";
+import { findTag } from "utils";
export function useLiveChatFeed(link: NostrLink, eZaps?: Array) {
+ const system = useContext(SnortContext);
const since = useMemo(() => unixNow() - WEEK, [link.id]);
const sub = useMemo(() => {
const rb = new RequestBuilder(`live:${link.id}:${link.author}`);
@@ -44,6 +46,12 @@ export function useLiveChatFeed(link: NostrLink, eZaps?: Array) {
return rb;
}, [etags]);
+ useEffect(() => {
+ const pubkeys = [...new Set(zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))];
+ system.ProfileLoader.TrackMetadata(pubkeys);
+ return () => system.ProfileLoader.UntrackMetadata(pubkeys);
+ }, [zaps]);
+
const reactionsSub = useRequestBuilder(NoteCollection, esub);
const reactions = reactionsSub.data ?? [];
diff --git a/src/hooks/stream-link.ts b/src/hooks/stream-link.ts
new file mode 100644
index 0000000..fcc6ae7
--- /dev/null
+++ b/src/hooks/stream-link.ts
@@ -0,0 +1,30 @@
+import { fetchNip05Pubkey, hexToBech32 } from "@snort/shared";
+import { NostrLink, tryParseNostrLink, NostrPrefix } from "@snort/system";
+import { useState, useEffect } from "react";
+import { useParams } from "react-router-dom";
+
+export function useStreamLink() {
+ const params = useParams();
+ const [link, setLink] = useState();
+
+ useEffect(() => {
+ if (params.id) {
+ const parsedLink = tryParseNostrLink(params.id);
+ if (parsedLink) {
+ setLink(parsedLink);
+ } else {
+ const [handle, domain] = (params.id.includes("@") ? params.id : `${params.id}@zap.stream`).split("@");
+ fetchNip05Pubkey(handle, domain).then(d => {
+ if (d) {
+ setLink({
+ id: d,
+ type: NostrPrefix.PublicKey,
+ encode: () => hexToBech32(NostrPrefix.PublicKey, d),
+ } as NostrLink);
+ }
+ });
+ }
+ }
+ }, [params.id]);
+ return link;
+}
diff --git a/src/hooks/zaps.ts b/src/hooks/zaps.ts
new file mode 100644
index 0000000..8995278
--- /dev/null
+++ b/src/hooks/zaps.ts
@@ -0,0 +1,40 @@
+import { unwrap } from "@snort/shared";
+import { NostrLink, RequestBuilder, NostrPrefix, EventKind, NoteCollection, parseZap } from "@snort/system";
+import { SnortContext, useRequestBuilder } from "@snort/system-react";
+import { System } from "index";
+import { useContext, useMemo, useEffect } from "react";
+import { findTag } from "utils";
+
+export function useZaps(link?: NostrLink, leaveOpen = false) {
+ const system = useContext(SnortContext);
+ const sub = useMemo(() => {
+ if (link) {
+ const b = new RequestBuilder(`zaps:${link.id}`);
+ b.withOptions({ leaveOpen });
+ if (link.type === NostrPrefix.Event || link.type === NostrPrefix.Note) {
+ b.withFilter().kinds([EventKind.ZapReceipt]).tag("e", [link.id]);
+ } else if (link.type === NostrPrefix.Address) {
+ b.withFilter()
+ .kinds([EventKind.ZapReceipt])
+ .tag("a", [`${link.kind}:${link.author}:${link.id}`]);
+ }
+ return b;
+ }
+ return null;
+ }, [link, leaveOpen]);
+
+ const { data: zaps } = useRequestBuilder(NoteCollection, sub);
+
+ useEffect(() => {
+ const pubkeys = zaps ? [...new Set(zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))] : [];
+ system.ProfileLoader.TrackMetadata(pubkeys);
+ return () => system.ProfileLoader.UntrackMetadata(pubkeys);
+ }, [zaps]);
+
+ return (
+ [...(zaps ?? [])]
+ .sort((a, b) => (b.created_at > a.created_at ? 1 : -1))
+ .map(ev => parseZap(ev, System.ProfileLoader.Cache))
+ .filter(z => z && z.valid) ?? []
+ );
+}
diff --git a/src/index.css b/src/index.css
index 5549569..da04b4a 100644
--- a/src/index.css
+++ b/src/index.css
@@ -76,6 +76,10 @@ a {
color: white;
}
+.g8 {
+ gap: 8px;
+}
+
.g24 {
gap: 24px;
}
diff --git a/src/index.tsx b/src/index.tsx
index 635e587..af86920 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -20,6 +20,8 @@ import { defaultRelays } from "const";
import { CatchAllRoutePage } from "pages/catch-all";
import { register } from "serviceWorker";
import { IntlProvider } from "intl";
+import { WidgetsPage } from "pages/widgets";
+import { AlertsPage } from "pages/alerts";
export enum StreamState {
Live = "live",
@@ -65,6 +67,10 @@ const router = createBrowserRouter([
path: "/providers/:id?",
element: ,
},
+ {
+ path: "/widgets",
+ element:
+ },
{
path: "*",
element: ,
@@ -74,7 +80,19 @@ const router = createBrowserRouter([
{
path: "/chat/:id",
element: ,
+ loader: async () => {
+ await System.Init();
+ return null;
+ },
},
+ {
+ path: "/alert/:id/:type",
+ element: ,
+ loader: async () => {
+ await System.Init();
+ return null;
+ },
+ }
]);
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLDivElement);
root.render(
diff --git a/src/lang.json b/src/lang.json
index f291da5..04ae068 100644
--- a/src/lang.json
+++ b/src/lang.json
@@ -143,6 +143,9 @@
"QceMQZ": {
"defaultMessage": "Goal: {amount}"
},
+ "Qe1MJu": {
+ "defaultMessage": "{name} with {amount}"
+ },
"RJOmzk": {
"defaultMessage": "I have read and agree with {provider}''s {terms}."
},
@@ -194,6 +197,9 @@
"hGQqkW": {
"defaultMessage": "Schedule"
},
+ "hpl4BP": {
+ "defaultMessage": "Chat Widget"
+ },
"ieGrWo": {
"defaultMessage": "Follow"
},
@@ -245,12 +251,18 @@
"rfC1Zq": {
"defaultMessage": "Save card"
},
+ "rgsbu9": {
+ "defaultMessage": "Current Viewers"
+ },
"s5ksS7": {
"defaultMessage": "Image Link"
},
"s7V+5p": {
"defaultMessage": "Confirm your age"
},
+ "tG1ST3": {
+ "defaultMessage": "Incoming Zap"
+ },
"thsiMl": {
"defaultMessage": "terms and conditions"
},
@@ -277,5 +289,8 @@
},
"x82IOl": {
"defaultMessage": "Mute"
+ },
+ "zVDHAu": {
+ "defaultMessage": "Zap Alert"
}
}
diff --git a/src/pages/alerts.css b/src/pages/alerts.css
new file mode 100644
index 0000000..75b66b3
--- /dev/null
+++ b/src/pages/alerts.css
@@ -0,0 +1,78 @@
+.zap-alert-widgets .zap-alert {
+ animation: cssAnimation 0s ease-in 5s forwards;
+ animation-fill-mode: forwards;
+}
+
+.zap-alert {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.zap-alert > div:nth-of-type(1) {
+ width: fit-content;
+ font-size: 21px;
+ font-weight: 600;
+ border-radius: 33px;
+ background: linear-gradient(135deg, #882bff 0%, #f83838 100%);
+ margin: 0;
+ padding: 8px 24px;
+ margin-bottom: -11px;
+ z-index: 2;
+}
+
+.zap-alert > div:nth-of-type(2) {
+ z-index: 1;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 17px;
+ background: #2d2d2d;
+ padding: 27px 90px;
+ font-size: 29px;
+ font-weight: 600;
+}
+
+.zap-alert .highlight {
+ color: #ff4468;
+}
+
+@keyframes cssAnimation {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+.views {
+ display: inline-block;
+ border-radius: 16px;
+ background: #222;
+ padding: 16px 24px;
+ font-size: 21px;
+ font-weight: 600;
+ line-height: 32px;
+}
+
+.top-zappers-widget {
+ display: inline-flex;
+ border-radius: 16px;
+ background: #222;
+ padding: 16px 24px;
+ font-size: 21px;
+ font-weight: 600;
+ align-items: center;
+ gap: 16px;
+}
+
+.top-zappers-widget .top-zapper {
+ background-color: #3f3f3f;
+ border: unset;
+}
+
+.top-zappers-widget .profile > img {
+ width: 24px;
+ height: 24px;
+}
diff --git a/src/pages/alerts.tsx b/src/pages/alerts.tsx
new file mode 100644
index 0000000..9164219
--- /dev/null
+++ b/src/pages/alerts.tsx
@@ -0,0 +1,29 @@
+import "./alerts.css";
+import Spinner from "element/spinner";
+import { useStreamLink } from "hooks/stream-link";
+import { useParams } from "react-router-dom";
+import { ZapAlerts } from "./widgets/zaps";
+import { Views } from "./widgets/views";
+import { TopZappersWidget } from "./widgets/top-zappers";
+
+export function AlertsPage() {
+ const params = useParams();
+ const link = useStreamLink();
+
+ if (!link) {
+ return
+ }
+
+ switch (params.type) {
+ case "zaps": {
+ return
+ }
+ case "views": {
+ return
+ }
+ case "top-zappers": {
+ return
+ }
+ }
+ return null;
+}
diff --git a/src/pages/stream-page.tsx b/src/pages/stream-page.tsx
index 63e4d28..3c4c824 100644
--- a/src/pages/stream-page.tsx
+++ b/src/pages/stream-page.tsx
@@ -1,11 +1,10 @@
import "./stream-page.css";
-import { NostrLink, NostrPrefix, TaggedNostrEvent, tryParseNostrLink } from "@snort/system";
-import { fetchNip05Pubkey } from "@snort/shared";
-import { useLocation, useNavigate, useParams } from "react-router-dom";
+import { NostrLink, TaggedNostrEvent } from "@snort/system";
+import { useLocation, useNavigate } from "react-router-dom";
import { Helmet } from "react-helmet";
import { LiveVideoPlayer } from "element/live-video-player";
-import { createNostrLink, findTag, getEventFromLocationState, getHost, hexToBech32 } from "utils";
+import { eventToLink, findTag, getEventFromLocationState, getHost } from "utils";
import { Profile, getName } from "element/profile";
import { LiveChat } from "element/live-chat";
import AsyncButton from "element/async-button";
@@ -24,7 +23,7 @@ import { StreamTimer } from "element/stream-time";
import { ShareMenu } from "element/share-menu";
import { ContentWarningOverlay, isContentWarningAccepted } from "element/content-warning";
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
-import { useEffect, useState } from "react";
+import { useStreamLink } from "hooks/stream-link";
function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent }) {
const login = useLogin();
@@ -97,30 +96,9 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent })
}
export function StreamPageHandler() {
- const params = useParams();
const location = useLocation();
const evPreload = getEventFromLocationState(location.state);
- const [link, setLink] = useState();
-
- useEffect(() => {
- if (params.id) {
- const parsedLink = tryParseNostrLink(params.id);
- if (parsedLink) {
- setLink(parsedLink);
- } else {
- const [handle, domain] = (params.id.includes("@") ? params.id : `${params.id}@zap.stream`).split("@");
- fetchNip05Pubkey(handle, domain).then(d => {
- if (d) {
- setLink({
- id: d,
- type: NostrPrefix.PublicKey,
- encode: () => hexToBech32(NostrPrefix.PublicKey, d),
- } as NostrLink);
- }
- });
- }
- }
- }, [params.id]);
+ const link = useStreamLink();
if (link) {
return ;
@@ -130,7 +108,8 @@ export function StreamPageHandler() {
export function StreamPage({ link, evPreload }: { evPreload?: NostrEvent; link: NostrLink }) {
const ev = useCurrentStreamFeed(link, true, evPreload);
const host = getHost(ev);
- const goal = useZapGoal(host, createNostrLink(ev), true);
+ const evLink = ev ? eventToLink(ev) : undefined;
+ const goal = useZapGoal(host, evLink, true);
const title = findTag(ev, "title");
const summary = findTag(ev, "summary");
@@ -161,7 +140,7 @@ export function StreamPage({ link, evPreload }: { evPreload?: NostrEvent; link:
-
+
);
}
diff --git a/src/pages/widgets.css b/src/pages/widgets.css
new file mode 100644
index 0000000..524ab5c
--- /dev/null
+++ b/src/pages/widgets.css
@@ -0,0 +1,10 @@
+.widgets {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+}
+
+.widgets > div {
+ background-color: #3f3f3f;
+ border-radius: 16px;
+ padding: 8px 12px;
+}
\ No newline at end of file
diff --git a/src/pages/widgets.tsx b/src/pages/widgets.tsx
new file mode 100644
index 0000000..5d627cb
--- /dev/null
+++ b/src/pages/widgets.tsx
@@ -0,0 +1,58 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import "./widgets.css";
+import { NostrPrefix, createNostrLink } from "@snort/system";
+import Copy from "element/copy";
+import { useCurrentStreamFeed } from "hooks/current-stream-feed";
+import { useLogin } from "hooks/login";
+import { FormattedMessage } from "react-intl";
+import { eventToLink, hexToBech32 } from "utils";
+import { ZapAlertItem } from "./widgets/zaps";
+import { TopZappersWidget } from "./widgets/top-zappers";
+import { Views } from "./widgets/views";
+
+export function WidgetsPage() {
+ const login = useLogin();
+ const profileLink = createNostrLink(NostrPrefix.PublicKey, login?.pubkey ?? "");
+ const current = useCurrentStreamFeed(profileLink);
+ const currentLink = current ? eventToLink(current) : undefined;
+ const npub = hexToBech32("npub", login?.pubkey);
+
+ const baseUrl = `${window.location.protocol}//${window.location.host}`;
+ return
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {currentLink && }
+
+
+
+
+
+
+ {currentLink && }
+
+
+}
\ No newline at end of file
diff --git a/src/pages/widgets/top-zappers.tsx b/src/pages/widgets/top-zappers.tsx
new file mode 100644
index 0000000..f6be45e
--- /dev/null
+++ b/src/pages/widgets/top-zappers.tsx
@@ -0,0 +1,19 @@
+import { NostrLink } from "@snort/system";
+import { TopZappers } from "element/top-zappers";
+import { useCurrentStreamFeed } from "hooks/current-stream-feed";
+import { useZaps } from "hooks/zaps";
+import { FormattedMessage } from "react-intl";
+import { eventToLink } from "utils";
+
+export function TopZappersWidget({ link }: { link: NostrLink }) {
+ const currentEvent = useCurrentStreamFeed(link, true);
+ const zaps = useZaps(currentEvent ? eventToLink(currentEvent) : undefined, true);
+ return ;
+}
\ No newline at end of file
diff --git a/src/pages/widgets/views.tsx b/src/pages/widgets/views.tsx
new file mode 100644
index 0000000..828c2d4
--- /dev/null
+++ b/src/pages/widgets/views.tsx
@@ -0,0 +1,13 @@
+import { NostrLink } from "@snort/system";
+import { useCurrentStreamFeed } from "hooks/current-stream-feed";
+import { FormattedMessage } from "react-intl";
+import { findTag } from "utils";
+
+export function Views({ link }: { link: NostrLink }) {
+ const current = useCurrentStreamFeed(link);
+
+ const viewers = findTag(current, "current_participants");
+ return
+
+
+}
\ No newline at end of file
diff --git a/src/pages/widgets/zaps.tsx b/src/pages/widgets/zaps.tsx
new file mode 100644
index 0000000..3d6a2fb
--- /dev/null
+++ b/src/pages/widgets/zaps.tsx
@@ -0,0 +1,34 @@
+import { hexToBech32 } from "@snort/shared";
+import { NostrLink, ParsedZap } from "@snort/system";
+import { useUserProfile } from "@snort/system-react";
+import { useCurrentStreamFeed } from "hooks/current-stream-feed";
+import { useZaps } from "hooks/zaps";
+import { formatSats } from "number";
+import { FormattedMessage } from "react-intl";
+import { eventToLink } from "utils";
+
+export function ZapAlerts({ link }: { link: NostrLink }) {
+ const currentEvent = useCurrentStreamFeed(link, true);
+ const currentLink = currentEvent ? eventToLink(currentEvent) : undefined;
+ const zaps = useZaps(currentLink, true);
+
+ return
+ {zaps.slice(0, 5).map(v => )}
+
+}
+
+export function ZapAlertItem({ item }: { item: ParsedZap }) {
+ const profile = useUserProfile(item.sender);
+ if (!profile) return;
+ return
+
+
+
+
+ {profile?.name ?? hexToBech32("npub", item?.sender ?? "").slice(0, 12)} ,
+ amount: {formatSats(item.amount)}
+ }} />
+
+
+}
\ No newline at end of file
diff --git a/src/translations/en.json b/src/translations/en.json
index c59dac1..47f9363 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -47,6 +47,7 @@
"QRHNuF": "What are we steaming today?",
"QRRCp0": "Stream URL",
"QceMQZ": "Goal: {amount}",
+ "Qe1MJu": "{name} with {amount}",
"RJOmzk": "I have read and agree with {provider}''s {terms}.",
"RXQdxR": "Please login to write messages!",
"RrCui3": "Summary",
@@ -64,6 +65,7 @@
"ebmhes": "Nostr Extension",
"fBI91o": "Zap",
"hGQqkW": "Schedule",
+ "hpl4BP": "Chat Widget",
"ieGrWo": "Follow",
"itPgxd": "Profile",
"izWS4J": "Unfollow",
@@ -81,8 +83,10 @@
"rWBFZA": "Sexually explicit material ahead!",
"rbrahO": "Close",
"rfC1Zq": "Save card",
+ "rgsbu9": "Current Viewers",
"s5ksS7": "Image Link",
"s7V+5p": "Confirm your age",
+ "tG1ST3": "Incoming Zap",
"thsiMl": "terms and conditions",
"tzMNF3": "Status",
"uYw2LD": "Stream",
@@ -91,5 +95,6 @@
"wEQDC6": "Edit",
"wOy57k": "Add stream goal",
"wzWWzV": "Top zappers",
- "x82IOl": "Mute"
+ "x82IOl": "Mute",
+ "zVDHAu": "Zap Alert"
}
\ No newline at end of file
diff --git a/src/utils.ts b/src/utils.ts
index 7dc194b..00a21fa 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,8 +1,9 @@
-import { NostrEvent, NostrPrefix, TaggedNostrEvent, encodeTLV, parseNostrLink } from "@snort/system";
+import { NostrEvent, NostrPrefix, TaggedNostrEvent, createNostrLink, encodeTLV } from "@snort/system";
import * as utils from "@noble/curves/abstract/utils";
import { bech32 } from "@scure/base";
import type { Tag, Tags } from "types";
import { LIVE_STREAM } from "const";
+import { unwrap } from "@snort/shared";
export function toAddress(e: NostrEvent): string {
if (e.kind && e.kind >= 30000 && e.kind <= 40000) {
@@ -76,11 +77,6 @@ export function eventLink(ev: NostrEvent | TaggedNostrEvent) {
}
}
-export function createNostrLink(ev?: NostrEvent) {
- if (!ev) return;
- return parseNostrLink(eventLink(ev));
-}
-
export function getHost(ev?: NostrEvent) {
return ev?.tags.find(a => a[0] === "p" && a[3] === "host")?.[1] ?? ev?.pubkey ?? "";
}
@@ -114,3 +110,11 @@ export function getEventFromLocationState(state: unknown | undefined | null) {
? (state as NostrEvent)
: undefined;
}
+
+export function eventToLink(ev: NostrEvent) {
+ if (ev.kind >= 30_000 && ev.kind < 40_000) {
+ const dTag = unwrap(findTag(ev, "d"));
+ return createNostrLink(NostrPrefix.Address, dTag, undefined, ev.kind, ev.pubkey);
+ }
+ return createNostrLink(NostrPrefix.Event, ev.id, undefined, ev.kind, ev.pubkey);
+}
diff --git a/webpack.config.js b/webpack.config.js
index 5a6793b..7274007 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -62,9 +62,11 @@ const config = {
__XXX: process.env["__XXX"] || JSON.stringify(false),
__XXX_HOST: JSON.stringify("https://xxzap.com"),
}),
- new WorkboxPlugin.InjectManifest({
- swSrc: "./src/service-worker.ts",
- }),
+ isProduction
+ ? new WorkboxPlugin.InjectManifest({
+ swSrc: "./src/service-worker.ts",
+ })
+ : false,
],
module: {
rules: [