diff --git a/frontend/src/Components/ComponentUtils/ConsentManager.js b/frontend/src/Components/ComponentUtils/ConsentManager.js
index e780746..ab9a248 100644
--- a/frontend/src/Components/ComponentUtils/ConsentManager.js
+++ b/frontend/src/Components/ComponentUtils/ConsentManager.js
@@ -5,12 +5,17 @@ import ConsentCheckboxes from './MultiUpload/ConsentCheckboxes';
/**
* Manages consents with save functionality
* Wraps ConsentCheckboxes and provides save for workshop + social media consents
+ *
+ * @param mode - 'edit' (default) shows save/discard, 'upload' hides them
*/
function ConsentManager({
- initialConsents,
+ initialConsents,
+ consents: externalConsents,
+ onConsentsChange,
token,
groupId,
- onRefresh
+ onRefresh,
+ mode = 'edit'
}) {
// Initialize with proper defaults
const defaultConsents = {
@@ -26,9 +31,14 @@ function ConsentManager({
const [errorMessage, setErrorMessage] = useState('');
const [showEmailHint, setShowEmailHint] = useState(false);
- // Update ONLY ONCE when initialConsents first arrives
+ // In upload mode: use external state
+ const isUploadMode = mode === 'upload';
+ const currentConsents = isUploadMode ? externalConsents : consents;
+ const setCurrentConsents = isUploadMode ? onConsentsChange : setConsents;
+
+ // Update ONLY ONCE when initialConsents first arrives (edit mode only)
React.useEffect(() => {
- if (initialConsents && !initialized) {
+ if (initialConsents && !initialized && !isUploadMode) {
// Deep copy to avoid shared references
const consentsCopy = {
workshopConsent: initialConsents.workshopConsent,
@@ -45,9 +55,10 @@ function ConsentManager({
setInitialized(true);
}
- }, [initialConsents, initialized]);
+ }, [initialConsents, initialized, isUploadMode]);
const hasChanges = () => {
+ if (isUploadMode) return false; // No changes tracking in upload mode
// Check workshop consent
if (consents.workshopConsent !== originalConsents.workshopConsent) {
return true;
@@ -191,20 +202,23 @@ function ConsentManager({
return (
- {/* Success Message */}
- {successMessage && (
-
- {successMessage}
-
- )}
+ {/* Alerts and Buttons only in edit mode */}
+ {!isUploadMode && (
+ <>
+ {/* Success Message */}
+ {successMessage && (
+
+ {successMessage}
+
+ )}
{/* Email Hint - show IMMEDIATELY when social media revoked (before save) */}
{hasChanges() && hasSocialMediaRevocations() && !successMessage && (
@@ -256,7 +270,9 @@ function ConsentManager({
)}
-
+ >
+ )}
+
);
}
diff --git a/frontend/src/Components/ComponentUtils/GroupMetadataEditor.js b/frontend/src/Components/ComponentUtils/GroupMetadataEditor.js
index 06ae97a..db171a5 100644
--- a/frontend/src/Components/ComponentUtils/GroupMetadataEditor.js
+++ b/frontend/src/Components/ComponentUtils/GroupMetadataEditor.js
@@ -6,11 +6,17 @@ import DescriptionInput from './MultiUpload/DescriptionInput';
/**
* Manages group metadata with save functionality
* Wraps DescriptionInput and provides save for title, description, name, year
+ *
+ * @param mode - 'edit' (default) shows save/discard, 'upload' hides them, 'moderate' uses different API
*/
function GroupMetadataEditor({
- initialMetadata,
+ initialMetadata,
+ metadata: externalMetadata,
+ onMetadataChange,
token,
- onRefresh
+ groupId,
+ onRefresh,
+ mode = 'edit'
}) {
const [metadata, setMetadata] = useState(initialMetadata || {
year: new Date().getFullYear(),
@@ -26,15 +32,22 @@ function GroupMetadataEditor({
});
const [saving, setSaving] = useState(false);
- // Update when initialMetadata changes
+ // In upload mode: use external state
+ const isUploadMode = mode === 'upload';
+ const isModerateMode = mode === 'moderate';
+ const currentMetadata = isUploadMode ? externalMetadata : metadata;
+ const setCurrentMetadata = isUploadMode ? onMetadataChange : setMetadata;
+
+ // Update when initialMetadata changes (edit mode only)
React.useEffect(() => {
- if (initialMetadata) {
+ if (initialMetadata && !isUploadMode) {
setMetadata(initialMetadata);
setOriginalMetadata(initialMetadata);
}
- }, [initialMetadata]);
+ }, [initialMetadata, isUploadMode]);
const hasChanges = () => {
+ if (isUploadMode) return false; // No changes tracking in upload mode
return JSON.stringify(metadata) !== JSON.stringify(originalMetadata);
};
@@ -51,8 +64,15 @@ function GroupMetadataEditor({
try {
setSaving(true);
- const res = await fetch(`/api/manage/${token}/metadata`, {
- method: 'PUT',
+ // Different API endpoints for manage vs moderate
+ const endpoint = isModerateMode
+ ? `/groups/${groupId}`
+ : `/api/manage/${token}/metadata`;
+
+ const method = isModerateMode ? 'PATCH' : 'PUT';
+
+ const res = await fetch(endpoint, {
+ method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metadata)
});
@@ -116,11 +136,11 @@ function GroupMetadataEditor({
- {hasChanges() && (
+ {!isUploadMode && hasChanges() && (
{/* Wartende Gruppen */}
diff --git a/frontend/src/Components/Pages/MultiUploadPage.js b/frontend/src/Components/Pages/MultiUploadPage.js
index ad75937..5a54ceb 100644
--- a/frontend/src/Components/Pages/MultiUploadPage.js
+++ b/frontend/src/Components/Pages/MultiUploadPage.js
@@ -1,29 +1,23 @@
import React, { useState, useEffect } from 'react';
-import { Button, Card, CardContent, Typography, Container, Box } from '@mui/material';
-import Swal from 'sweetalert2/dist/sweetalert2.js';
-import 'sweetalert2/src/sweetalert2.scss';
+import { Card, CardContent, Typography, Container, Box } from '@mui/material';
// Components
import Navbar from '../ComponentUtils/Headers/Navbar';
import Footer from '../ComponentUtils/Footer';
import MultiImageDropzone from '../ComponentUtils/MultiUpload/MultiImageDropzone';
import ImageGallery from '../ComponentUtils/ImageGallery';
-import DescriptionInput from '../ComponentUtils/MultiUpload/DescriptionInput';
+import GroupMetadataEditor from '../ComponentUtils/GroupMetadataEditor';
+import ConsentManager from '../ComponentUtils/ConsentManager';
import UploadProgress from '../ComponentUtils/MultiUpload/UploadProgress';
import Loading from '../ComponentUtils/LoadingAnimation/Loading';
-import ConsentCheckboxes from '../ComponentUtils/MultiUpload/ConsentCheckboxes';
// Utils
import { uploadImageBatch } from '../../Utils/batchUpload';
// Styles
import '../../App.css';
-// Background.css is now globally imported in src/index.js
-
-// Styles migrated to MUI sx props in-place below
function MultiUploadPage() {
-
const [selectedImages, setSelectedImages] = useState([]);
const [metadata, setMetadata] = useState({
year: new Date().getFullYear(),
@@ -54,29 +48,23 @@ function MultiUploadPage() {
}, [selectedImages]);
const handleImagesSelected = (newImages) => {
- console.log('handleImagesSelected called with:', newImages);
-
// Convert File objects to preview objects with URLs
const imageObjects = newImages.map((file, index) => ({
- id: `preview-${Date.now()}-${index}`, // Unique ID für Preview-Modus
- file: file, // Original File object for upload
- url: URL.createObjectURL(file), // Preview URL
+ id: `preview-${Date.now()}-${index}`,
+ file: file,
+ url: URL.createObjectURL(file),
name: file.name,
originalName: file.name,
size: file.size,
type: file.type
}));
- setSelectedImages(prev => {
- const updated = [...prev, ...imageObjects];
- return updated;
- });
+ setSelectedImages(prev => [...prev, ...imageObjects]);
};
const handleRemoveImage = (indexToRemove) => {
setSelectedImages(prev => {
const imageToRemove = prev[indexToRemove];
- // Clean up the object URL to avoid memory leaks
if (imageToRemove && imageToRemove.url && imageToRemove.url.startsWith('blob:')) {
URL.revokeObjectURL(imageToRemove.url);
}
@@ -85,7 +73,6 @@ function MultiUploadPage() {
};
const handleClearAll = () => {
- // Clean up all object URLs
selectedImages.forEach(img => {
if (img.url && img.url.startsWith('blob:')) {
URL.revokeObjectURL(img.url);
@@ -107,105 +94,71 @@ function MultiUploadPage() {
setIsEditMode(false);
};
- // Handle drag-and-drop reordering (only updates local state, no API call)
const handleReorder = (reorderedItems) => {
- console.log('Reordering images in preview:', reorderedItems);
setSelectedImages(reorderedItems);
};
- // Handle edit mode toggle
const handleEditMode = (enabled) => {
setIsEditMode(enabled);
};
- // Handle description changes
const handleDescriptionChange = (imageId, description) => {
setImageDescriptions(prev => ({
...prev,
- [imageId]: description.slice(0, 200) // Enforce max length
+ [imageId]: description.slice(0, 200)
}));
};
const handleUpload = async () => {
- if (selectedImages.length === 0) {
- Swal.fire({
- icon: 'warning',
- title: 'Keine Bilder ausgewählt',
- text: 'Bitte wählen Sie mindestens ein Bild zum Upload aus.',
- confirmButtonColor: '#4CAF50'
- });
- return;
- }
+ if (selectedImages.length === 0) return;
- if (!metadata.year || !metadata.title.trim()) {
- Swal.fire({
- icon: 'warning',
- title: 'Pflichtfelder fehlen',
- text: 'Bitte gebe das Jahr und den Titel an.',
- confirmButtonColor: '#4CAF50'
- });
- return;
- }
+ if (!metadata.year || !metadata.title.trim()) return;
- // GDPR: Validate workshop consent (mandatory)
- if (!consents.workshopConsent) {
- Swal.fire({
- icon: 'error',
- title: 'Einwilligung erforderlich',
- text: 'Die Zustimmung zur Anzeige in der Werkstatt ist erforderlich.',
- confirmButtonColor: '#f44336'
- });
- return;
- }
+ if (!consents.workshopConsent) return;
setUploading(true);
setUploadProgress(0);
try {
- // Simuliere Progress (da wir noch keinen echten Progress haben)
- const progressInterval = setInterval(() => {
- setUploadProgress(prev => {
- if (prev >= 90) {
- clearInterval(progressInterval);
- return 90;
- }
- return prev + 10;
- });
- }, 200);
+ const filesToUpload = selectedImages.map(img => img.file).filter(Boolean);
+
+ if (filesToUpload.length === 0) {
+ throw new Error('Keine gültigen Bilder zum Upload');
+ }
- // Extract the actual File objects from our image objects
- const filesToUpload = selectedImages.map(img => img.file || img);
-
- // Prepare descriptions array for backend
- const descriptionsArray = selectedImages.map(img => ({
- fileName: img.name,
- description: imageDescriptions[img.id] || ''
- }));
-
- const result = await uploadImageBatch(filesToUpload, metadata, descriptionsArray, consents);
-
- clearInterval(progressInterval);
- setUploadProgress(100);
+ // Map preview IDs to actual file names for backend
+ const descriptionsForUpload = {};
+ selectedImages.forEach(img => {
+ if (imageDescriptions[img.id]) {
+ descriptionsForUpload[img.originalName] = imageDescriptions[img.id];
+ }
+ });
- // Show success content
- setTimeout(() => {
- setUploadComplete(true);
- setUploadResult(result);
- }, 500);
+ const result = await uploadImageBatch({
+ images: filesToUpload,
+ metadata,
+ imageDescriptions: descriptionsForUpload,
+ consents,
+ onProgress: setUploadProgress
+ });
+
+ setUploadComplete(true);
+ setUploadResult(result);
} catch (error) {
- setUploading(false);
console.error('Upload error:', error);
-
- Swal.fire({
- icon: 'error',
- title: 'Upload fehlgeschlagen',
- text: error.message || 'Ein Fehler ist beim Upload aufgetreten.',
- confirmButtonColor: '#f44336'
- });
+ setUploading(false);
+ setUploadComplete(false);
}
};
+ const canUpload = () => {
+ return selectedImages.length > 0 &&
+ metadata.year &&
+ metadata.title.trim() &&
+ consents.workshopConsent;
+ };
+
return (
@@ -224,93 +177,70 @@ function MultiUploadPage() {
{!uploading ? (
<>
+ {/* Image Dropzone - stays inline as it's upload-specific */}
-
+ {/* Image Gallery with descriptions */}
+ {selectedImages.length > 0 && (
+
+ )}
{selectedImages.length > 0 && (
<>
-
-
-
-
+
🚀 {selectedImages.length} Bild{selectedImages.length !== 1 ? 'er' : ''} hochladen
-
+
-
🗑️ Alle entfernen
-
+
>
)}
-
-
>
) : (
@@ -397,34 +327,19 @@ function MultiUploadPage() {
}}>
{window.location.origin}/manage/{uploadResult.managementToken}
- {
const link = `${window.location.origin}/manage/${uploadResult.managementToken}`;
navigator.clipboard.writeText(link);
- Swal.fire({
- icon: 'success',
- title: 'Link kopiert!',
- text: 'Der Verwaltungslink wurde in die Zwischenablage kopiert.',
- timer: 2000,
- showConfirmButton: false
- });
}}
>
📋 Kopieren
-
+
@@ -445,27 +360,16 @@ function MultiUploadPage() {
Fragen oder Widerruf? Kontakt: it@hobbyhimmel.de
- window.location.reload()}
>
👍 Weitere Bilder hochladen
-
+
)}
@@ -481,4 +385,4 @@ function MultiUploadPage() {
);
}
-export default MultiUploadPage;
\ No newline at end of file
+export default MultiUploadPage;
diff --git a/frontend/src/Utils/batchUpload.js b/frontend/src/Utils/batchUpload.js
index 7e0ef30..8bcf8bb 100644
--- a/frontend/src/Utils/batchUpload.js
+++ b/frontend/src/Utils/batchUpload.js
@@ -14,9 +14,9 @@ export const uploadImageBatch = async ({ images, metadata, imageDescriptions = {
// Füge Metadaten hinzu
formData.append('metadata', JSON.stringify(metadata || {}));
- // Füge Beschreibungen hinzu (convert object to array format)
- const descriptionsArray = Object.entries(imageDescriptions).map(([id, description]) => ({
- imageId: id,
+ // Füge Beschreibungen hinzu (convert object to array format with fileName)
+ const descriptionsArray = Object.entries(imageDescriptions).map(([fileName, description]) => ({
+ fileName: fileName,
description
}));
if (descriptionsArray.length > 0) {