Files
Momento/memento-note/app/(public)/p/[slug]/report/page.tsx
Antigravity a6cdcba76f
Some checks failed
CI / Deploy production (on server) (push) Has been cancelled
CI / Lint, Unit Tests & Build (push) Has been cancelled
feat: publication pages — design moderne + modération IA + signalement
Page publique (/p/[slug]):
- Design éditorial moderne (Source Serif 4 + Inter, couleurs Momento)
- KaTeX rendu côté serveur (plus de 3184089 en brut)
- Callouts colorés, toggles, colonnes, code blocks rendus correctement
- Barre sticky avec logo + bouton Signaler
- Temps de lecture estimé
- Footer Momento
- Responsive

Modération:
- content-moderation.service.ts — IA classe safe/flagged/blocked
- Sera appelée automatiquement à la publication

Signalement:
- Page /p/[slug]/report — formulaire de signalement public
- API /api/notes/report — stocke notification au propriétaire + admins
- 8 motifs: illegal, haine, violence, sexuel, harcèlement, copyright, spam, autre

i18n: FR/EN
2026-06-19 22:11:51 +00:00

82 lines
4.4 KiB
TypeScript

'use client'
import { useState } from 'react'
import { Flag, X, Loader2, Check } from 'lucide-react'
export default function ReportPage({ params }: { params: { slug: string } }) {
const [reason, setReason] = useState('')
const [details, setDetails] = useState('')
const [submitting, setSubmitting] = useState(false)
const [done, setDone] = useState(false)
const handleSubmit = async () => {
setSubmitting(true)
try {
await fetch('/api/notes/report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ slug: params.slug, reason, details }),
})
setDone(true)
} catch {}
finally { setSubmitting(false) }
}
return (
<div style={{ minHeight: '100vh', background: '#FAF9F5', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }}>
<div style={{ maxWidth: '440px', width: '100%', background: 'white', borderRadius: '16px', border: '1px solid rgba(0,0,0,0.08)', padding: '32px' }}>
{done ? (
<div style={{ textAlign: 'center' }}>
<div style={{ width: '48px', height: '48px', borderRadius: '50%', background: '#f0fdf4', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 16px' }}>
<Check size={24} style={{ color: '#22c55e' }} />
</div>
<h2 style={{ fontFamily: 'Georgia, serif', fontSize: '18px', fontWeight: 600, margin: '0 0 8px' }}>Signalement envoyé</h2>
<p style={{ fontSize: '14px', color: '#666', margin: 0 }}>Merci. Notre équipe va examiner ce contenu dans les plus brefs délais.</p>
</div>
) : (
<>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }}>
<div style={{ width: '32px', height: '32px', borderRadius: '8px', background: '#fef2f2', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Flag size={16} style={{ color: '#ef4444' }} />
</div>
<h2 style={{ fontFamily: 'Georgia, serif', fontSize: '17px', fontWeight: 600, margin: 0 }}>Signaler ce contenu</h2>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ fontSize: '12px', fontWeight: 600, color: '#666', textTransform: 'uppercase', letterSpacing: '0.05em', display: 'block', marginBottom: '8px' }}>Motif</label>
<select value={reason} onChange={e => setReason(e.target.value)} style={{ width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #ddd', fontSize: '14px', outline: 'none', background: 'white' }}>
<option value="">Sélectionner...</option>
<option value="illegal">Contenu illégal</option>
<option value="hate">Discours de haine</option>
<option value="violence">Violence / Menaces</option>
<option value="sexual">Contenu sexuel</option>
<option value="harassment">Harcèlement</option>
<option value="copyright">Violation de copyright</option>
<option value="spam">Spam / Arnaque</option>
<option value="other">Autre</option>
</select>
</div>
<div style={{ marginBottom: '20px' }}>
<label style={{ fontSize: '12px', fontWeight: 600, color: '#666', textTransform: 'uppercase', letterSpacing: '0.05em', display: 'block', marginBottom: '8px' }}>Détails (optionnel)</label>
<textarea value={details} onChange={e => setDetails(e.target.value)} rows={3} placeholder="Expliquez le problème..." style={{ width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #ddd', fontSize: '14px', outline: 'none', resize: 'none', background: 'white' }} />
</div>
<button
onClick={handleSubmit}
disabled={!reason || submitting}
style={{
width: '100%', padding: '12px', borderRadius: '10px', border: 'none',
background: !reason || submitting ? '#ccc' : '#ef4444',
color: 'white', fontSize: '14px', fontWeight: 600, cursor: !reason || submitting ? 'not-allowed' : 'pointer',
}}
>
{submitting ? <Loader2 size={16} className="animate-spin" /> : 'Signaler'}
</button>
</>
)}
</div>
</div>
)
}