From 8231f2b60dc0864ec29bb45546816d1a1e6e019d Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 9 Jul 2025 09:09:34 +0200 Subject: [PATCH] Fix gallery login errors and photo upload issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix photo upload cross-device link error - Changed fs.rename to fs.copyFile + fs.unlink to handle Docker volume mounts - This fixes EXDEV errors when uploading photos from /tmp to storage - Improve gallery login error handling - Add console logging for debugging - Prevent form refresh with stopPropagation - Show specific error messages based on status codes - Keep password field populated on error for retry - Map 404 to gallery not found message - Enhanced upload error messages - Show specific error message for each failed file - Display backend error messages in toast notifications These fixes resolve: 1. Photo uploads failing silently with cross-device link errors 2. Login errors not displaying and form refreshing 3. Upload errors not showing user-friendly messages 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- backend/src/services/photoProcessor.js | 4 +++- .../src/components/gallery/UserPhotoUpload.tsx | 6 +++++- frontend/src/pages/GalleryPage.tsx | 16 +++++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/backend/src/services/photoProcessor.js b/backend/src/services/photoProcessor.js index 0af62e5..5861d57 100644 --- a/backend/src/services/photoProcessor.js +++ b/backend/src/services/photoProcessor.js @@ -63,7 +63,9 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ await fs.mkdir(destPath, { recursive: true }); const newPath = path.join(destPath, newFilename); - await fs.rename(file.path, newPath); + // Use copyFile and unlink instead of rename to avoid cross-device issues + await fs.copyFile(file.path, newPath); + await fs.unlink(file.path); // Generate thumbnail const thumbnailPath = await generateThumbnail(newPath); diff --git a/frontend/src/components/gallery/UserPhotoUpload.tsx b/frontend/src/components/gallery/UserPhotoUpload.tsx index bfb93b9..133efcf 100644 --- a/frontend/src/components/gallery/UserPhotoUpload.tsx +++ b/frontend/src/components/gallery/UserPhotoUpload.tsx @@ -78,9 +78,13 @@ export const UserPhotoUpload: React.FC = ({ }, }); successCount++; - } catch (error) { + } catch (error: any) { console.error(`Failed to upload ${file.name}:`, error); failedCount++; + + // Show specific error message + const errorMessage = error.response?.data?.error || error.message || 'Upload failed'; + toast.error(`${file.name}: ${errorMessage}`); } } diff --git a/frontend/src/pages/GalleryPage.tsx b/frontend/src/pages/GalleryPage.tsx index ddfeb34..470c19d 100644 --- a/frontend/src/pages/GalleryPage.tsx +++ b/frontend/src/pages/GalleryPage.tsx @@ -50,6 +50,8 @@ export const GalleryPage: React.FC = () => { const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); + e.stopPropagation(); // Prevent any bubbling + if (!password.trim()) { setLoginError(t('auth.pleaseEnterPassword')); return; @@ -66,13 +68,17 @@ export const GalleryPage: React.FC = () => { success: true }); } catch (error: any) { + console.error('Login error:', error); const errorMessage = error.response?.data?.error || 'Invalid password'; + const statusCode = error.response?.status; // Map backend error messages to user-friendly translations - if (errorMessage.toLowerCase().includes('invalid password')) { + if (statusCode === 401 || errorMessage.toLowerCase().includes('invalid password')) { setLoginError(t('auth.wrongPassword')); - } else if (error.response?.status === 429 || errorMessage.toLowerCase().includes('too many')) { + } else if (statusCode === 429 || errorMessage.toLowerCase().includes('too many')) { setLoginError(t('auth.tooManyAttempts')); + } else if (statusCode === 404) { + setLoginError(t('errors.galleryNotFound')); } else { setLoginError(t('auth.invalidPassword')); } @@ -80,8 +86,12 @@ export const GalleryPage: React.FC = () => { // Track failed password entry analyticsService.trackGalleryEvent('password_entry', { gallery: slug, - success: false + success: false, + statusCode }); + + // Keep the password field to allow retry + // Do not clear the password } finally { setIsLoggingIn(false); }