# Story 5.6: Feedback Utilisateur (Succès/Erreur) ## Status Ready for Dev ## Story **As a** visiteur, **I want** savoir clairement si mon message a été envoyé, **so that** je ne doute pas et j'évite les envois multiples. ## Acceptance Criteria 1. Pendant l'envoi, un indicateur de chargement est affiché (spinner ou texte) 2. Le bouton d'envoi est désactivé pendant le traitement 3. En cas de succès : message de confirmation visible, formulaire réinitialisé, localStorage vidé 4. En cas d'erreur : message d'erreur explicite, données conservées pour réessayer 5. L'envoi est fait en AJAX (pas de rechargement de page) 6. Le message de succès invite à vérifier les spams si pas de réponse ## Tasks / Subtasks - [] **Task 1 : Afficher l'état de chargement** (AC: 1, 2) - [] Masquer le texte du bouton (submitText.classList.add('hidden')) - [] Afficher le spinner (submitLoading.classList.remove('hidden')) - [] Désactiver le bouton (submitBtn.disabled = true) - [] **Task 2 : Envoyer en AJAX** (AC: 5) - [] Utiliser fetch() avec POST - [] Envoyer les données en JSON (Content-Type: application/json) - [] Inclure les tokens (CSRF + reCAPTCHA) - [] **Task 3 : Gérer le succès** (AC: 3, 6) - [] Masquer le formulaire (form.classList.add('hidden')) - [] Afficher le message de succès - [] Mention des spams (vérifier sous 48h) - [] Vider le localStorage (AppState.clearFormData()) - [] Réinitialiser le formulaire (form.reset()) - [] **Task 4 : Gérer les erreurs** (AC: 4) - [] Afficher le message d'erreur avec icône - [] Garder les données dans le formulaire - [] Message "Vos données ont été conservées" - [] Permettre de réessayer - [] **Task 5 : Réinitialiser l'état après feedback** - [] Masquer le spinner (finally block) - [] Réactiver le bouton - [] Scroll vers le message (scrollIntoView) ## Dev Notes ### Code JavaScript Complet ```javascript // assets/js/contact-form.js (compléter) class ContactFormSubmit { constructor(formId) { this.form = document.getElementById(formId); if (!this.form) return; this.submitBtn = document.getElementById('submit-btn'); this.submitText = document.getElementById('submit-text'); this.submitLoading = document.getElementById('submit-loading'); this.successMessage = document.getElementById('success-message'); this.errorMessage = document.getElementById('error-message'); this.errorText = document.getElementById('error-text'); this.isSubmitting = false; this.init(); } init() { // Écouter l'événement de validation réussie this.form.addEventListener('validSubmit', () => this.handleSubmit()); } async handleSubmit() { if (this.isSubmitting) return; this.setLoadingState(true); this.hideMessages(); try { // Récupérer les données const formData = window.contactFormValidator.getFormData(); // Ajouter le token CSRF const csrfInput = this.form.querySelector('[name="csrf_token"]'); if (csrfInput) { formData.csrf_token = csrfInput.value; } // Obtenir le token reCAPTCHA formData.recaptcha_token = await RecaptchaService.getToken('contact'); // Envoyer la requête const response = await fetch('/api/contact.php', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(formData) }); const result = await response.json(); if (result.success) { this.handleSuccess(result.message); } else { this.handleError(result.error || 'Une erreur est survenue'); } } catch (error) { console.error('Erreur envoi formulaire:', error); this.handleError('Impossible de contacter le serveur. Vérifiez votre connexion.'); } finally { this.setLoadingState(false); } } setLoadingState(loading) { this.isSubmitting = loading; if (this.submitBtn) { this.submitBtn.disabled = loading; } if (this.submitText && this.submitLoading) { if (loading) { this.submitText.classList.add('hidden'); this.submitLoading.classList.remove('hidden'); } else { this.submitText.classList.remove('hidden'); this.submitLoading.classList.add('hidden'); } } } handleSuccess(message) { // Masquer le formulaire this.form.classList.add('hidden'); // Afficher le message de succès if (this.successMessage) { this.successMessage.classList.remove('hidden'); // Scroll vers le message this.successMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }); } // Vider le localStorage AppState.clearFormData(); // Réinitialiser le formulaire (pour un éventuel nouvel envoi) this.form.reset(); // Déclencher l'événement de succès this.form.dispatchEvent(new CustomEvent('formSuccess')); } handleError(message) { // Afficher le message d'erreur if (this.errorMessage && this.errorText) { this.errorText.textContent = message; this.errorMessage.classList.remove('hidden'); // Scroll vers le message this.errorMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }); } // Les données sont conservées dans le formulaire pour réessayer } hideMessages() { if (this.successMessage) { this.successMessage.classList.add('hidden'); } if (this.errorMessage) { this.errorMessage.classList.add('hidden'); } } } // Initialisation document.addEventListener('DOMContentLoaded', () => { window.contactFormSubmit = new ContactFormSubmit('contact-form'); }); ``` ### HTML des Messages (dans contact.php) ```html
Merci pour votre message. Je vous répondrai dans les meilleurs délais.
Si vous ne recevez pas de réponse sous 48h, pensez à vérifier vos spams.
Vos données ont été conservées. Vous pouvez réessayer.