diff --git a/assets/js/contact-form.js b/assets/js/contact-form.js index 8c8834c..81d2f0e 100644 --- a/assets/js/contact-form.js +++ b/assets/js/contact-form.js @@ -324,8 +324,117 @@ const RecaptchaService = { } }; +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() { + this.form.addEventListener('validSubmit', () => this.handleSubmit()); + } + + async handleSubmit() { + if (this.isSubmitting) return; + + this.setLoadingState(true); + this.hideMessages(); + + try { + const formData = window.contactFormValidator.getFormData(); + const csrfInput = this.form.querySelector('[name="csrf_token"]'); + if (csrfInput) { + formData.csrf_token = csrfInput.value; + } + + formData.recaptcha_token = await RecaptchaService.getToken('contact'); + + 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) { + this.form.classList.add('hidden'); + + if (this.successMessage) { + this.successMessage.classList.remove('hidden'); + this.successMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + + AppState.clearFormData(); + this.form.reset(); + this.form.dispatchEvent(new CustomEvent('formSuccess')); + } + + handleError(message) { + if (this.errorMessage && this.errorText) { + this.errorText.textContent = message; + this.errorMessage.classList.remove('hidden'); + this.errorMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + + hideMessages() { + if (this.successMessage) { + this.successMessage.classList.add('hidden'); + } + if (this.errorMessage) { + this.errorMessage.classList.add('hidden'); + } + } +} + document.addEventListener('DOMContentLoaded', () => { RecaptchaService.init(); window.contactFormValidator = new FormValidator('contact-form'); window.contactFormPersistence = new ContactFormPersistence('contact-form'); + window.contactFormSubmit = new ContactFormSubmit('contact-form'); }); diff --git a/docs/stories/5.6.feedback-utilisateur.md b/docs/stories/5.6.feedback-utilisateur.md index 825aaba..2f40419 100644 --- a/docs/stories/5.6.feedback-utilisateur.md +++ b/docs/stories/5.6.feedback-utilisateur.md @@ -2,7 +2,7 @@ ## Status -Ready for Dev +review ## Story @@ -21,33 +21,33 @@ Ready for Dev ## 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) +- [x] **Task 1 : Afficher l'état de chargement** (AC: 1, 2) + - [x] Masquer le texte du bouton (submitText.classList.add('hidden')) + - [x] Afficher le spinner (submitLoading.classList.remove('hidden')) + - [x] 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) +- [x] **Task 2 : Envoyer en AJAX** (AC: 5) + - [x] Utiliser fetch() avec POST + - [x] Envoyer les données en JSON (Content-Type: application/json) + - [x] 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()) +- [x] **Task 3 : Gérer le succès** (AC: 3, 6) + - [x] Masquer le formulaire (form.classList.add('hidden')) + - [x] Afficher le message de succès + - [x] Mention des spams (vérifier sous 48h) + - [x] Vider le localStorage (AppState.clearFormData()) + - [x] 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 +- [x] **Task 4 : Gérer les erreurs** (AC: 4) + - [x] Afficher le message d'erreur avec icône + - [x] Garder les données dans le formulaire + - [x] Message "Vos données ont été conservées" + - [x] 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) +- [x] **Task 5 : Réinitialiser l'état après feedback** + - [x] Masquer le spinner (finally block) + - [x] Réactiver le bouton + - [x] Scroll vers le message (scrollIntoView) ## Dev Notes @@ -265,23 +265,27 @@ document.addEventListener('DOMContentLoaded', () => { ## Dev Agent Record ### Agent Model Used -Claude Opus 4.5 (claude-opus-4-5-20251101) +GPT-5 Codex + +### Implementation Plan +- Implémenter les tâches 1 à 5 dans l’ordre avec tests à chaque étape. +- Ajouter submit JS et messages UX dans contact.php. ### File List | File | Action | Description | |------|--------|-------------| -| `assets/js/contact-form.js` | Modified | Ajout classe ContactFormSubmit | -| `pages/contact.php` | Modified | Messages succès/erreur améliorés | +| `assets/js/contact-form.js` | Modified | Ajout classe ContactFormSubmit + AJAX | +| `pages/contact.php` | Modified | Messages succès/erreur + spinner | +| `tests/contact-submit.test.php` | Added | Tests submit AJAX/UX | +| `tests/contact.test.php` | Modified | Vérif submit-loading + messages | +| `tests/run.ps1` | Modified | Ajout du test contact-submit | ### Completion Notes -- Classe ContactFormSubmit avec gestion complète du cycle de vie -- État loading : spinner + bouton désactivé -- Envoi AJAX avec fetch() et JSON -- Tokens CSRF et reCAPTCHA inclus automatiquement -- Succès : formulaire masqué, message avec mention spams, localStorage vidé -- Erreur : message explicite, données conservées, possibilité de réessayer -- Scroll automatique vers les messages (scrollIntoView smooth) -- Gestion des erreurs réseau (catch) +- Classe ContactFormSubmit avec cycle complet (loading/succès/erreur) +- Envoi AJAX JSON avec tokens CSRF + reCAPTCHA +- Succès : formulaire masqué + message 48h + purge localStorage +- Erreur : message explicite + données conservées +- Tests : `powershell -ExecutionPolicy Bypass -File tests/run.ps1` ### Debug Log References Aucun problème rencontré. @@ -292,3 +296,4 @@ Aucun problème rencontré. |------|---------|-------------|--------| | 2026-01-22 | 0.1 | Création initiale | Sarah (PO) | | 2026-01-24 | 1.0 | Implémentation complète | James (Dev) | +| 2026-02-04 | 1.1 | Feedback utilisateur AJAX | Amelia (Dev) | diff --git a/pages/contact.php b/pages/contact.php index 6a77d27..11fe62e 100644 --- a/pages/contact.php +++ b/pages/contact.php @@ -134,13 +134,48 @@ include_template('navbar', compact('currentPage'));
+ + + + diff --git a/tests/contact-submit.test.php b/tests/contact-submit.test.php new file mode 100644 index 0000000..cd89bcb --- /dev/null +++ b/tests/contact-submit.test.php @@ -0,0 +1,19 @@ +