- Upload photo via client admin Supabase (service role, bypass RLS) avec redimensionnement sharp (800px max, JPEG q85) - Gestion d'erreurs propre dans les 5 repositories : helper dbError + Sentry.captureException - Formulaire : try/catch/finally pour éviter UI bloquée, validation taille photo côté client (30 Mo max) - Validation date avec date-fns (parseISO/isFuture) au lieu de construction manuelle - bodySizeLimit 30mb pour les server actions - Redirections UI vers /nouvelle-prise-de-position + bouton conditionnel sur /s - Tests contribute-statement use case (21 tests) + findByWikipediaUrl dans les mocks existants
98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
import { Metadata } from 'next'
|
|
import Link from 'next/link'
|
|
import { Effect } from 'effect'
|
|
import { createSSRSupabaseClient } from '../../infra/supabase/ssr'
|
|
import { createPublicFigureRepository } from '../../infra/database/public-figure-repository-supabase'
|
|
import { getAuthenticatedContributor } from '../actions/get-authenticated-contributor'
|
|
import { canPerform } from '../../domain/reputation/permissions'
|
|
import Button from '../../components/ui/Button'
|
|
import FigureAvatar from '../../components/figures/FigureAvatar'
|
|
import ContentWithSidebar from '../../components/layout/ContentWithSidebar'
|
|
import ErrorDisplay from '../../components/layout/ErrorDisplay'
|
|
import styles from './personalities.module.css'
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'Personnalités',
|
|
description:
|
|
'Les personnalités publiques référencées sur Débats.co et leurs prises de position sur les sujets de société.',
|
|
}
|
|
|
|
export default async function PersonalitiesPage() {
|
|
try {
|
|
const supabase = await createSSRSupabaseClient()
|
|
const publicFigureRepo = createPublicFigureRepository(supabase)
|
|
|
|
const contributor = await getAuthenticatedContributor()
|
|
const canAddPersonality = contributor
|
|
? canPerform(contributor.reputation, 'add_personality')
|
|
: false
|
|
|
|
const publicFigures = await Effect.runPromise(publicFigureRepo.findAll())
|
|
|
|
const publicFiguresWithStats = await Promise.all(
|
|
publicFigures.map(async (figure) => {
|
|
const stats = await Effect.runPromise(publicFigureRepo.getStats(figure.id))
|
|
return { figure, stats }
|
|
}),
|
|
)
|
|
|
|
publicFiguresWithStats.sort((a, b) => b.stats.subjectsCount - a.stats.subjectsCount)
|
|
|
|
return (
|
|
<ContentWithSidebar topMargin>
|
|
<div className={styles.pageHeader}>
|
|
<h1 className={styles.pageTitle}>LES PERSONNALITÉS</h1>
|
|
{canAddPersonality && (
|
|
<Button href="/nouvelle-prise-de-position" size="small">
|
|
Ajouter une personnalité
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className={styles.personalitiesIndex}>
|
|
{publicFiguresWithStats.length === 0 ? (
|
|
<p>Aucune personnalité pour le moment.</p>
|
|
) : (
|
|
publicFiguresWithStats.map(({ figure, stats }) => (
|
|
<div key={figure.id} className={styles.personalityItem}>
|
|
<div className={styles.personalityIdentity}>
|
|
<div className={styles.personalityAvatar}>
|
|
<FigureAvatar slug={figure.slug} name={figure.name} />
|
|
</div>
|
|
<h3 className={styles.personalityName}>
|
|
<Link href={`/p/${figure.slug}`}>{figure.name}</Link>
|
|
</h3>
|
|
<div className={styles.counters}>
|
|
<span className={styles.countItem}>
|
|
{stats.subjectsCount} sujet
|
|
{stats.subjectsCount !== 1 ? 's' : ''} actif
|
|
{stats.subjectsCount !== 1 ? 's' : ''}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.personalityPresentation}>
|
|
<p className={styles.presentationText}>{figure.presentation}</p>
|
|
</div>
|
|
|
|
<div className={styles.seeMore}>
|
|
<Link href={`/p/${figure.slug}`} className={styles.seeMoreLink}>
|
|
Voir les sujets actifs
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</ContentWithSidebar>
|
|
)
|
|
} catch (error) {
|
|
return (
|
|
<ErrorDisplay
|
|
title="Erreur"
|
|
message="Impossible de charger les personnalités."
|
|
detail={error instanceof Error ? error.message : 'Erreur inconnue'}
|
|
/>
|
|
)
|
|
}
|
|
}
|