Files
certigo/frontend/src/components/Sidebar.jsx
Nick Adam d1e9c2433c added fix for empty CSR history after uploading ones
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
2025-11-21 23:50:12 +01:00

245 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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