mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
wip: browse user
This commit is contained in:
parent
18a9ba3fb0
commit
c9bc7b81dd
19
src/app.tsx
19
src/app.tsx
@ -4,6 +4,7 @@ import { RouterProvider, createBrowserRouter, redirect } from 'react-router-dom'
|
||||
import { AuthCreateScreen } from '@app/auth/create';
|
||||
import { AuthImportScreen } from '@app/auth/import';
|
||||
import { OnboardingScreen } from '@app/auth/onboarding';
|
||||
import { BrowseScreen } from '@app/browse';
|
||||
import { ErrorScreen } from '@app/error';
|
||||
|
||||
import { Frame } from '@shared/frame';
|
||||
@ -61,11 +62,25 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: 'browse',
|
||||
element: <BrowseScreen />,
|
||||
errorElement: <ErrorScreen />,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
async lazy() {
|
||||
const { BrowseScreen } = await import('@app/browse');
|
||||
return { Component: BrowseScreen };
|
||||
const { BrowseUsersScreen } = await import('@app/browse/users');
|
||||
return { Component: BrowseUsersScreen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'relays',
|
||||
async lazy() {
|
||||
const { BrowseRelaysScreen } = await import('@app/browse/relays');
|
||||
return { Component: BrowseRelaysScreen };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'users/:pubkey',
|
||||
async lazy() {
|
||||
|
125
src/app/browse/components/userDrawer.tsx
Normal file
125
src/app/browse/components/userDrawer.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { Image } from '@shared/image';
|
||||
import { NIP05 } from '@shared/nip05';
|
||||
import { TextNote } from '@shared/notes';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { displayNpub } from '@utils/shortenKey';
|
||||
|
||||
export const UserDrawer = memo(function UserDrawer({ pubkey }: { pubkey: string }) {
|
||||
const { db } = useStorage();
|
||||
const { status, user } = useProfile(pubkey);
|
||||
const { addContact, removeContact } = useNostr();
|
||||
|
||||
const [followed, setFollowed] = useState(false);
|
||||
|
||||
const followUser = (pubkey: string) => {
|
||||
try {
|
||||
addContact(pubkey);
|
||||
|
||||
// update state
|
||||
setFollowed(true);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const unfollowUser = (pubkey: string) => {
|
||||
try {
|
||||
removeContact(pubkey);
|
||||
|
||||
// update state
|
||||
setFollowed(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (db.account.follows.includes(pubkey)) {
|
||||
setFollowed(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger asChild>
|
||||
<button type="button">
|
||||
<User pubkey={pubkey} variant="avatar" />
|
||||
</button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content className="fixed right-0 top-0 z-50 flex h-full w-[400px] items-center justify-center">
|
||||
<div className="h-full w-full overflow-y-auto border-l border-white/10 bg-white/20 px-3 py-3 backdrop-blur-xl">
|
||||
{status === 'loading' ? (
|
||||
<div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-14 w-14 rounded-lg"
|
||||
/>
|
||||
<div className="mt-2 flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h5 className="text-lg font-semibold leading-none">
|
||||
{user?.displayName || user?.name || 'No name'}
|
||||
</h5>
|
||||
{user?.nip05 ? (
|
||||
<NIP05
|
||||
pubkey={pubkey}
|
||||
nip05={user?.nip05}
|
||||
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
|
||||
/>
|
||||
) : (
|
||||
<span className="max-w-[15rem] truncate text-sm leading-none text-white/50">
|
||||
{displayNpub(pubkey, 16)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{user.about ? <TextNote content={user.about} /> : null}
|
||||
</div>
|
||||
<div className="mt-4 inline-flex items-center gap-2">
|
||||
{followed ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => unfollowUser(pubkey)}
|
||||
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-white/10 text-sm font-medium backdrop-blur-xl hover:bg-fuchsia-500"
|
||||
>
|
||||
Unfollow
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => followUser(pubkey)}
|
||||
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-white/10 text-sm font-medium backdrop-blur-xl hover:bg-fuchsia-500"
|
||||
>
|
||||
Follow
|
||||
</button>
|
||||
)}
|
||||
<Link
|
||||
to={`/chats/${pubkey}`}
|
||||
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-white/10 text-sm font-medium backdrop-blur-xl hover:bg-fuchsia-500"
|
||||
>
|
||||
Message
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
});
|
@ -1,7 +1,38 @@
|
||||
import { NavLink, Outlet } from 'react-router-dom';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function BrowseScreen() {
|
||||
return (
|
||||
<div>
|
||||
<p>TODO</p>
|
||||
<div className="relative h-full w-full">
|
||||
<div className="absolute left-0 right-0 top-4 flex w-full items-center justify-between px-3">
|
||||
<div className="w-10" />
|
||||
<div className="inline-flex gap-1 rounded-full border-t border-white/10 bg-white/20 p-1">
|
||||
<NavLink
|
||||
to="/browse/"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'inline-flex h-8 w-20 items-center justify-center rounded-full text-sm font-semibold',
|
||||
isActive ? 'bg-white/10 hover:bg-white/20' : ' hover:bg-white/5'
|
||||
)
|
||||
}
|
||||
>
|
||||
Users
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/browse/relays"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'inline-flex h-8 w-20 items-center justify-center rounded-full text-sm font-semibold',
|
||||
isActive ? 'bg-white/10 hover:bg-white/20' : ' hover:bg-white/5'
|
||||
)
|
||||
}
|
||||
>
|
||||
Relays
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
7
src/app/browse/relays.tsx
Normal file
7
src/app/browse/relays.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
export function BrowseRelaysScreen() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<p>TODO</p>
|
||||
</div>
|
||||
);
|
||||
}
|
22
src/app/browse/users.tsx
Normal file
22
src/app/browse/users.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { UserDrawer } from '@app/browse/components/userDrawer';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { User } from '@shared/user';
|
||||
|
||||
export function BrowseUsersScreen() {
|
||||
const { db } = useStorage();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div>
|
||||
<User pubkey={db.account.pubkey} variant="avatar" />
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
{db.account.follows.map((user) => (
|
||||
<UserDrawer key={user} pubkey={user} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -28,7 +28,7 @@ export function ComposerModal() {
|
||||
<Dialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-9 items-center gap-2 rounded-full border-t border-white/10 bg-white/20 px-4 text-sm font-semibold leading-none text-white/80 hover:bg-fuchsia-500 hover:text-white"
|
||||
className="flex h-9 items-center gap-2 rounded-full border-t border-white/10 bg-white/20 px-4 text-sm font-semibold text-white/80 hover:bg-fuchsia-500 hover:text-white"
|
||||
>
|
||||
New
|
||||
<ComposeIcon className="h-4 w-4 text-white" />
|
||||
|
@ -50,7 +50,7 @@ export function Navigation() {
|
||||
Home
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/browse"
|
||||
to="/browse/"
|
||||
preventScrollReset={true}
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
|
@ -20,7 +20,15 @@ export const User = memo(function User({
|
||||
}: {
|
||||
pubkey: string;
|
||||
time?: number;
|
||||
variant?: 'default' | 'simple' | 'mention' | 'repost' | 'chat' | 'large' | 'thread';
|
||||
variant?:
|
||||
| 'default'
|
||||
| 'simple'
|
||||
| 'mention'
|
||||
| 'repost'
|
||||
| 'chat'
|
||||
| 'large'
|
||||
| 'thread'
|
||||
| 'avatar';
|
||||
embedProfile?: string;
|
||||
}) {
|
||||
const { status, user } = useProfile(pubkey, embedProfile);
|
||||
@ -125,6 +133,16 @@ export const User = memo(function User({
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === 'avatar') {
|
||||
return (
|
||||
<Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-12 w-12 shrink-0 rounded-lg object-cover"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === 'repost') {
|
||||
return (
|
||||
<>
|
||||
|
@ -44,7 +44,7 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={params.id} title={params.title} />
|
||||
<div className="h-full">
|
||||
<div className="scrollbar-hide h-full overflow-y-auto">
|
||||
{status === 'loading' ? (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||
|
@ -25,7 +25,7 @@ export function useProfile(pubkey: string, embed?: string) {
|
||||
{
|
||||
kinds: [NDKKind.Metadata],
|
||||
authors: [cleanPubkey],
|
||||
limit: 1,
|
||||
limit: 100,
|
||||
},
|
||||
{ closeOnEose: true }
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user