feat: DM infrastructure overhaul + volume sliders + LiveKit dynamic URL

- Add DM edit/delete endpoints (REST + WebSocket)
- Add DM typing indicators with real-time broadcast
- Fix volume sliders to control LiveKit mic/speaker
- Fix Send Message button on user profile popout
- Add New DM modal with user search
- Fix message pagination (SQL cursor instead of in-memory)
- Guard optional attachments on DM messages
- Dynamic LiveKit URL from request Host header
- DM sidebar auto-sorts by most recent message
This commit is contained in:
Jannis Braun
2026-02-18 17:29:39 +01:00
parent 4f65ea9c86
commit 7168149d98
31 changed files with 1189 additions and 136 deletions
@@ -2,11 +2,13 @@ import React from 'react';
import { useServerStore } from '../../stores/serverStore';
import { useChatStore } from '../../stores/chatStore';
import { useUIStore } from '../../stores/uiStore';
import { useAuthStore } from '../../stores/authStore';
import { MessageList } from '../chat/MessageList';
import { MessageInput } from '../chat/MessageInput';
import { TypingIndicator } from '../chat/TypingIndicator';
import { VoiceGrid } from '../voice/VoiceGrid';
import { FriendsPage } from '../chat/FriendsPage';
import { Avatar } from '../ui/Avatar';
import { useVoiceStore } from '../../stores/voiceStore';
import { wsSend } from '../../hooks/useWebSocket';
@@ -24,22 +26,36 @@ export function MainContent() {
const isVoiceChannel = channel?.type === 'voice' || channel?.type === 'video';
// DM view or no server selected
const dmChannels = useServerStore((s) => s.dmChannels);
const authUser = useAuthStore((s) => s.user);
if (showDms || !currentServerId) {
if (!currentChannelId) {
return <FriendsPage />;
}
const dmChannel = dmChannels.find(dm => dm.id === currentChannelId);
const otherUser = dmChannel?.members.find(m => m.id !== authUser?.id);
const dmName = otherUser?.displayName ?? otherUser?.username ?? 'Direct Message';
const dmStatus = otherUser?.status as any;
return (
<div className="flex-1 flex flex-col bg-discord-bg-primary min-w-0 relative">
<div className="h-12 px-4 flex items-center shadow-header flex-shrink-0 z-10">
<div className="flex items-center gap-2 min-w-0">
<span className="text-discord-text-muted font-bold text-lg">@</span>
<span className="font-bold text-discord-text-primary truncate">Direct Message</span>
{otherUser && (
<Avatar src={otherUser.avatar} name={dmName} size={24} status={dmStatus} />
)}
<span className="font-bold text-discord-text-primary truncate">{dmName}</span>
{otherUser?.status && otherUser.status !== 'offline' && (
<span className="text-xs text-discord-text-muted capitalize">{otherUser.status}</span>
)}
</div>
</div>
<MessageList channelId={currentChannelId} />
<TypingIndicator channelId={currentChannelId} />
<MessageInput channelId={currentChannelId} channelName="Direct Message" />
<MessageInput channelId={currentChannelId} channelName={`@${dmName}`} />
</div>
);
}