index.tsx raw
1 import { useSecondaryPage } from '@/PageManager'
2 import { useStuffStatsById } from '@/hooks/useStuffStatsById'
3 import { formatAmount } from '@/lib/lightning'
4 import { toProfile } from '@/lib/link'
5 import { useScreenSize } from '@/providers/ScreenSizeProvider'
6 import { Zap } from 'lucide-react'
7 import { Event } from 'nostr-tools'
8 import { useEffect, useMemo, useRef, useState } from 'react'
9 import { useTranslation } from 'react-i18next'
10 import Content from '../Content'
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 ZapList({ event }: { event: Event }) {
19 const { t } = useTranslation()
20 const { push } = useSecondaryPage()
21 const { isSmallScreen } = useScreenSize()
22 const noteStats = useStuffStatsById(event.id)
23 const filteredZaps = useMemo(() => {
24 return (noteStats?.zaps ?? []).sort((a, b) => b.amount - a.amount)
25 }, [noteStats, event.id])
26
27 const [showCount, setShowCount] = useState(SHOW_COUNT)
28 const bottomRef = useRef<HTMLDivElement | null>(null)
29
30 useEffect(() => {
31 if (!bottomRef.current || filteredZaps.length <= showCount) return
32 const obs = new IntersectionObserver(
33 ([entry]) => {
34 if (entry.isIntersecting) setShowCount((c) => c + SHOW_COUNT)
35 },
36 { rootMargin: '10px', threshold: 0.1 }
37 )
38 obs.observe(bottomRef.current)
39 return () => obs.disconnect()
40 }, [filteredZaps.length, showCount])
41
42 return (
43 <div className="min-h-[80vh]">
44 {filteredZaps.slice(0, showCount).map((zap) => (
45 <div
46 key={zap.pr}
47 className="px-4 py-3 border-b transition-colors clickable flex gap-2"
48 onClick={() => push(toProfile(zap.pubkey))}
49 >
50 <div className="w-8 flex flex-col items-center mt-0.5">
51 <Zap className="text-yellow-400 size-5" />
52 <div className="text-sm font-semibold text-yellow-400">{formatAmount(zap.amount)}</div>
53 </div>
54
55 <div className="flex space-x-2 items-start">
56 <UserAvatar userId={zap.pubkey} size="medium" className="shrink-0 mt-0.5" />
57 <div className="flex-1">
58 <Username
59 userId={zap.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={zap.pubkey} append="ยท" />
65 <FormattedTimestamp
66 timestamp={zap.created_at}
67 className="shrink-0"
68 short={isSmallScreen}
69 />
70 </div>
71 <Content className="mt-2" content={zap.comment} />
72 </div>
73 </div>
74 </div>
75 ))}
76
77 <div ref={bottomRef} />
78
79 <div className="text-sm mt-2 text-center text-muted-foreground">
80 {filteredZaps.length > 0 ? t('No more zaps') : t('No zaps yet')}
81 </div>
82 </div>
83 )
84 }
85