Implemented subdomain-based feature separation for production deployment. **Backend:** - New hostGate middleware for host-based API protection - Public host blocks: /api/admin, /api/groups, /api/slideshow, /api/auth - Public host allows: /api/upload, /api/manage, /api/social-media/platforms - Rate limiting: 20 uploads/hour on public host (publicUploadLimiter) - Audit log enhancement: source_host, source_type tracking - Database migration 009: Added source tracking columns **Frontend:** - Host detection utility (hostDetection.js) with feature flags - React code splitting with lazy loading for internal features - Conditional routing: Internal routes only mounted on internal host - 404 page: Host-specific messaging and navbar - Clipboard fallback for HTTP environments **Configuration:** - Environment variables: PUBLIC_HOST, INTERNAL_HOST, ENABLE_HOST_RESTRICTION - Docker dev setup: HOST variables, TRUST_PROXY_HOPS configuration - Frontend .env.development: DANGEROUSLY_DISABLE_HOST_CHECK for Webpack **Testing:** - 20/20 hostGate unit tests passing - Local testing guide in README.dev.md - /etc/hosts setup for public.test.local, internal.test.local **Bug Fixes:** - Fixed clipboard API not available on HTTP - Fixed missing PUBLIC_HOST in frontend env-config.js - Fixed wrong navbar on 404 page for public host - Fixed social media platforms loading in UUID management **Documentation:** - CHANGELOG.md: Complete feature documentation - README.md: Feature overview - README.dev.md: Host-separation testing guide - TESTING-HOST-SEPARATION.md: Integration note
206 lines
8.3 KiB
JavaScript
206 lines
8.3 KiB
JavaScript
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 = () => {
|
|
// Fallback für HTTP (wenn navigator.clipboard nicht verfügbar)
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard.writeText(groupId).then(() => {
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
}).catch(err => {
|
|
console.error('Failed to copy:', err);
|
|
});
|
|
} else {
|
|
// Fallback: Erstelle temporäres Input-Element
|
|
try {
|
|
const input = document.createElement('input');
|
|
input.value = groupId;
|
|
document.body.appendChild(input);
|
|
input.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(input);
|
|
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;
|