refactor: Centralized styling with CSS and global MUI overrides
- Migrated all Pages from Material-UI to HTML+CSS (GroupsOverviewPage, ManagementPortalPage, ModerationGroupImagesPage, ModerationGroupsPage, PublicGroupImagesPage, SlideshowPage, MultiUploadPage) - Added comprehensive typography system in App.css (h1-h3, p, utility classes) - Added global Material-UI font overrides for Open Sans - Removed redundant fontFamily: 'roboto' from all components - Fixed button alignment in ImageGalleryCard (margin-top: auto) - Removed emojis from titles for cleaner UI - Standardized button padding (12px 30px) across application - Improved code consistency and maintainability with centralized CSS approach
This commit is contained in:
parent
25dda32c4e
commit
215acaa67f
|
|
@ -1,5 +1,193 @@
|
|||
/* Main shared styles for cards, buttons, modals used across pages */
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY - Zentrale Schrift-Definitionen
|
||||
============================================ */
|
||||
body {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
h1, .h1 {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 28px;
|
||||
color: #333333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h2, .h2 {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
color: #333333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
color: #333333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
p, .text-body {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.text-subtitle {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.text-small {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
/* ============================================
|
||||
LAYOUT & CONTAINERS
|
||||
============================================ */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PAGE HEADERS
|
||||
============================================ */
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 28px;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
UTILITY CLASSES
|
||||
============================================ */
|
||||
.flex-center {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.text-center-block {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/* Spacing utilities */
|
||||
.mt-1 { margin-top: 8px; }
|
||||
.mt-2 { margin-top: 16px; }
|
||||
.mt-3 { margin-top: 24px; }
|
||||
.mt-4 { margin-top: 32px; }
|
||||
.mb-1 { margin-bottom: 8px; }
|
||||
.mb-2 { margin-bottom: 16px; }
|
||||
.mb-3 { margin-bottom: 24px; }
|
||||
.mb-4 { margin-bottom: 32px; }
|
||||
.p-2 { padding: 16px; }
|
||||
.p-3 { padding: 24px; }
|
||||
|
||||
/* ============================================
|
||||
SUCCESS BOX (Upload Success)
|
||||
============================================ */
|
||||
.success-box {
|
||||
margin-top: 32px;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
|
||||
color: white;
|
||||
box-shadow: 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);
|
||||
}
|
||||
}
|
||||
|
||||
.success-box h2 {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.success-box p {
|
||||
font-size: 18px;
|
||||
margin-bottom: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: rgba(255,255,255,0.2);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-box-highlight {
|
||||
background: rgba(255,255,255,0.95);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
EXISTING STYLES BELOW
|
||||
============================================ */
|
||||
|
||||
/* Page-specific styles for GroupsOverviewPage */
|
||||
.groups-overview-container { padding-top: 20px; padding-bottom: 40px; min-height: 80vh; }
|
||||
.header-card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 30px; text-align: center; padding: 20px; }
|
||||
|
|
@ -52,7 +240,7 @@ p { font-family: 'Open Sans', sans-serif; color:#555; line-height:1.6; }
|
|||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn { padding:8px 12px; border:none; border-radius:6px; cursor:pointer; font-size:0.85rem; transition:background-color 0.2s; flex:1; min-width:80px; }
|
||||
.btn { padding:12px 30px; border:none; border-radius:6px; cursor:pointer; font-size:16px; transition:background-color 0.2s; min-width:80px; }
|
||||
.btn-secondary { background:#6c757d; color:white; }
|
||||
.btn-secondary:hover { background:#5a6268; }
|
||||
.btn-outline-secondary { background:transparent; border:1px solid #6c757d; color:#6c757d; }
|
||||
|
|
@ -63,7 +251,6 @@ p { font-family: 'Open Sans', sans-serif; color:#555; line-height:1.6; }
|
|||
.btn-warning:hover { background:#e0a800; }
|
||||
.btn-danger { background:#dc3545; color:white; }
|
||||
.btn-danger:hover { background:#c82333; }
|
||||
.btn-sm { padding:4px 8px; font-size:0.75rem; min-width:auto; }
|
||||
.btn:disabled { opacity:0.65; cursor:not-allowed; }
|
||||
|
||||
/* Modal */
|
||||
|
|
@ -104,3 +291,32 @@ p { font-family: 'Open Sans', sans-serif; color:#555; line-height:1.6; }
|
|||
.admin-auth-card { width:100%; max-width:420px; box-shadow:0 8px 24px rgba(0,0,0,0.08); }
|
||||
.admin-auth-form { width:100%; }
|
||||
.admin-auth-error { max-width:420px; background:#fff3f3; border:1px solid #ffcdd2; padding:24px; border-radius:12px; text-align:center; color:#b71c1c; }
|
||||
|
||||
/* ============================================
|
||||
MATERIAL-UI OVERRIDES - Globale Schriftart
|
||||
============================================ */
|
||||
/* TextField, Input, Textarea */
|
||||
.MuiTextField-root input,
|
||||
.MuiTextField-root textarea,
|
||||
.MuiInputBase-root,
|
||||
.MuiInputBase-input,
|
||||
.MuiOutlinedInput-input {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
.MuiFormLabel-root,
|
||||
.MuiInputLabel-root,
|
||||
.MuiTypography-root {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.MuiButton-root {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Checkbox Labels */
|
||||
.MuiFormControlLabel-label {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,9 @@
|
|||
background: #f8f9fa;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* ImageGalleryCard - No preview state */
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ function GroupMetadataEditor({
|
|||
>
|
||||
{/* Component Header */}
|
||||
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600, mb: 2 }}>
|
||||
📝 Projekt-Informationen
|
||||
Projekt-Informationen
|
||||
</Typography>
|
||||
|
||||
<DescriptionInput
|
||||
|
|
|
|||
|
|
@ -221,71 +221,30 @@ const ImageGalleryCard = ({
|
|||
mode === 'preview' ? (
|
||||
// Preview mode actions (for upload preview)
|
||||
<>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={() => onDelete(itemId)}
|
||||
>
|
||||
🗑️ Löschen
|
||||
</button>
|
||||
<button className="btn btn-danger" onClick={() => onDelete(itemId)}>🗑️ Löschen</button>
|
||||
{!isEditMode ? (
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => onEditMode?.(true)}
|
||||
>
|
||||
✏️ Edit
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={() => onEditMode?.(true)}>✏️ Edit </button>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-success btn-sm"
|
||||
onClick={() => onEditMode?.(false)}
|
||||
>
|
||||
✅ Fertig
|
||||
</button>
|
||||
<button className="btn btn-success" onClick={() => onEditMode?.(false)}>✅ Fertig</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// Moderation mode actions (for existing groups)
|
||||
<>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={() => onViewImages(item)}
|
||||
>
|
||||
✏️ Gruppe editieren
|
||||
</button>
|
||||
<button className="btn btn-secondary" onClick={() => onViewImages(item)}>✏️ Gruppe editieren</button>
|
||||
|
||||
{isPending ? (
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={() => onApprove(itemId, true)}
|
||||
>
|
||||
✅ Freigeben
|
||||
</button>
|
||||
<button className="btn btn-success" onClick={() => onApprove(itemId, true)}>✅ Freigeben</button>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-warning"
|
||||
onClick={() => onApprove(itemId, false)}
|
||||
>
|
||||
⏸️ Sperren
|
||||
</button>
|
||||
<button className="btn btn-warning" onClick={() => onApprove(itemId, false)}>⏸️ Sperren</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={() => onDelete(itemId)}
|
||||
>
|
||||
🗑️ Löschen
|
||||
</button>
|
||||
<button className="btn btn-danger" onClick={() => onDelete(itemId)}>🗑️ Löschen</button>
|
||||
</>
|
||||
)
|
||||
) : mode !== 'single-image' ? (
|
||||
// Public view mode (only for group cards, not single images)
|
||||
<button
|
||||
className="view-button"
|
||||
onClick={() => onViewImages(item)}
|
||||
title="Anzeigen"
|
||||
>
|
||||
Anzeigen
|
||||
</button>
|
||||
<button className="view-button" onClick={() => onViewImages(item)} title="Anzeigen">Anzeigen</button>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ function DescriptionInput({
|
|||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const fieldLabelSx = {
|
||||
fontFamily: 'roboto',
|
||||
fontSize: '14px',
|
||||
color: '#555555',
|
||||
marginBottom: '8px',
|
||||
|
|
@ -25,7 +24,6 @@ function DescriptionInput({
|
|||
};
|
||||
|
||||
const sectionTitleSx = {
|
||||
fontFamily: 'roboto',
|
||||
fontSize: '18px',
|
||||
color: '#333333',
|
||||
marginBottom: '15px',
|
||||
|
|
@ -68,7 +66,7 @@ function DescriptionInput({
|
|||
};
|
||||
|
||||
const requiredIndicatorSx = { color: '#E57373', fontSize: '16px' };
|
||||
const optionalIndicatorSx = { color: '#9E9E9E', fontSize: '12px', fontStyle: 'italic' };
|
||||
const optionalIndicatorSx = { color: '#9E9E9E', fontSize: '12px' };
|
||||
|
||||
return (
|
||||
<Box sx={{ marginTop: '20px', marginBottom: '20px' }}>
|
||||
|
|
|
|||
|
|
@ -77,15 +77,13 @@ function MultiImageDropzone({ onImagesSelected, selectedImages = [] }) {
|
|||
|
||||
const dropzoneTextSx = {
|
||||
fontSize: '18px',
|
||||
fontFamily: 'roboto',
|
||||
color: '#666666',
|
||||
margin: '10px 0'
|
||||
};
|
||||
|
||||
const dropzoneSubtextSx = {
|
||||
fontSize: '14px',
|
||||
color: '#999999',
|
||||
fontFamily: 'roboto'
|
||||
color: '#999999'
|
||||
};
|
||||
|
||||
const fileCountSx = {
|
||||
|
|
@ -106,7 +104,7 @@ function MultiImageDropzone({ onImagesSelected, selectedImages = [] }) {
|
|||
onClick={handleClick}
|
||||
>
|
||||
<Typography sx={dropzoneTextSx}>
|
||||
📸 Mehrere Bilder hier hinziehen oder klicken zum Auswählen
|
||||
Mehrere Bilder hier hinziehen oder klicken zum Auswählen
|
||||
</Typography>
|
||||
|
||||
<Typography sx={dropzoneSubtextSx}>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import {
|
||||
Container,
|
||||
Card,
|
||||
Typography,
|
||||
Box,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
|
||||
|
||||
|
||||
|
|
@ -63,14 +56,14 @@ function GroupsOverviewPage() {
|
|||
return (
|
||||
<div className="allContainer">
|
||||
<Navbar />
|
||||
<Container maxWidth="lg" className="page-container">
|
||||
<div className="loading-container">
|
||||
<CircularProgress size={60} color="primary" />
|
||||
<Typography variant="h6" style={{ marginTop: '20px', color: '#666666' }}>
|
||||
Slideshows werden geladen...
|
||||
</Typography>
|
||||
<div className="container">
|
||||
<div className="flex-center" style={{ minHeight: '400px' }}>
|
||||
<div className="text-center">
|
||||
<div className="loading-spinner" style={{ width: '60px', height: '60px', border: '4px solid #f3f3f3', borderTop: '4px solid #3498db', borderRadius: '50%', animation: 'spin 1s linear infinite', margin: '0 auto' }}></div>
|
||||
<p className="mt-3" style={{ color: '#666666' }}>Slideshows werden geladen...</p>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -86,53 +79,39 @@ function GroupsOverviewPage() {
|
|||
</Helmet>
|
||||
<Navbar />
|
||||
|
||||
<Container maxWidth="lg" className="page-container">
|
||||
<div className="container page-container">
|
||||
{/* Header */}
|
||||
<Card className="header-card">
|
||||
<Typography className="header-title">
|
||||
Alle Slideshows
|
||||
</Typography>
|
||||
<Typography className="header-subtitle">
|
||||
Übersicht aller erstellten Slideshows.
|
||||
</Typography>
|
||||
</Card>
|
||||
<div className="card">
|
||||
<h1 className="page-title">Alle Slideshows</h1>
|
||||
<p className="page-subtitle">Übersicht aller erstellten Slideshows.</p>
|
||||
</div>
|
||||
|
||||
{/* Groups Grid */}
|
||||
{error ? (
|
||||
<div className="empty-state">
|
||||
<Typography variant="h6" style={{ color: '#f44336', marginBottom: '20px' }}>
|
||||
😕 Fehler beim Laden
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{ color: '#666666', marginBottom: '30px' }}>
|
||||
{error}
|
||||
</Typography>
|
||||
<div className="empty-state">
|
||||
<h2 style={{ color: '#f44336' }} className="mb-3">😕 Fehler beim Laden</h2>
|
||||
<p style={{ color: '#666666' }} className="mb-4">{error}</p>
|
||||
<button onClick={loadGroups} className="btn btn-secondary">
|
||||
🔄 Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
) : groups.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<Typography variant="h4" style={{ color: '#666666', marginBottom: '20px' }}>
|
||||
📸 Keine Slideshows vorhanden
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{ color: '#999999', marginBottom: '30px' }}>
|
||||
<div className="empty-state">
|
||||
<h2 style={{ color: '#666666' }} className="mb-3">📸 Keine Slideshows vorhanden</h2>
|
||||
<p style={{ color: '#999999' }} className="mb-4">
|
||||
Erstellen Sie Ihre erste Slideshow, indem Sie mehrere Bilder hochladen.
|
||||
</Typography>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={handleCreateNew}
|
||||
style={{ fontSize: '16px', padding: '12px 24px' }}
|
||||
>
|
||||
</p>
|
||||
<button className="btn btn-success" onClick={handleCreateNew}>
|
||||
➕ Erste Slideshow erstellen
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Box marginBottom={2}>
|
||||
<Typography variant="h6" style={{ color: '#666666' }}>
|
||||
📊 {groups.length} Slideshow{groups.length !== 1 ? 's' : ''} gefunden
|
||||
</Typography>
|
||||
</Box>
|
||||
<div className="mb-3">
|
||||
<h3 style={{ color: '#666666' }}>
|
||||
{groups.length} Slideshow{groups.length !== 1 ? 's' : ''} gefunden
|
||||
</h3>
|
||||
</div>
|
||||
<ImageGallery
|
||||
items={groups}
|
||||
onViewImages={(group) => handleViewGroup(group.groupId)}
|
||||
|
|
@ -142,7 +121,7 @@ function GroupsOverviewPage() {
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<div className="footerContainer">
|
||||
<Footer />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Container, Card, CardContent, Typography, Box, Button } from '@mui/material';
|
||||
import Swal from 'sweetalert2';
|
||||
import NavbarUpload from '../ComponentUtils/Headers/NavbarUpload';
|
||||
import Footer from '../ComponentUtils/Footer';
|
||||
|
|
@ -180,9 +179,9 @@ function ManagementPortalPage() {
|
|||
return (
|
||||
<div className="allContainer">
|
||||
<NavbarUpload />
|
||||
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<div className="container flex-center" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
|
||||
<Loading />
|
||||
</Container>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -192,19 +191,15 @@ function ManagementPortalPage() {
|
|||
return (
|
||||
<div className="allContainer">
|
||||
<NavbarUpload />
|
||||
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}>
|
||||
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', textAlign: 'center' }}>
|
||||
<Typography variant="h5" color="error" gutterBottom>
|
||||
{error}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
||||
{error}
|
||||
</Typography>
|
||||
<Button variant="contained" onClick={() => navigate('/')}>
|
||||
<div className="container" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
|
||||
<div className="card text-center">
|
||||
<h2 style={{ color: '#f44336' }} className="mb-2">{error}</h2>
|
||||
<p style={{ color: '#666666' }} className="mb-4">{error}</p>
|
||||
<button className="btn btn-primary" onClick={() => navigate('/')}>
|
||||
Zur Startseite
|
||||
</Button>
|
||||
</Card>
|
||||
</Container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -214,19 +209,17 @@ function ManagementPortalPage() {
|
|||
<div className="allContainer">
|
||||
<NavbarUpload />
|
||||
|
||||
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}>
|
||||
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', mb: '20px' }}>
|
||||
<CardContent>
|
||||
<Typography sx={{ fontFamily: 'roboto', fontWeight: 400, fontSize: '28px', textAlign: 'center', mb: '10px', color: '#333333' }}>
|
||||
Mein Upload verwalten
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: 'roboto', fontWeight: 300, fontSize: '16px', color: '#666666', textAlign: 'center', mb: '30px' }}>
|
||||
<div className="container" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
|
||||
<div className="card mb-3">
|
||||
<div className="card-content">
|
||||
<h1 className="page-title text-center mb-2">Mein Upload verwalten</h1>
|
||||
<p className="page-subtitle text-center mb-4">
|
||||
Hier können Sie Ihre hochgeladenen Bilder verwalten, Metadaten bearbeiten und Einwilligungen ändern.
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
{/* Group Overview */}
|
||||
{group && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<div className="mb-4">
|
||||
<ImageGalleryCard
|
||||
item={group}
|
||||
showActions={false}
|
||||
|
|
@ -235,29 +228,25 @@ function ManagementPortalPage() {
|
|||
hidePreview={true}
|
||||
/>
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom sx={{ fontWeight: 600 }}>
|
||||
Erteilte Einwilligungen:
|
||||
</Typography>
|
||||
<div className="mt-3">
|
||||
<h3 className="text-small" style={{ fontWeight: 600 }}>Erteilte Einwilligungen:</h3>
|
||||
<ConsentBadges group={group} />
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Images Dropzone */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}>
|
||||
Weitere Bilder hinzufügen
|
||||
</Typography>
|
||||
<div className="mb-4">
|
||||
<h3 className="mb-2" style={{ fontWeight: 600 }}>Weitere Bilder hinzufügen</h3>
|
||||
<MultiImageDropzone
|
||||
onImagesSelected={handleImagesSelected}
|
||||
selectedImages={[]}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
{/* Image Descriptions Manager */}
|
||||
{group && group.images && group.images.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<div className="mb-4">
|
||||
<ImageDescriptionManager
|
||||
images={group.images}
|
||||
token={token}
|
||||
|
|
@ -265,44 +254,44 @@ function ManagementPortalPage() {
|
|||
onReorder={handleReorder}
|
||||
onRefresh={loadGroup}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Group Metadata Editor */}
|
||||
{group && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<div className="mb-4">
|
||||
<GroupMetadataEditor
|
||||
initialMetadata={group.metadata}
|
||||
token={token}
|
||||
onRefresh={loadGroup}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Consent Manager */}
|
||||
{group && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<div className="mb-4">
|
||||
<ConsentManager
|
||||
initialConsents={group.consents}
|
||||
token={token}
|
||||
groupId={group.groupId}
|
||||
onRefresh={loadGroup}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Group Button */}
|
||||
{group && (
|
||||
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}>
|
||||
<div className="mt-4 flex-center">
|
||||
<DeleteGroupButton
|
||||
token={token}
|
||||
groupName={group.title || group.name || 'diese Gruppe'}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footerContainer">
|
||||
<Footer />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Container, Box } from '@mui/material';
|
||||
|
||||
// Services
|
||||
import { adminGet } from '../../services/adminApi';
|
||||
|
|
@ -81,7 +80,7 @@ const ModerationGroupImagesPage = () => {
|
|||
<div className="allContainer">
|
||||
<Navbar />
|
||||
|
||||
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}>
|
||||
<div className="container" style={{ minHeight: '80vh', paddingTop: '20px', paddingBottom: '40px' }}>
|
||||
{/* Image Descriptions Manager */}
|
||||
<ImageDescriptionManager
|
||||
images={group.images}
|
||||
|
|
@ -99,15 +98,15 @@ const ModerationGroupImagesPage = () => {
|
|||
/>
|
||||
|
||||
{/* Back Button */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
|
||||
<div className="flex-center mt-4">
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={() => navigate('/moderation')}
|
||||
>
|
||||
↩ Zurück zur Übersicht
|
||||
</button>
|
||||
</Box>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footerContainer"><Footer /></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Container, Box, FormControl, FormLabel, FormGroup, FormControlLabel, Checkbox, Typography } from '@mui/material';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import Swal from 'sweetalert2/dist/sweetalert2.js';
|
||||
|
||||
|
|
@ -268,23 +267,14 @@ const ModerationGroupsPage = () => {
|
|||
<meta name="description" content="Interne Moderationsseite - Nicht öffentlich zugänglich" />
|
||||
</Helmet>
|
||||
|
||||
<Container className="moderation-content" maxWidth="lg" style={{ paddingTop: '20px' }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
gap: 2,
|
||||
mb: 3
|
||||
}}>
|
||||
<Typography variant="h4" component="h1">
|
||||
Moderation
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<div className="container moderation-content" style={{ paddingTop: '20px' }}>
|
||||
<div className="flex-center" style={{ justifyContent: 'space-between', flexWrap: 'wrap', gap: '16px', marginBottom: '24px' }}>
|
||||
<h1>Moderation</h1>
|
||||
<div className="flex-center" style={{ gap: '16px' }}>
|
||||
{user?.username && (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<p className="text-small" style={{ color: '#666666', margin: 0 }}>
|
||||
Eingeloggt als <strong>{user.username}</strong>
|
||||
</Typography>
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -295,8 +285,8 @@ const ModerationGroupsPage = () => {
|
|||
>
|
||||
{logoutPending ? 'Wird abgemeldet…' : 'Logout'}
|
||||
</button>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="moderation-stats">
|
||||
<div className="stat-item">
|
||||
|
|
@ -314,79 +304,65 @@ const ModerationGroupsPage = () => {
|
|||
</div>
|
||||
|
||||
{/* Filter und Export Controls */}
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: 2,
|
||||
mb: 3,
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap'
|
||||
}}>
|
||||
<FormControl component="fieldset" sx={{ minWidth: 250 }}>
|
||||
<FormLabel component="legend" sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<FilterListIcon sx={{ mr: 0.5, fontSize: 18 }} />
|
||||
<div style={{ display: 'flex', gap: '16px', marginBottom: '24px', alignItems: 'flex-start', flexWrap: 'wrap' }}>
|
||||
<fieldset style={{ minWidth: '250px', border: '1px solid #ccc', borderRadius: '8px', padding: '16px' }}>
|
||||
<legend style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', fontSize: '14px', fontWeight: 600 }}>
|
||||
<FilterListIcon style={{ marginRight: '4px', fontSize: '18px' }} />
|
||||
Consent-Filter
|
||||
</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={consentFilters.workshop}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, workshop: e.target.checked})}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label="Werkstatt"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={consentFilters.facebook}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, facebook: e.target.checked})}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label="Facebook"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={consentFilters.instagram}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, instagram: e.target.checked})}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label="Instagram"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={consentFilters.tiktok}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, tiktok: e.target.checked})}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label="TikTok"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
</legend>
|
||||
<div>
|
||||
<label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consentFilters.workshop}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, workshop: e.target.checked})}
|
||||
style={{ marginRight: '8px' }}
|
||||
/>
|
||||
Werkstatt
|
||||
</label>
|
||||
<label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consentFilters.facebook}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, facebook: e.target.checked})}
|
||||
style={{ marginRight: '8px' }}
|
||||
/>
|
||||
Facebook
|
||||
</label>
|
||||
<label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consentFilters.instagram}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, instagram: e.target.checked})}
|
||||
style={{ marginRight: '8px' }}
|
||||
/>
|
||||
Instagram
|
||||
</label>
|
||||
<label style={{ display: 'flex', alignItems: 'center', marginBottom: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consentFilters.tiktok}
|
||||
onChange={(e) => setConsentFilters({...consentFilters, tiktok: e.target.checked})}
|
||||
style={{ marginRight: '8px' }}
|
||||
/>
|
||||
TikTok
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={exportConsentData}
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
padding: '10px 20px'
|
||||
}}
|
||||
>
|
||||
📥 Consent-Daten exportieren
|
||||
Consent-Daten exportieren
|
||||
</button>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
{/* Wartende Gruppen */}
|
||||
<section className="moderation-section">
|
||||
<ImageGallery
|
||||
items={pendingGroups}
|
||||
title={`🔍 Wartende Freigabe (${pendingGroups.length})`}
|
||||
title={`Wartende Freigabe (${pendingGroups.length})`}
|
||||
onApprove={approveGroup}
|
||||
onViewImages={viewGroupImages}
|
||||
onDelete={deleteGroup}
|
||||
|
|
@ -400,7 +376,7 @@ const ModerationGroupsPage = () => {
|
|||
<section className="moderation-section">
|
||||
<ImageGallery
|
||||
items={approvedGroups}
|
||||
title={`✅ Freigegebene Gruppen (${approvedGroups.length})`}
|
||||
title={`Freigegebene Gruppen (${approvedGroups.length})`}
|
||||
onApprove={approveGroup}
|
||||
onViewImages={viewGroupImages}
|
||||
onDelete={deleteGroup}
|
||||
|
|
@ -426,7 +402,7 @@ const ModerationGroupsPage = () => {
|
|||
onDeleteImage={deleteImage}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
</div>
|
||||
<div className="footerContainer"><Footer /></div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -471,7 +447,7 @@ const ImageModal = ({ group, onClose, onDeleteImage }) => {
|
|||
<div className="image-actions">
|
||||
<span className="image-name">{image.originalName}</span>
|
||||
<button
|
||||
className="btn btn-danger btn-sm"
|
||||
className="btn btn-danger"
|
||||
onClick={() => onDeleteImage(group.groupId, image.id)}
|
||||
title="Bild löschen"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, Typography, Container, Box } from '@mui/material';
|
||||
|
||||
// Components
|
||||
import NavbarUpload from '../ComponentUtils/Headers/NavbarUpload';
|
||||
|
|
@ -163,17 +162,17 @@ function MultiUploadPage() {
|
|||
<div className="allContainer">
|
||||
{<NavbarUpload />}
|
||||
|
||||
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}>
|
||||
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', mb: '20px' }}>
|
||||
<CardContent>
|
||||
<Typography sx={{ fontFamily: 'roboto', fontWeight: 400, fontSize: '28px', textAlign: 'center', mb: '10px', color: '#333333' }}>
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<div className="card-content">
|
||||
<h1 className="page-title">
|
||||
Project Image Uploader
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: 'roboto', fontWeight: 300, fontSize: '16px', color: '#666666', textAlign: 'center', mb: '30px' }}>
|
||||
</h1>
|
||||
<p className="page-subtitle">
|
||||
Lade ein oder mehrere Bilder von deinem Projekt hoch und beschreibe dein Projekt in wenigen Worten.
|
||||
<br />
|
||||
Die Bilder werden nur hier im Hobbyhimmel auf dem Monitor gezeigt, es wird an keine Dritten weiter gegeben.
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
{!uploading ? (
|
||||
<>
|
||||
|
|
@ -215,15 +214,11 @@ function MultiUploadPage() {
|
|||
/>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'center', mt: 3, flexWrap: 'wrap' }}>
|
||||
<div className="flex-center">
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={handleUpload}
|
||||
disabled={!canUpload()}
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
padding: '12px 30px'
|
||||
}}
|
||||
>
|
||||
🚀 {selectedImages.length} Bild{selectedImages.length !== 1 ? 'er' : ''} hochladen
|
||||
</button>
|
||||
|
|
@ -231,14 +226,10 @@ function MultiUploadPage() {
|
|||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={handleClearAll}
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
padding: '12px 30px'
|
||||
}}
|
||||
>
|
||||
🗑️ Alle entfernen
|
||||
</button>
|
||||
</Box>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -254,85 +245,58 @@ function MultiUploadPage() {
|
|||
/>
|
||||
</>
|
||||
) : (
|
||||
<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 }}>
|
||||
<div className="success-box">
|
||||
<h2>
|
||||
✅ Upload erfolgreich!
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '18px', mb: 2 }}>
|
||||
</h2>
|
||||
<p>
|
||||
{uploadResult?.imageCount || 0} Bild{uploadResult?.imageCount === 1 ? '' : 'er'} {uploadResult?.imageCount === 1 ? 'wurde' : 'wurden'} hochgeladen.
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
<Box sx={{ bgcolor: 'rgba(255,255,255,0.2)', borderRadius: '8px', p: 2, mb: 2 }}>
|
||||
<Typography sx={{ fontSize: '14px', mb: 1 }}>
|
||||
<div className="info-box">
|
||||
<p className="text-small">
|
||||
Ihre Referenz-Nummer:
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '20px', fontFamily: 'monospace', fontWeight: 'bold', mb: 1 }}>
|
||||
</p>
|
||||
<p style={{ fontSize: '20px', fontFamily: 'monospace', fontWeight: 'bold', marginBottom: '8px' }}>
|
||||
{uploadResult?.groupId}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '12px', opacity: 0.9 }}>
|
||||
</p>
|
||||
<p className="text-small" style={{ opacity: 0.9 }}>
|
||||
Notieren Sie sich diese Nummer für spätere Anfragen an das Team.
|
||||
</Typography>
|
||||
</Box>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{uploadResult?.managementToken && (
|
||||
<Box sx={{
|
||||
bgcolor: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '8px',
|
||||
p: 2.5,
|
||||
mb: 2,
|
||||
border: '2px solid rgba(255,255,255,0.3)'
|
||||
}}>
|
||||
<Typography sx={{ fontSize: '16px', fontWeight: 'bold', mb: 1.5, color: '#2e7d32' }}>
|
||||
<div className="info-box-highlight">
|
||||
<h3 style={{ fontSize: '16px', fontWeight: 'bold', marginBottom: '12px', color: '#2e7d32' }}>
|
||||
🔗 Verwaltungslink für Ihren Upload
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '13px', mb: 1.5, color: '#333' }}>
|
||||
</h3>
|
||||
<p style={{ fontSize: '13px', marginBottom: '12px', color: '#333' }}>
|
||||
Mit diesem Link können Sie später Ihre Bilder verwalten, Einwilligungen widerrufen oder die Gruppe löschen:
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
<Box sx={{
|
||||
bgcolor: '#f5f5f5',
|
||||
p: 1.5,
|
||||
<div style={{
|
||||
background: '#f5f5f5',
|
||||
padding: '12px',
|
||||
borderRadius: '6px',
|
||||
mb: 1.5,
|
||||
marginBottom: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
gap: '8px',
|
||||
flexWrap: 'wrap'
|
||||
}}>
|
||||
<Typography sx={{
|
||||
<p style={{
|
||||
fontSize: '13px',
|
||||
fontFamily: 'monospace',
|
||||
color: '#1976d2',
|
||||
wordBreak: 'break-all',
|
||||
flex: 1,
|
||||
minWidth: '200px'
|
||||
minWidth: '200px',
|
||||
margin: 0
|
||||
}}>
|
||||
{window.location.origin}/manage/{uploadResult.managementToken}
|
||||
</Typography>
|
||||
</p>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
padding: '6px 16px'
|
||||
}}
|
||||
onClick={() => {
|
||||
const link = `${window.location.origin}/manage/${uploadResult.managementToken}`;
|
||||
// Fallback für HTTP (wenn navigator.clipboard nicht verfügbar)
|
||||
|
|
@ -351,43 +315,39 @@ function MultiUploadPage() {
|
|||
>
|
||||
📋 Kopieren
|
||||
</button>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
<Typography sx={{ fontSize: '11px', color: '#666', mb: 0.5 }}>
|
||||
<p className="text-small" style={{ color: '#666', marginBottom: '4px' }}>
|
||||
⚠️ <strong>Wichtig:</strong> Bewahren Sie diesen Link sicher auf! Jeder mit diesem Link kann Ihren Upload verwalten.
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '11px', color: '#666', fontStyle: 'italic' }}>
|
||||
</p>
|
||||
<p className="text-small" style={{ color: '#666', fontStyle: 'italic' }}>
|
||||
ℹ️ <strong>Hinweis:</strong> Über diesen Link können Sie nur die Bilder in der Werkstatt verwalten. Bereits auf Social Media Plattformen veröffentlichte Bilder müssen separat dort gelöscht werden.
|
||||
</Typography>
|
||||
</Box>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Typography sx={{ fontSize: '13px', mb: 2, opacity: 0.95 }}>
|
||||
<p style={{ fontSize: '13px', marginBottom: '16px', opacity: 0.95 }}>
|
||||
Die Bilder werden geprüft und nach Freigabe auf dem Werkstatt-Monitor angezeigt.
|
||||
{' '}Bei Social Media Einwilligung werden sie entsprechend veröffentlicht.
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
<Typography sx={{ fontSize: '12px', mb: 3, opacity: 0.9 }}>
|
||||
<p style={{ fontSize: '12px', marginBottom: '24px', opacity: 0.9 }}>
|
||||
<strong>Fragen oder Widerruf?</strong> Kontakt: <strong>it@hobbyhimmel.de</strong>
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
<button
|
||||
className="btn btn-success"
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
padding: '12px 30px'
|
||||
}}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
👍 Weitere Bilder hochladen
|
||||
</button>
|
||||
</Box>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footerContainer">
|
||||
<Footer />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Container } from '@mui/material';
|
||||
import Navbar from '../ComponentUtils/Headers/Navbar';
|
||||
import Footer from '../ComponentUtils/Footer';
|
||||
import ImageGalleryCard from '../ComponentUtils/ImageGalleryCard';
|
||||
|
|
@ -42,7 +41,7 @@ const PublicGroupImagesPage = () => {
|
|||
<div className="allContainer">
|
||||
<Navbar />
|
||||
|
||||
<Container maxWidth="lg" className="page-container" style={{ marginTop: '40px' }}>
|
||||
<div className="container page-container" style={{ marginTop: '40px' }}>
|
||||
<ImageGalleryCard
|
||||
item={group}
|
||||
showActions={false}
|
||||
|
|
@ -70,7 +69,7 @@ const PublicGroupImagesPage = () => {
|
|||
return acc;
|
||||
}, {}) : {}}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<div className="footerContainer"><Footer /></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Typography,
|
||||
Box,
|
||||
CircularProgress,
|
||||
IconButton
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Home as HomeIcon,
|
||||
ExitToApp as ExitIcon
|
||||
|
|
@ -172,12 +166,12 @@ function SlideshowPage() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={fullscreenSx}>
|
||||
<Box sx={loadingContainerSx}>
|
||||
<CircularProgress sx={{ color: 'white', mb: 2 }} />
|
||||
<Typography sx={{ color: 'white' }}>Slideshow wird geladen...</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', color: 'white' }}>
|
||||
<div className="loading-spinner" style={{ width: '60px', height: '60px', border: '4px solid rgba(255,255,255,0.3)', borderTop: '4px solid white', borderRadius: '50%', animation: 'spin 1s linear infinite', marginBottom: '16px' }}></div>
|
||||
<p style={{ color: 'white', margin: 0 }}>Slideshow wird geladen...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -192,27 +186,27 @@ function SlideshowPage() {
|
|||
|
||||
if (error) {
|
||||
return (
|
||||
<Box sx={fullscreenSx}>
|
||||
<Box sx={loadingContainerSx}>
|
||||
<Typography sx={{ color: 'white', fontSize: '24px' }}>{error}</Typography>
|
||||
<IconButton sx={homeButtonSx} onClick={() => navigate('/')} title="Zur Startseite">
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', color: 'white' }}>
|
||||
<p style={{ color: 'white', fontSize: '24px', margin: 0 }}>{error}</p>
|
||||
<button style={{ position: 'absolute', top: '20px', left: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Zur Startseite">
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentGroup || !currentImage) {
|
||||
return (
|
||||
<Box sx={fullscreenSx}>
|
||||
<Box sx={loadingContainerSx}>
|
||||
<Typography sx={{ color: 'white', fontSize: '24px' }}>Keine Bilder verfügbar</Typography>
|
||||
<IconButton sx={homeButtonSx} onClick={() => navigate('/')} title="Zur Startseite">
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', color: 'white' }}>
|
||||
<p style={{ color: 'white', fontSize: '24px', margin: 0 }}>Keine Bilder verfügbar</p>
|
||||
<button style={{ position: 'absolute', top: '20px', left: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Zur Startseite">
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -275,41 +269,41 @@ function SlideshowPage() {
|
|||
const metaTextSx = { color: '#999', fontSize: '12px', mt: 1, fontFamily: 'roboto' };
|
||||
|
||||
return (
|
||||
<Box sx={fullscreenSx}>
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: '#000', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', zIndex: 9999, overflow: 'hidden' }}>
|
||||
{/* Navigation Buttons */}
|
||||
<IconButton sx={homeButtonSx} onClick={() => navigate('/')} title="Zur Startseite">
|
||||
<button style={{ position: 'absolute', top: '20px', left: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Zur Startseite" onMouseOver={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.8)'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.5)'}>
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
</button>
|
||||
|
||||
<IconButton sx={exitButtonSx} onClick={() => navigate('/')} title="Slideshow beenden">
|
||||
<button style={{ position: 'absolute', top: '20px', right: '20px', color: 'white', backgroundColor: 'rgba(0,0,0,0.5)', border: 'none', borderRadius: '50%', width: '48px', height: '48px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={() => navigate('/')} title="Slideshow beenden" onMouseOver={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.8)'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.5)'}>
|
||||
<ExitIcon />
|
||||
</IconButton>
|
||||
</button>
|
||||
|
||||
{/* Hauptbild */}
|
||||
<Box component="img" src={getImageSrc(currentImage, false)} alt={currentImage.originalName} sx={{ ...slideshowImageSx, opacity: fadeOut ? 0 : 1 }} />
|
||||
<img src={getImageSrc(currentImage, false)} alt={currentImage.originalName} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', transition: `opacity ${TRANSITION_TIME}ms ease-in-out`, opacity: fadeOut ? 0 : 1 }} />
|
||||
|
||||
{/* Bildbeschreibung (wenn vorhanden) */}
|
||||
{currentImage.imageDescription && (
|
||||
<Box sx={imageDescriptionSx}>
|
||||
<Typography sx={imageDescriptionTextSx}>{currentImage.imageDescription}</Typography>
|
||||
</Box>
|
||||
<div style={{ position: 'fixed', bottom: '140px', left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0,0,0,0.7)', padding: '15px 30px', borderRadius: '8px', maxWidth: '80%', textAlign: 'center', backdropFilter: 'blur(5px)', zIndex: 10002 }}>
|
||||
<p style={{ color: 'white', fontSize: '18px', margin: 0, lineHeight: 1.4, fontFamily: 'Open Sans, sans-serif' }}>{currentImage.imageDescription}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Beschreibung */}
|
||||
<Box sx={descriptionContainerSx}>
|
||||
<div style={{ position: 'fixed', left: '40px', bottom: '40px', backgroundColor: 'rgba(0,0,0,0.8)', padding: '25px 35px', borderRadius: '12px', maxWidth: '35vw', minWidth: '260px', textAlign: 'left', backdropFilter: 'blur(5px)', zIndex: 10001, boxShadow: '0 4px 24px rgba(0,0,0,0.4)' }}>
|
||||
{/* Titel */}
|
||||
<Typography sx={titleTextSx}>{currentGroup.title || 'Unbenanntes Projekt'}</Typography>
|
||||
<h2 style={{ color: 'white', fontSize: '28px', fontWeight: 500, marginBottom: '8px', marginTop: 0, fontFamily: 'Open Sans, sans-serif' }}>{currentGroup.title || 'Unbenanntes Projekt'}</h2>
|
||||
|
||||
{/* Jahr und Name */}
|
||||
<Typography sx={yearAuthorTextSx}>{currentGroup.year}{currentGroup.name && ` • ${currentGroup.name}`}</Typography>
|
||||
<p style={{ color: '#FFD700', fontSize: '18px', fontWeight: 400, marginBottom: '8px', marginTop: 0, fontFamily: 'Open Sans, sans-serif' }}>{currentGroup.year}{currentGroup.name && ` • ${currentGroup.name}`}</p>
|
||||
|
||||
{/* Beschreibung (wenn vorhanden) */}
|
||||
{currentGroup.description && <Typography sx={descriptionTextSx}>{currentGroup.description}</Typography>}
|
||||
{currentGroup.description && <p style={{ color: '#E0E0E0', fontSize: '16px', fontWeight: 300, marginBottom: '8px', marginTop: 0, fontFamily: 'Open Sans, sans-serif', lineHeight: 1.4 }}>{currentGroup.description}</p>}
|
||||
|
||||
{/* Meta-Informationen */}
|
||||
<Typography sx={metaTextSx}>Bild {currentImageIndex + 1} von {currentGroup.images.length} • Slideshow {currentGroupIndex + 1} von {allGroups.length}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<p style={{ color: '#999', fontSize: '12px', marginTop: '8px', marginBottom: 0, fontFamily: 'Open Sans, sans-serif' }}>Bild {currentImageIndex + 1} von {currentGroup.images.length} • Slideshow {currentGroupIndex + 1} von {allGroups.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user