diff --git a/src/element/live-chat.css b/src/element/live-chat.css index 3f66675..7c0b8ea 100644 --- a/src/element/live-chat.css +++ b/src/element/live-chat.css @@ -12,7 +12,7 @@ font-weight: 600; font-size: 24px; line-height: 30px; - padding: 0px 0px 16px; + padding: 0px 0px 4px; } .live-chat>.messages { @@ -75,3 +75,42 @@ align-items: center; 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; +} diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx index c459b5b..f20ff21 100644 --- a/src/element/live-chat.tsx +++ b/src/element/live-chat.tsx @@ -4,9 +4,10 @@ import { NostrLink, TaggedRawEvent, EventPublisher, + ParsedZap, parseZap, } 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 { System } from "index"; @@ -26,6 +27,54 @@ export interface LiveChatOptions { 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 ( + <> +

Top zappers

+
+ {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 ( +
+ +

{formatSats(total)}

+
+ {pk === "anon" ? ( +

Anon

+ ) : ( + + )} +
+
+ ); + })} +
+ + ); +} + export function LiveChat({ link, options, @@ -35,11 +84,21 @@ export function LiveChat({ }) { const messages = useLiveChatFeed(link); 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 (
{(options?.showHeader ?? true) && (
Stream Chat
)} + {zaps.length > 0 && ( +
+ +
+ )}
{[...(messages.data ?? [])] .sort((a, b) => b.created_at - a.created_at) @@ -93,7 +152,7 @@ function ChatZap({ ev }: { ev: TaggedRawEvent }) { pubkey={parsed.anonZap ? "" : parsed.sender ?? ""} options={{ showAvatar: !parsed.anonZap, - overrideName: parsed.anonZap ? "Anonymous" : undefined, + overrideName: parsed.anonZap ? "Anon" : undefined, }} /> zapped