You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
3.1 KiB
94 lines
3.1 KiB
import UserItem from '@/components/UserItem' |
|
import { SEARCHABLE_RELAY_URLS } from '@/constants' |
|
import { useFetchRelayInfos, useSearchParams } from '@/hooks' |
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' |
|
import { useFeed } from '@/providers/FeedProvider' |
|
import client from '@/services/client.service' |
|
import dayjs from 'dayjs' |
|
import { Filter } from 'nostr-tools' |
|
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
|
|
const LIMIT = 50 |
|
|
|
const ProfileListPage = forwardRef(({ index }: { index?: number }, ref) => { |
|
const { t } = useTranslation() |
|
const { searchParams } = useSearchParams() |
|
const { relayUrls } = useFeed() |
|
const { searchableRelayUrls } = useFetchRelayInfos(relayUrls) |
|
const [until, setUntil] = useState<number>(() => dayjs().unix()) |
|
const [hasMore, setHasMore] = useState<boolean>(true) |
|
const [pubkeySet, setPubkeySet] = useState(new Set<string>()) |
|
const bottomRef = useRef<HTMLDivElement>(null) |
|
const filter = useMemo(() => { |
|
const f: Filter = { until } |
|
const search = searchParams.get('s') |
|
if (search) { |
|
f.search = search |
|
} |
|
return f |
|
}, [searchParams, until]) |
|
const urls = useMemo(() => { |
|
return filter.search ? searchableRelayUrls.concat(SEARCHABLE_RELAY_URLS).slice(0, 4) : relayUrls |
|
}, [relayUrls, searchableRelayUrls, filter]) |
|
const title = useMemo(() => { |
|
return filter.search ? `${t('Search')}: ${filter.search}` : t('All users') |
|
}, [filter]) |
|
|
|
useEffect(() => { |
|
if (!hasMore) return |
|
const options = { |
|
root: null, |
|
rootMargin: '10px', |
|
threshold: 1 |
|
} |
|
|
|
const observerInstance = new IntersectionObserver((entries) => { |
|
if (entries[0].isIntersecting && hasMore) { |
|
loadMore() |
|
} |
|
}, options) |
|
|
|
const currentBottomRef = bottomRef.current |
|
|
|
if (currentBottomRef) { |
|
observerInstance.observe(currentBottomRef) |
|
} |
|
|
|
return () => { |
|
if (observerInstance && currentBottomRef) { |
|
observerInstance.unobserve(currentBottomRef) |
|
} |
|
} |
|
}, [hasMore, filter, urls]) |
|
|
|
async function loadMore() { |
|
if (urls.length === 0) { |
|
return setHasMore(false) |
|
} |
|
const profiles = await client.fetchProfiles(urls, { ...filter, limit: LIMIT }) |
|
const newPubkeySet = new Set<string>() |
|
profiles.forEach((profile) => { |
|
if (!pubkeySet.has(profile.pubkey)) { |
|
newPubkeySet.add(profile.pubkey) |
|
} |
|
}) |
|
setPubkeySet((prev) => new Set([...prev, ...newPubkeySet])) |
|
setHasMore(profiles.length >= LIMIT) |
|
const lastProfileCreatedAt = profiles[profiles.length - 1].created_at |
|
setUntil(lastProfileCreatedAt ? lastProfileCreatedAt - 1 : 0) |
|
} |
|
|
|
return ( |
|
<SecondaryPageLayout ref={ref} index={index} title={title} displayScrollToTopButton> |
|
<div className="space-y-2 px-4"> |
|
{Array.from(pubkeySet).map((pubkey, index) => ( |
|
<UserItem key={`${index}-${pubkey}`} pubkey={pubkey} /> |
|
))} |
|
{hasMore && <div ref={bottomRef} />} |
|
</div> |
|
</SecondaryPageLayout> |
|
) |
|
}) |
|
ProfileListPage.displayName = 'ProfileListPage' |
|
export default ProfileListPage
|
|
|