Files
dumpsterChat/web/src/App.tsx
T
hobokenchicken b3b5ff495d fix: DM message ordering, consolidate input toolbar, add rich text/WYSIWYG, file upload with drag-drop
- Fix DM backend ListMessages to use DESC + reverse (match channel handler)
- Remove spurious .reverse() from frontend message/conversation stores
- Create shared MessageInput component with Slack-style single toolbar row
- Add file upload via + button with progress bar and drag-and-drop
- Add markdown/rich text toggle with full WYSIWYG block formatting
  (lists, blockquotes, links, headings, code blocks)
- Add frontend+backend security for file uploads (extension + content-type guards)
2026-07-06 17:33:20 +00:00

151 lines
5.6 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 { 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">
<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">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" 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;