2026-01-13 14:49:19 +01:00

167 lines
5.7 KiB
TypeScript

import { useState, useEffect, useMemo, useCallback } from 'react'
import { useArticles } from '@/hooks/useArticles'
import { useNostrAuth } from '@/hooks/useNostrAuth'
import { applyFiltersAndSort } from '@/lib/articleFiltering'
import { getAuthorsByCategory, sortAuthors } from '@/lib/authorFiltering'
import type { Article } from '@/types/nostr'
import type { ArticleFilters } from '@/components/ArticleFilters'
import { HomeView } from '@/components/HomeView'
function usePresentationArticles(allArticles: Article[]): Map<string, Article> {
return useMemo(() => {
const presentations = new Map<string, Article>()
allArticles.forEach((article) => {
if (article.isPresentation && article.pubkey) {
presentations.set(article.pubkey, article)
}
})
return presentations
}, [allArticles])
}
function useHomeState(): {
searchQuery: string
setSearchQuery: React.Dispatch<React.SetStateAction<string>>
selectedCategory: ArticleFilters['category']
setSelectedCategory: React.Dispatch<React.SetStateAction<ArticleFilters['category']>>
filters: ArticleFilters
setFilters: React.Dispatch<React.SetStateAction<ArticleFilters>>
unlockedArticles: Set<string>
setUnlockedArticles: React.Dispatch<React.SetStateAction<Set<string>>>
} {
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState<ArticleFilters['category']>(null)
const [filters, setFilters] = useState<ArticleFilters>({
authorPubkey: null,
sortBy: 'newest',
category: null,
})
const [unlockedArticles, setUnlockedArticles] = useState<Set<string>>(new Set())
return {
searchQuery,
setSearchQuery,
selectedCategory,
setSelectedCategory,
filters,
setFilters,
unlockedArticles,
setUnlockedArticles,
}
}
function useArticlesData(searchQuery: string): {
allArticlesRaw: Article[]
allArticles: Article[]
loading: boolean
error: string | null
loadArticleContent: (articleId: string, authorPubkey: string) => Promise<Article | null>
presentationArticles: Map<string, Article>
} {
const { articles: allArticlesRaw, allArticles, loading, error, loadArticleContent } = useArticles(searchQuery, null)
const presentationArticles = usePresentationArticles(allArticles)
return { allArticlesRaw, allArticles, loading, error, loadArticleContent, presentationArticles }
}
function useCategorySync(selectedCategory: ArticleFilters['category'], setFilters: (value: ArticleFilters | ((prev: ArticleFilters) => ArticleFilters)) => void): void {
useEffect(() => {
setFilters((prev) => ({
...prev,
category: selectedCategory,
}))
}, [selectedCategory, setFilters])
}
function useFilteredArticles(
allArticlesRaw: Article[],
searchQuery: string,
filters: ArticleFilters,
presentationArticles: Map<string, Article>
): Article[] {
return useMemo(
() => applyFiltersAndSort(allArticlesRaw, searchQuery, filters, presentationArticles),
[allArticlesRaw, searchQuery, filters, presentationArticles]
)
}
function useUnlockHandler(
loadArticleContent: (id: string, pubkey: string) => Promise<Article | null>,
setUnlockedArticles: React.Dispatch<React.SetStateAction<Set<string>>>
): (article: Article) => Promise<void> {
return useCallback(
async (article: Article) => {
const fullArticle = await loadArticleContent(article.id, article.pubkey)
if (fullArticle?.paid) {
setUnlockedArticles((prev) => new Set([...prev, article.id]))
}
},
[loadArticleContent, setUnlockedArticles]
)
}
function useHomeController(): {
searchQuery: string
setSearchQuery: React.Dispatch<React.SetStateAction<string>>
selectedCategory: ArticleFilters['category']
setSelectedCategory: React.Dispatch<React.SetStateAction<ArticleFilters['category']>>
filters: ArticleFilters
setFilters: React.Dispatch<React.SetStateAction<ArticleFilters>>
articles: Article[]
allArticles: Article[]
authors: Article[]
allAuthors: Article[]
loading: boolean
error: string | null
unlockedArticles: Set<string>
handleUnlock: (article: Article) => Promise<void>
} {
useNostrAuth()
const {
searchQuery,
setSearchQuery,
selectedCategory,
setSelectedCategory,
filters,
setFilters,
unlockedArticles,
setUnlockedArticles,
} = useHomeState()
const { allArticlesRaw, allArticles, loading, error, loadArticleContent, presentationArticles } =
useArticlesData(searchQuery)
// Presentation guard removed - users can browse without author page
useCategorySync(selectedCategory, setFilters)
const articles = useFilteredArticles(allArticlesRaw, searchQuery, filters, presentationArticles)
const handleUnlock = useUnlockHandler(loadArticleContent, setUnlockedArticles)
const { authors, allAuthors } = useAuthorsByCategory(presentationArticles, selectedCategory)
return { searchQuery, setSearchQuery, selectedCategory, setSelectedCategory, filters, setFilters, articles, allArticles, authors, allAuthors, loading, error, unlockedArticles, handleUnlock }
}
function useAuthorsByCategory(
presentationArticles: Map<string, Article>,
selectedCategory: ArticleFilters['category']
): { authors: Article[]; allAuthors: Article[] } {
const allAuthors = useMemo((): Article[] => {
const authorsArray = Array.from(presentationArticles.values())
return sortAuthors(authorsArray)
}, [presentationArticles])
const authors = useMemo((): Article[] => getAuthorsByCategory(presentationArticles, selectedCategory), [presentationArticles, selectedCategory])
return { authors, allAuthors }
}
export default function Home(): React.ReactElement {
const controller = useHomeController()
return (
<HomeView
{...controller}
onUnlock={(a) => {
void controller.handleUnlock(a)
}}
/>
)
}