forked from Kieran/zap.stream
feat: add top zappers to the chat
This commit is contained in:
parent
4ecba2f0d7
commit
018022689e
@ -12,7 +12,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
padding: 0px 0px 16px;
|
padding: 0px 0px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.live-chat>.messages {
|
.live-chat>.messages {
|
||||||
@ -75,3 +75,42 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-zappers h3 {
|
||||||
|
margin: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-zappers-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-zapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-zapper .top-zapper-amount {
|
||||||
|
margin: 0 8px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-zapper .top-zapper-name {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zapper-gold-icon {
|
||||||
|
color: #FFD700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zapper-silver-icon {
|
||||||
|
color: #C0C0C0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zapper-bronze-icon {
|
||||||
|
color: #CD7F32;
|
||||||
|
}
|
||||||
|
@ -4,9 +4,10 @@ import {
|
|||||||
NostrLink,
|
NostrLink,
|
||||||
TaggedRawEvent,
|
TaggedRawEvent,
|
||||||
EventPublisher,
|
EventPublisher,
|
||||||
|
ParsedZap,
|
||||||
parseZap,
|
parseZap,
|
||||||
} from "@snort/system";
|
} from "@snort/system";
|
||||||
import { useState, type KeyboardEvent, type ChangeEvent } from "react";
|
import { useState, useMemo, type KeyboardEvent, type ChangeEvent } from "react";
|
||||||
|
|
||||||
import useEmoji from "hooks/emoji";
|
import useEmoji from "hooks/emoji";
|
||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
@ -26,6 +27,54 @@ export interface LiveChatOptions {
|
|||||||
showHeader?: boolean;
|
showHeader?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function totalZapped(pubkey: string, zaps: ParsedZap[]) {
|
||||||
|
return zaps
|
||||||
|
.filter((z) => (z.anonZap ? pubkey === "anon" : z.sender === pubkey))
|
||||||
|
.reduce((acc, z) => acc + z.amount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
|
||||||
|
const zappers = zaps
|
||||||
|
.map((z) => (z.anonZap ? "anon" : z.sender))
|
||||||
|
.map((p) => p as string);
|
||||||
|
|
||||||
|
const sortedZappers = useMemo(() => {
|
||||||
|
const sorted = [...new Set([...zappers])];
|
||||||
|
sorted.sort((a, b) => totalZapped(b, zaps) - totalZapped(a, zaps));
|
||||||
|
return sorted.slice(0, 3);
|
||||||
|
}, [zappers]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>Top zappers</h3>
|
||||||
|
<div className="top-zappers-container">
|
||||||
|
{sortedZappers.map((pk, idx) => {
|
||||||
|
const total = totalZapped(pk, zaps);
|
||||||
|
const iconClass =
|
||||||
|
idx === 0
|
||||||
|
? "zapper-gold-icon"
|
||||||
|
: idx === 1
|
||||||
|
? "zapper-silver-icon"
|
||||||
|
: "zapper-bronze-icon";
|
||||||
|
return (
|
||||||
|
<div className="top-zapper" key={pk}>
|
||||||
|
<Icon name="zap" className={iconClass} />
|
||||||
|
<p className="top-zapper-amount">{formatSats(total)}</p>
|
||||||
|
<div className="top-zapper-name">
|
||||||
|
{pk === "anon" ? (
|
||||||
|
<p>Anon</p>
|
||||||
|
) : (
|
||||||
|
<Profile pubkey={pk} options={{ showName: false }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function LiveChat({
|
export function LiveChat({
|
||||||
link,
|
link,
|
||||||
options,
|
options,
|
||||||
@ -35,11 +84,21 @@ export function LiveChat({
|
|||||||
}) {
|
}) {
|
||||||
const messages = useLiveChatFeed(link);
|
const messages = useLiveChatFeed(link);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
|
const events = messages.data ?? [];
|
||||||
|
const zaps = events
|
||||||
|
.filter((ev) => ev.kind === EventKind.ZapReceipt)
|
||||||
|
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
|
||||||
|
.filter((z) => z);
|
||||||
return (
|
return (
|
||||||
<div className="live-chat">
|
<div className="live-chat">
|
||||||
{(options?.showHeader ?? true) && (
|
{(options?.showHeader ?? true) && (
|
||||||
<div className="header">Stream Chat</div>
|
<div className="header">Stream Chat</div>
|
||||||
)}
|
)}
|
||||||
|
{zaps.length > 0 && (
|
||||||
|
<div className="top-zappers">
|
||||||
|
<TopZappers zaps={zaps} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="messages">
|
<div className="messages">
|
||||||
{[...(messages.data ?? [])]
|
{[...(messages.data ?? [])]
|
||||||
.sort((a, b) => b.created_at - a.created_at)
|
.sort((a, b) => b.created_at - a.created_at)
|
||||||
@ -93,7 +152,7 @@ function ChatZap({ ev }: { ev: TaggedRawEvent }) {
|
|||||||
pubkey={parsed.anonZap ? "" : parsed.sender ?? ""}
|
pubkey={parsed.anonZap ? "" : parsed.sender ?? ""}
|
||||||
options={{
|
options={{
|
||||||
showAvatar: !parsed.anonZap,
|
showAvatar: !parsed.anonZap,
|
||||||
overrideName: parsed.anonZap ? "Anonymous" : undefined,
|
overrideName: parsed.anonZap ? "Anon" : undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
zapped
|
zapped
|
||||||
|
Loading…
Reference in New Issue
Block a user