2026-07-26 15:34:08 +07:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
|
|
2026-07-27 21:54:31 +07:00
|
|
|
import { ActiveSpeakersPanel } from "@/components/voice/active-speakers-panel";
|
|
|
|
|
import { MicrophoneCard } from "@/components/voice/microphone-card";
|
|
|
|
|
import { VoiceConnectionCard } from "@/components/voice/voice-connection-card";
|
2026-07-26 16:14:32 +07:00
|
|
|
import {
|
|
|
|
|
useGuilds,
|
2026-07-26 16:55:20 +07:00
|
|
|
useMicTransmit,
|
2026-07-26 16:14:32 +07:00
|
|
|
useSpeakers,
|
|
|
|
|
useVoiceChannels,
|
2026-07-26 16:55:20 +07:00
|
|
|
useVoiceConnect,
|
|
|
|
|
useVoiceDisconnect,
|
2026-07-26 16:14:32 +07:00
|
|
|
useVoiceStatus,
|
|
|
|
|
} from "@/hooks";
|
2026-07-26 15:34:08 +07:00
|
|
|
import { useWebSocket } from "@/lib/ws/context";
|
|
|
|
|
|
|
|
|
|
export default function VoicePage() {
|
|
|
|
|
const ws = useWebSocket();
|
2026-07-26 16:55:20 +07:00
|
|
|
const { data: voiceStatus } = useVoiceStatus();
|
|
|
|
|
const { data: guilds = [] } = useGuilds();
|
2026-07-27 21:54:31 +07:00
|
|
|
const [selectedGuild, setSelectedGuild] = useState("");
|
|
|
|
|
const { data: voiceChannels = [] } = useVoiceChannels(selectedGuild);
|
2026-07-26 16:14:32 +07:00
|
|
|
const { speakers, subscribe } = useSpeakers();
|
2026-07-26 16:55:20 +07:00
|
|
|
const connectMut = useVoiceConnect();
|
|
|
|
|
const disconnectMut = useVoiceDisconnect();
|
|
|
|
|
const micMut = useMicTransmit();
|
2026-07-26 15:34:08 +07:00
|
|
|
const [selectedChannel, setSelectedChannel] = useState("");
|
|
|
|
|
const [micActive, setMicActive] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-07-26 16:14:32 +07:00
|
|
|
const unsub = subscribe(ws);
|
|
|
|
|
return () => unsub();
|
|
|
|
|
}, [ws, subscribe]);
|
2026-07-26 15:34:08 +07:00
|
|
|
|
2026-07-27 21:54:31 +07:00
|
|
|
const handleGuildChange = useCallback((guildId: string | null) => {
|
|
|
|
|
if (!guildId) {
|
|
|
|
|
setSelectedGuild("");
|
2026-07-26 16:14:32 +07:00
|
|
|
setSelectedChannel("");
|
2026-07-27 21:54:31 +07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setSelectedGuild(guildId);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleMicToggle = useCallback(
|
|
|
|
|
async (checked: boolean) => {
|
|
|
|
|
setMicActive(checked);
|
|
|
|
|
try {
|
|
|
|
|
await micMut.mutateAsync(checked);
|
|
|
|
|
} catch {
|
|
|
|
|
setMicActive(!checked);
|
|
|
|
|
}
|
2026-07-26 16:14:32 +07:00
|
|
|
},
|
2026-07-27 21:54:31 +07:00
|
|
|
[micMut],
|
2026-07-26 16:14:32 +07:00
|
|
|
);
|
2026-07-26 15:34:08 +07:00
|
|
|
|
|
|
|
|
const activeSpeakers = speakers.filter((s) => s.speaking);
|
2026-07-27 21:54:31 +07:00
|
|
|
const connected = voiceStatus?.connected ?? false;
|
2026-07-26 15:34:08 +07:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-5 animate-fade-in-up">
|
2026-07-27 21:54:31 +07:00
|
|
|
<VoiceConnectionCard
|
|
|
|
|
selectedGuild={selectedGuild}
|
|
|
|
|
onGuildChange={handleGuildChange}
|
|
|
|
|
selectedChannel={selectedChannel}
|
|
|
|
|
onChannelChange={(v) => setSelectedChannel(v)}
|
|
|
|
|
guilds={guilds}
|
|
|
|
|
voiceChannels={voiceChannels}
|
|
|
|
|
connected={connected}
|
|
|
|
|
activeChannelName={voiceStatus?.activeChannelName}
|
|
|
|
|
connectMut={connectMut}
|
|
|
|
|
disconnectMut={disconnectMut}
|
|
|
|
|
/>
|
|
|
|
|
<ActiveSpeakersPanel activeSpeakers={activeSpeakers} />
|
|
|
|
|
<MicrophoneCard
|
|
|
|
|
connected={connected}
|
|
|
|
|
micActive={micActive}
|
|
|
|
|
onMicToggle={handleMicToggle}
|
|
|
|
|
/>
|
2026-07-26 15:34:08 +07:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|