deleted: backend/spaces.db-shm deleted: backend/spaces.db-wal modified: frontend/src/App.jsx modified: frontend/src/components/Sidebar.jsx new file: frontend/src/hooks/usePermissions.js modified: frontend/src/pages/Home.jsx modified: frontend/src/pages/Profile.jsx modified: frontend/src/pages/SpaceDetail.jsx
245 lines
9.4 KiB
JavaScript
245 lines
9.4 KiB
JavaScript
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||
import { useAuth } from '../contexts/AuthContext'
|
||
import { usePermissions } from '../hooks/usePermissions'
|
||
import { useState, useEffect } from 'react'
|
||
|
||
const Sidebar = ({ isOpen, setIsOpen }) => {
|
||
const location = useLocation()
|
||
const navigate = useNavigate()
|
||
const { user, logout } = useAuth()
|
||
const { isAdmin, hasFullAccess, accessibleSpaces } = usePermissions()
|
||
const [expandedMenus, setExpandedMenus] = useState({})
|
||
|
||
// Prüfe ob User Berechtigungsgruppen hat
|
||
const hasGroups = isAdmin || hasFullAccess || (accessibleSpaces && accessibleSpaces.length > 0)
|
||
|
||
// Menüpunkte - Home ist immer sichtbar, 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)
|
||
|
||
// Settings mit Unterpunkten
|
||
const settingsMenu = {
|
||
label: 'Settings',
|
||
icon: '⚙️',
|
||
path: '/settings',
|
||
subItems: [
|
||
{ path: '/settings/users', label: 'User', icon: '👥' },
|
||
{ path: '/settings/permissions', label: 'Berechtigungen', icon: '🔐' },
|
||
{ path: '/settings/providers', label: 'SSL Provider', icon: '🔒' },
|
||
]
|
||
}
|
||
|
||
const profileItem = { path: '/profile', label: 'Profil', icon: '👤' }
|
||
|
||
const isActive = (path) => {
|
||
if (path === '/') {
|
||
return location.pathname === '/'
|
||
}
|
||
return location.pathname.startsWith(path)
|
||
}
|
||
|
||
const toggleMenu = (menuPath) => {
|
||
setExpandedMenus(prev => ({
|
||
...prev,
|
||
[menuPath]: !prev[menuPath]
|
||
}))
|
||
}
|
||
|
||
const isMenuExpanded = (menuPath) => {
|
||
return expandedMenus[menuPath] || false
|
||
}
|
||
|
||
// Automatisch Settings-Menü expandieren, wenn auf einer Settings-Seite
|
||
useEffect(() => {
|
||
if (location.pathname.startsWith('/settings')) {
|
||
setExpandedMenus(prev => ({
|
||
...prev,
|
||
'/settings': true
|
||
}))
|
||
}
|
||
}, [location.pathname])
|
||
|
||
return (
|
||
<>
|
||
{/* Overlay for mobile */}
|
||
{isOpen && (
|
||
<div
|
||
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 lg:hidden transition-opacity duration-300"
|
||
onClick={() => setIsOpen(false)}
|
||
/>
|
||
)}
|
||
|
||
{/* Sidebar */}
|
||
<aside
|
||
className={`fixed lg:static top-0 left-0 h-full z-40 transform transition-all duration-300 ease-in-out bg-slate-800/95 backdrop-blur-sm border-r border-slate-600/50 shadow-xl ${
|
||
isOpen ? 'w-64 translate-x-0' : 'w-16 -translate-x-0 lg:translate-x-0'
|
||
}`}
|
||
>
|
||
<div className="p-4 border-b border-slate-700/50 flex items-center justify-between h-16">
|
||
{isOpen && (
|
||
<h1 className="text-xl font-bold text-white whitespace-nowrap overflow-hidden">
|
||
Certigo Addon
|
||
</h1>
|
||
)}
|
||
{/* Burger Menu Button - rechts in der Sidebar */}
|
||
<button
|
||
onClick={() => setIsOpen(!isOpen)}
|
||
className="ml-auto p-2 rounded-lg transition-all duration-200 flex-shrink-0 bg-slate-700/50 hover:bg-slate-700 text-white"
|
||
aria-label="Toggle menu"
|
||
>
|
||
<div className="w-5 h-4 relative flex flex-col justify-between">
|
||
<span
|
||
className={`block h-0.5 w-full bg-white rounded-full transition-all duration-300 ${
|
||
isOpen ? 'rotate-45 translate-y-1.5' : ''
|
||
}`}
|
||
/>
|
||
<span
|
||
className={`block h-0.5 w-full bg-white rounded-full transition-all duration-300 ${
|
||
isOpen ? 'opacity-0' : 'opacity-100'
|
||
}`}
|
||
/>
|
||
<span
|
||
className={`block h-0.5 w-full bg-white rounded-full transition-all duration-300 ${
|
||
isOpen ? '-rotate-45 -translate-y-1.5' : ''
|
||
}`}
|
||
/>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
<nav className="px-2 py-4 overflow-hidden flex flex-col h-[calc(100%-4rem)]">
|
||
<ul className="space-y-2 flex-1">
|
||
{menuItems.map((item) => (
|
||
<li key={item.path}>
|
||
<Link
|
||
to={item.path}
|
||
className={`flex items-center px-3 py-3 rounded-lg transition-all duration-200 ${
|
||
isActive(item.path)
|
||
? 'bg-slate-700 text-white font-semibold shadow-md'
|
||
: 'text-slate-300 hover:bg-slate-700/50 hover:text-white'
|
||
}`}
|
||
title={!isOpen ? item.label : ''}
|
||
>
|
||
<span className={`text-xl flex-shrink-0 ${isOpen ? 'mr-3' : 'mx-auto'}`}>
|
||
{item.icon}
|
||
</span>
|
||
{isOpen && (
|
||
<span className="whitespace-nowrap overflow-hidden">
|
||
{item.label}
|
||
</span>
|
||
)}
|
||
</Link>
|
||
</li>
|
||
))}
|
||
|
||
{/* Settings Menu mit Unterpunkten - nur für Admins */}
|
||
{isAdmin && (
|
||
<li>
|
||
<button
|
||
onClick={() => isOpen && toggleMenu(settingsMenu.path)}
|
||
className={`w-full flex items-center px-3 py-3 rounded-lg transition-all duration-200 ${
|
||
isActive(settingsMenu.path)
|
||
? 'bg-slate-700 text-white font-semibold shadow-md'
|
||
: 'text-slate-300 hover:bg-slate-700/50 hover:text-white'
|
||
}`}
|
||
title={!isOpen ? settingsMenu.label : ''}
|
||
>
|
||
<span className={`text-xl flex-shrink-0 ${isOpen ? 'mr-3' : 'mx-auto'}`}>
|
||
{settingsMenu.icon}
|
||
</span>
|
||
{isOpen && (
|
||
<>
|
||
<span className="whitespace-nowrap overflow-hidden">
|
||
{settingsMenu.label}
|
||
</span>
|
||
<svg
|
||
className={`w-4 h-4 ml-auto flex-shrink-0 transition-transform duration-200 ${
|
||
isMenuExpanded(settingsMenu.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>
|
||
{isOpen && isMenuExpanded(settingsMenu.path) && settingsMenu.subItems && (
|
||
<ul className="ml-4 mt-1 space-y-1">
|
||
{settingsMenu.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>
|
||
)}
|
||
</ul>
|
||
{/* Profil-Eintrag und Logout am unteren Ende */}
|
||
<div className="mt-auto pt-2 border-t border-slate-700/50 space-y-2">
|
||
<Link
|
||
to={profileItem.path}
|
||
className={`flex items-center px-3 py-3 rounded-lg transition-all duration-200 ${
|
||
isActive(profileItem.path)
|
||
? 'bg-slate-700 text-white font-semibold shadow-md'
|
||
: 'text-slate-300 hover:bg-slate-700/50 hover:text-white'
|
||
}`}
|
||
title={!isOpen ? (user?.username || profileItem.label) : ''}
|
||
>
|
||
<span className={`text-xl flex-shrink-0 ${isOpen ? 'mr-3' : 'mx-auto'}`}>
|
||
{profileItem.icon}
|
||
</span>
|
||
{isOpen && (
|
||
<span className="whitespace-nowrap overflow-hidden">
|
||
{user?.username || profileItem.label}
|
||
</span>
|
||
)}
|
||
</Link>
|
||
<button
|
||
onClick={() => {
|
||
logout()
|
||
navigate('/login')
|
||
}}
|
||
className={`w-full flex items-center px-3 py-3 rounded-lg transition-all duration-200 text-slate-300 hover:bg-red-600/20 hover:text-red-400 ${
|
||
isOpen ? '' : 'justify-center'
|
||
}`}
|
||
title={!isOpen ? 'Abmelden' : ''}
|
||
>
|
||
<span className={`text-xl flex-shrink-0 ${isOpen ? 'mr-3' : ''}`}>
|
||
🚪
|
||
</span>
|
||
{isOpen && (
|
||
<span className="whitespace-nowrap overflow-hidden">
|
||
Abmelden
|
||
</span>
|
||
)}
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
</aside>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default Sidebar
|
||
|