feat(frontend): Add consent management UI components
- Add ConsentCheckboxes component with workshop and social media consents - Add UploadSuccessDialog with group ID display and copy functionality - Integrate consent validation into MultiUploadPage - Extend batchUpload utility to send consent data - Add GDPR compliance notices and contact information - Block uploads without required workshop consent
This commit is contained in:
parent
6ba7f7bd33
commit
39f133eadf
|
|
@ -0,0 +1,206 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Typography,
|
||||
Paper,
|
||||
Divider,
|
||||
Alert
|
||||
} from '@mui/material';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import FacebookIcon from '@mui/icons-material/Facebook';
|
||||
import InstagramIcon from '@mui/icons-material/Instagram';
|
||||
import MusicNoteIcon from '@mui/icons-material/MusicNote';
|
||||
|
||||
const ICON_MAP = {
|
||||
'Facebook': FacebookIcon,
|
||||
'Instagram': InstagramIcon,
|
||||
'MusicNote': MusicNoteIcon,
|
||||
};
|
||||
|
||||
/**
|
||||
* ConsentCheckboxes Component
|
||||
*
|
||||
* GDPR-konforme Einwilligungsabfrage für Bildveröffentlichung
|
||||
* - Pflicht: Werkstatt-Anzeige Zustimmung
|
||||
* - Optional: Social Media Plattform-Zustimmungen
|
||||
*/
|
||||
function ConsentCheckboxes({ onConsentChange, consents, disabled = false }) {
|
||||
const [platforms, setPlatforms] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Lade verfügbare Plattformen vom Backend
|
||||
fetchPlatforms();
|
||||
}, []);
|
||||
|
||||
const fetchPlatforms = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/social-media/platforms');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load platforms');
|
||||
}
|
||||
const data = await response.json();
|
||||
setPlatforms(data);
|
||||
setError(null);
|
||||
} catch (error) {
|
||||
console.error('Error loading platforms:', error);
|
||||
setError('Plattformen konnten nicht geladen werden');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleWorkshopChange = (event) => {
|
||||
onConsentChange({
|
||||
...consents,
|
||||
workshopConsent: event.target.checked
|
||||
});
|
||||
};
|
||||
|
||||
const handleSocialMediaChange = (platformId) => (event) => {
|
||||
const updatedConsents = { ...consents };
|
||||
const platformConsents = updatedConsents.socialMediaConsents || [];
|
||||
|
||||
if (event.target.checked) {
|
||||
// Füge Consent hinzu
|
||||
platformConsents.push({ platformId, consented: true });
|
||||
} else {
|
||||
// Entferne Consent
|
||||
const index = platformConsents.findIndex(c => c.platformId === platformId);
|
||||
if (index > -1) {
|
||||
platformConsents.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
updatedConsents.socialMediaConsents = platformConsents;
|
||||
onConsentChange(updatedConsents);
|
||||
};
|
||||
|
||||
const isPlatformChecked = (platformId) => {
|
||||
return consents.socialMediaConsents?.some(c => c.platformId === platformId) || false;
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
p: 3,
|
||||
mb: 3,
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
border: '2px solid #e0e0e0'
|
||||
}}
|
||||
>
|
||||
{/* Aufklärungshinweis */}
|
||||
<Alert severity="info" icon={<InfoIcon />} sx={{ mb: 3 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Wichtiger Hinweis
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Alle hochgeladenen Inhalte werden vom Hobbyhimmel-Team geprüft, bevor sie
|
||||
angezeigt oder veröffentlicht werden. Wir behalten uns vor, Inhalte nicht
|
||||
zu zeigen oder rechtswidrige Inhalte zu entfernen.
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mt: 1 }}>
|
||||
Nach dem Upload erhalten Sie eine Gruppen-ID als Referenz für die Kontaktaufnahme.
|
||||
</Typography>
|
||||
</Alert>
|
||||
|
||||
{/* Pflicht-Zustimmung: Werkstatt-Anzeige */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600, color: '#333' }}>
|
||||
Anzeige in der Werkstatt *
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={consents.workshopConsent || false}
|
||||
onChange={handleWorkshopChange}
|
||||
disabled={disabled}
|
||||
required
|
||||
sx={{
|
||||
color: '#4CAF50',
|
||||
'&.Mui-checked': { color: '#4CAF50' }
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2" sx={{ color: '#555' }}>
|
||||
Ich willige ein, dass meine hochgeladenen Bilder auf dem Monitor in
|
||||
der offenen Werkstatt des Hobbyhimmels angezeigt werden dürfen.
|
||||
Die Bilder sind nur lokal im Hobbyhimmel sichtbar und werden nicht
|
||||
über das Internet zugänglich gemacht. <strong>(Pflichtfeld)</strong>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 3 }} />
|
||||
|
||||
{/* Optional: Social Media Veröffentlichung */}
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600, color: '#333' }}>
|
||||
Social Media Veröffentlichung (optional)
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 2, color: '#666' }}>
|
||||
Ich willige ein, dass meine Bilder und Texte auf folgenden Social Media
|
||||
Plattformen veröffentlicht werden dürfen (inklusive aller angegebenen
|
||||
Informationen wie Name und Beschreibung):
|
||||
</Typography>
|
||||
|
||||
{loading ? (
|
||||
<Typography sx={{ color: '#666', fontStyle: 'italic' }}>
|
||||
Lade Plattformen...
|
||||
</Typography>
|
||||
) : error ? (
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{platforms.map(platform => {
|
||||
const IconComponent = ICON_MAP[platform.icon_name] || InfoIcon;
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={platform.id}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={isPlatformChecked(platform.id)}
|
||||
onChange={handleSocialMediaChange(platform.id)}
|
||||
disabled={disabled}
|
||||
sx={{
|
||||
color: '#2196F3',
|
||||
'&.Mui-checked': { color: '#2196F3' }
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<IconComponent fontSize="small" sx={{ color: '#2196F3' }} />
|
||||
<Typography variant="body2">
|
||||
{platform.display_name}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Widerrufs-Hinweis */}
|
||||
<Alert severity="info" sx={{ mt: 3 }}>
|
||||
<Typography variant="caption" sx={{ display: 'block' }}>
|
||||
<strong>Widerruf Ihrer Einwilligung:</strong> Sie können Ihre Einwilligung
|
||||
jederzeit widerrufen. Kontaktieren Sie uns hierzu mit Ihrer Gruppen-ID unter:{' '}
|
||||
<strong>it@hobbyhimmel.de</strong>
|
||||
</Typography>
|
||||
</Alert>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConsentCheckboxes;
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
IconButton,
|
||||
Alert,
|
||||
Tooltip,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
/**
|
||||
* UploadSuccessDialog Component
|
||||
*
|
||||
* Zeigt Erfolgsmeldung nach Upload mit:
|
||||
* - Gruppen-ID (kopierbar)
|
||||
* - Anzahl hochgeladener Bilder
|
||||
* - GDPR Kontaktinformationen
|
||||
* - Hinweis auf Moderation
|
||||
*/
|
||||
function UploadSuccessDialog({ open, onClose, groupId, uploadCount }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopyGroupId = () => {
|
||||
navigator.clipboard.writeText(groupId).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy:', err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: '16px',
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.2)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Header mit Schließen-Button */}
|
||||
<DialogTitle sx={{ pb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CheckCircleIcon sx={{ color: '#4CAF50', fontSize: 32 }} />
|
||||
<Typography variant="h5" sx={{ fontWeight: 600, color: '#333' }}>
|
||||
Upload erfolgreich!
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton onClick={onClose} size="small">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ pb: 3 }}>
|
||||
{/* Success Message */}
|
||||
<Alert severity="success" sx={{ mb: 3 }}>
|
||||
<Typography variant="body2">
|
||||
<strong>{uploadCount}</strong> {uploadCount === 1 ? 'Bild wurde' : 'Bilder wurden'} erfolgreich hochgeladen
|
||||
und werden nach der Prüfung durch das Hobbyhimmel-Team angezeigt.
|
||||
</Typography>
|
||||
</Alert>
|
||||
|
||||
{/* Gruppen-ID Anzeige */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, color: '#666', fontWeight: 600 }}>
|
||||
Ihre Referenz-Nummer:
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
p: 2,
|
||||
bgcolor: '#f5f5f5',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid #e0e0e0'
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
flex: 1,
|
||||
fontFamily: 'monospace',
|
||||
color: '#1976d2',
|
||||
fontWeight: 600
|
||||
}}
|
||||
>
|
||||
{groupId}
|
||||
</Typography>
|
||||
<Tooltip title={copied ? 'Kopiert!' : 'Kopieren'}>
|
||||
<IconButton
|
||||
onClick={handleCopyGroupId}
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: copied ? '#4CAF50' : '#e0e0e0',
|
||||
color: copied ? '#fff' : '#666',
|
||||
transition: 'all 0.3s',
|
||||
'&:hover': {
|
||||
bgcolor: copied ? '#45a049' : '#d0d0d0'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ContentCopyIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Typography variant="caption" sx={{ display: 'block', mt: 1, color: '#666' }}>
|
||||
Notieren Sie sich diese Nummer für spätere Anfragen an das Hobbyhimmel-Team.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 2 }} />
|
||||
|
||||
{/* Nächste Schritte */}
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1.5, fontWeight: 600, color: '#333' }}>
|
||||
Was passiert jetzt?
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ color: '#666', minWidth: '20px' }}>•</Typography>
|
||||
<Typography variant="body2" sx={{ color: '#666' }}>
|
||||
Ihre Bilder werden vom Team geprüft
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ color: '#666', minWidth: '20px' }}>•</Typography>
|
||||
<Typography variant="body2" sx={{ color: '#666' }}>
|
||||
Nach Freigabe erscheinen sie auf dem Werkstatt-Monitor
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ color: '#666', minWidth: '20px' }}>•</Typography>
|
||||
<Typography variant="body2" sx={{ color: '#666' }}>
|
||||
Bei gewählter Social Media Einwilligung werden sie entsprechend veröffentlicht
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* GDPR Kontakt-Info */}
|
||||
<Alert severity="info" sx={{ mt: 2 }}>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
<strong>Fragen oder Widerruf Ihrer Einwilligung?</strong>
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
Kontaktieren Sie uns mit Ihrer Referenz-Nummer unter:{' '}
|
||||
<strong>it@hobbyhimmel.de</strong>
|
||||
</Typography>
|
||||
</Alert>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ px: 3, pb: 3 }}>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="contained"
|
||||
size="large"
|
||||
fullWidth
|
||||
sx={{
|
||||
bgcolor: '#1976d2',
|
||||
'&:hover': { bgcolor: '#1565c0' },
|
||||
textTransform: 'none',
|
||||
fontWeight: 600,
|
||||
py: 1.5
|
||||
}}
|
||||
>
|
||||
Schließen
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default UploadSuccessDialog;
|
||||
|
|
@ -11,6 +11,8 @@ import ImageGallery from '../ComponentUtils/ImageGallery';
|
|||
import DescriptionInput from '../ComponentUtils/MultiUpload/DescriptionInput';
|
||||
import UploadProgress from '../ComponentUtils/MultiUpload/UploadProgress';
|
||||
import Loading from '../ComponentUtils/LoadingAnimation/Loading';
|
||||
import ConsentCheckboxes from '../ComponentUtils/MultiUpload/ConsentCheckboxes';
|
||||
import UploadSuccessDialog from '../ComponentUtils/MultiUpload/UploadSuccessDialog';
|
||||
|
||||
// Utils
|
||||
import { uploadImageBatch } from '../../Utils/batchUpload';
|
||||
|
|
@ -30,12 +32,17 @@ function MultiUploadPage() {
|
|||
description: '',
|
||||
name: ''
|
||||
});
|
||||
const [consents, setConsents] = useState({
|
||||
workshopConsent: false,
|
||||
socialMediaConsents: []
|
||||
});
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [uploadComplete, setUploadComplete] = useState(false);
|
||||
const [uploadResult, setUploadResult] = useState(null);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [imageDescriptions, setImageDescriptions] = useState({});
|
||||
const [showSuccessDialog, setShowSuccessDialog] = useState(false);
|
||||
|
||||
// Cleanup object URLs when component unmounts
|
||||
useEffect(() => {
|
||||
|
|
@ -94,6 +101,10 @@ function MultiUploadPage() {
|
|||
description: '',
|
||||
name: ''
|
||||
});
|
||||
setConsents({
|
||||
workshopConsent: false,
|
||||
socialMediaConsents: []
|
||||
});
|
||||
setImageDescriptions({});
|
||||
setIsEditMode(false);
|
||||
};
|
||||
|
|
@ -138,6 +149,17 @@ function MultiUploadPage() {
|
|||
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;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
setUploadProgress(0);
|
||||
|
||||
|
|
@ -162,15 +184,16 @@ function MultiUploadPage() {
|
|||
description: imageDescriptions[img.id] || ''
|
||||
}));
|
||||
|
||||
const result = await uploadImageBatch(filesToUpload, metadata, descriptionsArray);
|
||||
const result = await uploadImageBatch(filesToUpload, metadata, descriptionsArray, consents);
|
||||
|
||||
clearInterval(progressInterval);
|
||||
setUploadProgress(100);
|
||||
|
||||
// Kurze Verzögerung für UX, dann Erfolgsmeldung anzeigen
|
||||
// Show success dialog
|
||||
setTimeout(() => {
|
||||
setUploadComplete(true);
|
||||
setUploadResult(result);
|
||||
setShowSuccessDialog(true);
|
||||
}, 500);
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -224,6 +247,12 @@ function MultiUploadPage() {
|
|||
|
||||
{selectedImages.length > 0 && (
|
||||
<>
|
||||
<ConsentCheckboxes
|
||||
consents={consents}
|
||||
onConsentChange={setConsents}
|
||||
disabled={uploading}
|
||||
/>
|
||||
|
||||
<DescriptionInput
|
||||
metadata={metadata}
|
||||
onMetadataChange={setMetadata}
|
||||
|
|
@ -251,7 +280,7 @@ function MultiUploadPage() {
|
|||
}
|
||||
}}
|
||||
onClick={handleUpload}
|
||||
disabled={uploading || selectedImages.length === 0}
|
||||
disabled={uploading || selectedImages.length === 0 || !consents.workshopConsent}
|
||||
size="large"
|
||||
>
|
||||
🚀 {selectedImages.length} Bild{selectedImages.length !== 1 ? 'er' : ''} hochladen
|
||||
|
|
@ -294,62 +323,25 @@ function MultiUploadPage() {
|
|||
totalFiles={selectedImages.length}
|
||||
isUploading={uploading}
|
||||
/>
|
||||
|
||||
{uploadComplete && uploadResult && (
|
||||
<Box sx={{
|
||||
mt: 4,
|
||||
p: 3,
|
||||
borderRadius: '12px',
|
||||
background: 'linear-gradient(135deg, #4CAF50 0%, #45a049 100%)',
|
||||
color: 'white',
|
||||
boxShadow: '0 4px 20px rgba(76, 175, 80, 0.4)',
|
||||
animation: 'slideIn 0.5s ease-out',
|
||||
'@keyframes slideIn': {
|
||||
from: {
|
||||
opacity: 0,
|
||||
transform: 'translateY(-20px)'
|
||||
},
|
||||
to: {
|
||||
opacity: 1,
|
||||
transform: 'translateY(0)'
|
||||
}
|
||||
}
|
||||
}}>
|
||||
<Typography sx={{ fontSize: '28px', fontWeight: 'bold', mb: 1 }}>
|
||||
✅ Upload erfolgreich!
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '18px', mb: 3 }}>
|
||||
{uploadResult.imageCount} Bild{uploadResult.imageCount !== 1 ? 'er' : ''} wurden hochgeladen.
|
||||
</Typography>
|
||||
<Button
|
||||
sx={{
|
||||
background: 'white',
|
||||
color: '#4CAF50',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '16px',
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
borderRadius: '25px',
|
||||
textTransform: 'none',
|
||||
'&:hover': {
|
||||
background: '#f0f0f0',
|
||||
transform: 'scale(1.05)',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.2)'
|
||||
},
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
👍 Alles klar!
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
|
||||
{/* Success Dialog */}
|
||||
{showSuccessDialog && uploadResult && (
|
||||
<UploadSuccessDialog
|
||||
open={showSuccessDialog}
|
||||
onClose={() => {
|
||||
setShowSuccessDialog(false);
|
||||
window.location.reload();
|
||||
}}
|
||||
groupId={uploadResult.groupId}
|
||||
uploadCount={uploadResult.imageCount}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="footerContainer">
|
||||
<Footer />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Batch-Upload Funktion für mehrere Bilder
|
||||
export const uploadImageBatch = async (images, metadata, descriptions = [], onProgress) => {
|
||||
export const uploadImageBatch = async (images, metadata, descriptions = [], consents = null, onProgress) => {
|
||||
if (!images || images.length === 0) {
|
||||
throw new Error('Keine Bilder zum Upload ausgewählt');
|
||||
}
|
||||
|
|
@ -14,11 +14,16 @@ export const uploadImageBatch = async (images, metadata, descriptions = [], onPr
|
|||
// Füge Metadaten hinzu
|
||||
formData.append('metadata', JSON.stringify(metadata || {}));
|
||||
|
||||
// Füge Beschreibungen hinzu (NEU)
|
||||
// Füge Beschreibungen hinzu
|
||||
if (descriptions && descriptions.length > 0) {
|
||||
formData.append('descriptions', JSON.stringify(descriptions));
|
||||
}
|
||||
|
||||
// Füge Einwilligungen hinzu (GDPR)
|
||||
if (consents) {
|
||||
formData.append('consents', JSON.stringify(consents));
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/upload/batch', {
|
||||
method: 'POST',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user