feat: React app shell with router, sidebar layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
13f030fe1f
commit
f096f7d7b0
3 changed files with 98 additions and 3 deletions
25
src/App.tsx
25
src/App.tsx
|
|
@ -1,9 +1,28 @@
|
|||
function App() {
|
||||
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
||||
import AppLayout from "./components/layout/AppLayout";
|
||||
|
||||
function EmptyState() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 text-gray-900">
|
||||
<h1 className="text-2xl font-bold p-8">Orchai</h1>
|
||||
<div className="flex items-center justify-center h-full text-gray-400">
|
||||
<p>Select a project or create a new one</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<AppLayout />}>
|
||||
<Route index element={<EmptyState />} />
|
||||
<Route path="/projects/new" element={<div className="p-8">Create project (coming next)</div>} />
|
||||
<Route path="/projects/:projectId" element={<div className="p-8">Project dashboard (coming next)</div>} />
|
||||
<Route path="/projects/:projectId/edit" element={<div className="p-8">Edit project (coming next)</div>} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
|||
13
src/components/layout/AppLayout.tsx
Normal file
13
src/components/layout/AppLayout.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { Outlet } from "react-router-dom";
|
||||
import Sidebar from "./Sidebar";
|
||||
|
||||
export default function AppLayout() {
|
||||
return (
|
||||
<div className="flex h-screen">
|
||||
<Sidebar />
|
||||
<main className="flex-1 overflow-y-auto bg-gray-50">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
src/components/layout/Sidebar.tsx
Normal file
63
src/components/layout/Sidebar.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { listProjects } from "../../lib/api";
|
||||
import type { Project } from "../../lib/types";
|
||||
|
||||
export default function Sidebar() {
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
listProjects().then(setProjects);
|
||||
}, []);
|
||||
|
||||
// Expose a refresh function via custom event
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
listProjects().then(setProjects);
|
||||
};
|
||||
window.addEventListener("orchai:refresh-projects", handler);
|
||||
return () => window.removeEventListener("orchai:refresh-projects", handler);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-gray-900 text-gray-100 flex flex-col h-screen">
|
||||
<div className="p-4 border-b border-gray-700">
|
||||
<h1 className="text-lg font-bold">Orchai</h1>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 overflow-y-auto p-2">
|
||||
<div className="flex items-center justify-between px-2 py-1 mb-1">
|
||||
<span className="text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||
Projects
|
||||
</span>
|
||||
<Link
|
||||
to="/projects/new"
|
||||
className="text-gray-400 hover:text-white text-lg leading-none"
|
||||
title="Add project"
|
||||
>
|
||||
+
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{projects.map((project) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
to={`/projects/${project.id}`}
|
||||
className={`block px-3 py-2 rounded text-sm ${
|
||||
projectId === project.id
|
||||
? "bg-gray-700 text-white"
|
||||
: "text-gray-300 hover:bg-gray-800 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{project.name}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{projects.length === 0 && (
|
||||
<p className="px-3 py-2 text-sm text-gray-500">No projects yet</p>
|
||||
)}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue