From 6e1757410a71846f985560fb7c16ed157e6e5d0e Mon Sep 17 00:00:00 2001 From: asepharyana Date: Thu, 2 Jul 2026 03:13:54 +0700 Subject: [PATCH] fix: split LiveShell into Astro layout + individual islands - Rewrite live.astro to use Astro Sidebar/Header/Card components - VoiceControls becomes self-contained (fetches guilds/channels internally) - Remove LiveShell.tsx (no longer needed) - Each React island uses appropriate client:load/idle directive --- .gitmodules | 13 -- services/frontend/src/islands/LiveShell.tsx | 142 ------------------ .../frontend/src/islands/VoiceControls.tsx | 44 ++++-- services/frontend/src/pages/live.astro | 106 ++++++++++++- 4 files changed, 131 insertions(+), 174 deletions(-) delete mode 100644 .gitmodules delete mode 100644 services/frontend/src/islands/LiveShell.tsx diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index dcf5fed..0000000 --- a/.gitmodules +++ /dev/null @@ -1,13 +0,0 @@ -[submodule "vendor/discord.js-selfbot-v13"] - path = vendor/discord.js-selfbot-v13 - url = https://github.com/MythEclipse/discord.js-selfbot-v13.git - -[submodule "vendor/discord-video-stream"] - path = vendor/discord-video-stream - url = https://github.com/MythEclipse/discord-video-stream.git -[submodule "vendor/drizzle-orm"] - path = vendor/drizzle-orm - url = https://github.com/MythEclipse/drizzle-orm.git -[submodule "vendor/better-sqlite3"] - path = vendor/better-sqlite3 - url = https://github.com/MythEclipse/better-sqlite3.git diff --git a/services/frontend/src/islands/LiveShell.tsx b/services/frontend/src/islands/LiveShell.tsx deleted file mode 100644 index 4c8f7ad..0000000 --- a/services/frontend/src/islands/LiveShell.tsx +++ /dev/null @@ -1,142 +0,0 @@ -// ─── LiveShell.tsx — Page shell for /live ──────────────────────────────────── -// Composes Sidebar, Header, ParticleBackground, and the four live islands -// (VoiceControls, ActiveSpeakers, AudioVisualizer, NowPlaying) into a full-page -// responsive layout. -// ───────────────────────────────────────────────────────────────────────────── - -import { useCallback, useEffect, useState } from "react"; -import { - getGuilds, - getVoiceChannels, - getVoiceStatus, -} from "../shared/api/client.js"; -import { Header } from "../shared/components/Header.js"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "../shared/components/index.js"; -import { ParticleBackground } from "../shared/components/particles/ParticleBackground.js"; -import { Sidebar } from "../shared/components/Sidebar.js"; -import { useTheme } from "../shared/hooks/useTheme.js"; -import type { Channel, Guild } from "../shared/types/guild.js"; -import type { VoiceStatus } from "../shared/types/voice.js"; -import { useVoiceStore } from "../stores/voice-store.js"; -import ActiveSpeakers from "./ActiveSpeakers.js"; -import AudioVisualizer from "./AudioVisualizer.js"; -import NowPlaying from "./NowPlaying.js"; -import VoiceControls from "./VoiceControls.js"; - -const emptyVoiceStatus: VoiceStatus = { - connected: false, - activeGuildId: null, - activeChannelId: null, - activeChannelName: null, - connections: [], -}; - -export default function LiveShell() { - const { mode, isDark, toggle: toggleTheme } = useTheme(); - const [voiceStatus, setVoiceStatus] = useState(emptyVoiceStatus); - const [guilds, setGuilds] = useState([]); - const [voiceChannels, setVoiceChannels] = useState([]); - - // Watch store for guild changes so we can fetch voice channels - const guildId = useVoiceStore((state) => state.guildId); - - // Fetch initial data on mount - useEffect(() => { - getVoiceStatus() - .then(setVoiceStatus) - .catch(() => { - /* offline — leave default */ - }); - getGuilds() - .then(setGuilds) - .catch(() => { - /* offline — leave default */ - }); - }, []); - - // Refetch voice channels when guild selection changes - useEffect(() => { - if (guildId) { - getVoiceChannels(guildId) - .then(setVoiceChannels) - .catch(() => setVoiceChannels([])); - } else { - setVoiceChannels([]); - } - }, [guildId]); - - const handleTabChange = useCallback((tab: string) => { - if (tab !== "live") { - window.location.href = "/"; - } - }, []); - - return ( -
- {/* Background layers */} - - - ); -} diff --git a/services/frontend/src/islands/VoiceControls.tsx b/services/frontend/src/islands/VoiceControls.tsx index daa86aa..7d44af3 100644 --- a/services/frontend/src/islands/VoiceControls.tsx +++ b/services/frontend/src/islands/VoiceControls.tsx @@ -1,29 +1,47 @@ // ─── VoiceControls.tsx — Voice bridge island ──────────────────────────────── -// Self-contained: reads/writes useVoiceStore, calls API for connect/disconnect. +// Self-contained: fetches guilds/channels internally, reads/writes useVoiceStore, +// calls API for connect/disconnect. // ───────────────────────────────────────────────────────────────────────────── import { Radio } from "lucide-react"; -import { useCallback, useState } from "react"; -import { connectVoice, disconnectVoice } from "../shared/api/client.js"; +import { useCallback, useEffect, useState } from "react"; +import { + connectVoice, + disconnectVoice, + getGuilds, + getVoiceChannels, +} from "../shared/api/client.js"; import { Button, Select } from "../shared/components/index.js"; import type { Channel, Guild } from "../shared/types/guild.js"; import { useVoiceStore } from "../stores/voice-store.js"; -interface VoiceControlsProps { - guilds: Guild[]; - voiceChannels: Channel[]; -} - -export default function VoiceControls({ - guilds, - voiceChannels, -}: VoiceControlsProps) { +export default function VoiceControls() { const connected = useVoiceStore((state) => state.connected); const guildId = useVoiceStore((state) => state.guildId); const channelId = useVoiceStore((state) => state.channelId); const setGuildChannel = useVoiceStore((state) => state.setGuildChannel); const setConnected = useVoiceStore((state) => state.setConnected); const [loading, setLoading] = useState(false); + const [guilds, setGuilds] = useState([]); + const [voiceChannels, setVoiceChannels] = useState([]); + + // Fetch guilds on mount + useEffect(() => { + getGuilds() + .then(setGuilds) + .catch(() => { /* offline */ }); + }, []); + + // Fetch voice channels when guild selection changes + useEffect(() => { + if (guildId) { + getVoiceChannels(guildId) + .then(setVoiceChannels) + .catch(() => setVoiceChannels([])); + } else { + setVoiceChannels([]); + } + }, [guildId]); const handleConnect = useCallback(async () => { if (!guildId || !channelId) return; @@ -51,7 +69,7 @@ export default function VoiceControls({ }, [setConnected]); return ( -
+

