mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-08-30 18:36:23 +00:00
7399315185
Rebuild the bundled web UI as a Next.js (React + TypeScript) static export implementing the Claude Activity Dashboard design: a left-nav "Control Center" hub over Runs, Repositories, Agents, Approvals, Git Servers, Activity, and Shared, styled with the Leeworks design-system tokens (flat, dark, border-led). The dashboard is a pure client of the existing API (same contract as curl): the browser prompts for the token once, stores it in localStorage, attaches it to every call, and renders all API values as React text so agent-authored strings stay inert. Control actions enqueue a command and poll it to a terminal state, matching the worker model. The build output is committed to src/handler/api/static/ so the wheel ships it and FastAPI serves it same-origin. app.py now mounts the export at "/" after the API routers (a non-shadowing fallback: unmatched paths 404, no SPA rewrite). UI-serving tests updated for the export; frontend source lives in frontend/. Claude-Session: https://claude.ai/code/session_01ATgVWRjFzG8nHEnwgZpJWD Co-authored-by: Claude <noreply@anthropic.com>
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
/* Root page: token gate → dashboard. Client-only; the exported HTML is a shell and every
|
|
* byte of data is fetched by the browser from the authed API after the token is supplied. */
|
|
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { DashboardProvider } from "@/components/store";
|
|
import { Dashboard } from "@/components/Dashboard";
|
|
import { TokenGate } from "@/components/TokenGate";
|
|
|
|
const TOKEN_KEY = "handler_token";
|
|
|
|
export default function Home() {
|
|
const [token, setToken] = useState<string | null>(null);
|
|
const [error, setError] = useState("");
|
|
|
|
// Read the stored token after mount (localStorage is client-only).
|
|
useEffect(() => {
|
|
const stored = window.localStorage.getItem(TOKEN_KEY);
|
|
if (stored) setToken(stored);
|
|
}, []);
|
|
|
|
const saveToken = useCallback((t: string) => {
|
|
window.localStorage.setItem(TOKEN_KEY, t);
|
|
setError("");
|
|
setToken(t);
|
|
}, []);
|
|
|
|
const signOut = useCallback(() => {
|
|
window.localStorage.removeItem(TOKEN_KEY);
|
|
setToken(null);
|
|
}, []);
|
|
|
|
// A 401 from any call clears the token and re-prompts with an error.
|
|
const onUnauthorized = useCallback(() => {
|
|
window.localStorage.removeItem(TOKEN_KEY);
|
|
setToken(null);
|
|
setError("Invalid token — please try again.");
|
|
}, []);
|
|
|
|
if (!token) {
|
|
return <TokenGate error={error} onSubmit={saveToken} />;
|
|
}
|
|
|
|
return (
|
|
<DashboardProvider token={token} onUnauthorized={onUnauthorized}>
|
|
<Dashboard onSignOut={signOut} />
|
|
</DashboardProvider>
|
|
);
|
|
}
|