refactor: clean up dead files, restructure nav, align QuickStartPage to monochrome

- Delete 6 unused files: ThemeToggle, themeStore, AppLayout-original,
  QuickStartPage-Enhanced, UpgradePrompt, SettingsPage
- Restructure Account/Settings navigation: merge Settings into Account page,
  make AccountSettingsPage the /account index, remove orphaned /account-settings route
- Remove "Settings" nav item (consolidated under Account)
- Add export preferences and team categories link to AccountSettingsPage
- Align QuickStartPage to monochrome design system: replace cyan/blue/violet
  accent colors with white opacity variants
- Replace non-functional search button with search spinner indicator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-10 17:46:35 -05:00
parent 89d343d49a
commit e5e5922097
11 changed files with 76 additions and 777 deletions

View File

@@ -1,36 +0,0 @@
import { Sun, Moon, Monitor } from 'lucide-react'
import { useThemeStore } from '@/store/themeStore'
import { cn } from '@/lib/utils'
export function ThemeToggle() {
const { theme, setTheme } = useThemeStore()
const options = [
{ value: 'light' as const, icon: Sun, label: 'Light' },
{ value: 'dark' as const, icon: Moon, label: 'Dark' },
{ value: 'system' as const, icon: Monitor, label: 'System' },
]
return (
<div className="flex items-center rounded-md border border-input bg-background p-1">
{options.map(({ value, icon: Icon, label }) => (
<button
key={value}
onClick={() => setTheme(value)}
className={cn(
'rounded p-1.5 transition-colors',
theme === value
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:text-foreground'
)}
aria-label={`Switch to ${label} theme`}
aria-pressed={theme === value}
>
<Icon className="h-4 w-4" />
</button>
))}
</div>
)
}
export default ThemeToggle

View File

