show top zappers on stream

This commit is contained in:
hzrd149
2023-07-04 12:55:38 -05:00
parent 85d6a2a098
commit 677bf684e7
4 changed files with 86 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
import { Button, ButtonProps, useDisclosure } from "@chakra-ui/react"; import { Button, ButtonProps, IconButton, useDisclosure } from "@chakra-ui/react";
import { readablizeSats } from "../../helpers/bolt11"; import { readablizeSats } from "../../helpers/bolt11";
import { totalZaps } from "../../helpers/zaps"; import { totalZaps } from "../../helpers/zaps";
import { useCurrentAccount } from "../../hooks/use-current-account"; import { useCurrentAccount } from "../../hooks/use-current-account";
@@ -31,10 +31,13 @@ export default function NoteZapButton({
eventZapsService.requestZaps(note.id, clientRelaysService.getReadUrls(), true); eventZapsService.requestZaps(note.id, clientRelaysService.getReadUrls(), true);
}; };
const total = totalZaps(zaps);
return ( return (
<> <>
{total > 0 ? (
<Button <Button
leftIcon={<LightningIcon color="yellow.500" />} leftIcon={<LightningIcon />}
aria-label="Zap Note" aria-label="Zap Note"
title="Zap Note" title="Zap Note"
colorScheme={hasZapped ? "brand" : undefined} colorScheme={hasZapped ? "brand" : undefined}
@@ -42,8 +45,19 @@ export default function NoteZapButton({
onClick={onOpen} onClick={onOpen}
isDisabled={!metadata?.allowsNostr} isDisabled={!metadata?.allowsNostr}
> >
{readablizeSats(totalZaps(zaps) / 1000)} {readablizeSats(total / 1000)}
</Button> </Button>
) : (
<IconButton
icon={<LightningIcon />}
aria-label="Zap Note"
title="Zap Note"
{...props}
onClick={onOpen}
isDisabled={!metadata?.allowsNostr}
/>
)}
{isOpen && ( {isOpen && (
<ZapModal <ZapModal
isOpen={isOpen} isOpen={isOpen}

View File

@@ -44,12 +44,14 @@ function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode
return ( return (
<ButtonGroup> <ButtonGroup>
{isMobile && toggleButton} {isMobile && toggleButton}
{!isMobile && (
<CopyIconButton <CopyIconButton
text={location.href + "?displayMode=log&colorMode=dark"} text={location.href + "?displayMode=log&colorMode=dark"}
aria-label="Copy chat log URL" aria-label="Copy chat log URL"
title="Copy chat log URL" title="Copy chat log URL"
size="sm" size="sm"
/> />
)}
<Button <Button
rightIcon={<ExternalLinkIcon />} rightIcon={<ExternalLinkIcon />}
size="sm" size="sm"

View File

@@ -1,4 +1,4 @@
import { useRef } from "react"; import { useMemo, useRef } from "react";
import { import {
Box, Box,
Button, Button,
@@ -36,6 +36,9 @@ import useSubject from "../../../../hooks/use-subject";
import { useTimelineLoader } from "../../../../hooks/use-timeline-loader"; import { useTimelineLoader } from "../../../../hooks/use-timeline-loader";
import { truncatedId } from "../../../../helpers/nostr-event"; import { truncatedId } from "../../../../helpers/nostr-event";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import TopZappers from "./top-zappers";
import { Kind } from "nostr-tools";
import { parseZapEvent } from "../../../../helpers/zaps";
const hideScrollbar = css` const hideScrollbar = css`
scrollbar-width: 0; scrollbar-width: 0;
@@ -67,6 +70,16 @@ export default function StreamChat({
const events = useSubject(timeline.timeline).sort((a, b) => b.created_at - a.created_at); const events = useSubject(timeline.timeline).sort((a, b) => b.created_at - a.created_at);
const zaps = useMemo(() => {
const parsed = [];
for (const event of events) {
try {
parsed.push(parseZapEvent(event));
} catch (e) {}
}
return parsed;
}, [events]);
const scrollBox = useRef<HTMLDivElement | null>(null); const scrollBox = useRef<HTMLDivElement | null>(null);
const callback = useTimelineCurserIntersectionCallback(timeline); const callback = useTimelineCurserIntersectionCallback(timeline);
@@ -104,7 +117,8 @@ export default function StreamChat({
{actions} {actions}
</CardHeader> </CardHeader>
)} )}
<CardBody display="flex" flexDirection="column" gap="2" overflow="hidden" p={0}> <CardBody display="flex" flexDirection="column" overflow="hidden" p={0}>
<TopZappers zaps={zaps} pt={!isPopup ? 0 : undefined} />
<Flex <Flex
overflowY="scroll" overflowY="scroll"
overflowX="hidden" overflowX="hidden"
@@ -113,6 +127,7 @@ export default function StreamChat({
flex={1} flex={1}
px="4" px="4"
py="2" py="2"
mb="2"
gap="2" gap="2"
css={isChatLog && hideScrollbar} css={isChatLog && hideScrollbar}
> >

View File

@@ -0,0 +1,35 @@
import { Box, Flex, FlexProps, Text } from "@chakra-ui/react";
import { ParsedZap } from "../../../../helpers/zaps";
import { UserAvatar } from "../../../../components/user-avatar";
import { UserLink } from "../../../../components/user-link";
import { LightningIcon } from "../../../../components/icons";
import { readablizeSats } from "../../../../helpers/bolt11";
export default function TopZappers({ zaps, ...props }: FlexProps & { zaps: ParsedZap[] }) {
const totals: Record<string, number> = {};
for (const zap of zaps) {
const p = zap.request.pubkey;
if (zap.payment.amount) {
totals[p] = (totals[p] || 0) + zap.payment.amount;
}
}
const sortedTotals = Array.from(Object.entries(totals)).sort((a, b) => b[1] - a[1]);
return (
<Flex overflowX="auto" overflowY="hidden" gap="4" py="2" px="4" {...props}>
{sortedTotals.map(([pubkey, total]) => (
<Flex key={pubkey} gap="2" alignItems="center" maxW="sm">
<UserAvatar pubkey={pubkey} size="sm" noProxy />
<Box>
<UserLink pubkey={pubkey} isTruncated fontWeight="bold" />
<Text whiteSpace="nowrap">
<LightningIcon />
{readablizeSats(total / 1000)}
</Text>
</Box>
</Flex>
))}
</Flex>
);
}