feat: add group DM support (3-10 members)

Unlock group DMs by removing the 2-member assumption across the stack.
No schema migration needed — dm_members junction table already supports
N members. Includes dedup bug fix, add/leave member endpoints, late-join
call support, group-aware sidebar rendering, and AddDmMemberModal.
This commit is contained in:
Jannis Braun
2026-02-23 22:39:06 +01:00
parent 653e59bfb2
commit 900eb67869
11 changed files with 475 additions and 27 deletions
+19 -1
View File
@@ -1,5 +1,5 @@
import { create } from 'zustand';
import type { Server, Channel, MemberWithUser, ServerWithChannelsAndMembers, Role, ServerFolder, DmChannel } from '@opencord/shared';
import type { Server, Channel, MemberWithUser, ServerWithChannelsAndMembers, Role, ServerFolder, DmChannel, User } from '@opencord/shared';
import { api } from '../api/client';
interface ServerState {
@@ -20,6 +20,8 @@ interface ServerState {
setDmChannels: (channels: DmChannel[]) => void;
addDmChannel: (channel: DmChannel) => void;
removeDmChannel: (id: string) => void;
addDmMember: (dmChannelId: string, user: User) => void;
removeDmMember: (dmChannelId: string, userId: string) => void;
closeDm: (id: string) => Promise<void>;
loadServers: () => Promise<void>;
loadServerDetail: (serverId: string) => Promise<void>;
@@ -66,6 +68,22 @@ export const useServerStore = create<ServerState>((set, get) => ({
dmChannels: state.dmChannels.filter(c => c.id !== id)
})),
addDmMember: (dmChannelId, user) => set((state) => ({
dmChannels: state.dmChannels.map(dm =>
dm.id === dmChannelId
? { ...dm, members: dm.members.some(m => m.id === user.id) ? dm.members : [...dm.members, user] }
: dm
),
})),
removeDmMember: (dmChannelId, userId) => set((state) => ({
dmChannels: state.dmChannels.map(dm =>
dm.id === dmChannelId
? { ...dm, members: dm.members.filter(m => m.id !== userId) }
: dm
),
})),
closeDm: async (id) => {
await api.dm.close(id);
set((state) => ({
+1
View File
@@ -11,6 +11,7 @@ type ModalType =
| 'serverSettings'
| 'imagePreview'
| 'newDm'
| 'addDmMember'
| null;
interface UIState {