94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
import UserItem from '@/components/UserItem'
|
|
import { SEARCHABLE_RELAY_URLS } from '@/constants'
|
|
import { useFetchRelayInfos } 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 { 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 searchParams = new URLSearchParams(window.location.search)
|
|
const search = searchParams.get('s')
|
|
if (search) {
|
|
f.search = search
|
|
}
|
|
return f
|
|
}, [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.searchProfiles(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
|