- Création lib/platformCommissions.ts : configuration centralisée des commissions - Articles : 800 sats (700 auteur, 100 plateforme) - Avis : 70 sats (49 lecteur, 21 plateforme) - Sponsoring : 0.046 BTC (0.042 auteur, 0.004 plateforme) - Validation des montants à chaque étape : - Publication : vérification du montant avant publication - Paiement : vérification du montant avant acceptation - Erreurs explicites si montant incorrect - Tracking des commissions sur Nostr : - Tags author_amount et platform_commission dans événements - Interface ContentDeliveryTracking étendue - Traçabilité complète pour audit - Logs structurés avec informations de commission - Documentation complète du système Les commissions sont maintenant systématiques, validées et traçables.
155 lines
4.6 KiB
TypeScript
155 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']>(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) {
|
|
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,
|
|
}))
|
|
}, [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)
|
|
}}
|
|
/>
|
|
)
|
|
}
|