diff --git a/admin/api/delete-story.php b/admin/api/delete-story.php new file mode 100644 index 0000000..7676341 --- /dev/null +++ b/admin/api/delete-story.php @@ -0,0 +1,64 @@ + false, 'error' => 'Non autorisé']); + exit; +} + +// Vérification de la méthode HTTP +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Méthode non autorisée']); + exit; +} + +// Récupération et validation des données +$input = json_decode(file_get_contents('php://input'), true); +$storyId = $input['id'] ?? null; + +if (!$storyId) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'ID du roman manquant']); + exit; +} + +try { + // Récupération du roman + $story = Stories::get($storyId); + if (!$story) { + throw new Exception('Roman non trouvé'); + } + + // Suppression de l'image de couverture si elle existe + if (!empty($story['cover'])) { + $coverPath = __DIR__ . '/../../' . $story['cover']; + if (file_exists($coverPath)) { + unlink($coverPath); + } + } + + // Suppression du fichier JSON du roman + $storyFile = __DIR__ . '/../../stories/' . $storyId . '.json'; + if (file_exists($storyFile)) { + unlink($storyFile); + + // Réponse de succès + echo json_encode([ + 'success' => true, + 'message' => 'Roman supprimé avec succès' + ]); + } else { + throw new Exception('Fichier du roman introuvable'); + } +} catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => 'Erreur lors de la suppression : ' . $e->getMessage() + ]); +} \ No newline at end of file diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..9985767 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,145 @@ +document.addEventListener('DOMContentLoaded', function() { + // Gestion de la suppression des romans + const storyList = document.querySelector('.stories-list'); + if (storyList) { + storyList.addEventListener('click', async (e) => { + if (e.target.matches('.delete-story')) { + const storyId = e.target.dataset.id; + if (confirmDeletion(storyId)) { + await deleteStory(storyId); + } + } + }); + } + + // Confirmation de suppression avec informations du roman + 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.`); + } + + // Suppression d'un roman via l'API + async function deleteStory(storyId) { + try { + const response = await fetch('api/delete-story.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id: storyId }) + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + // Animation de suppression et retrait du DOM + const storyCard = document.querySelector(`[data-id="${storyId}"]`).closest('.story-item'); + storyCard.style.opacity = '0'; + storyCard.style.transform = 'translateX(-100%)'; + setTimeout(() => { + storyCard.remove(); + showNotification('Roman supprimé avec succès'); + }, 300); + } else { + throw new Error(result.error || 'Erreur lors de la suppression'); + } + } else { + throw new Error('Erreur serveur'); + } + } catch (error) { + showNotification(error.message, 'error'); + } + } + + // Système de notification + function showNotification(message, type = 'success') { + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + notification.textContent = message; + + document.body.appendChild(notification); + + // Animation d'entrée + setTimeout(() => { + notification.style.opacity = '1'; + notification.style.transform = 'translateY(0)'; + }, 10); + + // Auto-suppression après 3 secondes + setTimeout(() => { + notification.style.opacity = '0'; + notification.style.transform = 'translateY(-100%)'; + setTimeout(() => notification.remove(), 300); + }, 3000); + } + + // Gestion du formulaire de déconnexion + const logoutForm = document.querySelector('.logout-form'); + if (logoutForm) { + logoutForm.addEventListener('submit', (e) => { + if (!confirm('Voulez-vous vraiment vous déconnecter ?')) { + e.preventDefault(); + } + }); + } + + // Fonction utilitaire pour détecter les changements non sauvegardés + let hasUnsavedChanges = false; + + function detectUnsavedChanges() { + const forms = document.querySelectorAll('form'); + forms.forEach(form => { + const initialState = new FormData(form).toString(); + + form.addEventListener('change', () => { + const currentState = new FormData(form).toString(); + hasUnsavedChanges = initialState !== currentState; + }); + }); + + // Avertissement avant de quitter la page avec des changements non sauvegardés + window.addEventListener('beforeunload', (e) => { + if (hasUnsavedChanges) { + e.preventDefault(); + e.returnValue = ''; + } + }); + } + + // Style des notifications + const style = document.createElement('style'); + style.textContent = ` + .notification { + position: fixed; + top: 20px; + right: 20px; + padding: 1rem 2rem; + border-radius: 4px; + background: white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + opacity: 0; + transform: translateY(-100%); + transition: opacity 0.3s ease, transform 0.3s ease; + z-index: 1000; + } + + .notification.success { + background-color: #4caf50; + color: white; + } + + .notification.error { + background-color: #f44336; + color: white; + } + + .story-item { + transition: opacity 0.3s ease, transform 0.3s ease; + } + `; + document.head.appendChild(style); + + // Initialisation des fonctionnalités + detectUnsavedChanges(); +}); \ No newline at end of file diff --git a/includes/stories.php b/includes/stories.php index 957e484..496ce48 100644 --- a/includes/stories.php +++ b/includes/stories.php @@ -3,6 +3,7 @@ class Stories { private static $storiesDir = __DIR__ . '/../stories/'; public static function getAll() { + self::ensureDirectoryExists(); $stories = []; foreach (glob(self::$storiesDir . '*.json') as $file) { $story = json_decode(file_get_contents($file), true); @@ -12,6 +13,7 @@ class Stories { } public static function get($id) { + self::ensureDirectoryExists(); $file = self::$storiesDir . $id . '.json'; if (!file_exists($file)) { return null; @@ -20,7 +22,40 @@ class Stories { } public static function save($story) { + self::ensureDirectoryExists(); + + // Vérifier que l'ID est valide + if (empty($story['id'])) { + throw new Exception('L\'ID du roman est requis'); + } + + // Créer le chemin complet du fichier $file = self::$storiesDir . $story['id'] . '.json'; - return file_put_contents($file, json_encode($story, JSON_PRETTY_PRINT)); + + // Encoder en JSON avec options de formatage + $jsonContent = json_encode($story, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + if ($jsonContent === false) { + throw new Exception('Erreur lors de l\'encodage JSON: ' . json_last_error_msg()); + } + + // Écrire le fichier + $bytesWritten = file_put_contents($file, $jsonContent); + if ($bytesWritten === false) { + throw new Exception('Erreur lors de l\'écriture du fichier: ' . error_get_last()['message']); + } + + return $bytesWritten; + } + + private static function ensureDirectoryExists() { + if (!file_exists(self::$storiesDir)) { + if (!mkdir(self::$storiesDir, 0755, true)) { + throw new Exception('Impossible de créer le dossier stories/'); + } + } + + if (!is_writable(self::$storiesDir)) { + throw new Exception('Le dossier stories/ n\'est pas accessible en écriture'); + } } } \ No newline at end of file