import { useCallback, useEffect, useState } from 'react'
import type { Review, Article } from '@/types/nostr'
import { getReviewsForArticle } from '@/lib/reviews'
import { getReviewTipsForArticle } from '@/lib/reviewAggregation'
import { ReviewForm } from './ReviewForm'
import { ReviewTipForm } from './ReviewTipForm'
import { t } from '@/lib/i18n'
interface ArticleReviewsProps {
article: Article
authorPubkey: string
}
export function ArticleReviews({ article, authorPubkey }: ArticleReviewsProps): React.ReactElement {
const data = useArticleReviewsData({ articleId: article.id, authorPubkey })
const reviewForm = useReviewFormState({ reload: data.reload })
const tipSelection = useReviewTipSelection({ article, reviews: data.reviews, reload: data.reload })
return (
{reviewForm.show && (
)}
{data.loading &&
{t('common.loading')}
}
{data.error &&
{data.error}
}
{!data.loading && !data.error && data.reviews.length === 0 && !reviewForm.show && (
{t('review.empty')}
)}
{!data.loading && !data.error &&
}
)
}
interface ReviewTipSelectionController {
article: Article
selectedReviewForTip: Review | null
onTipSuccess: () => void
clear: () => void
select: (reviewId: string) => void
}
interface ArticleReviewsData {
reviews: Review[]
tips: number
loading: boolean
error: string | null
reload: () => Promise
}
function useArticleReviewsData({
articleId,
authorPubkey,
}: {
articleId: string
authorPubkey: string
}): ArticleReviewsData {
const [reviews, setReviews] = useState([])
const [tips, setTips] = useState(0)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const reload = useCallback(async (): Promise => {
setLoading(true)
setError(null)
try {
const [list, tipsTotal] = await Promise.all([
getReviewsForArticle(articleId),
getReviewTipsForArticle({ authorPubkey, articleId }),
])
setReviews(list)
setTips(tipsTotal)
} catch (loadError) {
setError(loadError instanceof Error ? loadError.message : 'Erreur lors du chargement des critiques')
} finally {
setLoading(false)
}
}, [articleId, authorPubkey])
useEffect(() => {
void reload()
}, [reload])
return {
reviews,
tips,
loading,
error,
reload,
}
}
function useReviewFormState({ reload }: { reload: () => Promise }): {
show: boolean
open: () => void
close: () => void
onSuccess: () => void
} {
const [show, setShow] = useState(false)
const open = useCallback((): void => setShow(true), [])
const close = useCallback((): void => setShow(false), [])
const onSuccess = useCallback((): void => {
close()
void reload()
}, [close, reload])
return { show, open, close, onSuccess }
}
function useReviewTipSelection({
article,
reviews,
reload,
}: {
article: Article
reviews: Review[]
reload: () => Promise
}): ReviewTipSelectionController {
const [selectedReviewId, setSelectedReviewId] = useState(null)
const select = useCallback((reviewId: string): void => setSelectedReviewId(reviewId), [])
const clear = useCallback((): void => setSelectedReviewId(null), [])
const onTipSuccess = useCallback((): void => {
clear()
void reload()
}, [clear, reload])
const selectedReviewForTip = selectedReviewId ? findReviewById(reviews, selectedReviewId) : null
return { article, selectedReviewForTip, onTipSuccess, clear, select }
}
function findReviewById(reviews: Review[], reviewId: string): Review | null {
const review = reviews.find((r) => r.id === reviewId)
return review ?? null
}
function SelectedReviewTipForm({ selection }: { selection: ReviewTipSelectionController }): React.ReactElement | null {
if (!selection.selectedReviewForTip) {
return null
}
return (
)
}
function ArticleReviewsHeader({ tips, onAddReview }: { tips: number; onAddReview: () => void }): React.ReactElement {
return (
{t('review.title')}
{t('review.tips.total', { amount: tips })}
)
}
function ArticleReviewsList({ reviews, onTipReview }: { reviews: Review[]; onTipReview: (reviewId: string) => void }): React.ReactElement {
return (
{reviews.map((r) => (
{r.title && (
{r.title}
)}
{r.content}
{r.text && (
{r.text}
)}
{t('review.reviewer')}: {formatPubkey(r.reviewerPubkey)}
•
{formatDate(r.createdAt)}
))}
)
}
function formatPubkey(pubkey: string): string {
return `${pubkey.slice(0, 8)}...${pubkey.slice(-8)}`
}
function formatDate(timestamp: number): string {
return new Date(timestamp * 1000).toLocaleString()
}