feat: add cover image field to album creation and editing

- Added dedicated cover image upload field in create_album.php form
- Display current cover image preview when editing
- Drag-and-drop support for cover image with real-time preview
- Shows filename and file size after selection
- Updated save_album.php to handle cover image upload
- Updated update_album.php to handle cover image replacement
- Deletes old cover image when updating
- Cover image optional - first photo in album used as fallback
- Recommended cover dimensions: 500x500px or larger (square)
- File validation: max 5MB, supports JPG, PNG, GIF, WEBP
- All cover image changes included in transaction with rollback on error
This commit is contained in:
twotalesanimation
2025-12-05 10:14:35 +02:00
parent ad460ef85a
commit 5736757f19
3 changed files with 188 additions and 3 deletions

View File

@@ -208,6 +208,27 @@ require_once($rootPath . '/components/banner.php');
color: #999; color: #999;
margin-top: 5px; margin-top: 5px;
} }
.cover-preview-area {
margin-bottom: 15px;
}
.current-cover {
border-radius: 8px;
overflow: hidden;
margin-bottom: 15px;
}
.current-cover img {
width: 100%;
max-height: 250px;
object-fit: cover;
display: block;
}
#coverUploadArea {
cursor: pointer;
}
</style> </style>
<section class="tour-list-page py-100 rel"> <section class="tour-list-page py-100 rel">
@@ -234,6 +255,27 @@ require_once($rootPath . '/components/banner.php');
<div class="helper-text">Optional: Share details about when, where, or why you created this album</div> <div class="helper-text">Optional: Share details about when, where, or why you created this album</div>
</div> </div>
<div class="form-group">
<label for="cover_image">Album Cover Image</label>
<div class="cover-preview-area">
<?php if ($album && $album['cover_image']): ?>
<div class="current-cover">
<img id="currentCoverImg" src="<?php echo htmlspecialchars($album['cover_image']); ?>" alt="Current cover">
<p style="margin-top: 10px; font-size: 0.9rem; color: #666;">Current cover image</p>
</div>
<?php else: ?>
<div id="currentCoverImg" style="width: 100%; height: 200px; background: #f0f0f0; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #999; margin-bottom: 15px;">No cover image yet</div>
<?php endif; ?>
</div>
<div class="upload-area" id="coverUploadArea" style="margin-top: 15px;">
<input type="file" id="cover_image" name="cover_image" accept="image/*" class="upload-input">
<div style="font-size: 1.5rem; margin-bottom: 10px;">🖼️</div>
<p class="upload-text">Click to select cover image</p>
<div class="helper-text">Image will be used as album thumbnail. Recommended: Square image (500x500px or larger)</div>
</div>
<div id="coverFileName" style="margin-top: 10px;"></div>
</div>
<?php if ($album): ?> <?php if ($album): ?>
<div class="photos-section"> <div class="photos-section">
<h4>Photos in Album</h4> <h4>Photos in Album</h4>
@@ -280,8 +322,60 @@ require_once($rootPath . '/components/banner.php');
const uploadArea = document.getElementById('uploadArea'); const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('photos'); const fileInput = document.getElementById('photos');
const fileList = document.getElementById('fileList'); const fileList = document.getElementById('fileList');
const coverUploadArea = document.getElementById('coverUploadArea');
const coverImageInput = document.getElementById('cover_image');
const coverFileName = document.getElementById('coverFileName');
// Drag and drop // Cover image handling
coverUploadArea.addEventListener('click', () => {
coverImageInput.click();
});
coverImageInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (event) => {
const preview = document.getElementById('currentCoverImg');
if (preview.tagName === 'IMG') {
preview.src = event.target.result;
} else {
const img = document.createElement('img');
img.src = event.target.result;
img.alt = 'Cover preview';
preview.replaceWith(img);
img.id = 'currentCoverImg';
}
};
reader.readAsDataURL(file);
coverFileName.innerHTML = '<p style="color: #667eea; font-weight: 500;">Selected: ' + file.name + ' (' + (file.size / 1024 / 1024).toFixed(2) + ' MB)</p>';
}
});
// Drag and drop for cover
coverUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
coverUploadArea.classList.add('dragover');
});
coverUploadArea.addEventListener('dragleave', () => {
coverUploadArea.classList.remove('dragover');
});
coverUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
coverUploadArea.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
coverImageInput.files = e.dataTransfer.files;
const event = new Event('change', { bubbles: true });
coverImageInput.dispatchEvent(event);
}
});
// Regular photos drag and drop
uploadArea.addEventListener('dragover', (e) => { uploadArea.addEventListener('dragover', (e) => {
e.preventDefault(); e.preventDefault();
uploadArea.classList.add('dragover'); uploadArea.classList.add('dragover');

View File

@@ -58,6 +58,43 @@ try {
} }
} }
// Handle cover image upload
$coverImagePath = null;
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$maxSize = 5 * 1024 * 1024; // 5MB
$fileName = $_FILES['cover_image']['name'];
$fileTmpName = $_FILES['cover_image']['tmp_name'];
$fileSize = $_FILES['cover_image']['size'];
$fileMime = mime_content_type($fileTmpName);
// Validate file
if (!in_array($fileMime, $allowedMimes)) {
throw new Exception('Invalid cover image file type');
}
if ($fileSize > $maxSize) {
throw new Exception('Cover image file too large (max 5MB)');
}
// Generate unique filename
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
$newFileName = 'cover_' . uniqid() . '.' . $ext;
$filePath = $albumDir . '/' . $newFileName;
$coverImagePath = '/assets/uploads/gallery/' . $album_id . '/' . $newFileName;
if (!move_uploaded_file($fileTmpName, $filePath)) {
throw new Exception('Failed to upload cover image');
}
// Update cover image in album record
$updateCover = $conn->prepare("UPDATE photo_albums SET cover_image = ? WHERE album_id = ?");
$updateCover->bind_param("si", $coverImagePath, $album_id);
$updateCover->execute();
$updateCover->close();
}
// Handle photo uploads // Handle photo uploads
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) { if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
@@ -95,8 +132,8 @@ try {
throw new Exception('Failed to upload: ' . $fileName); throw new Exception('Failed to upload: ' . $fileName);
} }
// Set first photo as cover // Set first photo as cover if no cover image was uploaded
if ($firstPhoto) { if ($firstPhoto && !$coverImagePath) {
$updateCover = $conn->prepare("UPDATE photo_albums SET cover_image = ? WHERE album_id = ?"); $updateCover = $conn->prepare("UPDATE photo_albums SET cover_image = ? WHERE album_id = ?");
$updateCover->bind_param("si", $relativePath, $album_id); $updateCover->bind_param("si", $relativePath, $album_id);
$updateCover->execute(); $updateCover->execute();

View File

@@ -75,6 +75,60 @@ try {
$updateStmt->execute(); $updateStmt->execute();
$updateStmt->close(); $updateStmt->close();
// Handle cover image upload if provided
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$maxSize = 5 * 1024 * 1024; // 5MB
$fileName = $_FILES['cover_image']['name'];
$fileTmpName = $_FILES['cover_image']['tmp_name'];
$fileSize = $_FILES['cover_image']['size'];
$fileMime = mime_content_type($fileTmpName);
// Validate file
if (!in_array($fileMime, $allowedMimes)) {
throw new Exception('Invalid cover image file type');
}
if ($fileSize > $maxSize) {
throw new Exception('Cover image file too large (max 5MB)');
}
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
// Delete old cover if it exists
$oldCoverStmt = $conn->prepare("SELECT cover_image FROM photo_albums WHERE album_id = ?");
$oldCoverStmt->bind_param("i", $album_id);
$oldCoverStmt->execute();
$oldCoverResult = $oldCoverStmt->get_result();
if ($oldCoverResult->num_rows > 0) {
$oldCover = $oldCoverResult->fetch_assoc();
if ($oldCover['cover_image']) {
$oldCoverPath = $_SERVER['DOCUMENT_ROOT'] . $oldCover['cover_image'];
if (file_exists($oldCoverPath)) {
unlink($oldCoverPath);
}
}
}
$oldCoverStmt->close();
// Generate unique filename
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
$newFileName = 'cover_' . uniqid() . '.' . $ext;
$filePath = $albumDir . '/' . $newFileName;
$coverImagePath = '/assets/uploads/gallery/' . $album_id . '/' . $newFileName;
if (!move_uploaded_file($fileTmpName, $filePath)) {
throw new Exception('Failed to upload cover image');
}
// Update cover image in album record
$updateCover = $conn->prepare("UPDATE photo_albums SET cover_image = ? WHERE album_id = ?");
$updateCover->bind_param("si", $coverImagePath, $album_id);
$updateCover->execute();
$updateCover->close();
}
// Handle photo uploads if any // Handle photo uploads if any
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) { if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];