mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-14 06:39:19 +02:00
add tabs to notifications view
This commit is contained in:
parent
b512e62fa4
commit
4b5445a7c7
5
.changeset/strong-mayflies-drop.md
Normal file
5
.changeset/strong-mayflies-drop.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add tabs to notification view
|
@ -48,7 +48,7 @@ export function parseStreamEvent(stream: NostrEvent): ParsedStream {
|
|||||||
relays.shift();
|
relays.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
const startTime = starts ? parseInt(starts) : stream.created_at;
|
const startTime = starts ? parseInt(starts) : undefined;
|
||||||
const endTime = endsTag ? parseInt(endsTag) : undefined;
|
const endTime = endsTag ? parseInt(endsTag) : undefined;
|
||||||
|
|
||||||
if (!identifier) throw new Error("missing identifier");
|
if (!identifier) throw new Error("missing identifier");
|
||||||
|
@ -1,219 +1,145 @@
|
|||||||
import { ReactNode, forwardRef, memo, useCallback, useMemo, useRef } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { Box, Card, Flex, Switch, Text, useDisclosure } from "@chakra-ui/react";
|
import { Tab, TabList, TabPanel, TabPanelProps, TabPanels, Tabs, useDisclosure } from "@chakra-ui/react";
|
||||||
import { Kind, nip18, nip25 } from "nostr-tools";
|
import { Kind } from "nostr-tools";
|
||||||
|
|
||||||
import { UserAvatar } from "../../components/user-avatar";
|
import { NostrEvent } from "../../types/nostr-event";
|
||||||
import { UserLink } from "../../components/user-link";
|
|
||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
|
||||||
import { NostrEvent, isATag, isETag } from "../../types/nostr-event";
|
|
||||||
import { NoteLink } from "../../components/note-link";
|
|
||||||
import RequireCurrentAccount from "../../providers/require-current-account";
|
import RequireCurrentAccount from "../../providers/require-current-account";
|
||||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||||
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
import { useNotificationTimeline } from "../../providers/notification-timeline";
|
import { useNotificationTimeline } from "../../providers/notification-timeline";
|
||||||
import { parseZapEvent } from "../../helpers/nostr/zaps";
|
import { getReferences } from "../../helpers/nostr/events";
|
||||||
import { readablizeSats } from "../../helpers/bolt11";
|
|
||||||
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 PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
|
import NotificationItem from "./notification-item";
|
||||||
|
|
||||||
const Kind1Notification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
function RepliesNotificationsTab({ events }: { events: NostrEvent[] }) {
|
||||||
const refs = getReferences(event);
|
const timeline = useNotificationTimeline();
|
||||||
|
const filtered = events.filter((event) => {
|
||||||
if (refs.replyId) {
|
if (event.kind === Kind.Text) {
|
||||||
return (
|
const refs = getReferences(event);
|
||||||
<Card variant="outline" p="2" borderColor="blue.400" ref={ref}>
|
return !!refs.replyId;
|
||||||
<Flex gap="2" alignItems="center" mb="2">
|
}
|
||||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
return false;
|
||||||
<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>
|
|
||||||
<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>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 (
|
return (
|
||||||
<Card variant="outline" p="2" ref={ref}>
|
<>
|
||||||
<Flex gap="2" alignItems="center" mb="2">
|
{filtered.map((event) => (
|
||||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
<NotificationItem key={event.id} event={event} />
|
||||||
<UserLink pubkey={event.pubkey} />
|
))}
|
||||||
<Text>shared note</Text>
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
<NoteLink noteId={event.id} color="current" ml="auto">
|
</>
|
||||||
<Timestamp timestamp={event.created_at} />
|
|
||||||
</NoteLink>
|
|
||||||
</Flex>
|
|
||||||
{pointer && <EmbedEventPointer pointer={{ type: "nevent", data: pointer }} />}
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
const ReactionNotification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
function MentionsNotificationsTab({ events }: { events: NostrEvent[] }) {
|
||||||
const account = useCurrentAccount();
|
const timeline = useNotificationTimeline();
|
||||||
const pointer = nip25.getReactedEventPointer(event);
|
const filtered = events.filter((event) => {
|
||||||
if (!pointer || (account?.pubkey && pointer.author !== account.pubkey)) return null;
|
if (event.kind === Kind.Text) {
|
||||||
|
const refs = getReferences(event);
|
||||||
|
return !refs.replyId;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={ref}>
|
<>
|
||||||
<Flex gap="2" alignItems="center" mb="1">
|
{filtered.map((event) => (
|
||||||
<UserAvatar pubkey={event.pubkey} size="xs" />
|
<NotificationItem key={event.id} event={event} />
|
||||||
<UserLink pubkey={event.pubkey} />
|
))}
|
||||||
<Text>reacted {event.content} to your post</Text>
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
<Timestamp timestamp={event.created_at} ml="auto" />
|
</>
|
||||||
</Flex>
|
|
||||||
<EmbedEventPointer pointer={{ type: "nevent", data: pointer }} />
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
const ZapNotification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
function ReactionsNotificationsTab({ events }: { events: NostrEvent[] }) {
|
||||||
const zap = useMemo(() => {
|
const timeline = useNotificationTimeline();
|
||||||
try {
|
const filtered = events.filter((e) => e.kind === Kind.Reaction);
|
||||||
return parseZapEvent(event);
|
|
||||||
} catch (e) {}
|
|
||||||
}, [event]);
|
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<Card variant="outline" borderColor="yellow.400" p="2" ref={ref}>
|
<>
|
||||||
<Flex direction="row" gap="2" alignItems="center" mb="2">
|
{filtered.map((event) => (
|
||||||
<UserAvatar pubkey={zap.request.pubkey} size="xs" />
|
<NotificationItem key={event.id} event={event} />
|
||||||
<UserLink pubkey={zap.request.pubkey} />
|
))}
|
||||||
<Text>zapped {readablizeSats(zap.payment.amount / 1000)} sats</Text>
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
<Timestamp color="current" ml="auto" timestamp={zap.request.created_at} />
|
</>
|
||||||
</Flex>
|
|
||||||
{eventJSX}
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
const NotificationItem = memo(({ event }: { event: NostrEvent }) => {
|
function SharesNotificationsTab({ events }: { events: NostrEvent[] }) {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const timeline = useNotificationTimeline();
|
||||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
const filtered = events.filter((e) => e.kind === Kind.Repost);
|
||||||
|
|
||||||
switch (event.kind) {
|
return (
|
||||||
case Kind.Text:
|
<>
|
||||||
return <Kind1Notification event={event} ref={ref} />;
|
{filtered.map((event) => (
|
||||||
case Kind.Reaction:
|
<NotificationItem key={event.id} event={event} />
|
||||||
return <ReactionNotification event={event} ref={ref} />;
|
))}
|
||||||
case Kind.Repost:
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
return <ShareNotification event={event} ref={ref} />;
|
</>
|
||||||
case Kind.Zap:
|
);
|
||||||
return <ZapNotification event={event} ref={ref} />;
|
}
|
||||||
default:
|
|
||||||
return <EmbeddedUnknown event={event} />;
|
function ZapNotificationsTab({ events }: { events: NostrEvent[] }) {
|
||||||
}
|
const timeline = useNotificationTimeline();
|
||||||
});
|
const filtered = events.filter((e) => e.kind === Kind.Zap);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{filtered.map((event) => (
|
||||||
|
<NotificationItem key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function NotificationsPage() {
|
function NotificationsPage() {
|
||||||
const hideReplies = useDisclosure();
|
|
||||||
const hideMentions = useDisclosure();
|
|
||||||
const hideZaps = useDisclosure();
|
|
||||||
const hideReactions = useDisclosure();
|
|
||||||
const hideShares = useDisclosure();
|
|
||||||
|
|
||||||
const { people } = usePeopleListContext();
|
const { people } = usePeopleListContext();
|
||||||
const peoplePubkeys = useMemo(() => people?.map((p) => p.pubkey), [people]);
|
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 timeline = useNotificationTimeline();
|
const timeline = useNotificationTimeline();
|
||||||
const events = useSubject(timeline?.timeline).filter(eventFilter) ?? [];
|
|
||||||
|
|
||||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||||
|
const events = useSubject(timeline?.timeline).filter((e) => {
|
||||||
|
if (peoplePubkeys && e.kind !== Kind.Zap && !peoplePubkeys.includes(e.pubkey)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabPanelProps: TabPanelProps = { px: "0", pt: "2", display: "flex", flexDirection: "column", gap: "2" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IntersectionObserverProvider callback={callback}>
|
<IntersectionObserverProvider callback={callback}>
|
||||||
<VerticalPageLayout>
|
<VerticalPageLayout>
|
||||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
<Tabs isLazy colorScheme="brand">
|
||||||
<PeopleListSelection />
|
<TabList overflowX="auto" overflowY="hidden">
|
||||||
<Switch isChecked={!hideReplies.isOpen} onChange={hideReplies.onToggle}>
|
<Tab>Replies</Tab>
|
||||||
Replies
|
<Tab>Mentions</Tab>
|
||||||
</Switch>
|
<Tab>Reactions</Tab>
|
||||||
<Switch isChecked={!hideMentions.isOpen} onChange={hideMentions.onToggle}>
|
<Tab>Shares</Tab>
|
||||||
Mentions
|
<Tab>Zaps</Tab>
|
||||||
</Switch>
|
<PeopleListSelection ml="auto" flexShrink={0} />
|
||||||
<Switch isChecked={!hideReactions.isOpen} onChange={hideReactions.onToggle}>
|
</TabList>
|
||||||
Reactions
|
<TabPanels>
|
||||||
</Switch>
|
<TabPanel {...tabPanelProps}>
|
||||||
<Switch isChecked={!hideShares.isOpen} onChange={hideShares.onToggle}>
|
<RepliesNotificationsTab events={events} />
|
||||||
Shares
|
</TabPanel>
|
||||||
</Switch>
|
<TabPanel {...tabPanelProps}>
|
||||||
<Switch isChecked={!hideZaps.isOpen} onChange={hideZaps.onToggle}>
|
<MentionsNotificationsTab events={events} />
|
||||||
Zaps
|
</TabPanel>
|
||||||
</Switch>
|
<TabPanel {...tabPanelProps}>
|
||||||
</Flex>
|
<ReactionsNotificationsTab events={events} />
|
||||||
{events.map((event) => (
|
</TabPanel>
|
||||||
<NotificationItem key={event.id} event={event} />
|
<TabPanel {...tabPanelProps}>
|
||||||
))}
|
<SharesNotificationsTab events={events} />
|
||||||
|
</TabPanel>
|
||||||
<TimelineActionAndStatus timeline={timeline} />
|
<TabPanel {...tabPanelProps}>
|
||||||
|
<ZapNotificationsTab events={events} />
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
</VerticalPageLayout>
|
</VerticalPageLayout>
|
||||||
</IntersectionObserverProvider>
|
</IntersectionObserverProvider>
|
||||||
);
|
);
|
||||||
|
150
src/views/notifications/notification-item.tsx
Normal file
150
src/views/notifications/notification-item.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { ReactNode, forwardRef, memo, useMemo, useRef } from "react";
|
||||||
|
import { Box, Card, Flex, Text } 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, isATag, isETag } from "../../types/nostr-event";
|
||||||
|
import { NoteLink } from "../../components/note-link";
|
||||||
|
import { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
||||||
|
import { parseZapEvent } from "../../helpers/nostr/zaps";
|
||||||
|
import { readablizeSats } from "../../helpers/bolt11";
|
||||||
|
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";
|
||||||
|
|
||||||
|
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" wrap="wrap">
|
||||||
|
<UserAvatar pubkey={event.pubkey} size="xs" />
|
||||||
|
<UserLink pubkey={event.pubkey} />
|
||||||
|
{refs.replyId ? <Text>replied to:</Text> : <Text>mentioned you</Text>}
|
||||||
|
<NoteLink noteId={event.id} color="current" ml="auto">
|
||||||
|
<Timestamp timestamp={event.created_at} />
|
||||||
|
</NoteLink>
|
||||||
|
</Flex>
|
||||||
|
<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>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 (
|
||||||
|
<Box 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>
|
||||||
|
{pointer && <EmbedEventPointer pointer={{ type: "nevent", data: pointer }} />}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<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 = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
||||||
|
const zap = useMemo(() => {
|
||||||
|
try {
|
||||||
|
return parseZapEvent(event);
|
||||||
|
} catch (e) {}
|
||||||
|
}, [event]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<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 = ({ event }: { event: NostrEvent }) => {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, getEventUID(event));
|
||||||
|
|
||||||
|
switch (event.kind) {
|
||||||
|
case Kind.Text:
|
||||||
|
return <Kind1Notification event={event} ref={ref} />;
|
||||||
|
case Kind.Reaction:
|
||||||
|
return <ReactionNotification event={event} ref={ref} />;
|
||||||
|
case Kind.Repost:
|
||||||
|
return <ShareNotification event={event} ref={ref} />;
|
||||||
|
case Kind.Zap:
|
||||||
|
return <ZapNotification event={event} ref={ref} />;
|
||||||
|
default:
|
||||||
|
return <EmbeddedUnknown event={event} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(NotificationItem);
|
20
src/views/notifications/zaps.tsx
Normal file
20
src/views/notifications/zaps.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Kind } from "nostr-tools";
|
||||||
|
|
||||||
|
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||||
|
import useSubject from "../../hooks/use-subject";
|
||||||
|
import { useNotificationTimeline } from "../../providers/notification-timeline";
|
||||||
|
import NotificationItem from "./notification-item";
|
||||||
|
|
||||||
|
export default function ZapNotificationsTab() {
|
||||||
|
const timeline = useNotificationTimeline();
|
||||||
|
const events = useSubject(timeline?.timeline).filter((e) => e.kind === Kind.Zap) ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{events.map((event) => (
|
||||||
|
<NotificationItem key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user