debats/app/p/page.tsx
Jalil Arfaoui fc13ccad85 feat: formulaire unifié de contribution avec upload admin, gestion d'erreurs Sentry et validation date-fns
- 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
2026-02-24 01:22:56 +01:00

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