Files
dumpsterChat/web/src/App.tsx
T
hobokenchicken 5bdb758d23 feat(bots): bot framework polish + store
- /ws/bot endpoint: bot token auth via query param, SHA-256 lookup
- Bot WS actions: SEND_MESSAGE + DELETE_MESSAGE handled in gateway
- Bot messages: bot_id on messages table, bot badge in chat (green + BOT tag)
- Bot store: /bots lists all bots with server count + add-to-server
- Bot manager moved to /bots/manage
- Fix: command routes were double-nested under /bots/{botID}/commands
- Fix: fetchServerCommands route corrected to /bots/servers/...
2026-07-15 12:45:44 -04:00

164 lines
6.0 KiB
TypeScript

import { BrowserRouter, Routes, Route, Navigate, Link } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { LoginForm } from './components/LoginForm.tsx';
import { Layout } from './components/Layout.tsx';
import { ChatArea } from './components/ChatArea.tsx';
import { UserSettings } from './components/UserSettings.tsx';
import { BotManager } from './components/BotManager.tsx';
import { BotStore } from './components/BotStore.tsx';
import { CommandManager } from './components/CommandManager.tsx';
import { RoleManager } from './components/RoleManager.tsx';
import { JoinServer } from './components/JoinServer.tsx';
import { DMChat } from './components/DMChat.tsx';
import { ForgotPasswordPage } from './components/ForgotPasswordPage.tsx';
import { ResetPasswordPage } from './components/ResetPasswordPage.tsx';
import { useAuthStore } from './stores/auth.ts';
import { useWebSocketStore } from './stores/ws.ts';
import { InstallBanner } from './components/InstallBanner.tsx';
import { ConnectionStatus } from './components/ConnectionStatus.tsx';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
return isAuthenticated ? <>{children}</> : <Navigate to="/login" replace />;
}
function App() {
const fetchMe = useAuthStore((state) => state.fetchMe);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const [init, setInit] = useState(false);
useEffect(() => {
fetchMe().finally(() => setInit(true));
}, [fetchMe]);
// Connect WebSocket for real-time messages once authenticated
useEffect(() => {
if (isAuthenticated) {
useWebSocketStore.getState().connect();
}
}, [isAuthenticated]);
if (!init) {
return <div className="h-screen w-screen bg-gb-bg flex items-center justify-center text-gb-fg-s font-mono text-xs">Loading...</div>;
}
return (
<BrowserRouter>
<ConnectionStatus />
<InstallBanner />
<Routes>
<Route path="/login" element={<LoginForm />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/reset-password" element={<ResetPasswordPage />} />
<Route
path="/settings"
element={
<ProtectedRoute>
<div className="h-full w-full flex flex-col bg-gb-bg">
<header className="flex items-center gap-4 px-4 py-2 border-b border-gb-bg-t bg-gb-bg-s">
<Link to="/" className="text-gb-fg-f hover:text-gb-aqua font-mono text-sm">
[BACK]
</Link>
<span className="text-gb-orange font-mono text-sm">SETTINGS</span>
</header>
<div className="flex-1 overflow-hidden">
<UserSettings />
</div>
</div>
</ProtectedRoute>
}
/>
<Route
path="/bots"
element={
<ProtectedRoute>
<div className="h-full w-full flex flex-col bg-gb-bg">
<div className="flex-1 overflow-hidden">
<BotStore />
</div>
</div>
</ProtectedRoute>
}
/>
<Route
path="/bots/manage"
element={
<ProtectedRoute>
<div className="h-full w-full flex flex-col bg-gb-bg">
<header className="flex items-center gap-4 px-4 py-2 border-b border-gb-bg-t bg-gb-bg-s">
<Link to="/bots" className="text-gb-fg-f hover:text-gb-aqua font-mono text-sm">
[BACK]
</Link>
<span className="text-gb-orange font-mono text-sm">BOT MANAGER</span>
</header>
<div className="flex-1 overflow-hidden">
<BotManager />
</div>
</div>
</ProtectedRoute>
}
/>
<Route
path="/bots/:id/commands"
element={
<ProtectedRoute>
<div className="h-full w-full flex flex-col bg-gb-bg">
<header className="flex items-center gap-4 px-4 py-2 border-b border-gb-bg-t bg-gb-bg-s">
<Link to="/bots/manage" className="text-gb-fg-f hover:text-gb-aqua font-mono text-sm">
[BACK]
</Link>
<span className="text-gb-orange font-mono text-sm">SLASH COMMANDS</span>
</header>
<div className="flex-1 overflow-hidden">
<CommandManager />
</div>
</div>
</ProtectedRoute>
}
/>
<Route
path="/servers/:serverId/roles"
element={
<ProtectedRoute>
<div className="h-full w-full flex flex-col bg-gb-bg">
<header className="flex items-center gap-4 px-4 py-2 border-b border-gb-bg-t bg-gb-bg-s">
<Link to="/" className="text-gb-fg-f hover:text-gb-aqua font-mono text-sm">
[BACK]
</Link>
<span className="text-gb-orange font-mono text-sm">ROLE MANAGER</span>
</header>
<div className="flex-1 overflow-hidden">
<RoleManager />
</div>
</div>
</ProtectedRoute>
}
/>
<Route
path="/invites/:code"
element={
<ProtectedRoute>
<JoinServer />
</ProtectedRoute>
}
/>
<Route
path="/"
element={
<ProtectedRoute>
<Layout />
</ProtectedRoute>
}
>
<Route index element={<ChatArea />} />
<Route path="channels/:channelId" element={<ChatArea />} />
<Route path="dm/:conversationId" element={<DMChat />} />
<Route path="dm" element={<DMChat />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;