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:
71
frontend/src/components/ReportModal/ReportModal.jsx
Normal file
71
frontend/src/components/ReportModal/ReportModal.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user