add notifications screen
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
28
src/app/notification/components/mention.tsx
Normal file
28
src/app/notification/components/mention.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
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 createdAt = formatCreatedAt(event.created_at);
|
||||
const content = useMemo(() => parser(event), [event]);
|
||||
|
||||
return (
|
||||
<div className="flex h-min flex-col px-3 py-3">
|
||||
<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">mention you · {createdAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10 -mt-6 flex gap-3">
|
||||
<div className="h-10 w-10 shrink-0" />
|
||||
<NotiContent content={content} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
src/app/notification/components/reaction.tsx
Normal file
29
src/app/notification/components/reaction.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
|
||||
import { NotiUser } from '@app/notification/components/user';
|
||||
|
||||
import { MentionNote } from '@shared/notes';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
|
||||
export function NotiReaction({ event }: { event: NDKEvent }) {
|
||||
const root = event.tags.find((e) => e[0] === 'e')?.[1];
|
||||
const createdAt = formatCreatedAt(event.created_at);
|
||||
|
||||
return (
|
||||
<div className="flex h-min flex-col px-3 py-3">
|
||||
<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} · {createdAt}
|
||||
</p>
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
27
src/app/notification/components/repost.tsx
Normal file
27
src/app/notification/components/repost.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
|
||||
import { NotiUser } from '@app/notification/components/user';
|
||||
|
||||
import { MentionNote } from '@shared/notes';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
|
||||
export function NotiRepost({ event }: { event: NDKEvent }) {
|
||||
const root = event.tags.find((e) => e[0] === 'e')?.[1];
|
||||
const createdAt = formatCreatedAt(event.created_at);
|
||||
|
||||
return (
|
||||
<div className="flex h-min flex-col px-3 py-3">
|
||||
<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 · {createdAt}</p>
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
32
src/app/notification/components/user.tsx
Normal file
32
src/app/notification/components/user.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { displayNpub } from '@utils/shortenKey';
|
||||
|
||||
export function NotiUser({ pubkey }: { pubkey: string }) {
|
||||
const { status, user } = useProfile(pubkey);
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="relative h-8 w-8 shrink-0 animate-pulse rounded-md bg-zinc-800" />
|
||||
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
|
||||
<span className="h-4 w-1/2 animate-pulse rounded bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex shrink-0 items-start justify-start gap-3">
|
||||
<Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 shrink-0 rounded-md object-cover"
|
||||
/>
|
||||
<span className="max-w-[10rem] flex-1 truncate font-medium leading-none text-white">
|
||||
{user?.nip05 || user?.name || user?.display_name || displayNpub(pubkey, 16)}
|
||||
</span>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user