Voice Bridge

diff --git a/services/frontend/src/pages/live.astro b/services/frontend/src/pages/live.astro index ed35ea6..30ce86d 100644 --- a/services/frontend/src/pages/live.astro +++ b/services/frontend/src/pages/live.astro @@ -1,14 +1,108 @@ --- // ─── live.astro — Voice & Media live monitoring page ──────────────────────── -// Full-page layout with Sidebar navigation, Header, and the LiveShell React -// island that composes all live components (VoiceControls, ActiveSpeakers, -// AudioVisualizer, NowPlaying). -// Particles and MascotChat are embedded inside the LiveShell island. +// Layout shell (Sidebar, Header) rendered as zero-JS Astro components. +// Only the truly interactive parts are React islands with client directives. // ──────────────────────────────────────────────────────────────────────────── import BaseLayout from "../layouts/BaseLayout.astro"; -import LiveShell from "../islands/LiveShell"; +import Sidebar from "../components/sidebar/Sidebar.astro"; +import Header from "../components/header/Header.astro"; +import Card from "../components/ui/Card.astro"; + +// React islands — interactive only +import ThemeToggle from "../islands/ThemeToggle"; +import VoiceControls from "../islands/VoiceControls"; +import ActiveSpeakers from "../islands/ActiveSpeakers"; +import AudioVisualizer from "../islands/AudioVisualizer"; +import NowPlaying from "../islands/NowPlaying"; +import Particles from "../islands/Particles"; +import MascotChat from "../islands/MascotChat"; --- - +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ + +

Voice Connection

+ +
+ + + +

Live Audio

+ +
+ + + +

Now Playing

+ +
+
+ + + +
+
+
+
+ + + +
+ +