- Created new modular components: * ConsentManager: Manages workshop + social media consents with individual save * GroupMetadataEditor: Manages group metadata (title, description, name, year) with save * ImageDescriptionManager: Manages image descriptions with batch save * DeleteGroupButton: Standalone group deletion component - Refactored ManagementPortalPage to use modular components: * Each component in Paper box with heading inside (not outside) * HTML buttons with CSS classes (btn btn-success, btn btn-secondary) * Inline feedback with Material-UI Alert instead of SweetAlert2 popups * Icons: 💾 save, ↩ discard, 🗑️ delete * Individual save/discard functionality per component - Enhanced ConsentCheckboxes component: * Added children prop for flexible composition * Conditional heading for manage mode inside Paper box - Fixed DescriptionInput: * Removed duplicate heading (now only in parent component) - React state management improvements: * Deep copy pattern for nested objects/arrays * Sorted array comparison for order-insensitive change detection * Set-based comparison for detecting removed items * Initialization guard to prevent useEffect overwrites - Bug fixes: * Fixed image reordering using existing /api/groups/:groupId/reorder route * Fixed edit mode toggle with unsaved changes warning * Fixed consent state updates with proper object references * Fixed uploadImageBatch signature to use object destructuring * Removed unnecessary /api/manage/:token/reorder route from backend Next: Apply same modular pattern to MultiUploadPage and ModerationGroupImagesPage
246 lines
9.7 KiB
JavaScript
246 lines
9.7 KiB
JavaScript
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
|
|
*
|
|
* @param {Object} props
|
|
* @param {Function} props.onConsentChange - Callback wenn sich Consents ändern
|
|
* @param {Object} props.consents - Aktueller Consent-Status
|
|
* @param {boolean} props.disabled - Ob Checkboxen deaktiviert sind
|
|
* @param {string} props.mode - 'upload' (default) oder 'manage' (für Management Portal)
|
|
* @param {string} props.groupId - Gruppen-ID (nur für 'manage' Modus)
|
|
*/
|
|
function ConsentCheckboxes({
|
|
onConsentChange,
|
|
consents,
|
|
disabled = false,
|
|
mode = 'upload',
|
|
groupId = null,
|
|
children
|
|
}) {
|
|
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;
|
|
};
|
|
|
|
const isManageMode = mode === 'manage';
|
|
const isUploadMode = mode === 'upload';
|
|
|
|
return (
|
|
<Paper
|
|
sx={{
|
|
p: 3,
|
|
mb: 3,
|
|
borderRadius: '12px',
|
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
|
border: '2px solid #e0e0e0'
|
|
}}
|
|
>
|
|
{/* Component Header for manage mode */}
|
|
{isManageMode && (
|
|
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600, mb: 2 }}>
|
|
Einwilligungen
|
|
</Typography>
|
|
)}
|
|
|
|
{/* Aufklärungshinweis */}
|
|
<Alert severity="info" icon={<InfoIcon />} sx={{ mb: 3 }}>
|
|
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
|
|
{isUploadMode ? 'Wichtiger Hinweis' : 'Einwilligungen verwalten'}
|
|
</Typography>
|
|
{isUploadMode ? (
|
|
<>
|
|
<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>
|
|
</>
|
|
) : (
|
|
<Typography variant="body2">
|
|
Sie können Ihre Einwilligungen jederzeit widerrufen oder erteilen.
|
|
Änderungen werden erst nach dem Speichern übernommen.
|
|
</Typography>
|
|
)}
|
|
</Alert>
|
|
|
|
{/* Pflicht-Zustimmung: Werkstatt-Anzeige */}
|
|
<Box sx={{ mb: 3 }}>
|
|
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600, color: '#333' }}>
|
|
Anzeige in der Werkstatt {isUploadMode && '*'}
|
|
</Typography>
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={consents.workshopConsent || false}
|
|
onChange={handleWorkshopChange}
|
|
disabled={disabled}
|
|
required={isUploadMode}
|
|
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.
|
|
{isUploadMode && <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 */}
|
|
{isUploadMode && (
|
|
<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>
|
|
)}
|
|
|
|
{/* Additional content from parent (e.g., save buttons) */}
|
|
{children}
|
|
</Paper>
|
|
);
|
|
}
|
|
|
|
export default ConsentCheckboxes;
|