diff --git a/web/src/components/VoiceChannel.tsx b/web/src/components/VoiceChannel.tsx
index 5b27886..37afa34 100644
--- a/web/src/components/VoiceChannel.tsx
+++ b/web/src/components/VoiceChannel.tsx
@@ -1,4 +1,8 @@
import { useVoiceStore } from '../stores/voice.ts';
+import { useAuthStore } from '../stores/auth.ts';
+import { usePermissions } from '../lib/usePermissions.ts';
+import { useChannelStore } from '../stores/channel.ts';
+import { api } from '../lib/api.ts';
interface VoiceChannelProps {
channelId: string;
@@ -10,6 +14,29 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
const isConnected = useVoiceStore((state) => state.isConnected);
const joinVoice = useVoiceStore((state) => state.joinVoice);
const participants = useVoiceStore((state) => state.participants);
+
+ const currentUserId = useAuthStore((state) => state.user?.id);
+ const activeChannelId = useChannelStore((s) => s.activeChannelId);
+ const channelsByServer = useChannelStore((s) => s.channelsByServer);
+
+ const serverId = activeChannelId
+ ? Object.entries(channelsByServer).find(([_, channels]) => channels.some(c => c.id === activeChannelId))?.[0]
+ : null;
+
+ const { canMute } = usePermissions(serverId || null);
+
+ const handleMute = async (identity: string) => {
+ if (!currentRoom) return;
+ try {
+ await api.post(`/voice/rooms/${currentRoom}/participants/${identity}/mute`);
+ } catch (err) {
+ console.error('Failed to mute participant:', err);
+ }
+ };
+
+ const whisperTo = (identity: string, username: string) => {
+ useVoiceStore.getState().whisperTo(identity, username);
+ };
const isActive = currentRoom === channelId;
@@ -50,19 +77,43 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
{isActive && (
- {participants.map((p) => (
-
-
- {p.isMuted ? '○' : '●'}
-
-
- {p.username}
-
-
- ))}
+ {participants.map((p) => {
+ const isMe = p.identity === currentUserId;
+ return (
+
+
+ {p.isMuted ? '○' : '●'}
+
+
+ {p.username}
+
+
+ {!isMe && (
+
+ {canMute && !p.isMuted && (
+
+ )}
+
+
+ )}
+
+ );
+ })}
)}
diff --git a/web/src/components/VoicePanel.tsx b/web/src/components/VoicePanel.tsx
index 81791e3..c8857ce 100644
--- a/web/src/components/VoicePanel.tsx
+++ b/web/src/components/VoicePanel.tsx
@@ -1,42 +1,15 @@
import { useVoiceStore } from '../stores/voice.ts';
-import { useAuthStore } from '../stores/auth.ts';
-import { useServerStore } from '../stores/server.ts';
-import { api } from '../lib/api.ts';
import { VoiceControls } from './VoiceControls.tsx';
import { VideoGrid } from './VideoGrid.tsx';
import { AudioRenderers } from './AudioRenderers.tsx';
-import { usePermissions } from '../lib/usePermissions.ts';
-import { useChannelStore } from '../stores/channel.ts';
export function VoicePanel() {
const isConnected = useVoiceStore((state) => state.isConnected);
const currentRoom = useVoiceStore((state) => state.currentRoom);
const currentRoomName = useVoiceStore((state) => state.currentRoomName);
- const participants = useVoiceStore((state) => state.participants);
const error = useVoiceStore((state) => state.error);
const whispers = useVoiceStore((state) => state.whispers);
- const whisperTo = useVoiceStore((state) => state.whisperTo);
const dismissWhisper = useVoiceStore((state) => state.dismissWhisper);
- const currentUserId = useAuthStore((state) => state.user?.id);
-
- const activeChannelId = useChannelStore((s) => s.activeChannelId);
- const channelsByServer = useChannelStore((s) => s.channelsByServer);
- const activeServerId = useServerStore((s) => s.activeServerId);
-
- const serverId = activeChannelId
- ? Object.entries(channelsByServer).find(([_, channels]) => channels.some(c => c.id === activeChannelId))?.[0]
- : null;
-
- const { canMute } = usePermissions(serverId || activeServerId);
-
- const handleMute = async (identity: string) => {
- if (!currentRoom) return;
- try {
- await api.post(`/voice/rooms/${currentRoom}/participants/${identity}/mute`);
- } catch (err) {
- console.error('Failed to mute participant', err);
- }
- };
if (!currentRoom) return null;
@@ -93,68 +66,6 @@ export function VoicePanel() {
)}
- {/* Participants */}
-
-
- ── participants ──
-
- {participants.length === 0 && (
-
[empty channel]
- )}
- {participants.map((p) => {
- const isMe = p.identity === currentUserId;
- return (
-
-
- {p.isMuted ? '○' : '●'}
-
-
- {p.username}
-
- {p.isMuted && (
-
[muted]
- )}
- {p.isSpeaking && (
-
[speaking]
- )}
- {!isMe && (
-
- {canMute && !p.isMuted && (
-
- )}
-
-
- )}
-
- );
- })}
-
-
{/* Video grid */}