| 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 | }
|