add notifications screen
This commit is contained in:
@@ -75,6 +75,13 @@ const router = createBrowserRouter([
|
||||
return { Component: ChatScreen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'notifications',
|
||||
async lazy() {
|
||||
const { NotificationScreen } = await import('@app/notification');
|
||||
return { Component: NotificationScreen };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,16 +18,19 @@ export function ImportStep3Screen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const { db } = useStorage();
|
||||
const { fetchUserData } = useNostr();
|
||||
const { fetchUserData, prefetchEvents } = useNostr();
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
// show loading indicator
|
||||
setLoading(true);
|
||||
|
||||
const data = await fetchUserData();
|
||||
// prefetch data
|
||||
const user = await fetchUserData();
|
||||
const data = await prefetchEvents();
|
||||
|
||||
if (data.status === 'ok') {
|
||||
// redirect to next step
|
||||
if (user.status === 'ok' && data.status === 'ok') {
|
||||
navigate('/auth/onboarding/step-2', { replace: true });
|
||||
} else {
|
||||
console.log('error: ', data.message);
|
||||
|
||||
27
src/app/notification/components/content.tsx
Normal file
27
src/app/notification/components/content.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
import { Hashtag, MentionUser } from '@shared/notes';
|
||||
|
||||
import { RichContent } from '@utils/types';
|
||||
|
||||
export function NotiContent({ content }: { content: RichContent }) {
|
||||
return (
|
||||
<>
|
||||
<ReactMarkdown
|
||||
className="markdown"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
del: ({ children }) => {
|
||||
const key = children[0] as string;
|
||||
if (key.startsWith('pub') && key.length > 50 && key.length < 100)
|
||||
return <MentionUser pubkey={key.replace('pub-', '')} />;
|
||||
if (key.startsWith('tag')) return <Hashtag tag={key.replace('tag-', '')} />;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{content?.parsed}
|
||||
</ReactMarkdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { MentionNote, NoteContent } from '@shared/notes';
|
||||
import { NotiUser } from '@shared/notification';
|
||||
import { NotiContent } from '@app/notification/components/content';
|
||||
import { NotiUser } from '@app/notification/components/user';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
import { parser } from '@utils/parser';
|
||||
|
||||
export function NotiMention({ event }: { event: NDKEvent }) {
|
||||
const replyTo = event.tags.find((e) => e[0] === 'e')?.[1];
|
||||
const createdAt = formatCreatedAt(event.created_at);
|
||||
const content = useMemo(() => parser(event), [event]);
|
||||
|
||||
@@ -17,13 +16,12 @@ export function NotiMention({ event }: { event: NDKEvent }) {
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-1">
|
||||
<NotiUser pubkey={event.pubkey} />
|
||||
<p className="leading-none text-white/50">reply your postr</p>
|
||||
<p className="leading-none text-white/50">mention you · {createdAt}</p>
|
||||
</div>
|
||||
<span className="leading-none text-white/50">{createdAt}</span>
|
||||
</div>
|
||||
<div className="-mt-3 pl-[44px]">
|
||||
<NoteContent content={content} />
|
||||
{replyTo && <MentionNote id={replyTo} />}
|
||||
<div className="relative z-10 -mt-6 flex gap-3">
|
||||
<div className="h-10 w-10 shrink-0" />
|
||||
<NotiContent content={content} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1,7 +1,8 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
|
||||
import { NotiUser } from '@app/notification/components/user';
|
||||
|
||||
import { MentionNote } from '@shared/notes';
|
||||
import { NotiUser } from '@shared/notification';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
|
||||
@@ -14,13 +15,15 @@ export function NotiReaction({ event }: { event: NDKEvent }) {
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-1">
|
||||
<NotiUser pubkey={event.pubkey} />
|
||||
<p className="leading-none text-white/50">reacted {event.content}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="leading-none text-white/50">{createdAt}</span>
|
||||
<p className="leading-none text-white/50">
|
||||
reacted {event.content} · {createdAt}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-5 pl-[44px]">{root && <MentionNote id={root} />}</div>
|
||||
<div className="relative z-10 -mt-6 flex gap-3">
|
||||
<div className="h-10 w-10 shrink-0" />
|
||||
<div className="flex-1">{root && <MentionNote id={root} />}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
|
||||
import { NotiUser } from '@app/notification/components/user';
|
||||
|
||||
import { MentionNote } from '@shared/notes';
|
||||
import { NotiUser } from '@shared/notification';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
|
||||
@@ -14,13 +15,13 @@ export function NotiRepost({ event }: { event: NDKEvent }) {
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-1">
|
||||
<NotiUser pubkey={event.pubkey} />
|
||||
<p className="leading-none text-white/50">repostr your postr</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="leading-none text-white/50">{createdAt}</span>
|
||||
<p className="leading-none text-white/50">repostr your postr · {createdAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-5 pl-[44px]">{root && <MentionNote id={root} />}</div>
|
||||
<div className="relative z-10 -mt-6 flex gap-3">
|
||||
<div className="h-10 w-10 shrink-0" />
|
||||
<div className="flex-1">{root && <MentionNote id={root} />}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
79
src/app/notification/index.tsx
Normal file
79
src/app/notification/index.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { NotiMention } from '@app/notification/components/mention';
|
||||
import { NotiReaction } from '@app/notification/components/reaction';
|
||||
import { NotiRepost } from '@app/notification/components/repost';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
|
||||
export function NotificationScreen() {
|
||||
const { db } = useStorage();
|
||||
const { sub, fetchActivities } = useNostr();
|
||||
const { status, data } = useQuery(
|
||||
['notification', db.account.pubkey],
|
||||
async () => {
|
||||
return await fetchActivities();
|
||||
},
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
return <NotiMention key={event.id} event={event} />;
|
||||
case 6:
|
||||
return <NotiRepost key={event.id} event={event} />;
|
||||
case 7:
|
||||
return <NotiReaction key={event.id} event={event} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const filter: NDKFilter = {
|
||||
'#p': [db.account.pubkey],
|
||||
kinds: [1, 3, 6, 7, 9735],
|
||||
since: db.account.last_login_at ?? Math.floor(Date.now() / 1000),
|
||||
};
|
||||
|
||||
sub(filter, async (event) => {
|
||||
console.log('[notify] new noti', event.id);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-y-auto bg-white/10 px-3">
|
||||
<div className="mb-3 px-3 pt-11">
|
||||
<h3 className="text-xl font-bold">Notifications</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="col-span-2 flex flex-col">
|
||||
{status === 'loading' ? (
|
||||
<div className="inline-flex items-center justify-center px-4 py-3">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-white" />
|
||||
</div>
|
||||
) : data?.length < 1 ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<p className="mb-1 text-4xl">🎉</p>
|
||||
<p className="font-medium text-white/50">
|
||||
Yo!, you've no new notifications
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event) => renderItem(event))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,42 +17,49 @@ export const NDKInstance = () => {
|
||||
|
||||
// TODO: fully support NIP-11
|
||||
async function verifyRelays(relays: string[]) {
|
||||
const verifiedRelays: string[] = [];
|
||||
try {
|
||||
const urls: string[] = relays.map((relay) => {
|
||||
if (relay.startsWith('ws')) {
|
||||
return relay.replace('ws', 'http');
|
||||
}
|
||||
if (relay.startsWith('wss')) {
|
||||
return relay.replace('wss', 'https');
|
||||
}
|
||||
});
|
||||
|
||||
for (const relay of relays) {
|
||||
let url: string;
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort('timeout'), 5000);
|
||||
|
||||
if (relay.startsWith('ws')) {
|
||||
url = relay.replace('ws', 'http');
|
||||
}
|
||||
|
||||
if (relay.startsWith('wss')) {
|
||||
url = relay.replace('wss', 'https');
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort('timeout'), 5000);
|
||||
const res = await fetch(url, {
|
||||
const requests = urls.map((url) =>
|
||||
fetch(url, {
|
||||
headers: { Accept: 'application/nostr+json' },
|
||||
signal: controller.signal,
|
||||
});
|
||||
})
|
||||
);
|
||||
const responses = await Promise.all(requests);
|
||||
const errors = responses.filter((response) => !response.ok);
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
console.log('relay information: ', data);
|
||||
|
||||
verifiedRelays.push(relay);
|
||||
clearTimeout(timeoutId);
|
||||
} else {
|
||||
console.log('relay not working: ', res);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('fetch error', e);
|
||||
if (errors.length > 0) {
|
||||
throw errors.map((response) => Error(response.statusText));
|
||||
}
|
||||
}
|
||||
|
||||
return verifiedRelays;
|
||||
const verifiedRelays: string[] = responses.map((res) => {
|
||||
if (res.url.startsWith('http')) {
|
||||
return res.url.replace('htto', 'ws');
|
||||
}
|
||||
if (res.url.startsWith('https')) {
|
||||
return res.url.replace('https', 'wss');
|
||||
}
|
||||
});
|
||||
|
||||
// clear timeout
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// return all validate relays
|
||||
return verifiedRelays;
|
||||
} catch (e) {
|
||||
e.forEach((error) => console.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
async function initNDK() {
|
||||
|
||||
@@ -21,7 +21,7 @@ export function ComposerModal() {
|
||||
<Dialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 w-min items-center justify-center gap-1 rounded-md bg-white/10 px-8 text-sm font-medium text-white hover:bg-fuchsia-500 focus:outline-none active:translate-y-1 disabled:pointer-events-none disabled:opacity-50"
|
||||
className="inline-flex h-9 w-min items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-8 text-sm font-medium text-white hover:bg-fuchsia-600 focus:outline-none active:translate-y-1 disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<ComposeIcon className="h-4 w-4" />
|
||||
Postr
|
||||
|
||||
@@ -10,16 +10,18 @@ export function LumeBar() {
|
||||
const { db } = useStorage();
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-white/10 p-2 backdrop-blur-xl">
|
||||
<div className="flex items-center justify-between">
|
||||
<ActiveAccount data={db.account} />
|
||||
<Link
|
||||
to="/settings/general"
|
||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1"
|
||||
>
|
||||
<SettingsIcon className="h-4 w-4 text-white" />
|
||||
</Link>
|
||||
<Logout />
|
||||
<div className="absolute bottom-3 left-1/2 w-max -translate-x-1/2 transform">
|
||||
<div className="rounded-xl bg-white/10 p-2 backdrop-blur-xl">
|
||||
<div className="flex items-center gap-2">
|
||||
<ActiveAccount data={db.account} />
|
||||
<Link
|
||||
to="/settings/general"
|
||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1"
|
||||
>
|
||||
<SettingsIcon className="h-4 w-4 text-white" />
|
||||
</Link>
|
||||
<Logout />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ComposerModal } from '@shared/composer/modal';
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
BellIcon,
|
||||
NavArrowDownIcon,
|
||||
SpaceIcon,
|
||||
} from '@shared/icons';
|
||||
@@ -79,16 +80,29 @@ export function Navigation() {
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded bg-white/10">
|
||||
<SpaceIcon className="h-3 w-3 text-white" />
|
||||
</span>
|
||||
<span className="font-medium">Space</span>
|
||||
<span className="text-sm font-medium">Space</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/notifications"
|
||||
preventScrollReset={true}
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'flex h-9 items-center gap-2.5 rounded-md px-2',
|
||||
isActive ? 'bg-white/10 text-white' : 'text-white/80'
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded bg-white/10">
|
||||
<BellIcon className="h-3 w-3 text-white" />
|
||||
</span>
|
||||
<span className="text-sm font-medium">Notifications</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</div>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
<div className="absolute bottom-3 left-0 w-full px-10">
|
||||
<LumeBar />
|
||||
</div>
|
||||
<LumeBar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './user';
|
||||
export * from './modal';
|
||||
export * from './types/reaction';
|
||||
export * from './types/repost';
|
||||
export * from './types/mention';
|
||||
@@ -1,100 +0,0 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
|
||||
import { BellIcon, CancelIcon, LoaderIcon } from '@shared/icons';
|
||||
import { NotiMention, NotiReaction, NotiRepost } from '@shared/notification';
|
||||
|
||||
import { nHoursAgo } from '@utils/date';
|
||||
|
||||
export function NotificationModal({ pubkey }: { pubkey: string }) {
|
||||
const { ndk } = useNDK();
|
||||
const { status, data } = useQuery(
|
||||
['notification', pubkey],
|
||||
async () => {
|
||||
const events = await ndk.fetchEvents({
|
||||
'#p': [pubkey],
|
||||
kinds: [1, 6, 7, 9735],
|
||||
since: nHoursAgo(24),
|
||||
});
|
||||
const filterSelf = [...events].filter((el) => el.pubkey !== pubkey);
|
||||
const sorted = filterSelf.sort((a, b) => a.created_at - b.created_at);
|
||||
return sorted as unknown as NDKEvent[];
|
||||
},
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
}
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
return <NotiMention key={event.id} event={event} />;
|
||||
case 6:
|
||||
return <NotiRepost key={event.id} event={event} />;
|
||||
case 7:
|
||||
return <NotiReaction key={event.id} event={event} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1"
|
||||
>
|
||||
<BellIcon className="h-4 w-4 text-white" />
|
||||
</button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal className="relative z-10">
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xl" />
|
||||
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<div className="relative h-min w-full max-w-xl rounded-xl bg-white/10">
|
||||
<div className="h-min w-full shrink-0 rounded-t-xl border-b border-white/10 bg-white/5 px-5 py-5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title className="text-lg font-semibold leading-none text-white">
|
||||
Notification
|
||||
</Dialog.Title>
|
||||
<Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-white/10">
|
||||
<CancelIcon className="h-4 w-4 text-white/50" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-white/50">
|
||||
All things happen when you rest in 24 hours ago
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="scrollbar-hide flex h-[500px] flex-col divide-y divide-white/10 overflow-y-auto overflow-x-hidden">
|
||||
{status === 'loading' ? (
|
||||
<div className="inline-flex items-center justify-center px-4 py-3">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-white" />
|
||||
</div>
|
||||
) : data?.length < 1 ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<p className="mb-1 text-4xl">🎉</p>
|
||||
<p className="font-medium text-white/50">
|
||||
Yo!, you've no new notifications
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event) => renderItem(event))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
@@ -91,8 +91,6 @@ export function useNostr() {
|
||||
|
||||
const prefetchEvents = async () => {
|
||||
try {
|
||||
if (!ndk) return { status: 'failed', data: [], message: 'NDK instance not found' };
|
||||
|
||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(ndk));
|
||||
const dbEventsEmpty = await db.isEventsEmpty();
|
||||
|
||||
@@ -100,13 +98,13 @@ export function useNostr() {
|
||||
if (dbEventsEmpty || db.account.last_login_at === 0) {
|
||||
since = nHoursAgo(24);
|
||||
} else {
|
||||
since = db.account.last_login_at ?? nHoursAgo(24);
|
||||
since = db.account.last_login_at;
|
||||
}
|
||||
|
||||
console.log("prefetching events with user's network: ", db.account.network.length);
|
||||
console.log('prefetching events since: ', since);
|
||||
|
||||
const events = fetcher.allEventsIterator(
|
||||
const events = await fetcher.fetchAllEvents(
|
||||
relayUrls,
|
||||
{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article],
|
||||
@@ -116,7 +114,7 @@ export function useNostr() {
|
||||
);
|
||||
|
||||
// save all events to database
|
||||
for await (const event of events) {
|
||||
for (const event of events) {
|
||||
let root: string;
|
||||
let reply: string;
|
||||
if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) {
|
||||
@@ -125,7 +123,7 @@ export function useNostr() {
|
||||
root = event.tags.find((el) => el[3] === 'root')?.[1];
|
||||
reply = event.tags.find((el) => el[3] === 'reply')?.[1];
|
||||
}
|
||||
db.createEvent(
|
||||
await db.createEvent(
|
||||
event.id,
|
||||
JSON.stringify(event),
|
||||
event.pubkey,
|
||||
@@ -143,6 +141,31 @@ export function useNostr() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchActivities = async () => {
|
||||
try {
|
||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(ndk));
|
||||
const events = await fetcher.fetchAllEvents(
|
||||
relayUrls,
|
||||
{
|
||||
kinds: [
|
||||
NDKKind.Text,
|
||||
NDKKind.Contacts,
|
||||
NDKKind.Repost,
|
||||
NDKKind.Reaction,
|
||||
NDKKind.Zap,
|
||||
],
|
||||
'#p': [db.account.pubkey],
|
||||
},
|
||||
{ since: nHoursAgo(24) },
|
||||
{ sort: true }
|
||||
);
|
||||
|
||||
return events as unknown as NDKEvent[];
|
||||
} catch (e) {
|
||||
console.error('Error fetching activities', e);
|
||||
}
|
||||
};
|
||||
|
||||
const publish = async ({
|
||||
content,
|
||||
kind,
|
||||
@@ -184,5 +207,5 @@ export function useNostr() {
|
||||
return res;
|
||||
};
|
||||
|
||||
return { sub, fetchUserData, prefetchEvents, publish, createZap };
|
||||
return { sub, fetchUserData, prefetchEvents, fetchActivities, publish, createZap };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user