@@ -1,32 +0,0 @@
import { cn } from '@/lib/utils'
import { useSubscription } from '@/hooks/useSubscription'
interface UpgradePromptProps {
feature: string // e.g., "create more trees", "start more sessions"
className?: string
}
export function UpgradePrompt({ feature, className }: UpgradePromptProps) {
const { plan } = useSubscription()
return (
<div className={cn(
'glass-card rounded-2xl border border-white/[0.06] p-4',
className
)}>
<h3 className="font-semibold text-white">Plan Limit Reached</h3>
<p className="mt-1 text-sm text-white/70">
Your {plan} plan doesn't allow you to {feature}. Upgrade your plan to continue.
</p>
<button
className={cn(
'mt-3 rounded-xl bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
onClick={() => window.location.href = '/account'}
>
View Plans
</button>
</div>
)
}

View File

@@ -1,227 +0,0 @@
import { useState, useEffect, useCallback } from 'react'
import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions'
import { ThemeToggle } from '@/components/common/ThemeToggle'
import { BrandLogo } from '@/components/common/BrandLogo'
import { BrandWordmark } from '@/components/common/BrandWordmark'
import { Menu, X } from 'lucide-react'
import { cn } from '@/lib/utils'
export function AppLayout() {
const location = useLocation()
const navigate = useNavigate()
const { user, logout } = useAuthStore()
const { effectiveRole, isSuperAdmin } = usePermissions()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const handleLogout = async () => {
setMobileMenuOpen(false)
await logout()
navigate('/login')
}
// Close mobile menu on route change - key-based reset
const [prevPath, setPrevPath] = useState(location.pathname)
if (prevPath !== location.pathname) {
setPrevPath(location.pathname)
if (mobileMenuOpen) setMobileMenuOpen(false)
}
// Close on Escape
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') setMobileMenuOpen(false)
}, [])
useEffect(() => {
if (mobileMenuOpen) {
document.addEventListener('keydown', handleKeyDown)
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
return () => {
document.removeEventListener('keydown', handleKeyDown)
document.body.style.overflow = ''
}
}, [mobileMenuOpen, handleKeyDown])
const navItems = [
{ path: '/', label: 'Home' },
{ path: '/trees', label: 'Trees' },
{ path: '/my-trees', label: 'My Trees' },
{ path: '/sessions', label: 'Sessions' },
{ path: '/account', label: 'Account' },
{ path: '/settings', label: 'Settings' },
...(isSuperAdmin ? [{ path: '/admin', label: 'Admin Panel' }] : []),
]
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="sticky top-0 z-50 border-b border-border bg-card backdrop-blur-sm">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-8">
{/* Mobile hamburger */}
<button
onClick={() => setMobileMenuOpen(true)}
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground sm:hidden"
aria-label="Open menu"
>
<Menu className="h-5 w-5" />
</button>
<Link to="/" className="flex items-center gap-2">
<BrandLogo size="sm" />
<BrandWordmark size="sm" />
</Link>
<nav className="hidden items-center gap-1 sm:flex">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={cn(
'relative rounded-md px-3 py-2 text-sm font-medium transition-colors',
(item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path))
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
{item.label}
</Link>
))}
</nav>
</div>
<div className="flex items-center gap-4">
<span className="hidden text-sm text-muted-foreground sm:block">
{user?.name || user?.email}
</span>
{effectiveRole && effectiveRole !== 'engineer' && (
<span
className={cn(
'hidden rounded-full px-2 py-0.5 text-xs font-medium sm:inline-block',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)}
<ThemeToggle />
<button
onClick={handleLogout}
className={cn(
'hidden rounded-md px-3 py-1.5 text-sm font-medium sm:block',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
Logout
</button>
</div>
</div>
</header>
{/* Mobile Nav Drawer */}
{mobileMenuOpen && (
<div className="fixed inset-0 z-50 sm:hidden">
{/* Backdrop */}
<div
className="absolute inset-0 bg-background/80 backdrop-blur-sm animate-fade-in"
onClick={() => setMobileMenuOpen(false)}
aria-hidden="true"
/>
{/* Drawer */}
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-card shadow-xl animate-slide-in-left">
<div className="flex h-16 items-center justify-between border-b border-border px-4">
<Link to="/" className="flex items-center gap-2">
<BrandLogo size="sm" />
<BrandWordmark size="sm" />
</Link>
<button
onClick={() => setMobileMenuOpen(false)}
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
aria-label="Close menu"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="flex flex-col p-4">
{/* User info */}
<div className="mb-4 border-b border-border pb-4">
<p className="text-sm font-medium text-foreground">
{user?.name || user?.email}
</p>
{effectiveRole && effectiveRole !== 'engineer' && (
<span
className={cn(
'mt-1 inline-block rounded-full px-2 py-0.5 text-xs font-medium',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)}
</div>
{/* Nav items */}
<div className="space-y-1">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={cn(
'block rounded-md px-3 py-2.5 text-sm font-medium transition-colors',
(item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path))
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
{item.label}
</Link>
))}
</div>
{/* Theme toggle */}
<div className="mt-4 border-t border-border pt-4">
<div className="flex items-center justify-between px-3 py-2">
<span className="text-sm text-muted-foreground">Theme</span>
<ThemeToggle />
</div>
</div>
{/* Logout */}
<div className="mt-2">
<button
onClick={handleLogout}
className={cn(
'w-full rounded-md px-3 py-2.5 text-left text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
Logout
</button>
</div>
</div>
</nav>
</div>
)}
{/* Main Content */}
<main className="animate-fade-in">
<Outlet />
</main>
</div>
)
}
export default AppLayout

View File

@@ -50,7 +50,6 @@ export function AppLayout() {
{ path: '/my-trees', label: 'My Trees' },
{ path: '/sessions', label: 'Sessions' },
{ path: '/account', label: 'Account' },
{ path: '/settings', label: 'Settings' },
...(isSuperAdmin ? [{ path: '/admin', label: 'Admin Panel' }] : []),
]