Project-Image-Uploader/frontend/src/Components/ComponentUtils/MultiUpload/UploadSuccessDialog.js
matthias.lotz e4ddd229b8 feat: Public/Internal Host Separation
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
2025-11-25 22:02:53 +01:00

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;