The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 95b0e11125d2Commit detailsView latest

apps/dashboard/src/components/app-sidebar.tsx 182 lines · 5.4 KB · tsx
1
import {
2
  BarChart3,
3
  History,
4
  LayoutDashboard,
5
  LogOut,
6
  Moon,
7
  Plus,
8
  ScanLine,
9
  Shield,
10
  Settings,
11
  Sun,
12
} from 'lucide-react';
13
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
14
import {
15
  DropdownMenu,
16
  DropdownMenuContent,
17
  DropdownMenuItem,
18
  DropdownMenuSeparator,
19
  DropdownMenuTrigger,
20
} from '@/components/ui/dropdown-menu';
21
import {
22
  Sidebar,
23
  SidebarContent,
24
  SidebarFooter,
25
  SidebarGroup,
26
  SidebarGroupContent,
27
  SidebarHeader,
28
  SidebarMenu,
29
  SidebarMenuBadge,
30
  SidebarMenuButton,
31
  SidebarMenuItem,
32
  SidebarRail,
33
  SidebarSeparator,
34
  useSidebar,
35
} from '@/components/ui/sidebar';
36
import { ClaspMark } from '@/components/clasp-mark';
37
import { pageMeta, primaryNav, type Page } from '@/lib/navigation';
38
import { initials } from '@/lib/utils';
39
40
const navIcons = {
41
  dashboard: LayoutDashboard,
42
  history: History,
43
  agents: ScanLine,
44
  insights: BarChart3,
45
  shield: Shield,
46
  connect: Plus,
47
  settings: Settings,
48
} as const;
49
50
export type ShellUser = {
51
  name?: string;
52
  email?: string;
53
  picture?: string;
54
};
55
56
export type NavBadges = {
57
  dashboard: number;
58
  connect: number;
59
};
60
61
export function AppSidebar({
62
  page,
63
  navigate,
64
  user,
65
  theme,
66
  onToggleTheme,
67
  onSignOut,
68
  badges,
69
}: {
70
  page: Page;
71
  navigate: (page: Page) => void;
72
  user: ShellUser;
73
  theme: 'dark' | 'light';
74
  onToggleTheme: () => void;
75
  onSignOut: () => void;
76
  badges: NavBadges;
77
}) {
78
  const { setOpenMobile } = useSidebar();
79
  const go = (next: Page) => {
80
    navigate(next);
81
    setOpenMobile(false);
82
  };
83
  const name = user.name || 'OpenClasp user';
84
  const email = user.email || 'Signed in';
85
86
  return (
87
    <Sidebar collapsible="icon">
88
      <SidebarHeader>
89
        <SidebarMenu>
90
          <SidebarMenuItem>
91
            <SidebarMenuButton size="lg" tooltip="OpenClasp" onClick={() => go('dashboard')}>
92
              <ClaspMark className="sidebarBrandMark" size={18} />
93
              <span className="sidebarBrandText">
94
                <strong>openclasp</strong>
95
              </span>
96
            </SidebarMenuButton>
97
          </SidebarMenuItem>
98
        </SidebarMenu>
99
      </SidebarHeader>
100
      <SidebarContent>
101
        <SidebarGroup>
102
          <SidebarGroupContent>
103
            <SidebarMenu>
104
              {primaryNav.map((item) => {
105
                const Icon = navIcons[item];
106
                const count = item in badges ? badges[item as keyof NavBadges] : 0;
107
                return (
108
                  <SidebarMenuItem key={item}>
109
                    <SidebarMenuButton
110
                      isActive={page === item}
111
                      tooltip={pageMeta[item].label}
112
                      onClick={() => go(item)}
113
                    >
114
                      <Icon />
115
                      <span>{pageMeta[item].label}</span>
116
                    </SidebarMenuButton>
117
                    {count > 0 ? <SidebarMenuBadge>{count}</SidebarMenuBadge> : null}
118
                  </SidebarMenuItem>
119
                );
120
              })}
121
            </SidebarMenu>
122
          </SidebarGroupContent>
123
        </SidebarGroup>
124
        <SidebarSeparator />
125
        <SidebarGroup>
126
          <SidebarGroupContent>
127
            <SidebarMenu>
128
              <SidebarMenuItem>
129
                <SidebarMenuButton
130
                  isActive={page === 'settings'}
131
                  tooltip="settings"
132
                  onClick={() => go('settings')}
133
                >
134
                  <Settings />
135
                  <span>settings</span>
136
                </SidebarMenuButton>
137
              </SidebarMenuItem>
138
            </SidebarMenu>
139
          </SidebarGroupContent>
140
        </SidebarGroup>
141
      </SidebarContent>
142
      <SidebarFooter>
143
        <SidebarMenu>
144
          <SidebarMenuItem>
145
            <DropdownMenu>
146
              <DropdownMenuTrigger asChild>
147
                <SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent">
148
                  <Avatar size="sm" className="rounded-none">
149
                    {user.picture ? <AvatarImage src={user.picture} alt="" /> : null}
150
                    <AvatarFallback className="rounded-none bg-primary text-[10px] font-medium text-primary-foreground">
151
                      {initials(name)}
152
                    </AvatarFallback>
153
                  </Avatar>
154
                  <span className="grid min-w-0 flex-1 text-left text-sm leading-tight">
155
                    <span className="truncate font-medium">{name}</span>
156
                    <span className="truncate text-[11px] text-muted-foreground">{email}</span>
157
                  </span>
158
                </SidebarMenuButton>
159
              </DropdownMenuTrigger>
160
              <DropdownMenuContent className="min-w-56" side="top" align="start" sideOffset={6}>
161
                <DropdownMenuItem onClick={onToggleTheme}>
162
                  {theme === 'dark' ? <Sun /> : <Moon />}
163
                  {theme === 'dark' ? 'Light theme' : 'Dark theme'}
164
                </DropdownMenuItem>
165
                <DropdownMenuItem onClick={() => go('settings')}>
166
                  <Settings />
167
                  Settings
168
                </DropdownMenuItem>
169
                <DropdownMenuSeparator />
170
                <DropdownMenuItem variant="destructive" onClick={onSignOut}>
171
                  <LogOut />
172
                  Sign out
173
                </DropdownMenuItem>
174
              </DropdownMenuContent>
175
            </DropdownMenu>
176
          </SidebarMenuItem>
177
        </SidebarMenu>
178
      </SidebarFooter>
179
      <SidebarRail />
180
    </Sidebar>
181
  );
182
}