mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-05 18:38:44 +02:00
rebuild notifications view
This commit is contained in:
parent
03fb66156d
commit
b56156848c
5
.changeset/slow-carrots-rush.md
Normal file
5
.changeset/slow-carrots-rush.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Rebuild notifications view
|
@ -21,7 +21,7 @@ export default function EmbeddedNote({ event, ...props }: Omit<CardProps, "child
|
||||
<TrustProvider event={event}>
|
||||
<Card {...props}>
|
||||
<CardHeader padding="2" display="flex" gap="2" alignItems="center" flexWrap="wrap">
|
||||
<UserAvatarLink pubkey={event.pubkey} size="sm" />
|
||||
<UserAvatarLink pubkey={event.pubkey} size="xs" />
|
||||
<UserLink pubkey={event.pubkey} fontWeight="bold" isTruncated fontSize="lg" />
|
||||
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
|
||||
<Button size="sm" onClick={expand.onToggle} leftIcon={expand.isOpen ? <ArrowUpSIcon /> : <ArrowDownSIcon />}>
|
||||
|
@ -0,0 +1,45 @@
|
||||
import { Box, Card, CardProps, Divider, Flex, Link, Text } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import { NostrEvent, isATag } from "../../../types/nostr-event";
|
||||
import { UserLink } from "../../user-link";
|
||||
import { UserAvatar } from "../../user-avatar";
|
||||
import ChatMessageContent from "../../../views/streams/stream/stream-chat/chat-message-content";
|
||||
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
||||
import { parseStreamEvent } from "../../../helpers/nostr/stream";
|
||||
import StreamStatusBadge from "../../../views/streams/components/status-badge";
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
|
||||
export default function EmbeddedStreamMessage({
|
||||
message,
|
||||
...props
|
||||
}: Omit<CardProps, "children"> & { message: NostrEvent }) {
|
||||
const streamCoordinate = message.tags.find(isATag)?.[1];
|
||||
const streamEvent = useReplaceableEvent(streamCoordinate);
|
||||
const stream = streamEvent && parseStreamEvent(streamEvent);
|
||||
|
||||
return (
|
||||
<Card overflow="hidden" maxH="lg" display="block" p="2" {...props}>
|
||||
{stream && (
|
||||
<>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Link
|
||||
as={RouterLink}
|
||||
to={`/streams/${getSharableEventAddress(streamEvent) ?? ""}`}
|
||||
fontWeight="bold"
|
||||
fontSize="lg"
|
||||
>
|
||||
{stream.title}
|
||||
</Link>
|
||||
<StreamStatusBadge stream={stream} />
|
||||
</Flex>
|
||||
<Divider mb="2" />
|
||||
</>
|
||||
)}
|
||||
<UserAvatar pubkey={message.pubkey} size="xs" display="inline-block" mr="2" />
|
||||
<UserLink pubkey={message.pubkey} fontWeight="bold" />
|
||||
<span>: </span>
|
||||
<ChatMessageContent event={message} />
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -8,7 +8,7 @@ import { NostrEvent } from "../../types/nostr-event";
|
||||
import { Kind, nip19 } from "nostr-tools";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import RelayCard from "../../views/relays/components/relay-card";
|
||||
import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import { STREAM_CHAT_MESSAGE_KIND, STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import { GOAL_KIND } from "../../helpers/nostr/goal";
|
||||
import { safeDecode } from "../../helpers/nip19";
|
||||
import EmbeddedStream from "./event-types/embedded-stream";
|
||||
@ -20,6 +20,7 @@ import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../helpers/nostr/lists";
|
||||
import EmbeddedList from "./event-types/embedded-list";
|
||||
import EmbeddedArticle from "./event-types/embedded-article";
|
||||
import EmbeddedBadge from "./event-types/embedded-badge";
|
||||
import EmbeddedStreamMessage from "./event-types/embedded-stream-message";
|
||||
|
||||
export type EmbedProps = {
|
||||
goalProps?: EmbeddedGoalOptions;
|
||||
@ -46,6 +47,8 @@ export function EmbedEvent({
|
||||
return <EmbeddedArticle article={event} {...cardProps} />;
|
||||
case Kind.BadgeDefinition:
|
||||
return <EmbeddedBadge badge={event} {...cardProps} />;
|
||||
case STREAM_CHAT_MESSAGE_KIND:
|
||||
return <EmbeddedStreamMessage message={event} {...cardProps} />;
|
||||
}
|
||||
|
||||
return <EmbeddedUnknown event={event} {...cardProps} />;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Button, ButtonProps, IconButton, useDisclosure } from "@chakra-ui/react";
|
||||
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import { totalZaps } from "../../helpers/zaps";
|
||||
import { totalZaps } from "../../helpers/nostr/zaps";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import useEventZaps from "../../hooks/use-event-zaps";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
|
@ -16,7 +16,7 @@ import { NostrEvent } from "../../types/nostr-event";
|
||||
import { UserAvatarLink } from "../user-avatar-link";
|
||||
import { UserLink } from "../user-link";
|
||||
import { DislikeIcon, LightningIcon, LikeIcon } from "../icons";
|
||||
import { ParsedZap } from "../../helpers/zaps";
|
||||
import { ParsedZap } from "../../helpers/nostr/zaps";
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import useEventReactions from "../../hooks/use-event-reactions";
|
||||
import useEventZaps from "../../hooks/use-event-zaps";
|
||||
|
@ -27,7 +27,7 @@ import { useSigningContext } from "../providers/signing-provider";
|
||||
import appSettings from "../services/settings/app-settings";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
import useUserLNURLMetadata from "../hooks/use-user-lnurl-metadata";
|
||||
import { requestZapInvoice } from "../helpers/zaps";
|
||||
import { requestZapInvoice } from "../helpers/nostr/zaps";
|
||||
import { unique } from "../helpers/array";
|
||||
import { useUserRelays } from "../hooks/use-user-relays";
|
||||
import { RelayMode } from "../classes/relay";
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { bech32 } from "@scure/base";
|
||||
import { isETag, isPTag, NostrEvent } from "../types/nostr-event";
|
||||
import { ParsedInvoice, parsePaymentRequest } from "./bolt11";
|
||||
import { isETag, isPTag, NostrEvent } from "../../types/nostr-event";
|
||||
import { ParsedInvoice, parsePaymentRequest } from "../bolt11";
|
||||
|
||||
import { Kind0ParsedContent } from "./user-metadata";
|
||||
import { Kind0ParsedContent } from "../user-metadata";
|
||||
import { nip57, utils } from "nostr-tools";
|
||||
|
||||
// based on https://github.com/nbd-wtf/nostr-tools/blob/master/nip57.ts
|
||||
@ -67,13 +67,10 @@ export function parseZapEvent(event: NostrEvent): ParsedZap {
|
||||
const request = JSON.parse(zapRequestStr) as NostrEvent;
|
||||
const payment = parsePaymentRequest(bolt11);
|
||||
|
||||
const eventId = request.tags.find(isETag)?.[1];
|
||||
|
||||
return {
|
||||
event,
|
||||
request,
|
||||
payment,
|
||||
eventId,
|
||||
};
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { useMemo } from "react";
|
||||
import eventZapsService from "../services/event-zaps";
|
||||
import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import { parseZapEvent } from "../helpers/zaps";
|
||||
import { parseZapEvent } from "../helpers/nostr/zaps";
|
||||
|
||||
export default function useEventZaps(eventUID: string, additionalRelays: string[] = [], alwaysFetch = true) {
|
||||
const relays = useReadRelayUrls(additionalRelays);
|
||||
|
@ -4,7 +4,7 @@ import { getGoalAmount, getGoalRelays } from "../../../helpers/nostr/goal";
|
||||
import { LightningIcon } from "../../../components/icons";
|
||||
import useEventZaps from "../../../hooks/use-event-zaps";
|
||||
import { getEventUID } from "../../../helpers/nostr/events";
|
||||
import { totalZaps } from "../../../helpers/zaps";
|
||||
import { totalZaps } from "../../../helpers/nostr/zaps";
|
||||
import { readablizeSats } from "../../../helpers/bolt11";
|
||||
|
||||
export default function GoalProgress({ goal }: { goal: NostrEvent }) {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { memo, useCallback, useMemo, useRef } from "react";
|
||||
import { Card, CardBody, CardHeader, Flex, Text } from "@chakra-ui/react";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { ReactNode, forwardRef, memo, useCallback, useMemo, useRef } from "react";
|
||||
import { Box, Card, Flex, Switch, Text, useDisclosure } from "@chakra-ui/react";
|
||||
import { Kind, nip18, nip25 } from "nostr-tools";
|
||||
|
||||
import { UserAvatar } from "../../components/user-avatar";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { NostrEvent, isATag, isETag } from "../../types/nostr-event";
|
||||
import { NoteLink } from "../../components/note-link";
|
||||
import RequireCurrentAccount from "../../providers/require-current-account";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
@ -13,45 +13,86 @@ import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { useNotificationTimeline } from "../../providers/notification-timeline";
|
||||
import { parseZapEvent } from "../../helpers/zaps";
|
||||
import { parseZapEvent } from "../../helpers/nostr/zaps";
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import { getEventUID, getReferences } from "../../helpers/nostr/events";
|
||||
import { getEventUID, getReferences, parseCoordinate } from "../../helpers/nostr/events";
|
||||
import Timestamp from "../../components/timestamp";
|
||||
import { EmbedEvent, EmbedEventPointer } from "../../components/embed-event";
|
||||
import EmbeddedUnknown from "../../components/embed-event/event-types/embedded-unknown";
|
||||
import { NoteContents } from "../../components/note/note-contents";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
|
||||
const Kind1Notification = ({ event }: { event: NostrEvent }) => (
|
||||
<Card size="sm" variant="outline">
|
||||
<CardHeader>
|
||||
<Flex gap="2" alignItems="center">
|
||||
const Kind1Notification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
||||
const refs = getReferences(event);
|
||||
|
||||
if (refs.replyId) {
|
||||
return (
|
||||
<Card variant="outline" p="2" borderColor="blue.400" ref={ref}>
|
||||
<Flex gap="2" alignItems="center" mb="2">
|
||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
||||
<UserLink pubkey={event.pubkey} />
|
||||
{refs.replyId ? <Text>replied to post</Text> : <Text>mentioned you</Text>}
|
||||
<NoteLink noteId={event.id} color="current" ml="auto">
|
||||
<Timestamp timestamp={event.created_at} />
|
||||
</NoteLink>
|
||||
</Flex>
|
||||
{refs.replyId && <EmbedEventPointer pointer={{ type: "note", data: refs.replyId }} />}
|
||||
<NoteContents event={event} mt="2" />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<Flex gap="2" alignItems="center" mb="1">
|
||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
||||
<UserLink pubkey={event.pubkey} />
|
||||
<Text>replied to your post</Text>
|
||||
<Text>mentioned you in</Text>
|
||||
</Flex>
|
||||
<EmbedEvent event={event} />
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
const ShareNotification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
||||
const account = useCurrentAccount()!;
|
||||
const pointer = nip18.getRepostedEventPointer(event);
|
||||
if (pointer?.author !== account.pubkey) return null;
|
||||
|
||||
return (
|
||||
<Card variant="outline" p="2" ref={ref}>
|
||||
<Flex gap="2" alignItems="center" mb="2">
|
||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
||||
<UserLink pubkey={event.pubkey} />
|
||||
<Text>shared note</Text>
|
||||
<NoteLink noteId={event.id} color="current" ml="auto">
|
||||
<Timestamp timestamp={event.created_at} />
|
||||
</NoteLink>
|
||||
</Flex>
|
||||
</CardHeader>
|
||||
<CardBody pt={0}>
|
||||
<Text>{event.content.replace("\n", " ").slice(0, 64)}</Text>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
{pointer && <EmbedEventPointer pointer={{ type: "nevent", data: pointer }} />}
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
const ReactionNotification = ({ event }: { event: NostrEvent }) => {
|
||||
const refs = getReferences(event);
|
||||
const ReactionNotification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
||||
const account = useCurrentAccount();
|
||||
const pointer = nip25.getReactedEventPointer(event);
|
||||
if (!pointer || (account?.pubkey && pointer.author !== account.pubkey)) return null;
|
||||
|
||||
return (
|
||||
<Flex gap="2" alignItems="center" px="2">
|
||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
||||
<UserLink pubkey={event.pubkey} />
|
||||
<Text>reacted {event.content} to your post</Text>
|
||||
<NoteLink noteId={refs.replyId || event.id} color="current" ml="auto">
|
||||
<Timestamp timestamp={event.created_at} />
|
||||
</NoteLink>
|
||||
</Flex>
|
||||
<Box ref={ref}>
|
||||
<Flex gap="2" alignItems="center" mb="1">
|
||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
||||
<UserLink pubkey={event.pubkey} />
|
||||
<Text>reacted {event.content} to your post</Text>
|
||||
<Timestamp timestamp={event.created_at} ml="auto" />
|
||||
</Flex>
|
||||
<EmbedEventPointer pointer={{ type: "nevent", data: pointer }} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const ZapNotification = ({ event }: { event: NostrEvent }) => {
|
||||
const ZapNotification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
||||
const zap = useMemo(() => {
|
||||
try {
|
||||
return parseZapEvent(event);
|
||||
@ -60,65 +101,113 @@ const ZapNotification = ({ event }: { event: NostrEvent }) => {
|
||||
|
||||
if (!zap || !zap.payment.amount) return null;
|
||||
|
||||
const eventId = zap?.request.tags.find(isETag)?.[1];
|
||||
const coordinate = zap?.request.tags.find(isATag)?.[1];
|
||||
const parsedCoordinate = coordinate ? parseCoordinate(coordinate) : null;
|
||||
|
||||
let eventJSX: ReactNode | null = null;
|
||||
if (parsedCoordinate && parsedCoordinate.identifier) {
|
||||
eventJSX = (
|
||||
<EmbedEventPointer
|
||||
pointer={{
|
||||
type: "naddr",
|
||||
data: {
|
||||
pubkey: parsedCoordinate.pubkey,
|
||||
identifier: parsedCoordinate.identifier,
|
||||
kind: parsedCoordinate.kind,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (eventId) {
|
||||
eventJSX = <EmbedEventPointer pointer={{ type: "note", data: eventId }} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction="row"
|
||||
borderRadius="md"
|
||||
borderColor="yellow.400"
|
||||
borderWidth="1px"
|
||||
p="2"
|
||||
gap="2"
|
||||
alignItems="center"
|
||||
>
|
||||
<UserAvatar pubkey={zap.request.pubkey} size="xs" />
|
||||
<UserLink pubkey={zap.request.pubkey} />
|
||||
<Text>
|
||||
zapped {readablizeSats(zap.payment.amount / 1000)} sats
|
||||
{zap.eventId && (
|
||||
<span>
|
||||
{" "}
|
||||
on note: <NoteLink noteId={zap.eventId} />
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
<Timestamp color="current" ml="auto" timestamp={zap.request.created_at} />
|
||||
</Flex>
|
||||
<Card variant="outline" borderColor="yellow.400" p="2" ref={ref}>
|
||||
<Flex direction="row" gap="2" alignItems="center" mb="2">
|
||||
<UserAvatar pubkey={zap.request.pubkey} size="xs" />
|
||||
<UserLink pubkey={zap.request.pubkey} />
|
||||
<Text>zapped {readablizeSats(zap.payment.amount / 1000)} sats</Text>
|
||||
<Timestamp color="current" ml="auto" timestamp={zap.request.created_at} />
|
||||
</Flex>
|
||||
{eventJSX}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const NotificationItem = memo(({ event }: { event: NostrEvent }) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
||||
|
||||
let content = <Text>Unknown event type {event.kind}</Text>;
|
||||
switch (event.kind) {
|
||||
case Kind.Text:
|
||||
content = <Kind1Notification event={event} />;
|
||||
break;
|
||||
return <Kind1Notification event={event} ref={ref} />;
|
||||
case Kind.Reaction:
|
||||
content = <ReactionNotification event={event} />;
|
||||
break;
|
||||
return <ReactionNotification event={event} ref={ref} />;
|
||||
case Kind.Repost:
|
||||
return <ShareNotification event={event} ref={ref} />;
|
||||
case Kind.Zap:
|
||||
content = <ZapNotification event={event} />;
|
||||
break;
|
||||
return <ZapNotification event={event} ref={ref} />;
|
||||
default:
|
||||
return <EmbeddedUnknown event={event} />;
|
||||
}
|
||||
|
||||
return <div ref={ref}>{content}</div>;
|
||||
});
|
||||
|
||||
function NotificationsPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
const hideReplies = useDisclosure();
|
||||
const hideMentions = useDisclosure();
|
||||
const hideZaps = useDisclosure();
|
||||
const hideReactions = useDisclosure();
|
||||
const hideShares = useDisclosure();
|
||||
|
||||
const { people } = usePeopleListContext();
|
||||
const peoplePubkeys = useMemo(() => people?.map((p) => p.pubkey), [people]);
|
||||
|
||||
const eventFilter = useCallback(
|
||||
(event: NostrEvent) => {
|
||||
if (peoplePubkeys && event.kind !== Kind.Zap && !peoplePubkeys.includes(event.pubkey)) return false;
|
||||
|
||||
if (hideZaps.isOpen && event.kind === Kind.Zap) return false;
|
||||
if (hideReactions.isOpen && event.kind === Kind.Reaction) return false;
|
||||
if (hideShares.isOpen && event.kind === Kind.Repost) return false;
|
||||
if (event.kind === Kind.Text) {
|
||||
const refs = getReferences(event);
|
||||
if (hideReplies.isOpen && refs.replyId) return false;
|
||||
if (hideMentions.isOpen && !refs.replyId) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[hideMentions.isOpen, hideReplies.isOpen, hideZaps.isOpen, hideReactions.isOpen, hideShares.isOpen, peoplePubkeys],
|
||||
);
|
||||
|
||||
const eventFilter = useCallback((event: NostrEvent) => event.pubkey !== account.pubkey, [account]);
|
||||
const timeline = useNotificationTimeline();
|
||||
|
||||
const events = useSubject(timeline?.timeline) ?? [];
|
||||
const events = useSubject(timeline?.timeline).filter(eventFilter) ?? [];
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex gap="2" alignItems="center" py="2" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<Switch isChecked={!hideReplies.isOpen} onChange={hideReplies.onToggle}>
|
||||
Replies
|
||||
</Switch>
|
||||
<Switch isChecked={!hideMentions.isOpen} onChange={hideMentions.onToggle}>
|
||||
Mentions
|
||||
</Switch>
|
||||
<Switch isChecked={!hideReactions.isOpen} onChange={hideReactions.onToggle}>
|
||||
Reactions
|
||||
</Switch>
|
||||
<Switch isChecked={!hideShares.isOpen} onChange={hideShares.onToggle}>
|
||||
Shares
|
||||
</Switch>
|
||||
<Switch isChecked={!hideZaps.isOpen} onChange={hideZaps.onToggle}>
|
||||
Zaps
|
||||
</Switch>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4" pt="2" pb="12">
|
||||
{events.map((event) => (
|
||||
<NotificationItem key={event.id} event={event} />
|
||||
))}
|
||||
@ -132,7 +221,9 @@ function NotificationsPage() {
|
||||
export default function NotificationsView() {
|
||||
return (
|
||||
<RequireCurrentAccount>
|
||||
<NotificationsPage />
|
||||
<PeopleListProvider initList="global">
|
||||
<NotificationsPage />
|
||||
</PeopleListProvider>
|
||||
</RequireCurrentAccount>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
import { Flex, FlexProps, Text } from "@chakra-ui/react";
|
||||
|
||||
import { parseZapEvent } from "../../../helpers/zaps";
|
||||
import { parseZapEvent } from "../../../helpers/nostr/zaps";
|
||||
import { UserLink } from "../../../components/user-link";
|
||||
import { LightningIcon } from "../../../components/icons";
|
||||
import { readablizeSats } from "../../../helpers/bolt11";
|
||||
|
@ -7,7 +7,7 @@ import { UserLink } from "../../../../components/user-link";
|
||||
import { NostrEvent } from "../../../../types/nostr-event";
|
||||
import { useRegisterIntersectionEntity } from "../../../../providers/intersection-observer";
|
||||
import { LightningIcon } from "../../../../components/icons";
|
||||
import { parseZapEvent } from "../../../../helpers/zaps";
|
||||
import { parseZapEvent } from "../../../../helpers/nostr/zaps";
|
||||
import { readablizeSats } from "../../../../helpers/bolt11";
|
||||
import { TrustProvider } from "../../../../providers/trust";
|
||||
import ChatMessageContent from "./chat-message-content";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Box, Flex, Select, Text } from "@chakra-ui/react";
|
||||
import dayjs from "dayjs";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { ReactNode, useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
|
||||
import { ErrorBoundary, ErrorFallback } from "../../components/error-boundary";
|
||||
@ -9,9 +9,9 @@ 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 { isProfileZap, isNoteZap, parseZapEvent, totalZaps } from "../../helpers/zaps";
|
||||
import { isProfileZap, isNoteZap, parseZapEvent, totalZaps } from "../../helpers/nostr/zaps";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { NostrEvent, isATag, isETag, isPTag } from "../../types/nostr-event";
|
||||
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
@ -21,55 +21,59 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
|
||||
import { EmbedableContent, embedUrls } from "../../helpers/embeds";
|
||||
import { embedNostrLinks, renderGenericUrl } from "../../components/embed-types";
|
||||
import Timestamp from "../../components/timestamp";
|
||||
import { EmbedEventNostrLink, EmbedEventPointer } from "../../components/embed-event";
|
||||
import { parseCoordinate } from "../../helpers/nostr/events";
|
||||
|
||||
const Zap = ({ zapEvent }: { zapEvent: NostrEvent }) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useRegisterIntersectionEntity(ref, zapEvent.id);
|
||||
|
||||
try {
|
||||
const { request, payment, eventId } = parseZapEvent(zapEvent);
|
||||
const { request, payment } = parseZapEvent(zapEvent);
|
||||
|
||||
let embedContent: EmbedableContent = [request.content];
|
||||
embedContent = embedNostrLinks(embedContent);
|
||||
embedContent = embedUrls(embedContent, [renderGenericUrl]);
|
||||
const eventId = request.tags.find(isETag)?.[1];
|
||||
const coordinate = request.tags.find(isATag)?.[1];
|
||||
const parsedCoordinate = coordinate ? parseCoordinate(coordinate) : null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderWidth="1px"
|
||||
borderRadius="lg"
|
||||
overflow="hidden"
|
||||
padding="2"
|
||||
display="flex"
|
||||
gap="2"
|
||||
flexDirection="column"
|
||||
flexShrink={0}
|
||||
ref={ref}
|
||||
>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<UserAvatarLink pubkey={request.pubkey} size="xs" />
|
||||
<UserLink pubkey={request.pubkey} />
|
||||
<Text>Zapped</Text>
|
||||
{eventId && <NoteLink noteId={eventId} />}
|
||||
{payment.amount && (
|
||||
<Flex gap="2">
|
||||
<LightningIcon color="yellow.400" />
|
||||
<Text>{readablizeSats(payment.amount / 1000)} sats</Text>
|
||||
</Flex>
|
||||
)}
|
||||
<Timestamp ml="auto" timestamp={request.created_at} />
|
||||
</Flex>
|
||||
{embedContent && <Box>{embedContent}</Box>}
|
||||
</Box>
|
||||
let eventJSX: ReactNode | null = null;
|
||||
if (parsedCoordinate && parsedCoordinate.identifier) {
|
||||
eventJSX = (
|
||||
<EmbedEventPointer
|
||||
pointer={{
|
||||
type: "naddr",
|
||||
data: {
|
||||
pubkey: parsedCoordinate.pubkey,
|
||||
identifier: parsedCoordinate.identifier,
|
||||
kind: parsedCoordinate.kind,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
console.log(e);
|
||||
|
||||
return <ErrorFallback error={e} resetErrorBoundary={() => {}} />;
|
||||
}
|
||||
return null;
|
||||
} else if (eventId) {
|
||||
eventJSX = <EmbedEventPointer pointer={{ type: "note", data: eventId }} />;
|
||||
}
|
||||
|
||||
let embedContent: EmbedableContent = [request.content];
|
||||
embedContent = embedNostrLinks(embedContent);
|
||||
embedContent = embedUrls(embedContent, [renderGenericUrl]);
|
||||
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap" mb="2">
|
||||
<UserAvatarLink pubkey={request.pubkey} size="sm" />
|
||||
<UserLink pubkey={request.pubkey} fontWeight="bold" />
|
||||
<Text>Zapped</Text>
|
||||
{payment.amount && (
|
||||
<Flex gap="2">
|
||||
<LightningIcon color="yellow.400" />
|
||||
<Text>{readablizeSats(payment.amount / 1000)} sats</Text>
|
||||
</Flex>
|
||||
)}
|
||||
<Timestamp ml="auto" timestamp={request.created_at} />
|
||||
</Flex>
|
||||
{embedContent && <Box>{embedContent}</Box>}
|
||||
{eventJSX}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const UserZapsTab = () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user