Files
ds-chat/frontend/src/App.tsx
T
ksmithandClaude Sonnet 5 2e84ca42b7 Add custom emoji support (#18)
Site-wide, any user can upload -- usable both as reactions and inline
in message text via :shortcode:, alongside the existing built-in
Unicode picker. A :shortcode: reference is stored/sent as literal
text (same as the built-in shortcode convention) and resolved to an
image at render time, so it degrades to plain text if the emoji is
later deleted.

Backend: new custom_emoji table (shortcode unique, sized to fit
MessageReaction.emoji's existing column alongside its colons), upload/
list/delete endpoints (delete restricted to uploader or site admin).

Frontend: a CustomEmojiProvider context feeds a new "Custom" category
in the emoji picker (inline upload + hover-to-remove), extends the
composer's shortcode autocomplete, and a shared EmojiGlyph resolver
renders custom emoji wherever a value can appear -- message text,
reaction pills, and the picker itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 20:47:49 -06:00

89 lines
2.7 KiB
TypeScript

import { Navigate, Route, Routes } from 'react-router-dom'
import { AuthProvider, useAuth } from './context/AuthContext'
import { ChatSocketProvider } from './context/ChatSocketContext'
import { CustomEmojiProvider } from './context/CustomEmojiContext'
import { AdminRoute } from './components/AdminRoute'
import { DesktopNotificationBridge } from './components/DesktopNotificationBridge'
import { ProtectedRoute } from './components/ProtectedRoute'
import { UpdateBanner } from './components/UpdateBanner'
import { LoginPage } from './pages/LoginPage'
import { SignupPage } from './pages/SignupPage'
import { ForgotPasswordPage } from './pages/ForgotPasswordPage'
import { ResetPasswordPage } from './pages/ResetPasswordPage'
import { ChatShellPage } from './pages/ChatShellPage'
import { AdminPage } from './pages/AdminPage'
import { HelpPage } from './pages/HelpPage'
function AppRoutes() {
const routes = (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/reset-password" element={<ResetPasswordPage />} />
<Route
path="/rooms"
element={
<ProtectedRoute>
<ChatShellPage />
</ProtectedRoute>
}
/>
<Route
path="/rooms/:roomId"
element={
<ProtectedRoute>
<ChatShellPage />
</ProtectedRoute>
}
/>
<Route
path="/admin"
element={
<AdminRoute>
<AdminPage />
</AdminRoute>
}
/>
<Route
path="/help"
element={
<ProtectedRoute>
<HelpPage />
</ProtectedRoute>
}
/>
<Route path="*" element={<Navigate to="/rooms" replace />} />
</Routes>
)
// Only while actually logged in -- and keyed by user id so switching
// which account is logged in (same tab) tears down and re-establishes a
// fresh connection rather than an old one lingering under a new
// identity. Wraps every authenticated route (not just ChatShellPage),
// since the connection backs the presence indicator and cross-page
// signals (e.g. "added to a room") that matter regardless of which page
// is currently open.
const { user } = useAuth()
if (!user) return routes
return (
<ChatSocketProvider key={user.id}>
<CustomEmojiProvider>
<DesktopNotificationBridge />
{routes}
</CustomEmojiProvider>
</ChatSocketProvider>
)
}
function App() {
return (
<AuthProvider>
<UpdateBanner />
<AppRoutes />
</AuthProvider>
)
}
export default App