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

Viewing files at commit 088723c6b718Commit detailsView latest

apps/dashboard/src/components/app-shell.tsx 106 lines · 3.4 KB · tsx
1
import type { ReactNode } from 'react';
2
import { Bot, LogOut, Moon, Settings, Store, Sun } from 'lucide-react';
3
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
4
import {
5
  DropdownMenu,
6
  DropdownMenuContent,
7
  DropdownMenuItem,
8
  DropdownMenuSeparator,
9
  DropdownMenuTrigger,
10
} from '@/components/ui/dropdown-menu';
11
import type { ShellUser } from '@/components/app-sidebar';
12
import { ClaspMark } from '@/components/clasp-mark';
13
import { initials } from '@/lib/utils';
14
import type { Page } from '@/lib/navigation';
15
16
export function AppShell({
17
  page,
18
  navigate,
19
  user,
20
  theme,
21
  onToggleTheme,
22
  onSignOut,
23
  children,
24
}: {
25
  page: Page;
26
  navigate: (page: Page) => void;
27
  user: ShellUser;
28
  theme: 'dark' | 'light';
29
  onToggleTheme: () => void;
30
  onSignOut: () => void;
31
  badges?: unknown;
32
  agents?: { agentId: string; name?: string }[];
33
  attention?: { setup: number; invites: number; inbox: number };
34
  children: ReactNode;
35
}) {
36
  const current = page === 'marketplace' || page === 'settings' ? page : 'dashboard';
37
  const name = user.name || 'OpenClasp user';
38
39
  return (
40
    <div className="simpleShell">
41
      <a className="skipLink" href="#main">
42
        Skip to content
43
      </a>
44
      <header className="simpleHeader">
45
        <button className="simpleBrand" type="button" onClick={() => navigate('dashboard')}>
46
          <ClaspMark size={19} />
47
          <strong>openclasp</strong>
48
        </button>
49
        <div className="viewSwitch" role="tablist" aria-label="Dashboard view">
50
          <button
51
            type="button"
52
            role="tab"
53
            aria-selected={current === 'dashboard'}
54
            onClick={() => navigate('dashboard')}
55
          >
56
            <Bot />
57
            <span>My agents</span>
58
          </button>
59
          <button
60
            type="button"
61
            role="tab"
62
            aria-selected={current === 'marketplace'}
63
            onClick={() => navigate('marketplace')}
64
          >
65
            <Store />
66
            <span>Marketplace</span>
67
          </button>
68
          <button
69
            type="button"
70
            role="tab"
71
            aria-selected={current === 'settings'}
72
            onClick={() => navigate('settings')}
73
          >
74
            <Settings />
75
            <span>Settings</span>
76
          </button>
77
        </div>
78
        <DropdownMenu>
79
          <DropdownMenuTrigger asChild>
80
            <button className="accountButton" type="button" aria-label="Account menu">
81
              <Avatar size="sm" className="rounded-full">
82
                {user.picture ? <AvatarImage src={user.picture} alt="" /> : null}
83
                <AvatarFallback className="rounded-full bg-primary text-[10px] text-primary-foreground">
84
                  {initials(name)}
85
                </AvatarFallback>
86
              </Avatar>
87
            </button>
88
          </DropdownMenuTrigger>
89
          <DropdownMenuContent align="end" sideOffset={8}>
90
            <DropdownMenuItem onClick={onToggleTheme}>
91
              {theme === 'dark' ? <Sun /> : <Moon />}
92
              {theme === 'dark' ? 'Light theme' : 'Dark theme'}
93
            </DropdownMenuItem>
94
            <DropdownMenuSeparator />
95
            <DropdownMenuItem variant="destructive" onClick={onSignOut}>
96
              <LogOut /> Sign out
97
            </DropdownMenuItem>
98
          </DropdownMenuContent>
99
        </DropdownMenu>
100
      </header>
101
      <main id="main" className="simpleMain">
102
        {children}
103
      </main>
104
    </div>
105
  );
106
}