diff --git a/admin/export-import.php b/admin/export-import.php index cdf43a4..13907e4 100644 --- a/admin/export-import.php +++ b/admin/export-import.php @@ -186,5 +186,7 @@ $stories = Stories::getAll(); margin-bottom: var(--spacing-md); } + + \ No newline at end of file diff --git a/admin/index.php b/admin/index.php index fea6fe6..3eb589e 100644 --- a/admin/index.php +++ b/admin/index.php @@ -68,5 +68,7 @@ $stories = Stories::getAll(); + + \ No newline at end of file diff --git a/admin/options.php b/admin/options.php index 4c9a679..171f8a4 100644 --- a/admin/options.php +++ b/admin/options.php @@ -155,5 +155,7 @@ $config = Config::load(); }); }); + + \ No newline at end of file diff --git a/admin/profile.php b/admin/profile.php index 74fdd13..7dadcca 100644 --- a/admin/profile.php +++ b/admin/profile.php @@ -138,5 +138,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + \ No newline at end of file diff --git a/admin/story-edit.php b/admin/story-edit.php index 9a6eabd..e6f45e4 100644 --- a/admin/story-edit.php +++ b/admin/story-edit.php @@ -253,5 +253,7 @@ function generateSlug($title) { }); }); + + \ No newline at end of file diff --git a/assets/css/dialog.css b/assets/css/dialog.css new file mode 100644 index 0000000..95215ec --- /dev/null +++ b/assets/css/dialog.css @@ -0,0 +1,107 @@ +/* dialog.css */ +.confirm-dialog { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +.confirm-dialog.show { + opacity: 1; +} + +.confirm-dialog-content { + background-color: var(--bg-tertiary); + border-radius: var(--radius-md); + padding: var(--spacing-xl); + max-width: 90%; + width: 400px; + border: 1px solid var(--border-color); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + transform: translate(-50%, -60%); + position: absolute; + top: 50%; + left: 50%; + transition: transform 0.2s ease-in-out; +} + +.confirm-dialog.show .confirm-dialog-content { + transform: translate(-50%, -50%); +} + +.confirm-dialog h3 { + margin: 0 0 var(--spacing-md); + color: var(--text-primary); + font-size: 1.5rem; +} + +.confirm-dialog p { + margin: 0 0 var(--spacing-xl); + color: var(--text-secondary); + line-height: 1.5; +} + +.confirm-dialog-buttons { + display: flex; + justify-content: flex-end; + gap: var(--spacing-md); +} + +.confirm-dialog .button { + padding: var(--spacing-sm) var(--spacing-lg); + border: none; + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 1rem; + transition: all var(--transition-fast); +} + +.confirm-dialog .button.confirm { + background-color: var(--accent-primary); + color: var(--text-tertiary); +} + +.confirm-dialog .button.confirm:hover { + background-color: var(--accent-secondary); +} + +.confirm-dialog .button.danger { + background-color: var(--error-color); + color: white; +} + +.confirm-dialog .button.danger:hover { + background-color: #a33; +} + +.confirm-dialog .button.cancel { + background-color: var(--bg-secondary); + color: var(--text-primary); +} + +.confirm-dialog .button.cancel:hover { + background-color: var(--bg-primary); +} + +@media (max-width: 480px) { + .confirm-dialog-content { + width: calc(100% - 32px); + padding: var(--spacing-lg); + } + + .confirm-dialog-buttons { + flex-direction: column; + } + + .confirm-dialog .button { + width: 100%; + } +} \ No newline at end of file diff --git a/assets/js/admin.js b/assets/js/admin.js index 9985767..ab9a043 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -16,7 +16,17 @@ document.addEventListener('DOMContentLoaded', function() { function confirmDeletion(storyId) { const storyCard = document.querySelector(`[data-id="${storyId}"]`).closest('.story-item'); const title = storyCard.querySelector('h2').textContent; - return confirm(`Voulez-vous vraiment supprimer le roman "${title}" ? Cette action est irréversible.`); + + confirmDialog.show({ + title: 'Suppression du roman', + message: `Voulez-vous vraiment supprimer le roman "${title}" ? Cette action est irréversible.`, + confirmText: 'Supprimer', + confirmClass: 'danger', + onConfirm: async () => { + await deleteStory(storyId); + } + }); + return false; // Pour empêcher l'exécution immédiate } // Suppression d'un roman via l'API @@ -78,9 +88,15 @@ document.addEventListener('DOMContentLoaded', function() { const logoutForm = document.querySelector('.logout-form'); if (logoutForm) { logoutForm.addEventListener('submit', (e) => { - if (!confirm('Voulez-vous vraiment vous déconnecter ?')) { - e.preventDefault(); - } + e.preventDefault(); + confirmDialog.show({ + title: 'Confirmation de déconnexion', + message: 'Voulez-vous vraiment vous déconnecter ?', + confirmText: 'Se déconnecter', + onConfirm: () => { + logoutForm.submit(); + } + }); }); } diff --git a/assets/js/dialog.js b/assets/js/dialog.js new file mode 100644 index 0000000..2781108 --- /dev/null +++ b/assets/js/dialog.js @@ -0,0 +1,145 @@ +class ConfirmDialog { + constructor() { + this.createDialog(); + } + + createDialog() { + // Création du conteneur principal + this.dialog = document.createElement('div'); + this.dialog.className = 'confirm-dialog'; + this.dialog.style.display = 'none'; + + // Création du contenu + const content = document.createElement('div'); + content.className = 'confirm-dialog-content'; + + // En-tête avec titre + this.title = document.createElement('h3'); + content.appendChild(this.title); + + // Message + this.message = document.createElement('p'); + content.appendChild(this.message); + + // Conteneur des boutons + const buttons = document.createElement('div'); + buttons.className = 'confirm-dialog-buttons'; + + // Bouton Confirmer + this.confirmButton = document.createElement('button'); + this.confirmButton.className = 'button confirm'; + buttons.appendChild(this.confirmButton); + + // Bouton Annuler + this.cancelButton = document.createElement('button'); + this.cancelButton.className = 'button cancel'; + this.cancelButton.textContent = 'Annuler'; + buttons.appendChild(this.cancelButton); + + content.appendChild(buttons); + this.dialog.appendChild(content); + document.body.appendChild(this.dialog); + + // Fermeture au clic en dehors + this.dialog.addEventListener('click', (e) => { + if (e.target === this.dialog) { + this.hide(); + } + }); + + // Fermeture avec Echap + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && this.dialog.style.display === 'block') { + this.hide(); + } + }); + } + + show(options) { + const defaults = { + title: 'Confirmation', + message: 'Êtes-vous sûr ?', + confirmText: 'Confirmer', + confirmClass: 'confirm', + cancelText: 'Annuler', + onConfirm: () => {}, + onCancel: () => {} + }; + + const settings = { ...defaults, ...options }; + + this.title.textContent = settings.title; + this.message.textContent = settings.message; + this.confirmButton.textContent = settings.confirmText; + this.confirmButton.className = `button ${settings.confirmClass}`; + + // Réinitialisation des événements + const newConfirmBtn = this.confirmButton.cloneNode(true); + const newCancelBtn = this.cancelButton.cloneNode(true); + this.confirmButton.parentNode.replaceChild(newConfirmBtn, this.confirmButton); + this.cancelButton.parentNode.replaceChild(newCancelBtn, this.cancelButton); + this.confirmButton = newConfirmBtn; + this.cancelButton = newCancelBtn; + + // Nouveaux gestionnaires d'événements + this.confirmButton.addEventListener('click', () => { + settings.onConfirm(); + this.hide(); + }); + + this.cancelButton.addEventListener('click', () => { + settings.onCancel(); + this.hide(); + }); + + // Affichage avec animation + this.dialog.style.display = 'block'; + requestAnimationFrame(() => { + this.dialog.classList.add('show'); + }); + } + + hide() { + this.dialog.classList.remove('show'); + setTimeout(() => { + this.dialog.style.display = 'none'; + }, 200); + } +} + +// Création d'une instance globale +const confirmDialog = new ConfirmDialog(); + +// Exemple d'utilisation pour la suppression d'un chapitre +document.querySelectorAll('.delete-chapter').forEach(button => { + button.addEventListener('click', (e) => { + const chapterTitle = e.target.closest('.chapter-item').querySelector('.chapter-title').textContent; + + confirmDialog.show({ + title: 'Supprimer le chapitre', + message: `Voulez-vous vraiment supprimer le chapitre "${chapterTitle}" ? Cette action est irréversible.`, + confirmText: 'Supprimer', + confirmClass: 'danger', + onConfirm: () => { + // Code de suppression existant + } + }); + }); +}); + +// Exemple d'utilisation pour la suppression d'un roman +document.querySelectorAll('.delete-story').forEach(button => { + button.addEventListener('click', (e) => { + const storyTitle = e.target.closest('.story-item').querySelector('h2').textContent; + + confirmDialog.show({ + title: 'Supprimer le roman', + message: `Voulez-vous vraiment supprimer le roman "${storyTitle}" ? Cette action est irréversible.`, + confirmText: 'Supprimer', + confirmClass: 'danger', + onConfirm: () => { + // Code de suppression existant + } + }); + }); +}); \ No newline at end of file diff --git a/assets/js/story-edit.js b/assets/js/story-edit.js index 1f07b64..fc4e61f 100644 --- a/assets/js/story-edit.js +++ b/assets/js/story-edit.js @@ -183,11 +183,17 @@ document.addEventListener('DOMContentLoaded', function() { if (cancelEditBtn) { cancelEditBtn.addEventListener('click', () => { if (hasUnsavedChanges()) { - if (!confirm('Des modifications non sauvegardées seront perdues. Voulez-vous vraiment fermer ?')) { - return; - } + confirmDialog.show({ + title: 'Modifications non sauvegardées', + message: 'Des modifications non sauvegardées seront perdues. Voulez-vous vraiment fermer ?', + confirmText: 'Fermer sans sauvegarder', + onConfirm: () => { + modal.style.display = 'none'; + } + }); + } else { + modal.style.display = 'none'; } - modal.style.display = 'none'; }); } @@ -241,41 +247,44 @@ document.addEventListener('DOMContentLoaded', function() { const chapterItem = target.closest('.chapter-item'); const chapterId = chapterItem.dataset.id; const chapterTitle = chapterItem.querySelector('.chapter-title').textContent; - - if (confirm(`Voulez-vous vraiment supprimer le chapitre "${chapterTitle}" ? Cette action est irréversible.`)) { - try { - const response = await fetch('api/delete-chapter.php', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - storyId: storyId, - chapterId: chapterId - }) - }); - - if (!response.ok) { - throw new Error('Erreur réseau'); + + confirmDialog.show({ + title: 'Suppression du chapitre', + message: `Voulez-vous vraiment supprimer le chapitre "${chapterTitle}" ? Cette action est irréversible.`, + confirmText: 'Supprimer', + confirmClass: 'danger', + onConfirm: async () => { + try { + const response = await fetch('api/delete-chapter.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + storyId: storyId, + chapterId: chapterId + }) + }); + + if (!response.ok) throw new Error('Erreur réseau'); + + const result = await response.json(); + if (result.success) { + chapterItem.style.opacity = '0'; + chapterItem.style.transform = 'translateX(-100%)'; + setTimeout(() => { + chapterItem.remove(); + showNotification('Chapitre supprimé avec succès'); + }, 300); + } else { + throw new Error(result.error || 'Erreur lors de la suppression'); + } + } catch (error) { + console.error('Erreur:', error); + showNotification(error.message, 'error'); } - - const result = await response.json(); - if (result.success) { - // Animation de suppression - chapterItem.style.opacity = '0'; - chapterItem.style.transform = 'translateX(-100%)'; - setTimeout(() => { - chapterItem.remove(); - showNotification('Chapitre supprimé avec succès'); - }, 300); - } else { - throw new Error(result.error || 'Erreur lors de la suppression'); - } - } catch (error) { - console.error('Erreur:', error); - showNotification(error.message, 'error'); } - } + }); } }); }