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

Viewing files at commit 28cda791c50eCommit detailsView latest

apps/dashboard/src/components/app-shell.tsx 102 lines · 3.3 KB · tsx
1
import type { ReactNode } from 'react';
2
import { ArrowRight, LogOut, Moon, Sun } from 'lucide-react';
3
import {
4
  DropdownMenu,
5
  DropdownMenuContent,
6
  DropdownMenuItem,
7
  DropdownMenuSeparator,
8
  DropdownMenuTrigger,
9
} from '@/components/ui/dropdown-menu';
10
import type { ShellUser } from '@/components/app-sidebar';
11
import { ClaspMark } from '@/components/clasp-mark';
12
import { initials } from '@/lib/utils';
13
import type { Page } from '@/lib/navigation';
14
15
const views = [
16
  { page: 'dashboard' as const, label: 'agents' },
17
  { page: 'marketplace' as const, label: 'marketplace' },
18
  { page: 'settings' as const, label: 'settings' },
19
];
20
21
export function AppShell({
22
  page,
23
  navigate,
24
  user,
25
  theme,
26
  onToggleTheme,
27
  onSignOut,
28
  children,
29
}: {
30
  page: Page;
31
  navigate: (page: Page) => void;
32
  user: ShellUser;
33
  theme: 'dark' | 'light';
34
  onToggleTheme: () => void;
35
  onSignOut: () => void;
36
  badges?: unknown;
37
  agents?: { agentId: string; name?: string }[];
38
  attention?: { setup: number; invites: number; inbox: number };
39
  children: ReactNode;
40
}) {
41
  const current = page === 'marketplace' || page === 'settings' ? page : 'dashboard';
42
  const name = user.name || 'OpenClasp user';
43
44
  return (
45
    <div className="landingPage simpleShell">
46
      <a className="skipLink" href="#main">
47
        Skip to content
48
      </a>
49
      <header className="landingNav dashHeader">
50
        <button className="landingBrand" type="button" onClick={() => navigate('dashboard')}>
51
          <ClaspMark className="landingMark" size={28} />
52
          <strong>openclasp</strong>
53
        </button>
54
        <nav className="dashNav" aria-label="Dashboard">
55
          {views.map((item) => (
56
            <button
57
              key={item.page}
58
              type="button"
59
              aria-current={current === item.page ? 'page' : undefined}
60
              onClick={() => navigate(item.page)}
61
            >
62
              {item.label}
63
            </button>
64
          ))}
65
        </nav>
66
        <div className="landingNavActions">
67
          <button
68
            className="themeButton"
69
            type="button"
70
            onClick={onToggleTheme}
71
            aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
72
          >
73
            {theme === 'dark' ? <Sun /> : <Moon />}
74
          </button>
75
          <button className="navCta dashCta" type="button" onClick={() => navigate('connect')}>
76
            add agent <ArrowRight />
77
          </button>
78
          <DropdownMenu>
79
            <DropdownMenuTrigger asChild>
80
              <button className="accountMark" type="button" aria-label="Account menu">
81
                {initials(name)}
82
              </button>
83
            </DropdownMenuTrigger>
84
            <DropdownMenuContent align="end" sideOffset={8} className="dashMenu">
85
              <DropdownMenuItem onClick={onToggleTheme}>
86
                {theme === 'dark' ? <Sun /> : <Moon />}
87
                {theme === 'dark' ? 'Light theme' : 'Dark theme'}
88
              </DropdownMenuItem>
89
              <DropdownMenuSeparator />
90
              <DropdownMenuItem variant="destructive" onClick={onSignOut}>
91
                <LogOut /> Sign out
92
              </DropdownMenuItem>
93
            </DropdownMenuContent>
94
          </DropdownMenu>
95
        </div>
96
      </header>
97
      <main id="main" className="simpleMain">
98
        {children}
99
      </main>
100
    </div>
101
  );
102
}