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