diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9ad009c..4ac1b33 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,7 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Component, Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { DashboardLayout } from "./components/layout/DashboardLayout"; import { LivePanel } from "./components/live/LivePanel"; import { MessagesPanel } from "./components/messages/MessagesPanel"; -import { Tabs, TabsContent } from "./components/ui/tabs"; -import { AnalyticsPanel } from "./components/analytics"; import { AuthOverlay } from "./components/layout/AuthOverlay"; import { useDashboardSocket } from "./hooks/useDashboardSocket"; import { mergeMessages, useMessages } from "./hooks/useMessages"; @@ -14,6 +12,28 @@ import type { MessageRecord } from "./types/messages"; import type { DashboardTab } from "./types/ui"; import type { ActiveSpeaker } from "./types/voice"; +const AnalyticsPanel = lazy(() => import("./components/analytics").then((module) => ({ default: module.AnalyticsPanel }))); + +class AnalyticsErrorBoundary extends Component<{ children: React.ReactNode }, { hasError: boolean }> { + state = { hasError: false }; + + static getDerivedStateFromError() { + return { hasError: true }; + } + + override render() { + if (this.state.hasError) { + return ( +
+ Analytics failed to load. The rest of the dashboard is still available. +
+ ); + } + + return this.props.children; + } +} + const SAMPLE_RATE = 24000; const CHANNELS = 1; @@ -164,71 +184,80 @@ export default function App() { onTabChange={(tab) => patchUIState({ activeTab: tab })} >
- patchUIState({ activeTab: value as DashboardTab })}>
{tabs.map((tab) => ( - ))}
-
- patchUIState({ activeTab: value as DashboardTab })}> - - {!isAuthenticated ? ( - setIsAuthenticated(true)} /> - ) : ( - setIsAuthenticated(true)} /> + ) : ( + patchUIState({ selectedVoiceGuild: guildId, selectedVoiceChannel: "" })} + onChannelChange={(channelId) => patchUIState({ selectedVoiceChannel: channelId })} + onJoin={() => voice.joinVoice(selectedVoiceGuild, selectedVoiceChannel)} + onDisconnect={() => voice.leaveVoice()} + onListenToggle={toggleListening} + onStreamingToggle={toggleStreaming} + onQueueMusic={(source) => media.enqueue(source, "music")} + onStartScreen={(source) => media.enqueue(source, "screen")} + onSkip={media.skip} + onStop={media.stop} + onVolumeChange={media.setVolume} + /> + ) + ) : activeTab === "messages" ? ( + patchUIState({ selectedTextGuild: guildId, selectedTextChannel: "" })} + onChannelChange={(channelId) => patchUIState({ selectedTextChannel: channelId })} + onReanalyze={messages.reanalyze} + /> + ) : ( + + + Loading analytics... + + } + > + patchUIState({ selectedVoiceGuild: guildId, selectedVoiceChannel: "" })} - onChannelChange={(channelId) => patchUIState({ selectedVoiceChannel: channelId })} - onJoin={() => voice.joinVoice(selectedVoiceGuild, selectedVoiceChannel)} - onDisconnect={() => voice.leaveVoice()} - onListenToggle={toggleListening} - onStreamingToggle={toggleStreaming} - onQueueMusic={(source) => media.enqueue(source, "music")} - onStartScreen={(source) => media.enqueue(source, "screen")} - onSkip={media.skip} - onStop={media.stop} - onVolumeChange={media.setVolume} + channels={voice.textChannels} + selectedGuild={selectedTextGuild} + selectedChannel={selectedTextChannel} + onGuildChange={(guildId) => patchUIState({ selectedTextGuild: guildId, selectedTextChannel: "" })} + onChannelChange={(channelId) => patchUIState({ selectedTextChannel: channelId })} /> - )} - - - patchUIState({ selectedTextGuild: guildId, selectedTextChannel: "" })} - onChannelChange={(channelId) => patchUIState({ selectedTextChannel: channelId })} - onReanalyze={messages.reanalyze} - /> - - - patchUIState({ selectedTextGuild: guildId, selectedTextChannel: "" })} - onChannelChange={(channelId) => patchUIState({ selectedTextChannel: channelId })} - /> - - + + + )} ); }