feat: système de signalement bug/idée + modal changelog avec limitations

- AppReport entity + migration (type, message, fingerprint SHA-256, created_at)
- POST /api/report avec rate-limit 5/heure par fingerprint IP
- ReportModal : formulaire type radio (bug/idée) + textarea 1 000 chars max
- ChangelogModal : fonctionnalités en prod, roadmap, 5 limitations honnêtes
- Deux boutons flottants bas-gauche (? info + ✉ signalement)
- Légende remontée pour éviter le chevauchement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gwadaking
2026-04-14 12:52:32 -04:00
parent 83690b94ab
commit fd476f50c2
12 changed files with 643 additions and 7 deletions

View File

@@ -0,0 +1,71 @@
import { useState } from 'react';
import { api } from '../../api/client';
import './ReportModal.css';
export default function ReportModal({ onClose }) {
const [type, setType] = useState('bug');
const [message, setMessage] = useState('');
const [status, setStatus] = useState('idle'); // idle | loading | ok | error
const submit = async (e) => {
e.preventDefault();
if (status === 'loading' || status === 'ok') return;
setStatus('loading');
try {
await api.report.create({ type, message });
setStatus('ok');
} catch {
setStatus('error');
}
};
return (
<div className="rmodal-backdrop" onClick={onClose}>
<div className="rmodal" role="dialog" aria-modal="true" aria-label="Signalement" onClick={e => e.stopPropagation()}>
<button className="rmodal__close" onClick={onClose} aria-label="Fermer"></button>
{status === 'ok' ? (
<div className="rmodal__thanks">
<span className="rmodal__thanks-icon">🙏</span>
<p>Merci, votre retour a bien été envoyé !</p>
</div>
) : (
<form className="rmodal__form" onSubmit={submit}>
<h2 className="rmodal__title">Votre retour</h2>
<div className="rmodal__type-row">
<label className={`rmodal__type-btn${type === 'bug' ? ' rmodal__type-btn--active' : ''}`}>
<input type="radio" name="type" value="bug" checked={type === 'bug'} onChange={() => setType('bug')} />
🐛 Signaler un bug
</label>
<label className={`rmodal__type-btn${type === 'idea' ? ' rmodal__type-btn--active' : ''}`}>
<input type="radio" name="type" value="idea" checked={type === 'idea'} onChange={() => setType('idea')} />
💡 Soumettre une idée
</label>
</div>
<textarea
className="rmodal__textarea"
value={message}
onChange={e => setMessage(e.target.value)}
placeholder={type === 'bug' ? 'Décrivez le problème observé…' : 'Votre idée ou suggestion…'}
maxLength={1000}
required
minLength={5}
rows={5}
/>
<p className="rmodal__counter">{message.length}/1000</p>
{status === 'error' && (
<p className="rmodal__error">Une erreur est survenue. Réessayez dans un instant.</p>
)}
<button type="submit" className="rmodal__submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Envoi…' : 'Envoyer'}
</button>
</form>
)}
</div>
</div>
);
}