sprint9: app visibility — sidebar permission filter + TopBar + ProtectedRoute + route guards
Check Cross-Plugin Imports / check (push) Has been cancelled

This commit is contained in:
Agent Zero
2026-07-29 02:28:52 +02:00
parent 88c04286af
commit 48b2dfdb11
10 changed files with 221 additions and 80 deletions
+12 -2
View File
@@ -7,6 +7,7 @@ import { ChevronRight, FileText, Home, Settings, Users } from 'lucide-react';
import { usePluginStore } from '@/store/pluginStore';
import * as LucideIcons from 'lucide-react';
import { useMenuOrder } from '@/api/users';
import { usePermission } from '@/hooks/usePermission';
interface NavSingleItem {
to: string;
@@ -40,6 +41,7 @@ export function Sidebar() {
const location = useLocation();
const manifests = usePluginStore(s => s.manifests);
const { data: menuOrderData } = useMenuOrder();
const { hasPermission } = usePermission();
const allMenuItems = useMemo(() => {
const staticItems = singleItems.map(item => ({
@@ -50,10 +52,12 @@ export function Sidebar() {
order: item.order,
group: undefined as string | undefined,
isStatic: true as const,
permission: item.to === '/dashboard' ? 'dashboard:read' : item.to === '/contacts' ? 'contacts:read' : undefined,
}));
const pluginItems = manifests
.flatMap((m) => m.menu_items)
.filter(item => !item.permission || hasPermission(item.permission))
.map(item => ({
path: item.path,
labelKey: item.label_key,
@@ -62,6 +66,7 @@ export function Sidebar() {
order: item.order,
group: item.group,
isStatic: false as const,
permission: item.permission,
}));
const allItems = [...staticItems, ...pluginItems];
@@ -152,6 +157,8 @@ export function Sidebar() {
}
const elements: React.ReactNode[] = [];
for (const item of singles) {
// Skip if user lacks permission
if (item.permission && !hasPermission(item.permission)) continue;
elements.push(
<li key={item.path}>
<NavLink
@@ -175,6 +182,9 @@ export function Sidebar() {
);
}
for (const [group, items] of groups) {
// Filter group items by permission
const visibleItems = items.filter(item => !item.permission || hasPermission(item.permission));
if (visibleItems.length === 0) continue; // Hide empty groups
const groupKey = `plugin-group-${group}`;
const expanded = expandedItems.has(groupKey);
elements.push(
@@ -196,13 +206,13 @@ export function Sidebar() {
}
}}
>
{getIcon(items[0].icon as string)}
{getIcon(visibleItems[0].icon as string)}
<span className="flex-1 truncate">{group}</span>
{chevronIcon(expanded)}
</div>
{expanded && (
<ul className="mt-1 ml-4 space-y-1 border-l border-secondary-700 pl-2" role="group">
{items.map((child) => (
{visibleItems.map((child) => (
<li key={child.path}>
<NavLink
to={child.path}