diff --git a/admin/index.php b/admin/index.php
index 154e949..59105cd 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -36,7 +36,7 @@ $stories = Stories::getAll();
         <div class="stories-list">
             <?php foreach ($stories as $story): ?>
             <div class="story-item">
-                <img src="<?= htmlspecialchars($story['cover']) ?>" alt="" class="story-cover">
+                <img src="<?= htmlspecialchars('../' . $story['cover']) ?>" alt="" class="story-cover">
                 <div class="story-info">
                     <h2><?= htmlspecialchars($story['title']) ?></h2>
                     <p>Dernière modification : <?= htmlspecialchars($story['updated']) ?></p>
diff --git a/admin/story-edit.php b/admin/story-edit.php
index df9f88d..f1670c6 100644
--- a/admin/story-edit.php
+++ b/admin/story-edit.php
@@ -2,6 +2,7 @@
 require_once '../includes/config.php';
 require_once '../includes/auth.php';
 require_once '../includes/stories.php';
+require_once 'upload-handler.php';
 
 if (!Auth::check()) {
     header('Location: login.php');
@@ -28,21 +29,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
             'id' => $_POST['id'] ?? generateSlug($_POST['title']),
             'title' => $_POST['title'],
             'description' => $_POST['description'],
-            'cover' => $story['cover'] ?? '',  // Géré séparément par l'upload
+            'cover' => $story['cover'] ?? '',
             'created' => $story['created'] ?? date('Y-m-d'),
             'updated' => date('Y-m-d'),
             'chapters' => $story['chapters'] ?? []
         ];
 
         // Gestion de l'upload de couverture
-        if (isset($_FILES['cover']) && $_FILES['cover']['error'] === UPLOAD_ERR_OK) {
-            $uploadDir = '../assets/images/covers/';
-            $extension = strtolower(pathinfo($_FILES['cover']['name'], PATHINFO_EXTENSION));
-            $filename = $storyData['id'] . '.' . $extension;
-            
-            if (move_uploaded_file($_FILES['cover']['tmp_name'], $uploadDir . $filename)) {
-                $storyData['cover'] = 'assets/images/covers/' . $filename;
-            }
+        if (isset($_FILES['cover']) && $_FILES['cover']['error'] !== UPLOAD_ERR_NO_FILE) {
+            $uploadHandler = new CoverUploadHandler();
+            $storyData['cover'] = $uploadHandler->handleUpload($_FILES['cover'], $storyData['id']);
         }
 
         Stories::save($storyData);
diff --git a/admin/upload-handler.php b/admin/upload-handler.php
new file mode 100644
index 0000000..451ed10
--- /dev/null
+++ b/admin/upload-handler.php
@@ -0,0 +1,92 @@
+<?php
+class CoverUploadHandler {
+    private $uploadDir;
+    private $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
+    private $maxFileSize = 5242880; // 5MB
+    
+    public function __construct() {
+        $this->uploadDir = __DIR__ . '/../assets/images/covers/';
+        $this->ensureUploadDirectory();
+    }
+    
+    public function handleUpload($file, $storyId) {
+        try {
+            // Vérifications basiques
+            if ($file['error'] !== UPLOAD_ERR_OK) {
+                throw new Exception($this->getUploadErrorMessage($file['error']));
+            }
+            
+            // Vérifier le type MIME
+            $finfo = new finfo(FILEINFO_MIME_TYPE);
+            $mimeType = $finfo->file($file['tmp_name']);
+            if (!in_array($mimeType, $this->allowedTypes)) {
+                throw new Exception('Type de fichier non autorisé. Types acceptés : JPG, PNG, GIF');
+            }
+            
+            // Vérifier la taille
+            if ($file['size'] > $this->maxFileSize) {
+                throw new Exception('Fichier trop volumineux. Taille maximum : 5MB');
+            }
+            
+            // Générer le nom de fichier
+            $extension = $this->getExtensionFromMimeType($mimeType);
+            $filename = $storyId . '.' . $extension;
+            $targetPath = $this->uploadDir . $filename;
+            
+            // Supprimer l'ancienne image si elle existe
+            $this->removeOldCover($storyId);
+            
+            // Déplacer le fichier
+            if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
+                throw new Exception('Erreur lors du déplacement du fichier uploadé');
+            }
+            
+            // Retourner le chemin relatif pour stockage en BDD
+            return 'assets/images/covers/' . $filename;
+            
+        } catch (Exception $e) {
+            error_log('Erreur upload couverture: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+    
+    private function ensureUploadDirectory() {
+        if (!file_exists($this->uploadDir)) {
+            if (!mkdir($this->uploadDir, 0755, true)) {
+                throw new Exception('Impossible de créer le dossier d\'upload');
+            }
+        }
+        
+        if (!is_writable($this->uploadDir)) {
+            throw new Exception('Le dossier d\'upload n\'est pas accessible en écriture');
+        }
+    }
+    
+    private function removeOldCover($storyId) {
+        foreach (glob($this->uploadDir . $storyId . '.*') as $file) {
+            unlink($file);
+        }
+    }
+    
+    private function getExtensionFromMimeType($mimeType) {
+        $map = [
+            'image/jpeg' => 'jpg',
+            'image/png' => 'png',
+            'image/gif' => 'gif'
+        ];
+        return $map[$mimeType] ?? 'jpg';
+    }
+    
+    private function getUploadErrorMessage($error) {
+        $errors = [
+            UPLOAD_ERR_INI_SIZE => 'Le fichier dépasse la taille maximale autorisée par PHP',
+            UPLOAD_ERR_FORM_SIZE => 'Le fichier dépasse la taille maximale autorisée par le formulaire',
+            UPLOAD_ERR_PARTIAL => 'Le fichier n\'a été que partiellement uploadé',
+            UPLOAD_ERR_NO_FILE => 'Aucun fichier n\'a été uploadé',
+            UPLOAD_ERR_NO_TMP_DIR => 'Dossier temporaire manquant',
+            UPLOAD_ERR_CANT_WRITE => 'Échec de l\'écriture du fichier sur le disque',
+            UPLOAD_ERR_EXTENSION => 'Une extension PHP a arrêté l\'upload'
+        ];
+        return $errors[$error] ?? 'Erreur inconnue lors de l\'upload';
+    }
+}
\ No newline at end of file