Add comprehensive backend features and mobile UI improvements
Backend: - Add 2FA authentication with TOTP support - Add API keys management system - Add audit logging for security events - Add file upload/management system - Add notifications system with preferences - Add session management - Add webhooks integration - Add analytics endpoints - Add export functionality - Add password policy enforcement - Add new database migrations for core tables Frontend: - Add module position system (top/bottom sidebar sections) - Add search and notifications module configuration tabs - Add mobile logo replacing hamburger menu - Center page title absolutely when no tabs present - Align sidebar footer toggles with navigation items - Add lighter icon color in dark theme for mobile - Add API keys management page - Add notifications page with context - Add admin analytics and audit logs pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { useAuth } from '../contexts/AuthContext';
|
||||
import { useModules } from '../contexts/ModulesContext';
|
||||
import { useSiteConfig } from '../contexts/SiteConfigContext';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useNotifications } from '../contexts/NotificationsContext';
|
||||
import { appModules } from '../modules';
|
||||
import UserMenu from './UserMenu';
|
||||
import '../styles/Sidebar.css';
|
||||
@@ -27,7 +28,8 @@ export default function Sidebar() {
|
||||
} = useSidebar();
|
||||
const { viewMode, toggleViewMode, isUserModeEnabled } = useViewMode();
|
||||
const { user } = useAuth();
|
||||
const { isModuleEnabled, isModuleEnabledForUser, moduleOrder, hasInitialized: modulesInitialized } = useModules();
|
||||
const { isModuleEnabled, isModuleEnabledForUser, moduleOrder, moduleStates, hasInitialized: modulesInitialized } = useModules();
|
||||
const { unreadCount } = useNotifications();
|
||||
|
||||
// When admin is in "user mode", show only user-permitted modules
|
||||
// Otherwise, show all globally enabled modules (admin view)
|
||||
@@ -38,6 +40,8 @@ export default function Sidebar() {
|
||||
.find((cat) => cat.id === 'main')
|
||||
?.modules.filter((m) => {
|
||||
if (!m.enabled) return false;
|
||||
// Dashboard is always shown
|
||||
if (m.id === 'dashboard') return true;
|
||||
if (shouldUseUserPermissions) {
|
||||
return isModuleEnabledForUser(m.id, user?.permissions, user?.is_superuser || false);
|
||||
}
|
||||
@@ -45,7 +49,7 @@ export default function Sidebar() {
|
||||
}) || []);
|
||||
|
||||
// Sort modules based on moduleOrder (dashboard always first, then ordered features)
|
||||
const mainModules = [...mainModulesFiltered].sort((a, b) => {
|
||||
const sortedModules = [...mainModulesFiltered].sort((a, b) => {
|
||||
// Dashboard always comes first
|
||||
if (a.id === 'dashboard') return -1;
|
||||
if (b.id === 'dashboard') return 1;
|
||||
@@ -63,6 +67,19 @@ export default function Sidebar() {
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Split modules by position (top = main nav, bottom = above footer)
|
||||
const topModules = sortedModules.filter(m => {
|
||||
if (m.id === 'dashboard') return true; // Dashboard always at top
|
||||
const state = moduleStates[m.id as keyof typeof moduleStates];
|
||||
return !state || state.position === 'top';
|
||||
});
|
||||
|
||||
const bottomModules = sortedModules.filter(m => {
|
||||
if (m.id === 'dashboard') return false; // Dashboard never at bottom
|
||||
const state = moduleStates[m.id as keyof typeof moduleStates];
|
||||
return state && state.position === 'bottom';
|
||||
});
|
||||
|
||||
const handleCollapseClick = () => {
|
||||
if (isMobileOpen) {
|
||||
closeMobileMenu();
|
||||
@@ -222,7 +239,7 @@ export default function Sidebar() {
|
||||
|
||||
<nav className="sidebar-nav">
|
||||
<div className="nav-section">
|
||||
{mainModules.map((module) => (
|
||||
{topModules.map((module) => (
|
||||
<NavLink
|
||||
key={module.id}
|
||||
to={module.path}
|
||||
@@ -233,12 +250,36 @@ export default function Sidebar() {
|
||||
>
|
||||
<span className="nav-icon material-symbols-outlined">{module.icon}</span>
|
||||
<span className="nav-label">{t.sidebar[module.id as keyof typeof t.sidebar]}</span>
|
||||
{module.id === 'notifications' && unreadCount > 0 && (
|
||||
<span className="nav-badge" aria-label={`${unreadCount} unread notifications`}>
|
||||
{unreadCount > 99 ? '99+' : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-footer">
|
||||
{bottomModules.map((module) => (
|
||||
<NavLink
|
||||
key={module.id}
|
||||
to={module.path}
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
onClick={(e) => handleNavClick(e, module.path)}
|
||||
onMouseEnter={(e) => handleItemMouseEnter(t.sidebar[module.id as keyof typeof t.sidebar], e)}
|
||||
onMouseLeave={handleItemMouseLeave}
|
||||
>
|
||||
<span className="nav-icon material-symbols-outlined">{module.icon}</span>
|
||||
<span className="nav-label">{t.sidebar[module.id as keyof typeof t.sidebar]}</span>
|
||||
{module.id === 'notifications' && unreadCount > 0 && (
|
||||
<span className="nav-badge" aria-label={`${unreadCount} unread notifications`}>
|
||||
{unreadCount > 99 ? '99+' : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
))}
|
||||
|
||||
{user?.is_superuser && isUserModeEnabled && (
|
||||
<button
|
||||
className={`view-mode-toggle ${viewMode === 'user' ? 'user-mode' : 'admin-mode'}`}
|
||||
|
||||
Reference in New Issue
Block a user