From e91209f7cb38a5b840e74ed6acd8d490ef9d2294 Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 1 Sep 2025 22:56:44 +0200 Subject: [PATCH] fix: resolve multiple issues from GitHub issue #14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed duplicate German translation for 'downloadSelected' button - Added client_max_body_size configuration in nginx for file uploads - Fixed date parsing in FeedbackModerationPanel to handle timestamps - Fixed admin authentication context (req.admin vs req.user) in feedback routes - Enhanced clipboard functionality with fallback for non-HTTPS contexts - Fixed authentication token handling for numeric event IDs in uploads These changes ensure comment moderation works properly, file uploads are configured correctly, and the UI handles all edge cases properly. Fixes #14 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- backend/src/routes/adminFeedback.js | 8 ++--- frontend/nginx.conf | 8 +++++ .../admin/FeedbackModerationPanel.tsx | 7 ++++- frontend/src/config/api.ts | 21 +++++++++++-- frontend/src/i18n/locales/de.json | 1 - frontend/src/pages/admin/EventDetailsPage.tsx | 30 +++++++++++++++++-- 6 files changed, 64 insertions(+), 11 deletions(-) diff --git a/backend/src/routes/adminFeedback.js b/backend/src/routes/adminFeedback.js index a8406cc..3cccf7d 100644 --- a/backend/src/routes/adminFeedback.js +++ b/backend/src/routes/adminFeedback.js @@ -60,8 +60,8 @@ router.put('/events/:eventId/feedback-settings', settings: updatedSettings }, eventId, { type: 'admin', - id: req.user.id, - name: req.user.username + id: req.admin.id, + name: req.admin.username }); res.json(updatedSettings); @@ -167,7 +167,7 @@ router.put('/feedback/:feedbackId/:action', return res.status(400).json({ error: 'Invalid action' }); } - await feedbackService.moderateFeedback(feedbackId, action, req.user.id); + await feedbackService.moderateFeedback(feedbackId, action, req.admin.id); res.json({ success: true }); } catch (error) { @@ -184,7 +184,7 @@ router.delete('/feedback/:feedbackId', try { const { feedbackId } = req.params; - await feedbackService.deleteFeedback(feedbackId, req.user.id); + await feedbackService.deleteFeedback(feedbackId, req.admin.id); res.json({ success: true }); } catch (error) { diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 2e31dd3..0d03b3d 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -4,6 +4,10 @@ server { root /usr/share/nginx/html; index index.html; + # Allow larger file uploads (up to 100MB) + client_max_body_size 100M; + client_body_timeout 300s; + # Gzip compression gzip on; gzip_vary on; @@ -49,6 +53,10 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; proxy_read_timeout 86400; + + # Allow larger uploads for API endpoints + client_max_body_size 100M; + client_body_timeout 300s; } # Photo serving proxy diff --git a/frontend/src/components/admin/FeedbackModerationPanel.tsx b/frontend/src/components/admin/FeedbackModerationPanel.tsx index 18c15ab..34dbb03 100644 --- a/frontend/src/components/admin/FeedbackModerationPanel.tsx +++ b/frontend/src/components/admin/FeedbackModerationPanel.tsx @@ -118,7 +118,12 @@ export const FeedbackModerationPanel: React.FC = ( • - {format(parseISO(item.created_at), 'MMM d, h:mm a')} + {format( + typeof item.created_at === 'string' + ? parseISO(item.created_at) + : new Date(item.created_at), + 'MMM d, h:mm a' + )}

{item.comment}

diff --git a/frontend/src/config/api.ts b/frontend/src/config/api.ts index 14bd410..65ed3dc 100644 --- a/frontend/src/config/api.ts +++ b/frontend/src/config/api.ts @@ -48,10 +48,25 @@ api.interceptors.request.use( const galleryMatch = config.url?.match(/gallery\/([^\/]+)/); if (galleryMatch && galleryMatch[1]) { - const gallerySlug = galleryMatch[1]; + const galleryIdOrSlug = galleryMatch[1]; // Remove any query parameters from the slug - const cleanSlug = gallerySlug.split('?')[0]; - const token = localStorage.getItem(`gallery_token_${cleanSlug}`); + const cleanIdOrSlug = galleryIdOrSlug.split('?')[0]; + + // Check if it's a numeric ID (for upload endpoints) + let token = null; + if (/^\d+$/.test(cleanIdOrSlug)) { + // It's an event ID - try to find the token from current page slug + const pathParts = window.location.pathname.split('/'); + if (pathParts[1] === 'gallery' && pathParts[2]) { + const gallerySlug = pathParts[2]; + const cleanSlug = gallerySlug.split('?')[0]; + token = localStorage.getItem(`gallery_token_${cleanSlug}`); + } + } else { + // It's a slug - use it directly + token = localStorage.getItem(`gallery_token_${cleanIdOrSlug}`); + } + if (token) { if (!config.headers) { config.headers = {}; diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index b7e75e0..1a47adf 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -134,7 +134,6 @@ "sortByName": "Nach Name sortieren", "sortBySize": "Nach Größe sortieren", "allPhotos": "Alle Fotos", - "downloadSelected": "Ausgewählte herunterladen", "shareGallery": "Galerie teilen", "needHelp": "Hilfe benötigt? Kontaktieren Sie uns unter", "noPhotosFound": "Keine Fotos gefunden", diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx index 784299c..ab3c2ba 100644 --- a/frontend/src/pages/admin/EventDetailsPage.tsx +++ b/frontend/src/pages/admin/EventDetailsPage.tsx @@ -279,12 +279,38 @@ export const EventDetailsPage: React.FC = () => { const handleCopyLink = async () => { try { - await navigator.clipboard.writeText(event.share_link); + // Check if share_link exists + if (!event.share_link) { + toast.error(t('errors.noShareLink', 'No share link available')); + return; + } + + // Try modern clipboard API first + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(event.share_link); + } else { + // Fallback for non-HTTPS contexts or older browsers + const textArea = document.createElement('textarea'); + textArea.value = event.share_link; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + if (!successful) { + throw new Error('Copy failed'); + } + } + setCopiedLink(true); setTimeout(() => setCopiedLink(false), 2000); toast.success(t('toast.linkCopied')); } catch (err) { - toast.error(t('errors.somethingWentWrong')); + console.error('Copy failed:', err); + toast.error(t('errors.copyFailed', 'Failed to copy link. Please copy manually.')); } };