import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { DEFAULT_FAVORITE_RELAYS } from '@/constants' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useNostr } from '@/providers/NostrProvider' import { forwardRef, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import PrimaryPageLayout from '@/layouts/PrimaryPageLayout' import { MessageSquarePlus } from 'lucide-react' import ThreadCard from '@/pages/primary/DiscussionsPage/ThreadCard' import TopicFilter from '@/pages/primary/DiscussionsPage/TopicFilter' import CreateThreadDialog, { DISCUSSION_TOPICS } from '@/pages/primary/DiscussionsPage/CreateThreadDialog' import { NostrEvent } from 'nostr-tools' import client from '@/services/client.service' import { useSecondaryPage } from '@/PageManager' import { toNote } from '@/lib/link' const DiscussionsPage = forwardRef((_, ref) => { const { t } = useTranslation() const { favoriteRelays } = useFavoriteRelays() const { pubkey } = useNostr() const { push } = useSecondaryPage() const [selectedTopic, setSelectedTopic] = useState('general') const [selectedRelay, setSelectedRelay] = useState(null) const [allThreads, setAllThreads] = useState([]) const [threads, setThreads] = useState([]) const [loading, setLoading] = useState(false) const [showCreateThread, setShowCreateThread] = useState(false) // Use DEFAULT_FAVORITE_RELAYS for logged-out users, or user's favorite relays for logged-in users const availableRelays = pubkey && favoriteRelays.length > 0 ? favoriteRelays : DEFAULT_FAVORITE_RELAYS // Available topic IDs for matching const availableTopicIds = DISCUSSION_TOPICS.map(topic => topic.id) useEffect(() => { fetchAllThreads() }, [selectedRelay]) useEffect(() => { filterThreadsByTopic() }, [allThreads, selectedTopic]) const fetchAllThreads = async () => { setLoading(true) try { // Filter by relay if selected, otherwise use all available relays const relayUrls = selectedRelay ? [selectedRelay] : availableRelays // Fetch all kind 11 events (limit 100, newest first) with relay source tracking const events = await client.fetchEvents(relayUrls, [ { kinds: [11], // Thread events '#-': ['-'], // Must have the "-" tag for relay privacy limit: 100 } ]) // Filter and sort threads, adding relay source information const validThreads = events .filter(event => { // Ensure it has a title tag const titleTag = event.tags.find(tag => tag[0] === 'title' && tag[1]) return titleTag && event.content.trim().length > 0 }) .map(event => ({ ...event, _relaySource: selectedRelay || 'multiple' // Track which relay(s) it was found on })) .sort((a, b) => b.created_at - a.created_at) setAllThreads(validThreads) } catch (error) { console.error('Error fetching threads:', error) setAllThreads([]) } finally { setLoading(false) } } const filterThreadsByTopic = () => { const categorizedThreads = allThreads.map(thread => { // Find all 't' tags in the thread const topicTags = thread.tags.filter(tag => tag[0] === 't' && tag[1]) // Find the first matching topic from our available topics let matchedTopic = 'general' // Default to general for (const topicTag of topicTags) { if (availableTopicIds.includes(topicTag[1])) { matchedTopic = topicTag[1] break // Use the first match found } } return { ...thread, _categorizedTopic: matchedTopic } }) // Filter threads for the selected topic const threadsForTopic = categorizedThreads .filter(thread => thread._categorizedTopic === selectedTopic) .map(thread => { // Remove the temporary categorization property but keep relay source const { _categorizedTopic, ...cleanThread } = thread return cleanThread }) setThreads(threadsForTopic) } const handleCreateThread = () => { setShowCreateThread(true) } const handleThreadCreated = () => { setShowCreateThread(false) fetchAllThreads() // Refresh all threads } return (
{availableRelays.length > 1 && ( )}
} displayScrollToTopButton >

{t('Discussions')} - {DISCUSSION_TOPICS.find(t => t.id === selectedTopic)?.label}

{loading ? (
{t('Loading threads...')}
) : threads.length === 0 ? (

{t('No threads yet')}

{t('Be the first to start a discussion in this topic!')}

) : (
{threads.map(thread => ( { push(toNote(thread)) }} /> ))}
)}
{showCreateThread && ( setShowCreateThread(false)} onThreadCreated={handleThreadCreated} /> )}
) }) DiscussionsPage.displayName = 'DiscussionsPage' export default DiscussionsPage