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

Viewing files at commit 8941e5beacddCommit detailsView latest

apps/dashboard/src/components/app-shell.tsx 99 lines · 3.2 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
  children: ReactNode;
37
}) {
38
  const current = page === 'marketplace' || page === 'settings' ? page : 'dashboard';
39
  const name = user.name || 'OpenClasp user';
40
41
  return (
42
    <div className="landingPage simpleShell">
43
      <a className="skipLink" href="#main">
44
        Skip to content
45
      </a>
46
      <header className="landingNav dashHeader">
47
        <button className="landingBrand" type="button" onClick={() => navigate('dashboard')}>
48
          <ClaspMark className="landingMark" size={28} />
49
          <strong>openclasp</strong>
50
        </button>
51
        <nav className="dashNav" aria-label="Dashboard">
52
          {views.map((item) => (
53
            <button
54
              key={item.page}
55
              type="button"
56
              aria-current={current === item.page ? 'page' : undefined}
57
              onClick={() => navigate(item.page)}
58
            >
59
              {item.label}
60
            </button>
61
          ))}
62
        </nav>
63
        <div className="landingNavActions">
64
          <button
65
            className="themeButton"
66
            type="button"
67
            onClick={onToggleTheme}
68
            aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
69
          >
70
            {theme === 'dark' ? <Sun /> : <Moon />}
71
          </button>
72
          <button className="navCta dashCta" type="button" onClick={() => navigate('connect')}>
73
            add agent <ArrowRight />
74
          </button>
75
          <DropdownMenu>
76
            <DropdownMenuTrigger asChild>
77
              <button className="accountMark" type="button" aria-label="Account menu">
78
                {initials(name)}
79
              </button>
80
            </DropdownMenuTrigger>
81
            <DropdownMenuContent align="end" sideOffset={8} className="dashMenu">
82
              <DropdownMenuItem onClick={onToggleTheme}>
83
                {theme === 'dark' ? <Sun /> : <Moon />}
84
                {theme === 'dark' ? 'Light theme' : 'Dark theme'}
85
              </DropdownMenuItem>
86
              <DropdownMenuSeparator />
87
              <DropdownMenuItem variant="destructive" onClick={onSignOut}>
88
                <LogOut /> Sign out
89
              </DropdownMenuItem>
90
            </DropdownMenuContent>
91
          </DropdownMenu>
92
        </div>
93
      </header>
94
      <main id="main" className="simpleMain">
95
        {children}
96
      </main>
97
    </div>
98
  );
99
}