index.tsx raw
1 import ImageWithLightbox from '@/components/ImageWithLightbox'
2 import NormalFeed from '@/components/NormalFeed'
3 import ProfileList from '@/components/ProfileList'
4 import { Skeleton } from '@/components/ui/skeleton'
5 import { useFetchEvent } from '@/hooks/useFetchEvent'
6 import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
7 import { getFollowPackInfoFromEvent } from '@/lib/event-metadata'
8 import { cn } from '@/lib/utils'
9 import { useNostr } from '@/providers/NostrProvider'
10 import client from '@/services/client.service'
11 import { TFeedSubRequest } from '@/types'
12 import { forwardRef, useEffect, useMemo, useState } from 'react'
13 import { useTranslation } from 'react-i18next'
14
15 const FollowPackPage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => {
16 const { t } = useTranslation()
17 const [tab, setTab] = useState<'users' | 'feed'>('users')
18
19 const { event, isFetching } = useFetchEvent(id)
20
21 const { title, description, image, pubkeys } = useMemo(() => {
22 if (!event) return { title: '', description: '', image: '', pubkeys: [] }
23 return getFollowPackInfoFromEvent(event)
24 }, [event])
25
26 if (isFetching) {
27 return (
28 <SecondaryPageLayout ref={ref} index={index} title={t('Follow Pack')}>
29 <div className="px-4 py-3 space-y-2">
30 <Skeleton className="h-48 w-full" />
31 <Skeleton className="h-7 py-1 w-full" />
32 </div>
33 </SecondaryPageLayout>
34 )
35 }
36
37 if (!event) {
38 return (
39 <SecondaryPageLayout ref={ref} index={index} title={t('Follow Pack')}>
40 <div className="p-4 text-center text-muted-foreground">{t('Follow pack not found')}</div>
41 </SecondaryPageLayout>
42 )
43 }
44
45 return (
46 <SecondaryPageLayout ref={ref} index={index} title={t('Follow Pack')} displayScrollToTopButton>
47 <div>
48 {/* Header */}
49 <div className="px-4 pt-3 space-y-2">
50 {image && (
51 <ImageWithLightbox
52 image={{ url: image, pubkey: event.pubkey }}
53 className="w-full h-48 object-cover rounded-lg"
54 classNames={{
55 wrapper: 'w-full h-48 border-none'
56 }}
57 />
58 )}
59
60 <div className="flex items-center gap-2">
61 <h3 className="text-2xl font-semibold mb-1 truncate">{title}</h3>
62 <span className="text-xs text-muted-foreground shrink-0">
63 {t('n users', { count: pubkeys.length })}
64 </span>
65 </div>
66
67 {description && (
68 <p className="text-sm text-muted-foreground whitespace-pre-wrap">{description}</p>
69 )}
70
71 <div className="inline-flex items-center rounded-lg border bg-muted/50">
72 <button
73 onClick={() => setTab('users')}
74 className={cn(
75 'px-3 py-1.5 text-sm font-medium rounded-l-lg transition-colors',
76 tab === 'users'
77 ? 'bg-background text-foreground shadow-sm'
78 : 'text-muted-foreground hover:text-foreground'
79 )}
80 >
81 {t('Users')}
82 </button>
83 <button
84 onClick={() => setTab('feed')}
85 className={cn(
86 'px-3 py-1.5 text-sm font-medium rounded-r-lg transition-colors',
87 tab === 'feed'
88 ? 'bg-background text-foreground shadow-sm'
89 : 'text-muted-foreground hover:text-foreground'
90 )}
91 >
92 {t('Feed')}
93 </button>
94 </div>
95 </div>
96
97 {/* Content */}
98 {tab === 'users' && <ProfileList pubkeys={pubkeys} />}
99 {tab === 'feed' && pubkeys.length > 0 && <Feed pubkeys={pubkeys} />}
100 </div>
101 </SecondaryPageLayout>
102 )
103 })
104 FollowPackPage.displayName = 'FollowPackPage'
105 export default FollowPackPage
106
107 function Feed({ pubkeys }: { pubkeys: string[] }) {
108 const { pubkey: myPubkey } = useNostr()
109 const [subRequests, setSubRequests] = useState<TFeedSubRequest[]>([])
110
111 useEffect(() => {
112 client.generateSubRequestsForPubkeys(pubkeys, myPubkey).then(setSubRequests)
113 }, [pubkeys, myPubkey])
114
115 return <NormalFeed subRequests={subRequests} />
116 }
117