mirror of
https://github.com/lumehq/lume.git
synced 2025-03-29 03:02:14 +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 { 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';
|
import { useChannelMetadata } from '@utils/hooks/useChannelMetadata';
|
||||||
|
|
||||||
@ -19,26 +19,32 @@ export const BrowseChannelItem = ({ data }: { data: any }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="h-64 w-full rounded-md bg-zinc-900">
|
||||||
onClick={() => openChannel(data.event_id)}
|
<div className="relative h-24">
|
||||||
className="group relative flex items-center gap-2 border-b border-zinc-800 px-3 py-2.5 hover:bg-black/20"
|
<div className="h-24 w-full rounded-t-md bg-zinc-800">
|
||||||
>
|
<ImageWithFallback
|
||||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md border border-white/10">
|
src={channel?.banner || DEFAULT_CHANNEL_BANNER}
|
||||||
<ImageWithFallback
|
alt={data.id}
|
||||||
src={channel?.picture || DEFAULT_AVATAR}
|
fill={true}
|
||||||
alt={data.id}
|
className="h-full w-full rounded-t-md object-cover"
|
||||||
fill={true}
|
/>
|
||||||
className="rounded-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>
|
||||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
<div className="mt-7 px-4">
|
||||||
<span className="truncate font-medium leading-tight text-zinc-200">{channel?.name}</span>
|
<div className="flex flex-col">
|
||||||
<span className="text-sm leading-tight text-zinc-400">{channel?.about}</span>
|
<h3 className="w-full truncate font-semibold leading-tight text-zinc-100">{channel?.name}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute right-2 top-1/2 hidden -translate-y-1/2 transform group-hover:inline-flex">
|
<div className="line-clamp-3 text-sm text-zinc-400">{channel?.about}</div>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,7 @@ export default function ChannelList() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-px">
|
<div className="flex flex-col gap-px">
|
||||||
<Link
|
<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"
|
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">
|
<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 APP_VERSION = '0.2.5';
|
||||||
export const DEFAULT_AVATAR = 'https://void.cat/d/KmypFh2fBdYCEvyJrPiN89.webp';
|
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