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 })}
- />
-
-
+
+
+ )}
);
}