mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 11:43:30 +00:00
update personal screen
This commit is contained in:
parent
8aa2ef39c5
commit
64b4745993
40
src/app/personal/components/contactCard.tsx
Normal file
40
src/app/personal/components/contactCard.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { compactNumber } from '@utils/number';
|
||||||
|
|
||||||
|
export function ContactCard() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { ndk } = useNDK();
|
||||||
|
const { status, data } = useQuery({
|
||||||
|
queryKey: ['contacts'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const user = ndk.getUser({ hexpubkey: db.account.pubkey });
|
||||||
|
return await user.follows();
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-1 h-44 rounded-2xl bg-neutral-100 transition-all duration-150 ease-smooth hover:scale-105 dark:bg-neutral-900">
|
||||||
|
{status === 'pending' ? (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full flex-col justify-between p-4">
|
||||||
|
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
||||||
|
{compactNumber.format(data.size)}
|
||||||
|
</h3>
|
||||||
|
<div className="mt-auto text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
|
Contacts
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
51
src/app/personal/components/postCard.tsx
Normal file
51
src/app/personal/components/postCard.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { compactNumber } from '@utils/number';
|
||||||
|
|
||||||
|
export function PostCard() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { status, data } = useQuery({
|
||||||
|
queryKey: ['user-stats', db.account.pubkey],
|
||||||
|
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://api.nostr.band/v0/stats/profile/${db.account.pubkey}`,
|
||||||
|
{
|
||||||
|
signal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Error');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await res.json();
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-1 h-44 rounded-2xl bg-neutral-100 transition-all duration-150 ease-smooth hover:scale-105 dark:bg-neutral-900">
|
||||||
|
{status === 'pending' ? (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full flex-col justify-between p-4">
|
||||||
|
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
||||||
|
{compactNumber.format(data.stats[db.account.pubkey].pub_note_count)}
|
||||||
|
</h3>
|
||||||
|
<div className="mt-auto text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
|
Posts
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
58
src/app/personal/components/profileCard.tsx
Normal file
58
src/app/personal/components/profileCard.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import * as Avatar from '@radix-ui/react-avatar';
|
||||||
|
import { minidenticon } from 'minidenticons';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { useProfile } from '@utils/hooks/useProfile';
|
||||||
|
import { displayNpub } from '@utils/shortenKey';
|
||||||
|
|
||||||
|
export function ProfileCard() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { status, user } = useProfile(db.account.pubkey);
|
||||||
|
|
||||||
|
const svgURI =
|
||||||
|
'data:image/svg+xml;utf8,' +
|
||||||
|
encodeURIComponent(minidenticon(db.account.pubkey, 90, 50));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-4 h-56 w-full rounded-2xl bg-neutral-100 transition-all duration-150 ease-smooth hover:scale-105 dark:bg-neutral-900">
|
||||||
|
{status === 'pending' ? (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full flex-col justify-end p-4">
|
||||||
|
<div className="flex flex-col gap-2.5">
|
||||||
|
<Avatar.Root className="shrink-0">
|
||||||
|
<Avatar.Image
|
||||||
|
src={user?.picture || user?.image}
|
||||||
|
alt={db.account.pubkey}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
style={{ contentVisibility: 'auto' }}
|
||||||
|
className="h-16 w-16 rounded-xl border border-neutral-200/50 shadow-[rgba(17,_17,_26,_0.1)_0px_0px_16px] dark:border-neutral-800/50"
|
||||||
|
/>
|
||||||
|
<Avatar.Fallback delayMs={300}>
|
||||||
|
<img
|
||||||
|
src={svgURI}
|
||||||
|
alt={db.account.pubkey}
|
||||||
|
className="h-16 w-16 rounded-xl bg-black dark:bg-white"
|
||||||
|
/>
|
||||||
|
</Avatar.Fallback>
|
||||||
|
</Avatar.Root>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-3xl font-semibold leading-8 text-neutral-900 dark:text-neutral-100">
|
||||||
|
{user?.display_name || user?.name}
|
||||||
|
</h3>
|
||||||
|
<p className="text-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
{user.nip05 || displayNpub(db.account.pubkey, 16)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
40
src/app/personal/components/relayCard.tsx
Normal file
40
src/app/personal/components/relayCard.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { compactNumber } from '@utils/number';
|
||||||
|
|
||||||
|
export function RelayCard() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { ndk } = useNDK();
|
||||||
|
const { status, data } = useQuery({
|
||||||
|
queryKey: ['relays'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const user = ndk.getUser({ hexpubkey: db.account.pubkey });
|
||||||
|
return await user.relayList();
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-1 h-44 rounded-2xl bg-neutral-100 transition-all duration-150 ease-smooth hover:scale-105 dark:bg-neutral-900">
|
||||||
|
{status === 'pending' ? (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full flex-col justify-between p-4">
|
||||||
|
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
||||||
|
{compactNumber.format(data.relays.length)}
|
||||||
|
</h3>
|
||||||
|
<div className="mt-auto text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
|
Relays
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
53
src/app/personal/components/zapCard.tsx
Normal file
53
src/app/personal/components/zapCard.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { compactNumber } from '@utils/number';
|
||||||
|
|
||||||
|
export function ZapCard() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { status, data } = useQuery({
|
||||||
|
queryKey: ['user-stats', db.account.pubkey],
|
||||||
|
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://api.nostr.band/v0/stats/profile/${db.account.pubkey}`,
|
||||||
|
{
|
||||||
|
signal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Error');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await res.json();
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-1 h-44 rounded-2xl bg-neutral-100 transition-all duration-150 ease-smooth hover:scale-105 dark:bg-neutral-900">
|
||||||
|
{status === 'pending' ? (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full flex-col justify-between p-4">
|
||||||
|
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
||||||
|
{compactNumber.format(
|
||||||
|
data.stats[db.account.pubkey].zaps_received.msats / 1000
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
<div className="mt-auto text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
|
Sats received
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,9 +1,19 @@
|
|||||||
import { useStorage } from '@libs/storage/provider';
|
import { ContactCard } from '@app/personal/components/contactCard';
|
||||||
|
import { PostCard } from '@app/personal/components/postCard';
|
||||||
import { UserProfile } from '@shared/userProfile';
|
import { ProfileCard } from '@app/personal/components/profileCard';
|
||||||
|
import { RelayCard } from '@app/personal/components/relayCard';
|
||||||
|
import { ZapCard } from '@app/personal/components/zapCard';
|
||||||
|
|
||||||
export function PersonalScreen() {
|
export function PersonalScreen() {
|
||||||
const { db } = useStorage();
|
return (
|
||||||
|
<div className="mx-auto my-10 max-w-xl">
|
||||||
return <div></div>;
|
<ProfileCard />
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<ContactCard />
|
||||||
|
<RelayCard />
|
||||||
|
<PostCard />
|
||||||
|
<ZapCard />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
12
src/main.jsx
12
src/main.jsx
@ -25,7 +25,17 @@ const container = document.getElementById('root');
|
|||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
|
<PersistQueryClientProvider
|
||||||
|
client={queryClient}
|
||||||
|
persistOptions={{
|
||||||
|
persister,
|
||||||
|
dehydrateOptions: {
|
||||||
|
shouldDehydrateQuery: (query) => {
|
||||||
|
if (query.queryKey !== 'widgets') return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<StorageProvider>
|
<StorageProvider>
|
||||||
<NDKProvider>
|
<NDKProvider>
|
||||||
<Toaster position="top-center" closeButton />
|
<Toaster position="top-center" closeButton />
|
||||||
|
Loading…
Reference in New Issue
Block a user