Browse Source

minor navigation bug-fixes

imwald
Silberengel 5 months ago
parent
commit
1c78d41c66
  1. 109
      src/PageManager.tsx
  2. 2
      src/components/SearchBar/index.tsx
  3. 185
      src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
  4. 116
      src/pages/primary/DiscussionsPage/index.tsx

109
src/PageManager.tsx

@ -614,8 +614,23 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
const pushSecondaryPage = (url: string, index?: number) => { const pushSecondaryPage = (url: string, index?: number) => {
console.log('pushSecondaryPage called with:', url)
setSecondaryStack((prevStack) => { setSecondaryStack((prevStack) => {
console.log('Current secondary stack length:', prevStack.length)
// For relay pages, clear the stack and start fresh to avoid confusion
if (url.startsWith('/relays/')) {
console.log('Clearing stack for relay navigation')
const { newStack, newItem } = pushNewPageToStack([], url, maxStackSize, 0)
console.log('New stack length:', newStack.length, 'New item:', !!newItem)
if (newItem) {
window.history.pushState({ index: newItem.index, url }, '', url)
}
return newStack
}
if (isCurrentPage(prevStack, url)) { if (isCurrentPage(prevStack, url)) {
console.log('Page already exists, scrolling to top')
const currentItem = prevStack[prevStack.length - 1] const currentItem = prevStack[prevStack.length - 1]
if (currentItem?.ref?.current) { if (currentItem?.ref?.current) {
currentItem.ref.current.scrollToTop('instant') currentItem.ref.current.scrollToTop('instant')
@ -623,7 +638,9 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
return prevStack return prevStack
} }
console.log('Creating new page for URL:', url)
const { newStack, newItem } = pushNewPageToStack(prevStack, url, maxStackSize, index) const { newStack, newItem } = pushNewPageToStack(prevStack, url, maxStackSize, index)
console.log('New stack length:', newStack.length, 'New item:', !!newItem)
if (newItem) { if (newItem) {
window.history.pushState({ index: newItem.index, url }, '', url) window.history.pushState({ index: newItem.index, url }, '', url)
} }
@ -696,16 +713,26 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
) : ( ) : (
<> <>
{!!secondaryStack.length && {!!secondaryStack.length &&
secondaryStack.map((item, index) => ( secondaryStack.map((item, index) => {
<div const isLast = index === secondaryStack.length - 1
key={item.index} console.log('Rendering secondary stack item:', {
style={{ index,
display: index === secondaryStack.length - 1 ? 'block' : 'none' isLast,
}} url: item.url,
> hasComponent: !!item.component,
{item.component} display: isLast ? 'block' : 'none'
</div> })
))} return (
<div
key={item.index}
style={{
display: isLast ? 'block' : 'none'
}}
>
{item.component}
</div>
)
})}
{primaryPages.map(({ name, element, props }) => ( {primaryPages.map(({ name, element, props }) => (
<div <div
key={name} key={name}
@ -756,13 +783,40 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
}} }}
> >
<Sidebar /> <Sidebar />
<MainContentArea {secondaryStack.length > 0 ? (
primaryPages={primaryPages} // Show secondary pages when there are any in the stack
currentPrimaryPage={currentPrimaryPage} <div className="flex-1 overflow-auto">
primaryNoteView={primaryNoteView} {secondaryStack.map((item, index) => {
primaryViewType={primaryViewType} const isLast = index === secondaryStack.length - 1
goBack={goBack} console.log('Rendering desktop secondary stack item:', {
/> index,
isLast,
url: item.url,
hasComponent: !!item.component,
display: isLast ? 'block' : 'none'
})
return (
<div
key={item.index}
style={{
display: isLast ? 'block' : 'none'
}}
>
{item.component}
</div>
)
})}
</div>
) : (
// Show primary pages when no secondary pages
<MainContentArea
primaryPages={primaryPages}
currentPrimaryPage={currentPrimaryPage}
primaryNoteView={primaryNoteView}
primaryViewType={primaryViewType}
goBack={goBack}
/>
)}
</div> </div>
</div> </div>
<TooManyRelaysAlertDialog /> <TooManyRelaysAlertDialog />
@ -807,19 +861,36 @@ function isCurrentPage(stack: TStackItem[], url: string) {
const currentPage = stack[stack.length - 1] const currentPage = stack[stack.length - 1]
if (!currentPage) return false if (!currentPage) return false
console.log('isCurrentPage check:', { currentUrl: currentPage.url, newUrl: url, match: currentPage.url === url })
return currentPage.url === url return currentPage.url === url
} }
function findAndCreateComponent(url: string, index: number) { function findAndCreateComponent(url: string, index: number) {
const path = url.split('?')[0].split('#')[0] const path = url.split('?')[0].split('#')[0]
console.log('findAndCreateComponent called with:', { url, path, routes: routes.length })
for (const { matcher, element } of routes) { for (const { matcher, element } of routes) {
const match = matcher(path) const match = matcher(path)
console.log('Trying route matcher, match result:', !!match)
if (!match) continue if (!match) continue
if (!element) return {} if (!element) {
console.log('No element for this route')
return {}
}
const ref = createRef<TPageRef>() const ref = createRef<TPageRef>()
return { component: cloneElement(element, { ...match.params, index, ref } as any), ref }
// Decode URL parameters for relay pages
const params = { ...match.params }
if (params.url && typeof params.url === 'string') {
params.url = decodeURIComponent(params.url)
console.log('Decoded URL parameter:', params.url)
}
console.log('Creating component with params:', params)
return { component: cloneElement(element, { ...params, index, ref } as any), ref }
} }
console.log('No matching route found for:', path)
return {} return {}
} }

2
src/components/SearchBar/index.tsx

@ -282,7 +282,7 @@ const SearchBar = forwardRef<
className={cn( className={cn(
'bg-surface-background rounded-b-lg shadow-lg z-50', 'bg-surface-background rounded-b-lg shadow-lg z-50',
isSmallScreen isSmallScreen
? 'fixed top-12 inset-x-0' ? 'absolute top-full -translate-y-1 inset-x-0 pt-1'
: 'absolute top-full -translate-y-1 inset-x-0 pt-1 ' : 'absolute top-full -translate-y-1 inset-x-0 pt-1 '
)} )}
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}

185
src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx

@ -9,7 +9,8 @@ import { Slider } from '@/components/ui/slider'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
import { Hash, X, Users, Code, Coins, Newspaper, BookOpen, Scroll, Cpu, Trophy, Film, Heart, TrendingUp, Utensils, MapPin, Home, PawPrint, Shirt, Image, Zap, Settings, Book, Network, Car, Eye, Edit3 } from 'lucide-react' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Hash, X, Users, Code, Coins, Newspaper, BookOpen, Scroll, Cpu, Trophy, Film, Heart, TrendingUp, Utensils, MapPin, Home, PawPrint, Shirt, Image, Zap, Settings, Book, Network, Car, Eye, Edit3, ChevronDown, Check } from 'lucide-react'
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
@ -114,6 +115,7 @@ export default function CreateThreadDialog({
const [minPow, setMinPow] = useState(0) const [minPow, setMinPow] = useState(0)
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false) const [showAdvancedOptions, setShowAdvancedOptions] = useState(false)
const [isLoadingRelays, setIsLoadingRelays] = useState(true) const [isLoadingRelays, setIsLoadingRelays] = useState(true)
const [isTopicSelectorOpen, setIsTopicSelectorOpen] = useState(false)
// Readings options state // Readings options state
const [isReadingGroup, setIsReadingGroup] = useState(false) const [isReadingGroup, setIsReadingGroup] = useState(false)
@ -121,12 +123,12 @@ export default function CreateThreadDialog({
const [subject, setSubject] = useState('') const [subject, setSubject] = useState('')
const [showReadingsPanel, setShowReadingsPanel] = useState(false) const [showReadingsPanel, setShowReadingsPanel] = useState(false)
// Create combined topics list (predefined + dynamic) // Create combined topics list (predefined + dynamic) with hierarchy
const allAvailableTopics = useMemo(() => { const allAvailableTopics = useMemo(() => {
const combined = [...DISCUSSION_TOPICS] const combined = [...DISCUSSION_TOPICS]
if (dynamicTopics) { if (dynamicTopics) {
// Add dynamic main topics // Add dynamic main topics first
dynamicTopics.mainTopics.forEach(dynamicTopic => { dynamicTopics.mainTopics.forEach(dynamicTopic => {
combined.push({ combined.push({
id: dynamicTopic.id, id: dynamicTopic.id,
@ -135,13 +137,56 @@ export default function CreateThreadDialog({
}) })
}) })
// Add dynamic subtopics // Add dynamic subtopics grouped under their main topics
dynamicTopics.subtopics.forEach(dynamicTopic => { dynamicTopics.subtopics.forEach(dynamicTopic => {
combined.push({ // Try to find a related main topic
id: dynamicTopic.id, const predefinedMainTopic = DISCUSSION_TOPICS.find(pt =>
label: `${dynamicTopic.label} (${dynamicTopic.count}) 📌`, dynamicTopic.id.toLowerCase().includes(pt.id.toLowerCase()) ||
icon: Hash // Use Hash icon for dynamic topics pt.id.toLowerCase().includes(dynamicTopic.id.toLowerCase())
}) )
const relatedDynamicMainTopic = dynamicTopics.mainTopics.find(dt =>
dynamicTopic.id.toLowerCase().includes(dt.id.toLowerCase()) ||
dt.id.toLowerCase().includes(dynamicTopic.id.toLowerCase())
)
const parentTopic = predefinedMainTopic?.id || relatedDynamicMainTopic?.id
if (parentTopic) {
// Find the index of the parent topic and insert after it
const parentIndex = combined.findIndex(topic => topic.id === parentTopic)
if (parentIndex !== -1) {
combined.splice(parentIndex + 1, 0, {
id: dynamicTopic.id,
label: ` └─ ${dynamicTopic.label} (${dynamicTopic.count}) 📌`,
icon: Hash // Use Hash icon for dynamic topics
})
} else {
// Fallback: add at the end if parent not found
combined.push({
id: dynamicTopic.id,
label: `${dynamicTopic.label} (${dynamicTopic.count}) 📌`,
icon: Hash // Use Hash icon for dynamic topics
})
}
} else {
// No parent found, group under "General"
const generalIndex = combined.findIndex(topic => topic.id === 'general')
if (generalIndex !== -1) {
combined.splice(generalIndex + 1, 0, {
id: dynamicTopic.id,
label: ` └─ ${dynamicTopic.label} (${dynamicTopic.count}) 📌`,
icon: Hash // Use Hash icon for dynamic topics
})
} else {
// Fallback: add at the end if General not found
combined.push({
id: dynamicTopic.id,
label: `${dynamicTopic.label} (${dynamicTopic.count}) 📌`,
icon: Hash // Use Hash icon for dynamic topics
})
}
}
}) })
} }
@ -270,13 +315,70 @@ export default function CreateThreadDialog({
// Only add topic tag if it's a specific topic (not 'all' or 'general') // Only add topic tag if it's a specific topic (not 'all' or 'general')
if (selectedTopic !== 'all' && selectedTopic !== 'general') { if (selectedTopic !== 'all' && selectedTopic !== 'general') {
tags.push(['t', normalizeTopic(selectedTopic)]) // Check if this is a dynamic subtopic
const selectedDynamicTopic = dynamicTopics?.allTopics.find(dt => dt.id === selectedTopic)
if (selectedDynamicTopic?.isSubtopic) {
// For subtopics, we need to find the parent main topic
// First, try to find a predefined main topic that might be related
const predefinedMainTopic = DISCUSSION_TOPICS.find(pt =>
selectedTopic.toLowerCase().includes(pt.id.toLowerCase()) ||
pt.id.toLowerCase().includes(selectedTopic.toLowerCase())
)
if (predefinedMainTopic) {
// Add the predefined main topic first, then the subtopic
tags.push(['t', normalizeTopic(predefinedMainTopic.id)])
tags.push(['t', normalizeTopic(selectedTopic)])
} else {
// If no predefined main topic found, try to find a dynamic main topic
const relatedDynamicMainTopic = dynamicTopics?.mainTopics.find(dt =>
selectedTopic.toLowerCase().includes(dt.id.toLowerCase()) ||
dt.id.toLowerCase().includes(selectedTopic.toLowerCase())
)
if (relatedDynamicMainTopic) {
// Add the dynamic main topic first, then the subtopic
tags.push(['t', normalizeTopic(relatedDynamicMainTopic.id)])
tags.push(['t', normalizeTopic(selectedTopic)])
} else {
// Fallback: just add the subtopic and let the system categorize it under 'general'
// Don't add 'general' as a t-tag since it's the default fallback
tags.push(['t', normalizeTopic(selectedTopic)])
}
}
} else {
// Regular topic (predefined or dynamic main topic)
tags.push(['t', normalizeTopic(selectedTopic)])
}
} }
// Add hashtags as t-tags (deduplicate with selectedTopic if it's not 'all' or 'general') // Add hashtags as t-tags (deduplicate with selectedTopic and any parent topics)
const uniqueHashtags = (selectedTopic !== 'all' && selectedTopic !== 'general') let uniqueHashtags = hashtags
? hashtags.filter(hashtag => hashtag !== normalizeTopic(selectedTopic)) if (selectedTopic !== 'all' && selectedTopic !== 'general') {
: hashtags const selectedDynamicTopic = dynamicTopics?.allTopics.find(dt => dt.id === selectedTopic)
if (selectedDynamicTopic?.isSubtopic) {
// For subtopics, deduplicate against both the subtopic and its potential parent
const predefinedMainTopic = DISCUSSION_TOPICS.find(pt =>
selectedTopic.toLowerCase().includes(pt.id.toLowerCase()) ||
pt.id.toLowerCase().includes(selectedTopic.toLowerCase())
)
const relatedDynamicMainTopic = dynamicTopics?.mainTopics.find(dt =>
selectedTopic.toLowerCase().includes(dt.id.toLowerCase()) ||
dt.id.toLowerCase().includes(selectedTopic.toLowerCase())
)
const parentTopic = predefinedMainTopic?.id || relatedDynamicMainTopic?.id
uniqueHashtags = hashtags.filter(hashtag =>
hashtag !== normalizeTopic(selectedTopic) &&
(parentTopic ? hashtag !== normalizeTopic(parentTopic) : true)
)
} else {
// Regular topic
uniqueHashtags = hashtags.filter(hashtag => hashtag !== normalizeTopic(selectedTopic))
}
}
for (const hashtag of uniqueHashtags) { for (const hashtag of uniqueHashtags) {
tags.push(['t', hashtag]) tags.push(['t', hashtag])
} }
@ -395,18 +497,49 @@ export default function CreateThreadDialog({
{/* Topic Selection */} {/* Topic Selection */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="topic">{t('Topic')}</Label> <Label htmlFor="topic">{t('Topic')}</Label>
<select <Popover open={isTopicSelectorOpen} onOpenChange={setIsTopicSelectorOpen}>
id="topic" <PopoverTrigger asChild>
value={selectedTopic} <Button
onChange={(e) => setSelectedTopic(e.target.value)} variant="outline"
className="w-full px-3 py-2 bg-white dark:bg-gray-800 text-black dark:text-white border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500" role="combobox"
> aria-expanded={isTopicSelectorOpen}
{allAvailableTopics.map((topic) => ( className="w-full justify-between"
<option key={topic.id} value={topic.id}> >
{topic.label} {selectedTopicInfo?.label || t('Select topic...')}
</option> <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
))} </Button>
</select> </PopoverTrigger>
<PopoverContent
className="w-[--radix-popover-trigger-width] p-2 z-[10000]"
align="start"
side="bottom"
sideOffset={4}
>
<div className="max-h-60 overflow-y-auto">
{allAvailableTopics.map((topic) => {
const Icon = topic.icon
return (
<div
key={topic.id}
className="flex items-center p-2 hover:bg-accent cursor-pointer rounded"
onClick={() => {
setSelectedTopic(topic.id)
setIsTopicSelectorOpen(false)
}}
>
<Check
className={`mr-2 h-4 w-4 ${
selectedTopic === topic.id ? 'opacity-100' : 'opacity-0'
}`}
/>
<Icon className="mr-2 h-4 w-4" />
{topic.label}
</div>
)
})}
</div>
</PopoverContent>
</Popover>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{t('Threads are organized by topics. Choose a topic that best fits your discussion.')} {t('Threads are organized by topics. Choose a topic that best fits your discussion.')}
</p> </p>

116
src/pages/primary/DiscussionsPage/index.tsx

@ -14,6 +14,7 @@ import client from '@/services/client.service'
import { DISCUSSION_TOPICS } from './CreateThreadDialog' import { DISCUSSION_TOPICS } from './CreateThreadDialog'
import ThreadCard from './ThreadCard' import ThreadCard from './ThreadCard'
import CreateThreadDialog from './CreateThreadDialog' import CreateThreadDialog from './CreateThreadDialog'
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
// Simple event map type // Simple event map type
type EventMapEntry = { type EventMapEntry = {
@ -271,7 +272,17 @@ function getEnhancedTopicFromTags(allTopics: string[], predefinedTopicIds: strin
return 'general' return 'general'
} }
const DiscussionsPage = forwardRef(() => { function DiscussionsPageTitlebar() {
const { t } = useTranslation()
return (
<div className="flex items-center gap-2">
<h1 className="text-lg font-semibold">{t('Discussions')}</h1>
</div>
)
}
const DiscussionsPage = forwardRef((_, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const { favoriteRelays, blockedRelays } = useFavoriteRelays() const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const { pubkey } = useNostr() const { pubkey } = useNostr()
@ -625,43 +636,45 @@ const DiscussionsPage = forwardRef(() => {
}>() }>()
searchedEntries.forEach((entry) => { searchedEntries.forEach((entry) => {
const mainTopic = entry.categorizedTopic
// Initialize main topic group if it doesn't exist
if (!mainTopicGroups.has(mainTopic)) {
mainTopicGroups.set(mainTopic, {
entries: [],
subtopics: new Map()
})
}
const group = mainTopicGroups.get(mainTopic)!
// Check if this entry has any dynamic subtopics // Check if this entry has any dynamic subtopics
const entrySubtopics = entry.allTopics.filter(topic => { const entrySubtopics = entry.allTopics.filter(topic => {
const dynamicTopic = dynamicTopics.allTopics.find(dt => dt.id === topic && dt.isSubtopic) const dynamicTopic = dynamicTopics.allTopics.find(dt => dt.id === topic && dt.isSubtopic)
return !!dynamicTopic return !!dynamicTopic
}) })
// Debug logging for subtopic detection
// if (entrySubtopics.length > 0) {
// console.log('Found subtopics for entry:', {
// threadId: entry.event.id.substring(0, 8),
// allTopics: entry.allTopics,
// entrySubtopics,
// dynamicTopics: dynamicTopics.allTopics.map(dt => ({ id: dt.id, isSubtopic: dt.isSubtopic }))
// })
// }
if (entrySubtopics.length > 0) { if (entrySubtopics.length > 0) {
// Group under the first subtopic found // This entry has subtopics - group under the main topic with the subtopic
const mainTopic = entry.categorizedTopic
const subtopic = entrySubtopics[0] const subtopic = entrySubtopics[0]
// Initialize main topic group if it doesn't exist
if (!mainTopicGroups.has(mainTopic)) {
mainTopicGroups.set(mainTopic, {
entries: [],
subtopics: new Map()
})
}
const group = mainTopicGroups.get(mainTopic)!
// Add to subtopic group
if (!group.subtopics.has(subtopic)) { if (!group.subtopics.has(subtopic)) {
group.subtopics.set(subtopic, []) group.subtopics.set(subtopic, [])
} }
group.subtopics.get(subtopic)!.push(entry) group.subtopics.get(subtopic)!.push(entry)
} else { } else {
// No subtopic, add to main topic // No subtopic, add to main topic
const mainTopic = entry.categorizedTopic
// Initialize main topic group if it doesn't exist
if (!mainTopicGroups.has(mainTopic)) {
mainTopicGroups.set(mainTopic, {
entries: [],
subtopics: new Map()
})
}
const group = mainTopicGroups.get(mainTopic)!
group.entries.push(entry) group.entries.push(entry)
} }
}) })
@ -688,16 +701,13 @@ const DiscussionsPage = forwardRef(() => {
group.subtopics.forEach((entries) => sortEntries(entries)) group.subtopics.forEach((entries) => sortEntries(entries))
}) })
// Convert to array format for rendering with proper hierarchy // Sort groups by most recent activity (newest first)
const result: Array<[string, EventMapEntry[], Map<string, EventMapEntry[]>]> = [] const sortedGroups = new Map<string, { entries: EventMapEntry[], subtopics: Map<string, EventMapEntry[]> }>()
mainTopicGroups.forEach((group, mainTopic) => { const sortedEntries = Array.from(mainTopicGroups.entries()).sort(([, aGroup], [, bGroup]) => {
// Add main topic with its subtopics const aEntries = aGroup.entries
result.push([mainTopic, group.entries, group.subtopics]) const bEntries = bGroup.entries
})
// Sort groups by most recent activity (newest first)
result.sort(([, aEntries], [, bEntries]) => {
if (aEntries.length === 0 && bEntries.length === 0) return 0 if (aEntries.length === 0 && bEntries.length === 0) return 0
if (aEntries.length === 0) return 1 if (aEntries.length === 0) return 1
if (bEntries.length === 0) return -1 if (bEntries.length === 0) return -1
@ -716,7 +726,11 @@ const DiscussionsPage = forwardRef(() => {
return bMostRecent - aMostRecent // Newest first return bMostRecent - aMostRecent // Newest first
}) })
return result sortedEntries.forEach(([topic, group]) => {
sortedGroups.set(topic, group)
})
return sortedGroups
}, [searchedEntries, dynamicTopics]) }, [searchedEntries, dynamicTopics])
// Handle refresh // Handle refresh
@ -772,11 +786,14 @@ const DiscussionsPage = forwardRef(() => {
} }
return ( return (
<div className="flex flex-col h-full"> <PrimaryPageLayout
{/* Header */} ref={ref}
<div className="flex flex-col gap-4 p-4 border-b"> pageName="discussions"
titlebar={<DiscussionsPageTitlebar />}
displayScrollToTopButton
>
<div className="flex flex-col gap-4 p-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h1 className="text-2xl font-bold">{t('Discussions')}</h1>
<button <button
onClick={() => setShowCreateDialog(true)} onClick={() => setShowCreateDialog(true)}
className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 w-full sm:w-auto" className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 w-full sm:w-auto"
@ -845,35 +862,32 @@ const DiscussionsPage = forwardRef(() => {
</div> </div>
{/* Content */} {/* Content */}
<div className="flex-1 overflow-y-auto p-2 sm:p-4"> <div className="p-2 sm:p-4 pb-20 sm:pb-4">
{loading ? ( {loading ? (
<div className="text-center py-8">{t('Loading...')}</div> <div className="text-center py-8">{t('Loading...')}</div>
) : isSearching ? ( ) : isSearching ? (
<div className="text-center py-8">{t('Searching...')}</div> <div className="text-center py-8">{t('Searching...')}</div>
) : ( ) : (
<div className="space-y-6"> <div className="space-y-6 pb-8">
{groupedEvents.map(([topic, events, subtopics]) => { {Array.from(groupedEvents.entries()).map(([mainTopic, group]) => {
const topicInfo = availableTopics.find(t => t.topic === topic) const topicInfo = availableTopics.find(t => t.topic === mainTopic)
const isDynamicMain = topicInfo?.isDynamic && topicInfo?.isMainTopic const isDynamicMain = topicInfo?.isDynamic && topicInfo?.isMainTopic
const isDynamicSubtopic = topicInfo?.isDynamic && topicInfo?.isSubtopic
return ( return (
<div key={topic} className="space-y-4"> <div key={mainTopic} className="space-y-4">
{/* Main Topic Header */} {/* Main Topic Header */}
<h2 className="text-lg font-semibold mb-3 capitalize flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2"> <h2 className="text-lg font-semibold mb-3 capitalize flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
{isDynamicMain && <span className="text-orange-500">🔥</span>} {isDynamicMain && <span className="text-orange-500">🔥</span>}
{isDynamicSubtopic && <span className="text-blue-500">📌</span>} {mainTopic} ({group.entries.length + Array.from(group.subtopics.values()).reduce((sum, events) => sum + events.length, 0)} {group.entries.length + Array.from(group.subtopics.values()).reduce((sum, events) => sum + events.length, 0) === 1 ? t('thread') : t('threads')})
{topic} ({events.length} {events.length === 1 ? t('thread') : t('threads')})
</span> </span>
{isDynamicMain && <span className="text-xs bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 px-2 py-1 rounded w-fit">Main Topic</span>} {isDynamicMain && <span className="text-xs bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 px-2 py-1 rounded w-fit">Main Topic</span>}
{isDynamicSubtopic && <span className="text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded w-fit">Subtopic</span>}
</h2> </h2>
{/* Main Topic Threads */} {/* Main Topic Threads */}
{events.length > 0 && ( {group.entries.length > 0 && (
<div className="space-y-3"> <div className="space-y-3">
{events.map((entry) => ( {group.entries.map((entry) => (
<ThreadCard <ThreadCard
key={entry.event.id} key={entry.event.id}
thread={entry.event} thread={entry.event}
@ -888,9 +902,9 @@ const DiscussionsPage = forwardRef(() => {
)} )}
{/* Subtopic Groups */} {/* Subtopic Groups */}
{subtopics.size > 0 && ( {group.subtopics.size > 0 && (
<div className="ml-2 sm:ml-4 space-y-4"> <div className="ml-2 sm:ml-4 space-y-4">
{Array.from(subtopics.entries()).map(([subtopic, subtopicEvents]) => { {Array.from(group.subtopics.entries()).map(([subtopic, subtopicEvents]) => {
const subtopicInfo = availableTopics.find(t => t.topic === subtopic) const subtopicInfo = availableTopics.find(t => t.topic === subtopic)
const isSubtopicDynamic = subtopicInfo?.isDynamic && subtopicInfo?.isSubtopic const isSubtopicDynamic = subtopicInfo?.isDynamic && subtopicInfo?.isSubtopic
@ -939,7 +953,7 @@ const DiscussionsPage = forwardRef(() => {
onThreadCreated={handleCreateThread} onThreadCreated={handleCreateThread}
/> />
)} )}
</div> </PrimaryPageLayout>
) )
}) })

Loading…
Cancel
Save