b3b5ff495d
- 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)
151 lines
5.6 KiB
TypeScript
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;
|