index.tsx raw
1 import { useSecondaryPage } from '@/PageManager'
2 import { useStuffStatsById } from '@/hooks/useStuffStatsById'
3 import { getEventKey } from '@/lib/event'
4 import { toProfile } from '@/lib/link'
5 import { useScreenSize } from '@/providers/ScreenSizeProvider'
6 import { useUserTrust } from '@/providers/UserTrustProvider'
7 import { Repeat } from 'lucide-react'
8 import { Event } from 'nostr-tools'
9 import { useEffect, useMemo, useRef, useState } from 'react'
10 import { useTranslation } from 'react-i18next'
11 import { FormattedTimestamp } from '../FormattedTimestamp'
12 import Nip05 from '../Nip05'
13 import UserAvatar from '../UserAvatar'
14 import Username from '../Username'
15
16 const SHOW_COUNT = 20
17
18 export default function RepostList({ event }: { event: Event }) {
19 const { t } = useTranslation()
20 const { push } = useSecondaryPage()
21 const { isSmallScreen } = useScreenSize()
22 const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
23 const noteStats = useStuffStatsById(getEventKey(event))
24 const filteredReposts = useMemo(() => {
25 return (noteStats?.reposts ?? [])
26 .filter((repost) => !hideUntrustedInteractions || isUserTrusted(repost.pubkey))
27 .sort((a, b) => b.created_at - a.created_at)
28 }, [noteStats, event.id, hideUntrustedInteractions, isUserTrusted])
29
30 const [showCount, setShowCount] = useState(SHOW_COUNT)
31 const bottomRef = useRef<HTMLDivElement | null>(null)
32
33 useEffect(() => {
34 if (!bottomRef.current || filteredReposts.length <= showCount) return
35 const obs = new IntersectionObserver(
36 ([entry]) => {
37 if (entry.isIntersecting) setShowCount((c) => c + SHOW_COUNT)
38 },
39 { rootMargin: '10px', threshold: 0.1 }
40 )
41 obs.observe(bottomRef.current)
42 return () => obs.disconnect()
43 }, [filteredReposts.length, showCount])
44
45 return (
46 <div className="min-h-[80vh]">
47 {filteredReposts.slice(0, showCount).map((repost) => (
48 <div
49 key={repost.id}
50 className="px-4 py-3 border-b transition-colors clickable flex items-center gap-3"
51 onClick={() => push(toProfile(repost.pubkey))}
52 >
53 <Repeat className="text-green-400 size-5" />
54
55 <UserAvatar userId={repost.pubkey} size="medium" className="shrink-0" />
56
57 <div className="flex-1 w-0">
58 <Username
59 userId={repost.pubkey}
60 className="text-sm font-semibold text-muted-foreground hover:text-foreground max-w-fit truncate"
61 skeletonClassName="h-3"
62 />
63 <div className="flex items-center gap-1 text-sm text-muted-foreground">
64 <Nip05 pubkey={repost.pubkey} append="ยท" />
65 <FormattedTimestamp
66 timestamp={repost.created_at}
67 className="shrink-0"
68 short={isSmallScreen}
69 />
70 </div>
71 </div>
72 </div>
73 ))}
74
75 <div ref={bottomRef} />
76
77 <div className="text-sm mt-2 text-center text-muted-foreground">
78 {filteredReposts.length > 0 ? t('No more reposts') : t('No reposts yet')}
79 </div>
80 </div>
81 )
82 }
83