Nicolas Cantu 90ff8282f1 feat: Implémentation système de commissions systématique et incontournable
- 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.
2025-12-27 21:11:09 +01:00

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)
}}
/>
)
}