feat(frontend): add React SPA with chat, auth, and admin pages
- Vite 6 + React 19 + TypeScript + Tailwind 4 + Zustand 5 - Login page with JWT auth - Chat interface: MessageList, ChatInput, ChatContainer - WebSocket hook with auto-reconnect and exponential backoff - Sidebar with new chat button + session navigation - Admin dashboard placeholder - 57 modules, 242KB JS, TypeScript zero errors
This commit is contained in:
1 parent
192144563b
commit
4a71965686
35 files changed
+3146
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,44 @@
|
||||
// API client — simple fetch wrapper with JWT support
|
||||
const BASE = '/api/v1'
|
||||
|
||||
let token: string | null = localStorage.getItem('cyrene_token')
|
||||
|
||||
export function setToken(t: string | null) {
|
||||
token = t
|
||||
if (t) localStorage.setItem('cyrene_token', t)
|
||||
else localStorage.removeItem('cyrene_token')
|
||||
}
|
||||
|
||||
export function getToken() { return token }
|
||||
|
||||
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers as Record<string, string> || {}),
|
||||
}
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`
|
||||
|
||||
const res = await fetch(`${BASE}${path}`, { ...options, headers })
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }))
|
||||
throw new Error(err.error || err.message || `HTTP ${res.status}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export const api = {
|
||||
login: (u: string, p: string) =>
|
||||
request<{ token: string }>('/auth/login', { method: 'POST', body: JSON.stringify({ username: u, password: p }) }),
|
||||
|
||||
health: () => request<{ status: string }>('/health'),
|
||||
|
||||
sessions: {
|
||||
list: () => request<{ sessions: { id: string; title: string }[] }>('/sessions'),
|
||||
create: () => request<{ session_id: string }>('/sessions', { method: 'POST' }),
|
||||
delete: (id: string) => request<void>(`/sessions/${id}`, { method: 'DELETE' }),
|
||||
},
|
||||
|
||||
admin: {
|
||||
dashboard: () => request<{ dashboard: Record<string, unknown> }>('/admin/dashboard'),
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user