2026-06-09 15:15:35 +03:30
|
|
|
import type { ReactNode } from 'react'
|
2026-06-10 00:02:59 +03:30
|
|
|
import { Link, useLocation } from 'react-router'
|
2026-06-10 12:54:13 +03:30
|
|
|
import {
|
2026-06-13 11:09:02 +03:30
|
|
|
BookMarked,
|
2026-06-10 12:54:13 +03:30
|
|
|
Bot,
|
2026-06-10 18:13:52 +03:30
|
|
|
Boxes,
|
2026-06-10 12:54:13 +03:30
|
|
|
ChartColumn,
|
|
|
|
|
Gauge,
|
|
|
|
|
Inbox,
|
|
|
|
|
type LucideIcon,
|
|
|
|
|
LayoutDashboard,
|
|
|
|
|
LogOut,
|
|
|
|
|
Network,
|
|
|
|
|
ShieldCheck,
|
|
|
|
|
Users,
|
|
|
|
|
} from 'lucide-react'
|
2026-06-09 15:15:35 +03:30
|
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
|
import { Separator } from '@/components/ui/separator'
|
|
|
|
|
import { cn } from '@/lib/utils'
|
|
|
|
|
import { useAuth } from '@/store/auth'
|
|
|
|
|
|
|
|
|
|
export function AppShell({ children }: { children: ReactNode }) {
|
|
|
|
|
const email = useAuth((s) => s.email)
|
|
|
|
|
const logout = useAuth((s) => s.logout)
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex min-h-screen bg-background text-foreground">
|
|
|
|
|
<aside className="flex w-60 shrink-0 flex-col bg-sidebar text-sidebar-foreground">
|
|
|
|
|
<div className="flex items-center gap-3 px-5 py-4">
|
|
|
|
|
<span className="grid size-8 place-items-center rounded-md bg-sidebar-primary font-bold text-sidebar-primary-foreground">
|
|
|
|
|
T
|
|
|
|
|
</span>
|
|
|
|
|
<div className="leading-tight">
|
|
|
|
|
<div className="font-semibold tracking-tight">TeamUp.AI</div>
|
|
|
|
|
<div className="text-xs text-sidebar-foreground/60">command center</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Separator className="bg-sidebar-border" />
|
|
|
|
|
|
|
|
|
|
<nav className="flex flex-1 flex-col gap-1 p-3">
|
2026-06-10 00:02:59 +03:30
|
|
|
<NavItem icon={LayoutDashboard} label="Board" to="/" />
|
2026-06-10 12:54:13 +03:30
|
|
|
<NavItem icon={Inbox} label="Cartable" to="/cartable" />
|
2026-06-10 08:53:43 +03:30
|
|
|
<NavItem icon={ShieldCheck} label="Review inbox" to="/reviews" />
|
2026-06-10 12:54:13 +03:30
|
|
|
<NavItem icon={Bot} label="AI seats" to="/seats" />
|
2026-06-13 11:09:02 +03:30
|
|
|
<NavItem icon={BookMarked} label="Skills" to="/skills" />
|
2026-06-10 12:54:13 +03:30
|
|
|
<NavItem icon={Network} label="Org chart" to="/org" />
|
2026-06-10 18:13:52 +03:30
|
|
|
<NavItem icon={Boxes} label="Structure" to="/structure" />
|
2026-06-10 12:54:13 +03:30
|
|
|
<NavItem icon={Users} label="Members" to="/members" />
|
|
|
|
|
<NavItem icon={Gauge} label="Performance" to="/performance" />
|
2026-06-10 12:07:35 +03:30
|
|
|
<NavItem icon={ChartColumn} label="Analytics" to="/analytics" />
|
2026-06-09 15:15:35 +03:30
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<Separator className="bg-sidebar-border" />
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2 p-3">
|
|
|
|
|
<span className="truncate text-xs text-sidebar-foreground/70">{email ?? 'signed in'}</span>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={logout}
|
|
|
|
|
className="text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
|
|
|
>
|
|
|
|
|
<LogOut data-icon="inline-start" />
|
|
|
|
|
Sign out
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<main className="flex-1 overflow-auto">{children}</main>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NavItem({
|
|
|
|
|
icon: Icon,
|
|
|
|
|
label,
|
2026-06-10 00:02:59 +03:30
|
|
|
to,
|
2026-06-09 15:15:35 +03:30
|
|
|
muted,
|
|
|
|
|
}: {
|
|
|
|
|
icon: LucideIcon
|
|
|
|
|
label: string
|
2026-06-10 00:02:59 +03:30
|
|
|
to?: string
|
2026-06-09 15:15:35 +03:30
|
|
|
muted?: boolean
|
|
|
|
|
}) {
|
2026-06-10 00:02:59 +03:30
|
|
|
const location = useLocation()
|
|
|
|
|
const active = to ? location.pathname === to : false
|
|
|
|
|
|
|
|
|
|
const className = cn(
|
|
|
|
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm',
|
|
|
|
|
active ? 'bg-sidebar-accent font-medium text-sidebar-accent-foreground' : 'text-sidebar-foreground/80',
|
|
|
|
|
muted ? 'opacity-50' : 'hover:bg-sidebar-accent/60',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const content = (
|
|
|
|
|
<>
|
2026-06-09 15:15:35 +03:30
|
|
|
<Icon className="size-4" />
|
|
|
|
|
{label}
|
|
|
|
|
{muted && <span className="ml-auto text-[10px] uppercase tracking-wide opacity-70">soon</span>}
|
2026-06-10 00:02:59 +03:30
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!to || muted) {
|
|
|
|
|
return <span className={className}>{content}</span>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Link to={to} className={className}>
|
|
|
|
|
{content}
|
|
|
|
|
</Link>
|
2026-06-09 15:15:35 +03:30
|
|
|
)
|
|
|
|
|
}
|