feat: The Other Dude v9.0.1 — full-featured email system

ci: add GitHub Pages deployment workflow for docs site

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-08 17:46:37 -05:00
commit b840047e19
511 changed files with 106948 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
import { type ReactNode } from 'react'
import { Sidebar } from './Sidebar'
import { Header } from './Header'
import { ShortcutsDialog } from './ShortcutsDialog'
import { CommandPalette } from '@/components/command-palette/CommandPalette'
import { Toaster } from '@/components/ui/toast'
interface AppLayoutProps {
children: ReactNode
}
export function AppLayout({ children }: AppLayoutProps) {
return (
<div className="flex h-screen overflow-hidden bg-background">
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-accent focus:text-white focus:px-4 focus:py-2 focus:rounded-md focus:text-sm focus:font-medium"
>
Skip to main content
</a>
<Sidebar />
<div className="flex flex-col flex-1 min-w-0 overflow-hidden">
<Header />
<main id="main-content" tabIndex={-1} className="flex-1 overflow-auto p-2 sm:p-4">{children}</main>
</div>
<Toaster />
<CommandPalette />
<ShortcutsDialog />
</div>
)
}

View File

@@ -0,0 +1,225 @@
import { useEffect } from 'react'
import { useNavigate, Link } from '@tanstack/react-router'
import { LogOut, ChevronDown, User, Search, Sun, Moon, RefreshCw, Menu, Settings } from 'lucide-react'
import { useQuery } from '@tanstack/react-query'
import { useCommandPalette } from '@/components/command-palette/useCommandPalette'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useAuth, isSuperAdmin } from '@/lib/auth'
import { useUIStore } from '@/lib/store'
import { tenantsApi } from '@/lib/api'
import { useEventStreamContext } from '@/contexts/EventStreamContext'
import type { ConnectionState } from '@/hooks/useEventStream'
// ─── Connection State Indicator ──────────────────────────────────────────────
const CONNECTION_CONFIG: Record<
ConnectionState,
{ colorClass: string; label: string; pulse: boolean }
> = {
connected: { colorClass: 'bg-success', label: 'Connected', pulse: false },
connecting: { colorClass: 'bg-warning', label: 'Connecting...', pulse: true },
reconnecting: { colorClass: 'bg-warning', label: 'Reconnecting...', pulse: true },
disconnected: { colorClass: 'bg-error', label: 'Disconnected', pulse: false },
}
function formatTime(date: Date): string {
return date.toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
}
function ConnectionIndicator({
state,
lastConnectedAt,
onReconnect,
}: {
state: ConnectionState
lastConnectedAt: Date | null
onReconnect: () => void
}) {
const { colorClass, label, pulse } = CONNECTION_CONFIG[state]
return (
<div className="relative group flex items-center">
<div
className={`h-2 w-2 rounded-full ${colorClass} ${pulse ? 'animate-pulse' : ''}`}
role="status"
aria-label={`Real-time connection: ${label}`}
/>
{/* Tooltip on hover */}
<div className="absolute right-0 top-full mt-2 hidden group-hover:block z-50">
<div className="bg-surface border border-border rounded-lg shadow-lg px-3 py-2 text-xs whitespace-nowrap">
<p className="font-medium text-text-primary">{label}</p>
{lastConnectedAt && (
<p className="text-text-muted mt-0.5">
Last connected: {formatTime(lastConnectedAt)}
</p>
)}
{state === 'disconnected' && (
<button
onClick={onReconnect}
className="flex items-center gap-1 mt-1.5 text-accent hover:text-accent-hover transition-colors"
>
<RefreshCw className="h-3 w-3" />
Reconnect
</button>
)}
</div>
</div>
</div>
)
}
// ─── Header ──────────────────────────────────────────────────────────────────
export function Header() {
const { user, logout } = useAuth()
const { selectedTenantId, setSelectedTenantId, theme, setTheme } = useUIStore()
const { connectionState, lastConnectedAt, reconnect } = useEventStreamContext()
const navigate = useNavigate()
const SYSTEM_TENANT_ID = '00000000-0000-0000-0000-000000000000'
const { data: tenants } = useQuery({
queryKey: ['tenants'],
queryFn: tenantsApi.list,
enabled: isSuperAdmin(user),
select: (data) => data.filter((t) => t.id !== SYSTEM_TENANT_ID),
})
const selectedTenant = tenants?.find((t) => t.id === selectedTenantId)
// Auto-select when there's exactly one tenant and nothing selected
useEffect(() => {
if (isSuperAdmin(user) && tenants && tenants.length === 1 && !selectedTenantId) {
setSelectedTenantId(tenants[0].id)
}
}, [tenants, selectedTenantId, user, setSelectedTenantId])
const handleLogout = async () => {
await logout()
void navigate({ to: '/login' })
}
const roleLabel: Record<string, string> = {
super_admin: 'Super Admin',
tenant_admin: 'Admin',
operator: 'Operator',
viewer: 'Viewer',
}
return (
<header className="flex items-center justify-between h-12 px-4 border-b border-border bg-sidebar flex-shrink-0" data-testid="header">
{/* Left: hamburger + tenant context */}
<div className="flex items-center gap-2">
<button
onClick={() => useUIStore.getState().setMobileSidebarOpen(true)}
className="lg:hidden p-1.5 rounded-md text-text-muted hover:text-text-primary hover:bg-elevated transition-colors"
aria-label="Open menu"
>
<Menu className="h-5 w-5" />
</button>
{isSuperAdmin(user) && tenants && tenants.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 text-sm text-text-secondary hover:text-text-primary transition-colors" data-testid="header-org-selector">
<span>{selectedTenant ? selectedTenant.name : 'All Organizations'}</span>
<ChevronDown className="h-3.5 w-3.5" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuLabel>Organization Context</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => setSelectedTenantId(null)}>
All Organizations
</DropdownMenuItem>
{tenants.map((tenant) => (
<DropdownMenuItem
key={tenant.id}
onClick={() => setSelectedTenantId(tenant.id)}
>
{tenant.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
{/* Right: actions + user menu */}
<div className="flex items-center gap-2">
{/* Search trigger */}
<button
onClick={() => useCommandPalette.getState().setOpen(true)}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg border border-border text-text-muted text-sm hover:text-text-secondary hover:border-border-bright transition-colors"
aria-label="Search (Cmd+K)"
data-testid="header-search"
>
<Search className="h-3.5 w-3.5" />
<span className="hidden lg:inline">Search...</span>
<kbd className="hidden lg:inline-flex items-center gap-0.5 rounded border border-border px-1.5 py-0.5 text-[10px] font-mono text-text-muted">
Cmd+K
</kbd>
</button>
{/* Dark/light mode toggle */}
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="p-2 rounded-lg text-text-muted hover:text-text-primary hover:bg-elevated transition-colors"
aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
data-testid="header-theme-toggle"
>
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</button>
{/* SSE connection state indicator */}
<ConnectionIndicator
state={connectionState}
lastConnectedAt={lastConnectedAt}
onReconnect={reconnect}
/>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors" aria-label="User menu" data-testid="header-user-menu">
<div className="w-6 h-6 rounded-full bg-elevated flex items-center justify-center">
<User className="h-3.5 w-3.5" />
</div>
<span className="hidden lg:block">{user?.name ?? user?.email}</span>
{/* Only show role if it differs from the display name */}
{(user?.name ?? user?.email) !== (roleLabel[user?.role ?? ''] ?? user?.role) && (
<span className="hidden lg:block text-xs text-text-muted">
{roleLabel[user?.role ?? ''] ?? user?.role}
</span>
)}
<ChevronDown className="h-3.5 w-3.5" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>
<div className="font-normal text-text-secondary text-xs">{user?.email}</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link to="/settings" className="flex items-center gap-2">
<Settings className="h-4 w-4" />
Settings
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => void handleLogout()} className="text-error" data-testid="button-sign-out">
<LogOut className="h-4 w-4" />
Sign out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
)
}

View File

@@ -0,0 +1,35 @@
import { motion, useReducedMotion } from 'framer-motion'
import { type ReactNode } from 'react'
const variants = {
initial: { opacity: 0, y: 8 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -8 },
}
interface PageTransitionProps {
children: ReactNode
/** Unique key for AnimatePresence to detect page changes */
pageKey: string
}
export function PageTransition({ children, pageKey }: PageTransitionProps) {
const prefersReducedMotion = useReducedMotion()
if (prefersReducedMotion) {
return <div key={pageKey}>{children}</div>
}
return (
<motion.div
key={pageKey}
variants={variants}
initial="initial"
animate="animate"
exit="exit"
transition={{ duration: 0.2, ease: 'easeOut' }}
>
{children}
</motion.div>
)
}

View File

@@ -0,0 +1,70 @@
import { useState } from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { shortcuts, categoryLabels, type ShortcutDef } from '@/lib/shortcuts'
import { ShortcutHint } from '@/components/ui/shortcut-hint'
import { useShortcut } from '@/hooks/useShortcut'
export function ShortcutsDialog() {
const [open, setOpen] = useState(false)
// ? key opens the shortcuts dialog
useShortcut('?', () => setOpen(true))
// Group shortcuts by category
const grouped = shortcuts.reduce<
Record<ShortcutDef['category'], ShortcutDef[]>
>(
(acc, shortcut) => {
acc[shortcut.category].push(shortcut)
return acc
},
{ global: [], navigation: [], 'device-list': [] },
)
const categories: ShortcutDef['category'][] = [
'global',
'navigation',
'device-list',
]
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Keyboard Shortcuts</DialogTitle>
</DialogHeader>
<div className="space-y-5 mt-2">
{categories.map((category) => {
const items = grouped[category]
if (items.length === 0) return null
return (
<div key={category}>
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-2">
{categoryLabels[category]}
</h3>
<div className="space-y-1">
{items.map((shortcut) => (
<div
key={shortcut.key}
className="flex items-center justify-between py-1.5 px-2 rounded hover:bg-elevated/50"
>
<span className="text-sm text-text-secondary">
{shortcut.description}
</span>
<ShortcutHint keys={shortcut.key} />
</div>
))}
</div>
</div>
)
})}
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,344 @@
import { useEffect } from 'react'
import { Link, useRouterState } from '@tanstack/react-router'
import {
Monitor,
Building2,
Users,
Settings,
ChevronLeft,
ChevronRight,
Bell,
BellRing,
Download,
Terminal,
FileCode,
FileText,
MapPin,
LayoutDashboard,
Network,
Wrench,
ClipboardList,
Calendar,
Key,
Layers,
Shield,
ShieldCheck,
Eye,
Info,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { useAuth, isSuperAdmin, isTenantAdmin } from '@/lib/auth'
import { useUIStore } from '@/lib/store'
import { AlertBadge } from '@/components/alerts/AlertBadge'
import { RugLogo } from '@/components/brand/RugLogo'
interface NavItem {
label: string
href: string
icon: React.FC<{ className?: string }>
exact?: boolean
badge?: React.ReactNode
}
interface NavSection {
label: string
items: NavItem[]
visible: boolean
}
export function Sidebar() {
const { user } = useAuth()
const { sidebarCollapsed, toggleSidebar, mobileSidebarOpen, setMobileSidebarOpen } = useUIStore()
const routerState = useRouterState()
const currentPath = routerState.location.pathname
// Keyboard toggle: [ key collapses/expands sidebar
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (
e.key === '[' &&
!['INPUT', 'TEXTAREA', 'SELECT'].includes(
(e.target as HTMLElement).tagName,
)
) {
e.preventDefault()
toggleSidebar()
}
}
document.addEventListener('keydown', handleKeyDown)
return () => document.removeEventListener('keydown', handleKeyDown)
}, [toggleSidebar])
const sections: NavSection[] = [
{
label: 'Fleet',
visible: true,
items: [
{
label: 'Dashboard',
href: '/',
icon: LayoutDashboard,
exact: true,
},
// Only show Devices for non-super_admin (super_admin uses Organizations in Admin)
...(!isSuperAdmin(user) && user?.tenant_id
? [
{
label: 'Devices',
href: `/tenants/${user.tenant_id}/devices`,
icon: Monitor,
},
]
: []),
{
label: 'Map',
href: '/map',
icon: MapPin,
},
],
},
{
label: 'Manage',
visible: true,
items: [
{
label: 'Config Editor',
href: '/config-editor',
icon: Terminal,
},
{
label: 'Batch Config',
href: '/batch-config',
icon: Wrench,
},
{
label: 'Bulk Commands',
href: '/bulk-commands',
icon: Layers,
},
{
label: 'Templates',
href: '/templates',
icon: FileCode,
},
{
label: 'Firmware',
href: '/firmware',
icon: Download,
},
{
label: 'Maintenance',
href: '/maintenance',
icon: Calendar,
},
{
label: 'VPN',
href: '/vpn',
icon: Shield,
},
{
label: 'Certificates',
href: '/certificates',
icon: ShieldCheck,
},
],
},
{
label: 'Monitor',
visible: true,
items: [
{
label: 'Topology',
href: '/topology',
icon: Network,
},
{
label: 'Alerts',
href: '/alerts',
icon: Bell,
badge: <AlertBadge />,
},
{
label: 'Alert Rules',
href: '/alert-rules',
icon: BellRing,
},
{
label: 'Audit Trail',
href: '/audit',
icon: ClipboardList,
},
...(isTenantAdmin(user)
? [
{
label: 'Transparency',
href: '/transparency',
icon: Eye,
},
]
: []),
{
label: 'Reports',
href: '/reports',
icon: FileText,
},
],
},
{
label: 'Admin',
visible: isSuperAdmin(user) || isTenantAdmin(user),
items: [
...(isTenantAdmin(user) && user?.tenant_id
? [
{
label: 'Users',
href: `/tenants/${user.tenant_id}/users`,
icon: Users,
},
]
: []),
...(isSuperAdmin(user) || isTenantAdmin(user)
? [
{
label: 'Organizations',
href: '/tenants',
icon: Building2,
},
]
: []),
{
label: 'API Keys',
href: '/settings/api-keys',
icon: Key,
},
{
label: 'Settings',
href: '/settings',
icon: Settings,
},
{
label: 'About',
href: '/about',
icon: Info,
},
],
},
]
const visibleSections = sections.filter((s) => s.visible)
const isActive = (item: NavItem) => {
if (item.exact) return currentPath === item.href
// Settings should only match exact to avoid catching everything
if (item.href === '/settings') return currentPath === '/settings' || currentPath.startsWith('/settings/')
return currentPath.startsWith(item.href) && item.href.length > 1
}
const sidebarContent = (showCollapsed: boolean) => (
<>
{/* Logo */}
<div
className={cn(
'flex items-center h-12 px-3 border-b border-border',
showCollapsed ? 'justify-center' : 'gap-2',
)}
>
<RugLogo size={showCollapsed ? 24 : 28} className="flex-shrink-0" />
{!showCollapsed && (
<span className="text-sm font-semibold text-text-primary truncate">
TOD
</span>
)}
</div>
{/* Navigation */}
<nav className="flex-1 py-2 overflow-y-auto">
{visibleSections.map((section, sectionIdx) => (
<div key={section.label}>
{showCollapsed && sectionIdx > 0 && (
<div className="mx-2 my-1 border-t border-border" />
)}
{!showCollapsed && (
<div className="px-3 pt-4 pb-1 text-[10px] font-semibold uppercase tracking-wider text-text-muted">
{section.label}
</div>
)}
{section.items.map((item) => {
const Icon = item.icon
const active = isActive(item)
return (
<Link
key={`${section.label}-${item.label}`}
to={item.href}
onClick={() => setMobileSidebarOpen(false)}
data-testid={`nav-${item.label.toLowerCase().replace(/\s+/g, '-')}`}
className={cn(
'flex items-center gap-2.5 px-3 py-2 mx-1 rounded-md text-sm transition-colors min-h-[44px]',
active
? 'bg-accent-muted text-accent'
: 'text-text-secondary hover:text-text-primary hover:bg-elevated/50',
showCollapsed && 'justify-center px-0',
)}
title={showCollapsed ? item.label : undefined}
>
<Icon className="h-4 w-4 flex-shrink-0" />
{!showCollapsed && (
<>
<span className="truncate">{item.label}</span>
{item.badge && <span className="ml-auto">{item.badge}</span>}
</>
)}
</Link>
)
})}
</div>
))}
</nav>
{/* Collapse toggle (hidden on mobile) */}
<button
onClick={toggleSidebar}
className="hidden lg:flex items-center justify-center h-10 border-t border-border text-text-muted hover:text-text-secondary transition-colors"
title={showCollapsed ? 'Expand sidebar ([)' : 'Collapse sidebar ([)'}
aria-label={showCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
data-testid="sidebar-toggle"
>
{showCollapsed ? (
<ChevronRight className="h-4 w-4" />
) : (
<ChevronLeft className="h-4 w-4" />
)}
</button>
</>
)
return (
<>
{/* Desktop sidebar */}
<aside
data-testid="sidebar"
className={cn(
'hidden lg:flex flex-col border-r border-border bg-sidebar transition-all duration-200',
sidebarCollapsed ? 'w-12' : 'w-60',
)}
>
{sidebarContent(sidebarCollapsed)}
</aside>
{/* Mobile overlay */}
{mobileSidebarOpen && (
<>
<div
className="lg:hidden fixed inset-0 z-40 bg-black/50"
onClick={() => setMobileSidebarOpen(false)}
/>
<aside className="lg:hidden fixed inset-y-0 left-0 z-50 w-60 flex flex-col bg-sidebar border-r border-border shadow-xl">
{sidebarContent(false)}
</aside>
</>
)}
</>
)
}