Story 5.3: persistance localStorage

This commit is contained in:
2026-02-04 21:10:40 +01:00
parent fb95a39792
commit a4e0cb71e9
7 changed files with 222 additions and 42 deletions

View File

@@ -1,6 +1,6 @@
/**
/**
* Validation du formulaire de contact
* JavaScript vanilla - pas de dépendances
* JavaScript vanilla - pas de dépendances
*/
class FormValidator {
@@ -21,13 +21,13 @@ class FormValidator {
required: true,
minLength: 2,
maxLength: 100,
message: 'Veuillez entrer votre nom (2 caractères minimum)'
message: 'Veuillez entrer votre nom (2 caractères minimum)'
},
prenom: {
required: true,
minLength: 2,
maxLength: 100,
message: 'Veuillez entrer votre prénom (2 caractères minimum)'
message: 'Veuillez entrer votre prénom (2 caractères minimum)'
},
email: {
required: true,
@@ -36,19 +36,19 @@ class FormValidator {
},
categorie: {
required: true,
message: 'Veuillez sélectionner une catégorie'
message: 'Veuillez sélectionner une catégorie'
},
objet: {
required: true,
minLength: 5,
maxLength: 200,
message: 'Veuillez entrer un objet (5 caractères minimum)'
message: 'Veuillez entrer un objet (5 caractères minimum)'
},
message: {
required: true,
minLength: 20,
maxLength: 5000,
message: 'Veuillez entrer votre message (20 caractères minimum)'
message: 'Veuillez entrer votre message (20 caractères minimum)'
}
};
@@ -98,7 +98,7 @@ class FormValidator {
if (isValid && rule.maxLength && value.length > rule.maxLength) {
isValid = false;
errorMessage = `Maximum ${rule.maxLength} caractères`;
errorMessage = `Maximum ${rule.maxLength} caractères`;
}
if (isValid && rule.email && value) {
@@ -212,6 +212,89 @@ class FormValidator {
}
}
class ContactFormPersistence {
constructor(formId) {
this.form = document.getElementById(formId);
if (!this.form) return;
this.debounceTimer = null;
this.init();
}
init() {
this.loadSavedData();
this.bindEvents();
}
bindEvents() {
this.form.addEventListener('input', () => {
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => this.saveData(), 500);
});
const clearBtn = document.getElementById('clear-form-btn');
if (clearBtn) {
clearBtn.addEventListener('click', () => this.clearForm());
}
this.form.addEventListener('formSuccess', () => {
AppState.clearFormData();
});
}
saveData() {
const formData = new FormData(this.form);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
AppState.saveFormData(data);
}
loadSavedData() {
const savedData = AppState.getFormData();
if (!savedData) return;
Object.keys(savedData).forEach((key) => {
const field = this.form.querySelector(`[name="${key}"]`);
if (field && savedData[key]) {
field.value = savedData[key];
}
});
const messageField = this.form.querySelector('[name="message"]');
const countEl = document.getElementById('message-count');
if (messageField && countEl) {
countEl.textContent = messageField.value.length;
}
}
clearForm() {
if (!confirm('Êtes-vous sûr de vouloir effacer le formulaire ?')) {
return;
}
AppState.clearFormData();
this.form.reset();
const countEl = document.getElementById('message-count');
if (countEl) {
countEl.textContent = '0';
}
this.form.querySelectorAll('.input-error').forEach((el) => {
el.classList.remove('input-error');
});
this.form.querySelectorAll('[data-error]').forEach((el) => {
el.classList.add('hidden');
el.textContent = '';
});
}
}
document.addEventListener('DOMContentLoaded', () => {
window.contactFormValidator = new FormValidator('contact-form');
window.contactFormPersistence = new ContactFormPersistence('contact-form');
});

57
assets/js/state.js Normal file
View File

@@ -0,0 +1,57 @@
/**
* Gestionnaire d'état pour le localStorage
*/
const AppState = {
STORAGE_KEY: 'portfolio_contact_form',
EXCLUDED_FIELDS: ['csrf_token', 'password', 'recaptcha_token'],
isStorageAvailable() {
try {
const test = '__storage_test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
},
saveFormData(data) {
if (!this.isStorageAvailable()) return;
try {
const filteredData = {};
Object.keys(data).forEach((key) => {
if (!this.EXCLUDED_FIELDS.includes(key)) {
filteredData[key] = data[key];
}
});
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(filteredData));
} catch (e) {
console.warn('Impossible de sauvegarder dans localStorage:', e);
}
},
getFormData() {
if (!this.isStorageAvailable()) return null;
try {
const data = localStorage.getItem(this.STORAGE_KEY);
return data ? JSON.parse(data) : null;
} catch (e) {
console.warn('Impossible de charger depuis localStorage:', e);
return null;
}
},
clearFormData() {
if (!this.isStorageAvailable()) return;
try {
localStorage.removeItem(this.STORAGE_KEY);
} catch (e) {
// Silencieux
}
}
};