implemented LE and ACME and fixed some bugs
This commit is contained in:
@@ -13,14 +13,24 @@ const Sidebar = ({ isOpen, setIsOpen }) => {
|
||||
// Prüfe ob User Berechtigungsgruppen hat
|
||||
const hasGroups = isAdmin || hasFullAccess || (accessibleSpaces && accessibleSpaces.length > 0)
|
||||
|
||||
// Menüpunkte - Home ist immer sichtbar, andere nur mit Gruppen
|
||||
// Menüpunkte - andere nur mit Gruppen
|
||||
const menuItems = [
|
||||
{ path: '/', label: 'Home', icon: '🏠', alwaysVisible: true },
|
||||
{ path: '/spaces', label: 'Spaces', icon: '📁', requiresGroups: true },
|
||||
{ path: '/audit-logs', label: 'Audit Log', icon: '📋', requiresGroups: true },
|
||||
{ path: '/impressum', label: 'Impressum', icon: 'ℹ️', requiresGroups: true },
|
||||
].filter(item => item.alwaysVisible || !item.requiresGroups || hasGroups)
|
||||
|
||||
// Home mit Unterpunkten
|
||||
const homeMenu = {
|
||||
label: 'Home',
|
||||
icon: '🏠',
|
||||
path: '/',
|
||||
alwaysVisible: true,
|
||||
subItems: [
|
||||
{ path: '/queue', label: 'Queue', icon: '⏰', requiresGroups: true },
|
||||
].filter(item => !item.requiresGroups || hasGroups)
|
||||
}
|
||||
|
||||
// Settings mit Unterpunkten
|
||||
const settingsMenu = {
|
||||
label: 'Settings',
|
||||
@@ -53,7 +63,7 @@ const Sidebar = ({ isOpen, setIsOpen }) => {
|
||||
return expandedMenus[menuPath] || false
|
||||
}
|
||||
|
||||
// Automatisch Settings-Menü expandieren, wenn auf einer Settings-Seite
|
||||
// Automatisch Menüs expandieren, wenn auf einer entsprechenden Seite
|
||||
useEffect(() => {
|
||||
if (location.pathname.startsWith('/settings')) {
|
||||
setExpandedMenus(prev => ({
|
||||
@@ -61,6 +71,12 @@ const Sidebar = ({ isOpen, setIsOpen }) => {
|
||||
'/settings': true
|
||||
}))
|
||||
}
|
||||
if (location.pathname === '/queue' || location.pathname === '/') {
|
||||
setExpandedMenus(prev => ({
|
||||
...prev,
|
||||
'/': true
|
||||
}))
|
||||
}
|
||||
}, [location.pathname])
|
||||
|
||||
return (
|
||||
@@ -112,6 +128,82 @@ const Sidebar = ({ isOpen, setIsOpen }) => {
|
||||
</div>
|
||||
<nav className="px-2 py-4 overflow-hidden flex flex-col h-[calc(100%-4rem)]">
|
||||
<ul className="space-y-2 flex-1">
|
||||
{/* Home Menu mit Unterpunkten */}
|
||||
<li>
|
||||
<div className={`flex items-center rounded-lg transition-all duration-200 ${
|
||||
(isActive(homeMenu.path) || location.pathname === '/queue')
|
||||
? 'bg-slate-700 shadow-md'
|
||||
: ''
|
||||
}`}>
|
||||
<Link
|
||||
to={homeMenu.path}
|
||||
className={`flex-1 flex items-center px-3 py-3 rounded-lg transition-all duration-200 ${
|
||||
(isActive(homeMenu.path) || location.pathname === '/queue')
|
||||
? 'text-white font-semibold'
|
||||
: 'text-slate-300 hover:bg-slate-700/50 hover:text-white'
|
||||
}`}
|
||||
title={!isOpen ? homeMenu.label : ''}
|
||||
>
|
||||
<span className={`text-xl flex-shrink-0 ${isOpen ? 'mr-3' : 'mx-auto'}`}>
|
||||
{homeMenu.icon}
|
||||
</span>
|
||||
{isOpen && (
|
||||
<span className="whitespace-nowrap overflow-hidden">
|
||||
{homeMenu.label}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
{isOpen && homeMenu.subItems && homeMenu.subItems.length > 0 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
toggleMenu(homeMenu.path)
|
||||
}}
|
||||
className={`p-2 rounded-lg transition-colors ${
|
||||
(isActive(homeMenu.path) || location.pathname === '/queue')
|
||||
? 'text-white hover:bg-slate-600'
|
||||
: 'text-slate-400 hover:text-slate-300 hover:bg-slate-700/50'
|
||||
}`}
|
||||
title="Menü erweitern"
|
||||
>
|
||||
<svg
|
||||
className={`w-4 h-4 transition-transform duration-200 ${
|
||||
isMenuExpanded(homeMenu.path) ? 'rotate-90' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isOpen && isMenuExpanded(homeMenu.path) && homeMenu.subItems && homeMenu.subItems.length > 0 && (
|
||||
<ul className="ml-4 mt-1 space-y-1">
|
||||
{homeMenu.subItems.map((subItem) => (
|
||||
<li key={subItem.path}>
|
||||
<Link
|
||||
to={subItem.path}
|
||||
className={`flex items-center px-3 py-2 rounded-lg transition-all duration-200 ${
|
||||
isActive(subItem.path)
|
||||
? 'bg-slate-600 text-white font-semibold'
|
||||
: 'text-slate-400 hover:bg-slate-700/50 hover:text-slate-200'
|
||||
}`}
|
||||
>
|
||||
<span className="text-lg flex-shrink-0 mr-2">
|
||||
{subItem.icon}
|
||||
</span>
|
||||
<span className="whitespace-nowrap overflow-hidden">
|
||||
{subItem.label}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
|
||||
{menuItems.map((item) => (
|
||||
<li key={item.path}>
|
||||
<Link
|
||||
|
||||
75
frontend/src/components/Toast.jsx
Normal file
75
frontend/src/components/Toast.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
const Toast = ({ message, type = 'error', onClose, duration = 4000 }) => {
|
||||
useEffect(() => {
|
||||
if (duration > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
onClose()
|
||||
}, duration)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [duration, onClose])
|
||||
|
||||
const typeStyles = {
|
||||
error: 'bg-red-500/90 border-red-400/50 text-white',
|
||||
warning: 'bg-amber-500/90 border-amber-400/50 text-white',
|
||||
info: 'bg-blue-500/90 border-blue-400/50 text-white',
|
||||
success: 'bg-emerald-500/90 border-emerald-400/50 text-white'
|
||||
}
|
||||
|
||||
const iconStyles = {
|
||||
error: 'text-red-200',
|
||||
warning: 'text-amber-200',
|
||||
info: 'text-blue-200',
|
||||
success: 'text-emerald-200'
|
||||
}
|
||||
|
||||
const icons = {
|
||||
error: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
warning: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
),
|
||||
info: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
success: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-start gap-3 px-4 py-3 rounded-lg border-2 shadow-2xl backdrop-blur-sm min-w-[300px] max-w-[500px] animate-slide-in-right transition-all duration-300 ${typeStyles[type]}`}
|
||||
role="alert"
|
||||
>
|
||||
<div className={`flex-shrink-0 ${iconStyles[type]}`}>
|
||||
{icons[type]}
|
||||
</div>
|
||||
<div className="flex-1 text-sm font-medium">
|
||||
{message}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex-shrink-0 text-white/80 hover:text-white transition-colors"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Toast
|
||||
|
||||
Reference in New Issue
Block a user