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

Viewing files at commit 874239cc591fCommit detailsView latest

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