- 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>
72 lines
2.7 KiB
JavaScript
72 lines
2.7 KiB
JavaScript
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>
|
|
);
|
|
}
|