feat(web): scaffold Next.js 16 dashboard with design system and auth client (#82)

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #82.
This commit is contained in:
2026-03-13 13:18:09 +00:00
committed by jason.woltje
parent 5d936d58a0
commit 780f85e0d6
26 changed files with 1070 additions and 83 deletions

View File

@@ -0,0 +1,19 @@
import type { ReactNode } from 'react';
import { Sidebar } from './sidebar';
import { Topbar } from './topbar';
interface AppShellProps {
children: ReactNode;
}
export function AppShell({ children }: AppShellProps): React.ReactElement {
return (
<div className="min-h-screen">
<Sidebar />
<div className="pl-sidebar">
<Topbar />
<main className="p-6">{children}</main>
</div>
</div>
);
}

View File

@@ -0,0 +1,59 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@/lib/cn';
interface NavItem {
label: string;
href: string;
icon: string;
}
const navItems: NavItem[] = [
{ label: 'Chat', href: '/chat', icon: '💬' },
{ label: 'Tasks', href: '/tasks', icon: '📋' },
{ label: 'Projects', href: '/projects', icon: '📁' },
{ label: 'Settings', href: '/settings', icon: '⚙️' },
];
export function Sidebar(): React.ReactElement {
const pathname = usePathname();
return (
<aside className="fixed left-0 top-0 z-30 flex h-screen w-sidebar flex-col border-r border-surface-border bg-surface-card">
<div className="flex h-14 items-center px-4">
<Link href="/" className="text-lg font-semibold text-text-primary">
Mosaic
</Link>
</div>
<nav className="flex-1 space-y-1 px-2 py-2">
{navItems.map((item) => {
const isActive = pathname === item.href || pathname.startsWith(`${item.href}/`);
return (
<Link
key={item.href}
href={item.href}
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors',
isActive
? 'bg-blue-600/20 text-blue-400'
: 'text-text-secondary hover:bg-surface-elevated hover:text-text-primary',
)}
>
<span className="text-base" aria-hidden="true">
{item.icon}
</span>
{item.label}
</Link>
);
})}
</nav>
<div className="border-t border-surface-border p-4">
<p className="text-xs text-text-muted">Mosaic Stack v0.0.4</p>
</div>
</aside>
);
}

View File

@@ -0,0 +1,32 @@
'use client';
import { useSession, signOut } from '@/lib/auth-client';
export function Topbar(): React.ReactElement {
const { data: session } = useSession();
return (
<header className="sticky top-0 z-20 flex h-14 items-center justify-between border-b border-surface-border bg-surface-card/80 px-6 backdrop-blur-sm">
<div />
<div className="flex items-center gap-4">
{session?.user ? (
<>
<span className="text-sm text-text-secondary">
{session.user.name ?? session.user.email}
</span>
<button
type="button"
onClick={() => signOut()}
className="rounded-md px-3 py-1.5 text-sm text-text-muted transition-colors hover:bg-surface-elevated hover:text-text-primary"
>
Sign out
</button>
</>
) : (
<span className="text-sm text-text-muted">Not signed in</span>
)}
</div>
</header>
);
}