feat(dashboard): add React foundation and overview
This commit is contained in:
1 parent
59c3a61a88
commit
51e3e28a9b
20 files changed
+3347
-244
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { useState } from 'react';
|
||||
import { NavLink, Outlet } from 'react-router-dom';
|
||||
import { navigationItems } from './navigation.js';
|
||||
|
||||
export function AppShell() {
|
||||
const [isNavigationOpen, setIsNavigationOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<a className="skip-link" href="#main-content">Skip to content</a>
|
||||
<aside className={isNavigationOpen ? 'sidebar is-open' : 'sidebar'}>
|
||||
<div className="brand">
|
||||
<span className="brand-mark" aria-hidden="true">R</span>
|
||||
<div>
|
||||
<strong>Reddit Bot</strong>
|
||||
<span>Operator console</span>
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="Primary navigation" id="primary-navigation">
|
||||
{navigationItems.map((item) => (
|
||||
<NavLink
|
||||
aria-label={item.label}
|
||||
className={({ isActive }) => (isActive ? 'nav-link is-active' : 'nav-link')}
|
||||
end={item.path === '/'}
|
||||
key={item.path}
|
||||
onClick={() => setIsNavigationOpen(false)}
|
||||
to={item.path}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<small>{item.description}</small>
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
{isNavigationOpen ? (
|
||||
<button
|
||||
aria-label="Close navigation backdrop"
|
||||
className="navigation-backdrop"
|
||||
onClick={() => setIsNavigationOpen(false)}
|
||||
type="button"
|
||||
/>
|
||||
) : null}
|
||||
<div className="workspace">
|
||||
<header className="mobile-header">
|
||||
<span>Reddit Bot</span>
|
||||
<button
|
||||
aria-controls="primary-navigation"
|
||||
aria-expanded={isNavigationOpen}
|
||||
aria-label={isNavigationOpen ? 'Close navigation' : 'Open navigation'}
|
||||
className="icon-button"
|
||||
onClick={() => setIsNavigationOpen((current) => !current)}
|
||||
type="button"
|
||||
>
|
||||
<span aria-hidden="true">{isNavigationOpen ? '×' : '☰'}</span>
|
||||
</button>
|
||||
</header>
|
||||
<main id="main-content" tabIndex={-1}>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface NavigationItem {
|
||||
label: string;
|
||||
path: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const navigationItems: readonly NavigationItem[] = [
|
||||
{ label: 'Overview', path: '/', description: 'System health and current activity' },
|
||||
{ label: 'Conversations', path: '/conversations', description: 'Inbox and message history' },
|
||||
{ label: 'Media', path: '/media', description: 'Private media and sync controls' },
|
||||
{ label: 'Performance', path: '/performance', description: 'Response and strategy trends' },
|
||||
{ label: 'Operations', path: '/operations', description: 'Jobs, events, and diagnostics' },
|
||||
{ label: 'Settings', path: '/settings', description: 'Runtime and access configuration' },
|
||||
];
|
||||
@@ -0,0 +1,17 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 15_000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
export function AppProviders({ children }: PropsWithChildren) {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createBrowserRouter, type RouteObject } from 'react-router-dom';
|
||||
import { AppShell } from './app-shell.js';
|
||||
import { OverviewPage } from '../features/overview/overview-page.js';
|
||||
import { PlaceholderPage } from '../features/placeholder-page.js';
|
||||
|
||||
export function createDashboardRoutes(): RouteObject[] {
|
||||
return [
|
||||
{
|
||||
path: '/',
|
||||
element: <AppShell />,
|
||||
children: [
|
||||
{ index: true, element: <OverviewPage /> },
|
||||
{ path: 'conversations', element: <PlaceholderPage title="Conversations" description="Review conversations, drafts, and replies." /> },
|
||||
{ path: 'media', element: <PlaceholderPage title="Media" description="Browse private media and monitor syncs." /> },
|
||||
{ path: 'performance', element: <PlaceholderPage title="Performance" description="Understand response and strategy performance." /> },
|
||||
{ path: 'operations', element: <PlaceholderPage title="Operations" description="Inspect jobs, events, logs, and diagnostics." /> },
|
||||
{ path: 'settings', element: <PlaceholderPage title="Settings" description="Manage runtime behavior and Reddit access." /> },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const dashboardRouter = createBrowserRouter(createDashboardRoutes());
|
||||
Reference in new issue
Block a user