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

Viewing files at commit 66a0492edc4aCommit detailsView latest

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