Phase 1 of frontend migration (epic #6, issue #7). Project setup (web/): - React 19, React Router 7, Vite 6, TypeScript 5.7 - Catppuccin Mocha theme CSS variables matching existing Go templates - Vite dev proxy to Go backend at :8080 for /api/*, /login, /logout, /auth/*, /health, /ready Shared infrastructure: - api/client.ts: typed fetch wrapper (get/post/put/del) with 401 redirect and credentials:include for session cookies - api/types.ts: TypeScript interfaces for all API response types (User, Item, Project, Schema, Revision, BOMEntry, Audit, Error) - context/AuthContext.tsx: AuthProvider calling GET /api/auth/me - hooks/useAuth.ts: useAuth() hook exposing user/loading/logout UI shell: - AppShell.tsx: header nav matching current Go template navbar (Items, Projects, Schemas, Audit, Settings) with role badges (admin=mauve, editor=blue, viewer=teal) and active tab highlighting - LoginPage: redirects to Go-served /login during transition - Placeholder pages: Items, Projects, Schemas fetch from API and display data in tables; Audit shows summary stats; Settings shows current user profile Go server changes: - routes.go: serve web/dist/ at /app/* with SPA index.html fallback (only activates when web/dist/ directory exists) - .gitignore: web/node_modules/, web/dist/ - Makefile: web-install, web-dev, web-build targets
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { get } from '../api/client';
|
|
import type { Schema } from '../api/types';
|
|
|
|
export function SchemasPage() {
|
|
const [schemas, setSchemas] = useState<Schema[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
get<Schema[]>('/api/schemas')
|
|
.then(setSchemas)
|
|
.catch((e: Error) => setError(e.message))
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
if (loading) return <p style={{ color: 'var(--ctp-subtext0)' }}>Loading schemas...</p>;
|
|
if (error) return <p style={{ color: 'var(--ctp-red)' }}>Error: {error}</p>;
|
|
|
|
return (
|
|
<div>
|
|
<h2 style={{ marginBottom: '1rem' }}>Schemas ({schemas.length})</h2>
|
|
<div style={{
|
|
backgroundColor: 'var(--ctp-surface0)',
|
|
borderRadius: '0.75rem',
|
|
padding: '1rem',
|
|
overflowX: 'auto',
|
|
}}>
|
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
<thead>
|
|
<tr>
|
|
<th style={thStyle}>Name</th>
|
|
<th style={thStyle}>Format</th>
|
|
<th style={thStyle}>Description</th>
|
|
<th style={thStyle}>Segments</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{schemas.map((s) => (
|
|
<tr key={s.name}>
|
|
<td style={{ ...tdStyle, fontFamily: "'JetBrains Mono', monospace", color: 'var(--ctp-peach)' }}>
|
|
{s.name}
|
|
</td>
|
|
<td style={{ ...tdStyle, fontFamily: "'JetBrains Mono', monospace" }}>{s.format}</td>
|
|
<td style={tdStyle}>{s.description}</td>
|
|
<td style={tdStyle}>{s.segments.length}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const thStyle: React.CSSProperties = {
|
|
padding: '0.75rem 1rem',
|
|
textAlign: 'left',
|
|
borderBottom: '1px solid var(--ctp-surface1)',
|
|
color: 'var(--ctp-subtext1)',
|
|
fontWeight: 600,
|
|
fontSize: '0.85rem',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.05em',
|
|
};
|
|
|
|
const tdStyle: React.CSSProperties = {
|
|
padding: '0.75rem 1rem',
|
|
borderBottom: '1px solid var(--ctp-surface1)',
|
|
};
|