feat: runtime login screen — remove token from build

- Add LoginScreen component with Gateway URL + Token fields
- Store credentials in localStorage (not in bundle)
- Auto-reconnect with stored credentials on reload
- Add logout button (LogOut icon) in Header
- Remove VITE_GATEWAY_TOKEN from .env.example
- VITE_GATEWAY_WS_URL now only pre-fills the URL field
- Dark neon theme consistent with rest of app

Closes feedback item #4
This commit is contained in:
Nicolas Varrot
2026-02-11 12:48:58 +00:00
parent a01bae8c1c
commit 36f948027b
7 changed files with 274 additions and 31 deletions

View File

@@ -3,11 +3,30 @@ import { useGateway } from './hooks/useGateway';
import { Header } from './components/Header';
import { Sidebar } from './components/Sidebar';
import { Chat } from './components/Chat';
import { LoginScreen } from './components/LoginScreen';
export default function App() {
const { status, messages, sessions, activeSession, isGenerating, sendMessage, abort, switchSession } = useGateway();
const {
status, messages, sessions, activeSession, isGenerating,
sendMessage, abort, switchSession,
authenticated, login, logout, connectError, isConnecting,
} = useGateway();
const [sidebarOpen, setSidebarOpen] = useState(false);
// Still checking stored credentials
if (authenticated === null) {
return (
<div className="h-dvh flex items-center justify-center bg-[#1e1e24] text-zinc-500">
<div className="animate-pulse text-sm">Connecting</div>
</div>
);
}
// Not authenticated — show login
if (!authenticated) {
return <LoginScreen onConnect={login} error={connectError} isConnecting={isConnecting} />;
}
return (
<div className="h-dvh flex bg-[#1e1e24] text-zinc-300 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.02),transparent_50%),radial_gradient(ellipse_at_bottom_right,rgba(99,102,241,0.04),transparent_50%)]" role="application" aria-label="PinchChat">
<Sidebar
@@ -18,7 +37,7 @@ export default function App() {
onClose={() => setSidebarOpen(false)}
/>
<div className="flex-1 flex flex-col min-w-0">
<Header status={status} sessionKey={activeSession} onToggleSidebar={() => setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} />
<Header status={status} sessionKey={activeSession} onToggleSidebar={() => setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} />
<Chat messages={messages} isGenerating={isGenerating} status={status} onSend={sendMessage} onAbort={abort} />
</div>
</div>