From d65dc11c73a884c02439139e5339822f80af6115 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 14 Aug 2026 11:28:08 +0700 Subject: [PATCH] fix(frontend): restore voice guild/channel picker + media URL queue input - voice/view: add Select for guild + voice channels + Connect/Disconnect bar - media/view: restore URL queue input + Screen toggle + Queue button (tsc clean, next build green) --- .../src/app/(dashboard)/media/view.tsx | 85 +++++--- .../src/app/(dashboard)/voice/view.tsx | 206 ++++++++++-------- 2 files changed, 164 insertions(+), 127 deletions(-) diff --git a/services/frontend/src/app/(dashboard)/media/view.tsx b/services/frontend/src/app/(dashboard)/media/view.tsx index e1fc536..c95f090 100644 --- a/services/frontend/src/app/(dashboard)/media/view.tsx +++ b/services/frontend/src/app/(dashboard)/media/view.tsx @@ -1,30 +1,23 @@ "use client"; -import { - Pause, - Play, - Repeat2, - SkipForward, - Square, - Volume2, -} from "lucide-react"; +import { Pause, Play, Repeat2, SkipForward, Square, Volume2 } from "lucide-react"; import { motion } from "motion/react"; -import { useEffect } from "react"; +import { useState } from "react"; import { Waveform } from "@/components/charts/waveform"; import { StaggerGroup, StaggerItem } from "@/components/motion/stagger"; -import { Avatar } from "@/components/primitives/avatar"; import { Badge } from "@/components/primitives/badge"; import { Button } from "@/components/primitives/button"; +import { Input } from "@/components/primitives/input"; import { Progress } from "@/components/primitives/progress"; import { useMediaLoop, + useMediaQueue, useMediaSkip, useMediaState, useMediaStop, useMediaWsSync, } from "@/hooks"; import type { MediaState } from "@/lib/types"; -import { cn } from "@/lib/utils"; import { useWebSocket } from "@/lib/ws/context"; export default function MediaView({ @@ -33,7 +26,8 @@ export default function MediaView({ initialStatus?: MediaState; }) { const ws = useWebSocket(); - const { data: state, mutate } = useMediaState(initialStatus); + const { data: state } = useMediaState(initialStatus); + const queueMut = useMediaQueue(); const skip = useMediaSkip(); const stop = useMediaStop(); const loopMut = useMediaLoop(); @@ -45,23 +39,55 @@ export default function MediaView({ const loop = state?.loop ?? false; const duration = current?.durationMs ?? 0; - const [seed] = useWaveformSeed(); + const [queueUrl, setQueueUrl] = useState(""); + const [screenMode, setScreenMode] = useState(false); + + const handleQueue = () => { + if (!queueUrl.trim()) return; + queueMut.mutate({ url: queueUrl.trim(), mode: screenMode ? "screen" : "music" }); + setQueueUrl(""); + }; return (
+ {/* URL queue input */} +
+ setQueueUrl(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleQueue()} + className="flex-1 h-9" + /> + + +
+ {/* Turntable hero */}
{current && ( {current.title} @@ -97,11 +123,7 @@ export default function MediaView({ variant="primary" onClick={() => loopMut.mutate(!loop)} > - {playing ? ( - - ) : ( - - )} + {playing ? : } @@ -131,9 +153,9 @@ export default function MediaView({ {loop ? "loop" : "queue"}
- {queue.map((item, i) => ( + {queue.map((item) => ( ("stage"); + const [selectedGuild, setSelectedGuild] = useState(""); + const [selectedChannel, setSelectedChannel] = useState(""); const initialSpeakers = useMemo( () => initialStatus?.activeSpeakers ?? [], [initialStatus], ); const { speakers, subscribe } = useSpeakers(initialSpeakers); + const { data: guilds = [] } = useGuilds(); + const { data: voiceChannels = [] } = useVoiceChannels(selectedGuild); const connect = useVoiceConnect(); const disconnect = useVoiceDisconnect(); const listen = useVoiceListen(ws); @@ -57,45 +47,101 @@ export default function VoiceView({ }, [subscribe, ws]); const active = speakers.filter((s) => s.speaking); + const connected = initialStatus?.connected ?? false; + + const handleGuildChange = (e: React.ChangeEvent) => { + const g = e.target.value; + setSelectedGuild(g); + setSelectedChannel(""); + }; return (
- {/* Connection bar */} -
- - {initialStatus?.connected ? "Connected" : "Disconnected"} - - {initialStatus?.activeChannelName && ( - - #{initialStatus.activeChannelName} - - )} - {initialStatus?.connected ? ( - + {/* Connection bar with guild + voice channel pickers */} +
+
+ + {connected ? "Connected" : "Disconnected"} + + {connected && initialStatus?.activeChannelName && ( + + + {initialStatus.activeChannelName} + + )} +
+ + {!connected ? ( +
+ + + + + +
) : ( )}
{/* Stage hero */} -
+
- {/* Tabs */} -
- {(["stage", "activity"] as const).map((t) => ( - - ))} + {/* Activity timeline */} +
+

+ Live session timeline +

+ ({ + id: s.userId, + label: s.username, + value: s.speaking ? 3 : 1, + tone: s.speaking ? "signal" : "neutral", + }))} + />
- {tab === "stage" && } - {tab === "activity" && ( -
-

Live session timeline

- ({ - id: s.userId, - label: s.username, - value: s.speaking ? 3 : 1, - tone: s.speaking ? "signal" : "neutral", - }))} - /> -
- )} +
); }