small improvements to users zap tab

This commit is contained in:
hzrd149 2023-03-11 14:50:41 -06:00
parent fdf18c8a5f
commit 4e89806203
6 changed files with 63 additions and 24 deletions

View File

@ -67,7 +67,9 @@ I would recomend you use a browser extension like [Alby](https://getalby.com/) o
- [ ] [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md): Authentication of clients to relays
- [ ] [NIP-50](https://github.com/nostr-protocol/nips/blob/master/50.md): Keywords filter
- [ ] [NIP-56](https://github.com/nostr-protocol/nips/blob/master/56.md): Reporting
- [ ] [NIP-51](https://github.com/nostr-protocol/nips/blob/master/51.md): Lists (Mute, Pin, People List, and Bookmark list)
- [x] [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md): Lightning Zaps
- [ ] [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md): Badges
- [x] [NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md): Relay List Metadata
## TODO

View File

@ -9,6 +9,7 @@ import {
CardBody,
CardFooter,
CardHeader,
CardProps,
Flex,
Heading,
IconButton,
@ -37,8 +38,9 @@ import { ExpandProvider } from "./expanded";
export type NoteProps = {
event: NostrEvent;
maxHeight?: number;
variant?: CardProps["variant"];
};
export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteProps) => {
const isMobile = useIsMobile();
const account = useCurrentAccount();
const { openModal } = useContext(PostModalContext);
@ -51,7 +53,7 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
return (
<ExpandProvider>
<Card variant="outline">
<Card variant={variant}>
<CardHeader padding="2">
<Flex flex="1" gap="2" alignItems="center" wrap="wrap">
<UserAvatarLink pubkey={event.pubkey} size={isMobile ? "xs" : "sm"} />

View File

@ -15,7 +15,7 @@ const NoteView = () => {
const isRoot = rootId === focusId;
const rootPost = thread.get(rootId);
if (isRoot && rootPost) {
pageContent = <ThreadPost post={rootPost} initShowReplies />;
pageContent = <ThreadPost post={rootPost} initShowReplies focusId={focusId} />;
}
const post = thread.get(focusId);
@ -34,11 +34,11 @@ const NoteView = () => {
{parentPosts.map((parent) => (
<Note key={parent.event.id + "-rely"} event={parent.event} maxHeight={200} />
))}
<ThreadPost key={post.event.id} post={post} initShowReplies />
<ThreadPost key={post.event.id} post={post} initShowReplies focusId={focusId} />
</>
);
} else if (events[focusId]) {
pageContent = <Note event={events[focusId]} />;
pageContent = <Note event={events[focusId]} variant="filled" />;
}
return (

View File

@ -8,9 +8,10 @@ import { useIsMobile } from "../../hooks/use-is-mobile";
export type ThreadItemProps = {
post: ThreadItemData;
initShowReplies?: boolean;
focusId?: string;
};
export const ThreadPost = ({ post, initShowReplies }: ThreadItemProps) => {
export const ThreadPost = ({ post, initShowReplies, focusId }: ThreadItemProps) => {
const isMobile = useIsMobile();
const [showReplies, setShowReplies] = useState(initShowReplies ?? post.replies.length === 1);
const toggle = () => setShowReplies((v) => !v);
@ -19,7 +20,7 @@ export const ThreadPost = ({ post, initShowReplies }: ThreadItemProps) => {
return (
<Flex direction="column" gap="2">
<Note event={post.event} />
<Note event={post.event} variant={focusId === post.event.id ? "filled" : "outline"} />
{post.replies.length > 0 && (
<>
<Button variant="link" size="sm" alignSelf="flex-start" onClick={toggle}>
@ -29,7 +30,7 @@ export const ThreadPost = ({ post, initShowReplies }: ThreadItemProps) => {
{showReplies && (
<Flex direction="column" gap="2" pl={isMobile ? 2 : 4} borderLeftColor="gray.500" borderLeftWidth="1px">
{post.replies.map((child) => (
<ThreadPost key={child.event.id} post={child} />
<ThreadPost key={child.event.id} post={child} focusId={focusId} />
))}
</Flex>
)}

View File

@ -21,7 +21,7 @@ import { useOutletContext } from "react-router-dom";
import { RelayMode } from "../../classes/relay";
import { RelayIcon } from "../../components/icons";
import { Note } from "../../components/note";
import { isNote } from "../../helpers/nostr-event";
import { isNote, truncatedId } from "../../helpers/nostr-event";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import useFallbackUserRelays from "../../hooks/use-fallback-user-relays";
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
@ -41,10 +41,10 @@ const UserNotesTab = () => {
const { isOpen: showReplies, onToggle: toggleReplies } = useDisclosure();
const { events, loading, loadMore } = useTimelineLoader(
`${pubkey}-notes`,
`${truncatedId(pubkey)}-notes`,
relays,
{ authors: [pubkey], kinds: [1] },
{ pageSize: moment.duration(1, "day").asSeconds() }
{ pageSize: moment.duration(1, "day").asSeconds(), startLimit: 20 }
);
const timeline = showReplies ? events : events.filter(isNote);

View File

@ -1,18 +1,23 @@
import { Box, Button, Flex, Spinner, Text, useDisclosure } from "@chakra-ui/react";
import { Box, Button, Flex, Select, Spinner, Text, useDisclosure } from "@chakra-ui/react";
import moment from "moment";
import { useState } from "react";
import { useOutletContext } from "react-router-dom";
import { RelayMode } from "../../classes/relay";
import { ErrorBoundary, ErrorFallback } from "../../components/error-boundary";
import QuoteNote from "../../components/note/quote-note";
import { LightningIcon } from "../../components/icons";
import { NoteLink } from "../../components/note-link";
import { UserAvatarLink } from "../../components/user-avatar-link";
import { UserLink } from "../../components/user-link";
import { readablizeSats } from "../../helpers/bolt11";
import { convertTimestampToDate } from "../../helpers/date";
import { isProfileZap, parseZapNote } from "../../helpers/zaps";
import { truncatedId } from "../../helpers/nostr-event";
import { isProfileZap, isNoteZap, parseZapNote, totalZaps } from "../../helpers/zaps";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import useFallbackUserRelays from "../../hooks/use-fallback-user-relays";
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
import { NostrEvent } from "../../types/nostr-event";
const ZapNote = ({ zapEvent }: { zapEvent: NostrEvent }) => {
const Zap = ({ zapEvent }: { zapEvent: NostrEvent }) => {
const { isOpen, onToggle } = useDisclosure();
try {
const { request, payment, eventId } = parseZapNote(zapEvent);
@ -30,7 +35,14 @@ const ZapNote = ({ zapEvent }: { zapEvent: NostrEvent }) => {
<Flex gap="2" alignItems="center" wrap="wrap">
<UserAvatarLink pubkey={request.pubkey} size="xs" />
<UserLink pubkey={request.pubkey} />
{payment.amount && <Text>{readablizeSats(payment.amount / 1000)}</Text>}
<Text>Zapped</Text>
{eventId && <NoteLink noteId={eventId} />}
{payment.amount && (
<>
<LightningIcon color="yellow.400" />
<Text>{readablizeSats(payment.amount / 1000)} sats</Text>
</>
)}
{request.content && (
<Button variant="link" onClick={onToggle}>
Show message
@ -39,7 +51,6 @@ const ZapNote = ({ zapEvent }: { zapEvent: NostrEvent }) => {
<Text ml="auto">{moment(convertTimestampToDate(request.created_at)).fromNow()}</Text>
</Flex>
{request.content && isOpen && <Text>{request.content}</Text>}
{eventId && <QuoteNote noteId={eventId} />}
</Box>
);
} catch (e) {
@ -54,21 +65,44 @@ const ZapNote = ({ zapEvent }: { zapEvent: NostrEvent }) => {
const UserZapsTab = () => {
const { pubkey } = useOutletContext() as { pubkey: string };
const readRelays = useReadRelayUrls();
const [filter, setFilter] = useState("both");
// get user relays
const userRelays = useFallbackUserRelays(pubkey)
.filter((r) => r.mode & RelayMode.WRITE)
.map((r) => r.url);
const relays = useReadRelayUrls(userRelays);
const { events, loading, loadMore } = useTimelineLoader(
`${pubkey}-zaps`,
readRelays,
{ "#p": [pubkey], kinds: [9735], since: moment().subtract(1, "day").unix() },
{ pageSize: moment.duration(1, "day").asSeconds() }
`${truncatedId(pubkey)}-zaps`,
relays,
{ "#p": [pubkey], kinds: [9735] },
{ pageSize: moment.duration(1, "week").asSeconds() }
);
const timeline = events.filter(isProfileZap);
const timeline =
filter === "note" ? events.filter(isNoteZap) : filter === "profile" ? events.filter(isProfileZap) : events;
return (
<Flex direction="column" gap="2" pr="2" pl="2">
<Flex gap="2" alignItems="center">
<Select value={filter} onChange={(e) => setFilter(e.target.value)} maxW="lg">
<option value="both">Note & Profile Zaps</option>
<option value="note">Note Zaps</option>
<option value="profile">Profile Zaps</option>
</Select>
{timeline.length && (
<>
<LightningIcon color="yellow.400" />
<Text>
{readablizeSats(totalZaps(timeline) / 1000)} sats in the last{" "}
{moment(convertTimestampToDate(timeline[timeline.length - 1].created_at)).fromNow(true)}
</Text>
</>
)}
</Flex>
{timeline.map((event) => (
<ErrorBoundary key={event.id}>
<ZapNote zapEvent={event} />
<Zap zapEvent={event} />
</ErrorBoundary>
))}
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}