Nicolas Cantu 3000872dbc refactoring
- **Motivations :** Assurer passage du lint strict et clarifier la logique paiements/publications.

- **Root causes :** Fonctions trop longues, promesses non gérées et typages WebLN/Nostr incomplets.

- **Correctifs :** Refactor PaymentModal (handlers void), extraction helpers articlePublisher, simplification polling sponsoring/zap, corrections curly et awaits.

- **Evolutions :** Nouveau module articlePublisherHelpers pour présentation/aiguillage contenu privé.

- **Page affectées :** components/PaymentModal.tsx, lib/articlePublisher.ts, lib/articlePublisherHelpers.ts, lib/paymentPolling.ts, lib/sponsoring.ts, lib/nostrZapVerification.ts et dépendances liées.
2025-12-22 17:56:00 +01:00

157 lines
4.6 KiB
TypeScript

import { useState, useEffect, useMemo, useCallback } from 'react'
import { useRouter } from 'next/router'
import { useArticles } from '@/hooks/useArticles'
import { useNostrConnect } from '@/hooks/useNostrConnect'
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
import { applyFiltersAndSort } from '@/lib/articleFiltering'
import type { Article } from '@/types/nostr'
import type { ArticleFilters } from '@/components/ArticleFilters'
import { HomeView } from '@/components/HomeView'
function usePresentationGuard(connected: boolean, pubkey: string | null) {
const router = useRouter()
const { checkPresentationExists } = useAuthorPresentation(pubkey ?? null)
useEffect(() => {
const ensurePresentation = async () => {
if (!connected || !pubkey) {
return
}
const presentation = await checkPresentationExists()
if (!presentation) {
await router.push('/presentation')
}
}
void ensurePresentation()
}, [checkPresentationExists, connected, pubkey, router])
}
function usePresentationArticles(allArticles: Article[]) {
const [presentationArticles, setPresentationArticles] = useState<Map<string, Article>>(new Map())
useEffect(() => {
const presentations = new Map<string, Article>()
allArticles.forEach((article) => {
if (article.isPresentation && article.pubkey) {
presentations.set(article.pubkey, article)
}
})
setPresentationArticles(presentations)
}, [allArticles])
return presentationArticles
}
function useHomeState() {
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState<ArticleFilters['category']>('all')
const [filters, setFilters] = useState<ArticleFilters>({
authorPubkey: null,
minPrice: null,
maxPrice: null,
sortBy: 'newest',
category: 'all',
})
const [unlockedArticles, setUnlockedArticles] = useState<Set<string>>(new Set())
return {
searchQuery,
setSearchQuery,
selectedCategory,
setSelectedCategory,
filters,
setFilters,
unlockedArticles,
setUnlockedArticles,
}
}
function useArticlesData(searchQuery: string) {
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) {
useEffect(() => {
setFilters((prev) => ({
...prev,
category: selectedCategory ?? 'all',
}))
}, [selectedCategory, setFilters])
}
function useFilteredArticles(
allArticlesRaw: Article[],
searchQuery: string,
filters: ArticleFilters,
presentationArticles: Map<string, 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>>>
) {
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() {
const { connected, pubkey } = useNostrConnect()
const {
searchQuery,
setSearchQuery,
selectedCategory,
setSelectedCategory,
filters,
setFilters,
unlockedArticles,
setUnlockedArticles,
} = useHomeState()
const { allArticlesRaw, allArticles, loading, error, loadArticleContent, presentationArticles } =
useArticlesData(searchQuery)
usePresentationGuard(connected, pubkey)
useCategorySync(selectedCategory, setFilters)
const articles = useFilteredArticles(allArticlesRaw, searchQuery, filters, presentationArticles)
const handleUnlock = useUnlockHandler(loadArticleContent, setUnlockedArticles)
return {
searchQuery,
setSearchQuery,
selectedCategory,
setSelectedCategory,
filters,
setFilters,
articles,
allArticles,
loading,
error,
unlockedArticles,
handleUnlock,
}
}
export default function Home() {
const controller = useHomeController()
return (
<HomeView
{...controller}
onUnlock={(a) => {
void controller.handleUnlock(a)
}}
/>
)
}