feat: add React frontend with auth, project list, import, and project detail pages
Converts packages/web from vanilla TypeScript Vite scaffold to React with: - React 19, react-router-dom v7, @tanstack/react-query v5, Tailwind CSS v4 - JWT auth context with auto-refresh token support - Login/Register pages, protected Layout with auth guard - Projects list with grid cards and delete action - ImportDialog supporting URL or file upload with API key display - ProjectDetail with 4 tabs: Documentation, Modules, MCP Integration, Settings - All TypeScript compiles cleanly (noEmit check passes) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
58
packages/web/src/pages/ProjectDetail.tsx
Normal file
58
packages/web/src/pages/ProjectDetail.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../lib/api';
|
||||
import DocPreview from './tabs/DocPreview';
|
||||
import ModuleManagement from './tabs/ModuleManagement';
|
||||
import McpIntegration from './tabs/McpIntegration';
|
||||
import ProjectSettings from './tabs/ProjectSettings';
|
||||
|
||||
type ProjectData = {
|
||||
id: string; name: string; description: string | null; baseUrl: string | null;
|
||||
openApiVersion: string;
|
||||
modules: Array<{ id: string; name: string; description: string | null; _count: { endpoints: number } }>;
|
||||
_count: { endpoints: number };
|
||||
};
|
||||
|
||||
const tabs = ['Documentation', 'Modules', 'MCP Integration', 'Settings'] as const;
|
||||
type Tab = (typeof tabs)[number];
|
||||
|
||||
export default function ProjectDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [activeTab, setActiveTab] = useState<Tab>('Documentation');
|
||||
|
||||
const { data: project, isLoading } = useQuery({
|
||||
queryKey: ['project', id],
|
||||
queryFn: () => apiFetch<ProjectData>(`/projects/${id}`),
|
||||
});
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (!project) return <div>Project not found</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4"><Link to="/" className="text-sm text-blue-600 hover:underline">← Back to projects</Link></div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">{project.name}</h2>
|
||||
{project.description && <p className="text-sm text-gray-500 mt-1">{project.description}</p>}
|
||||
</div>
|
||||
<div className="text-sm text-gray-400">OpenAPI {project.openApiVersion} · {project._count.endpoints} endpoints</div>
|
||||
</div>
|
||||
<div className="border-b mb-6">
|
||||
<div className="flex gap-6">
|
||||
{tabs.map((tab) => (
|
||||
<button key={tab} onClick={() => setActiveTab(tab)}
|
||||
className={`pb-2 text-sm font-medium border-b-2 transition-colors ${activeTab === tab ? 'border-blue-600 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`}>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === 'Documentation' && <DocPreview projectId={project.id} />}
|
||||
{activeTab === 'Modules' && <ModuleManagement projectId={project.id} />}
|
||||
{activeTab === 'MCP Integration' && <McpIntegration project={project} />}
|
||||
{activeTab === 'Settings' && <ProjectSettings project={project} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user