add notifications screen

This commit is contained in:
Ren Amamiya
2023-08-22 16:34:47 +07:00
parent 4830f0b236
commit 0912948b31
18 changed files with 375 additions and 301 deletions

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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>
);
}

View File

@@ -1,5 +0,0 @@
export * from './user';
export * from './modal';
export * from './types/reaction';
export * from './types/repost';
export * from './types/mention';

View File

@@ -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&apos;ve no new notifications
</p>
</div>
) : (
data.map((event) => renderItem(event))
)}
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@@ -1,30 +0,0 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import { MentionNote, NoteContent } from '@shared/notes';
import { NotiUser } from '@shared/notification';
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]);
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">reply your postr</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>
</div>
);
}

View File

@@ -1,26 +0,0 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { MentionNote } from '@shared/notes';
import { NotiUser } from '@shared/notification';
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}</p>
</div>
<div>
<span className="leading-none text-white/50">{createdAt}</span>
</div>
</div>
<div className="-mt-5 pl-[44px]">{root && <MentionNote id={root} />}</div>
</div>
);
}

View File

@@ -1,26 +0,0 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { MentionNote } from '@shared/notes';
import { NotiUser } from '@shared/notification';
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</p>
</div>
<div>
<span className="leading-none text-white/50">{createdAt}</span>
</div>
</div>
<div className="-mt-5 pl-[44px]">{root && <MentionNote id={root} />}</div>
</div>
);
}

View File

@@ -1,32 +0,0 @@
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>
);
}