mirror of
https://github.com/lumehq/lume.git
synced 2025-03-26 01:31:48 +01:00
[wip] added channel explore page
This commit is contained in:
parent
dd0c794ebc
commit
e6b6ab38e0
67
src/app/explore/channels/page.tsx
Normal file
67
src/app/explore/channels/page.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
'use client';
|
||||
|
||||
import { BrowseChannelItem } from '@components/channels/browseChannelItem';
|
||||
|
||||
import { getChannels } from '@utils/storage';
|
||||
|
||||
import { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { VirtuosoGrid } from 'react-virtuoso';
|
||||
|
||||
export default function Page() {
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
const virtuosoRef = useRef(null);
|
||||
const limit = useRef(20);
|
||||
const offset = useRef(0);
|
||||
|
||||
const itemContent: any = useCallback(
|
||||
(index: string | number) => {
|
||||
return <BrowseChannelItem key={data[index].event_id} data={data[index]} />;
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
const computeItemKey = useCallback(
|
||||
(index: string | number) => {
|
||||
return data[index].event_id;
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
const initialData = useCallback(async () => {
|
||||
const result: any = await getChannels(limit.current, offset.current);
|
||||
console.log(result);
|
||||
setData((data) => [...data, ...result]);
|
||||
}, [setData]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
offset.current += limit.current;
|
||||
// query next page
|
||||
const result: any = await getChannels(limit.current, offset.current);
|
||||
setData((data) => [...data, ...result]);
|
||||
}, [setData]);
|
||||
|
||||
useEffect(() => {
|
||||
initialData().catch(console.error);
|
||||
}, [initialData]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<div className="mx-auto h-full w-full max-w-2xl px-4">
|
||||
<Suspense fallback={<>Loading...</>}>
|
||||
<VirtuosoGrid
|
||||
ref={virtuosoRef}
|
||||
data={data}
|
||||
itemContent={itemContent}
|
||||
itemClassName="col-span-1"
|
||||
listClassName="grid grid-cols-2 gap-4"
|
||||
computeItemKey={computeItemKey}
|
||||
overscan={200}
|
||||
endReached={loadMore}
|
||||
className="scrollbar-hide h-full w-full overflow-y-auto overflow-x-hidden"
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
23
src/app/explore/layout.tsx
Normal file
23
src/app/explore/layout.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import AppHeader from '@components/appHeader';
|
||||
import MultiAccounts from '@components/multiAccounts';
|
||||
|
||||
export default function NostrLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="relative h-11 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
||||
>
|
||||
<AppHeader collector={true} />
|
||||
</div>
|
||||
<div className="relative flex min-h-0 w-full flex-1">
|
||||
<div className="relative w-[68px] shrink-0 border-r border-zinc-900">
|
||||
<MultiAccounts />
|
||||
</div>
|
||||
<div className="w-full">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { BrowseChannelItem } from '@components/channels/browseChannelItem';
|
||||
|
||||
import { getChannels } from '@utils/storage';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function Page() {
|
||||
const [list, setList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
getChannels(100, 0)
|
||||
.then((res) => setList(res))
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-y-auto">
|
||||
{list.map((channel) => (
|
||||
<BrowseChannelItem key={channel.id} data={channel} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
import { DEFAULT_AVATAR, DEFAULT_CHANNEL_BANNER } from '@stores/constants';
|
||||
|
||||
import { useChannelMetadata } from '@utils/hooks/useChannelMetadata';
|
||||
|
||||
@ -19,26 +19,32 @@ export const BrowseChannelItem = ({ data }: { data: any }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => openChannel(data.event_id)}
|
||||
className="group relative flex items-center gap-2 border-b border-zinc-800 px-3 py-2.5 hover:bg-black/20"
|
||||
>
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md border border-white/10">
|
||||
<ImageWithFallback
|
||||
src={channel?.picture || DEFAULT_AVATAR}
|
||||
alt={data.id}
|
||||
fill={true}
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
<div className="h-64 w-full rounded-md bg-zinc-900">
|
||||
<div className="relative h-24">
|
||||
<div className="h-24 w-full rounded-t-md bg-zinc-800">
|
||||
<ImageWithFallback
|
||||
src={channel?.banner || DEFAULT_CHANNEL_BANNER}
|
||||
alt={data.id}
|
||||
fill={true}
|
||||
className="h-full w-full rounded-t-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative -top-6 z-10 px-4">
|
||||
<div className="relative h-11 w-11 rounded-md bg-white">
|
||||
<ImageWithFallback
|
||||
src={channel?.picture || DEFAULT_AVATAR}
|
||||
alt={data.id}
|
||||
fill={true}
|
||||
className="rounded-md object-cover ring-1 ring-black/50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
||||
<span className="truncate font-medium leading-tight text-zinc-200">{channel?.name}</span>
|
||||
<span className="text-sm leading-tight text-zinc-400">{channel?.about}</span>
|
||||
</div>
|
||||
<div className="absolute right-2 top-1/2 hidden -translate-y-1/2 transform group-hover:inline-flex">
|
||||
<button className="inline-flex h-8 w-16 items-center justify-center rounded-md bg-fuchsia-500 px-4 text-sm font-medium shadow-button hover:bg-fuchsia-600 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50">
|
||||
Join
|
||||
</button>
|
||||
<div className="mt-7 px-4">
|
||||
<div className="flex flex-col">
|
||||
<h3 className="w-full truncate font-semibold leading-tight text-zinc-100">{channel?.name}</h3>
|
||||
</div>
|
||||
<div className="line-clamp-3 text-sm text-zinc-400">{channel?.about}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ export default function ChannelList() {
|
||||
return (
|
||||
<div className="flex flex-col gap-px">
|
||||
<Link
|
||||
href="/nostr/channels"
|
||||
href="/explore/channels"
|
||||
className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900"
|
||||
>
|
||||
<div className="inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900 group-hover:bg-zinc-800">
|
||||
|
@ -1,2 +1,4 @@
|
||||
export const APP_VERSION = '0.2.5';
|
||||
export const DEFAULT_AVATAR = 'https://void.cat/d/KmypFh2fBdYCEvyJrPiN89.webp';
|
||||
export const DEFAULT_CHANNEL_BANNER =
|
||||
'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg';
|
||||
|
Loading…
x
Reference in New Issue
Block a user