@ -40,6 +40,7 @@ export default function TrendingNotes() {
const { zapReplyThreshold } = useZap ( )
const { zapReplyThreshold } = useZap ( )
const [ nostrEvents , setNostrEvents ] = useState < NostrEvent [ ] > ( [ ] )
const [ nostrEvents , setNostrEvents ] = useState < NostrEvent [ ] > ( [ ] )
const [ nostrLoading , setNostrLoading ] = useState ( false )
const [ nostrLoading , setNostrLoading ] = useState ( false )
const [ nostrError , setNostrError ] = useState < string | null > ( null )
const [ showCount , setShowCount ] = useState ( SHOW_COUNT )
const [ showCount , setShowCount ] = useState ( SHOW_COUNT )
const [ activeTab , setActiveTab ] = useState < TrendingTab > ( 'nostr' )
const [ activeTab , setActiveTab ] = useState < TrendingTab > ( 'nostr' )
const [ sortOrder , setSortOrder ] = useState < SortOrder > ( 'most-popular' )
const [ sortOrder , setSortOrder ] = useState < SortOrder > ( 'most-popular' )
@ -49,25 +50,48 @@ export default function TrendingNotes() {
const [ cacheEvents , setCacheEvents ] = useState < NostrEvent [ ] > ( [ ] )
const [ cacheEvents , setCacheEvents ] = useState < NostrEvent [ ] > ( [ ] )
const [ cacheLoading , setCacheLoading ] = useState ( false )
const [ cacheLoading , setCacheLoading ] = useState ( false )
const bottomRef = useRef < HTMLDivElement > ( null )
const bottomRef = useRef < HTMLDivElement > ( null )
const isFetchingNostrRef = useRef ( false )
// Load Nostr.band trending feed when tab is active
// Load Nostr.band trending feed when tab is active
useEffect ( ( ) = > {
useEffect ( ( ) = > {
const loadTrending = async ( ) = > {
const loadTrending = async ( ) = > {
// Prevent concurrent fetches
if ( isFetchingNostrRef . current ) {
return
}
try {
try {
isFetchingNostrRef . current = true
setNostrLoading ( true )
setNostrLoading ( true )
setNostrError ( null )
const events = await client . fetchTrendingNotes ( )
const events = await client . fetchTrendingNotes ( )
setNostrEvents ( events )
setNostrEvents ( events )
setNostrError ( null )
} catch ( error ) {
} catch ( error ) {
logger . warn ( 'Failed to load nostr.band trending notes' , error as Error )
if ( error instanceof Error && error . message === 'TIMEOUT' ) {
setNostrError ( 'timeout' )
logger . warn ( 'nostr.band API request timed out after 5 seconds' )
} else {
logger . warn ( 'Failed to load nostr.band trending notes' , error as Error )
setNostrError ( null ) // Other errors are handled silently (empty array)
}
} finally {
} finally {
setNostrLoading ( false )
setNostrLoading ( false )
isFetchingNostrRef . current = false
}
}
}
}
if ( activeTab === 'nostr' && nostrEvents . length === 0 && ! nostrLoading ) {
if ( activeTab === 'nostr' && nostrEvents . length === 0 && ! nostrLoading && ! nostrError && ! isFetchingNostrRef . current ) {
loadTrending ( )
loadTrending ( )
}
}
} , [ activeTab , nostrEvents . length , nostrLoading ] )
} , [ activeTab , nostrEvents . length , nostrLoading , nostrError ] )
// Reset error when switching away from nostr tab
useEffect ( ( ) = > {
if ( activeTab !== 'nostr' ) {
setNostrError ( null )
}
} , [ activeTab ] )
// Debug: Track cacheEvents changes
// Debug: Track cacheEvents changes
useEffect ( ( ) = > {
useEffect ( ( ) = > {
@ -562,34 +586,34 @@ export default function TrendingNotes() {
< span className = "text-sm font-medium text-muted-foreground" > Trending : < / span >
< span className = "text-sm font-medium text-muted-foreground" > Trending : < / span >
< div className = "flex gap-1" >
< div className = "flex gap-1" >
< button
< button
onClick = { ( ) = > setActiveTab ( 'nostr ' ) }
onClick = { ( ) = > setActiveTab ( 'relays ' ) }
className = { ` px-3 py-1 text-sm rounded-md transition-colors ${
className = { ` px-3 py-1 text-sm rounded-md transition-colors ${
activeTab === 'nostr '
activeTab === 'relays '
? 'bg-primary text-primary-foreground'
? 'bg-primary text-primary-foreground'
: 'bg-muted hover:bg-muted/80 text-muted-foreground'
: 'bg-muted hover:bg-muted/80 text-muted-foreground'
} ` }
} ` }
>
>
on Nostr
on your relays
< / button >
< / button >
< button
< button
onClick = { ( ) = > setActiveTab ( 'relay s' ) }
onClick = { ( ) = > setActiveTab ( 'hashtag s' ) }
className = { ` px-3 py-1 text-sm rounded-md transition-colors ${
className = { ` px-3 py-1 text-sm rounded-md transition-colors ${
activeTab === 'relay s'
activeTab === 'hashtag s'
? 'bg-primary text-primary-foreground'
? 'bg-primary text-primary-foreground'
: 'bg-muted hover:bg-muted/80 text-muted-foreground'
: 'bg-muted hover:bg-muted/80 text-muted-foreground'
} ` }
} ` }
>
>
on your relay s
hashtag s
< / button >
< / button >
< button
< button
onClick = { ( ) = > setActiveTab ( 'hashtags ' ) }
onClick = { ( ) = > setActiveTab ( 'nostr ' ) }
className = { ` px-3 py-1 text-sm rounded-md transition-colors ${
className = { ` px-3 py-1 text-sm rounded-md transition-colors ${
activeTab === 'hashtags '
activeTab === 'nostr '
? 'bg-primary text-primary-foreground'
? 'bg-primary text-primary-foreground'
: 'bg-muted hover:bg-muted/80 text-muted-foreground'
: 'bg-muted hover:bg-muted/80 text-muted-foreground'
} ` }
} ` }
>
>
hashtags
on Nostr
< / button >
< / button >
< / div >
< / div >
< / div >
< / div >
@ -675,8 +699,15 @@ export default function TrendingNotes() {
) }
) }
< / div >
< / div >
{ /* Show loading message for nostr tab */ }
{ /* Show error message for nostr tab timeout (show instead of loading when error occurs, only if no events) */ }
{ activeTab === 'nostr' && nostrLoading && nostrEvents . length === 0 && (
{ activeTab === 'nostr' && nostrError === 'timeout' && ! nostrLoading && filteredEvents . length === 0 && (
< div className = "text-center text-sm text-muted-foreground mt-8 px-4 py-2 bg-muted/50 rounded-md mx-4" >
{ t ( 'The nostr.band relay appears to be temporarily out of service. Please try again later.' ) }
< / div >
) }
{ /* Show loading message for nostr tab (only if not in error state) */ }
{ activeTab === 'nostr' && nostrLoading && nostrEvents . length === 0 && ! nostrError && (
< div className = "text-center text-sm text-muted-foreground mt-8" >
< div className = "text-center text-sm text-muted-foreground mt-8" >
Loading trending notes from nostr . band . . .
Loading trending notes from nostr . band . . .
< / div >
< / div >
@ -691,6 +722,14 @@ export default function TrendingNotes() {
{ filteredEvents . map ( ( event ) = > (
{ filteredEvents . map ( ( event ) = > (
< NoteCard key = { event . id } className = "w-full" event = { event } / >
< NoteCard key = { event . id } className = "w-full" event = { event } / >
) ) }
) ) }
{ /* Show error message at the end for nostr tab timeout (only if there are events) */ }
{ activeTab === 'nostr' && nostrError === 'timeout' && ! nostrLoading && filteredEvents . length > 0 && (
< div className = "text-center text-sm text-muted-foreground mt-4 px-4 py-2 bg-muted/50 rounded-md mx-4" >
{ t ( 'The nostr.band relay appears to be temporarily out of service. Please try again later.' ) }
< / div >
) }
{ ( ( ) = > {
{ ( ( ) = > {
const totalAvailableLength =
const totalAvailableLength =
activeTab === 'nostr'
activeTab === 'nostr'