diff --git a/packages/web/src/api/client.ts b/packages/web/src/api/client.ts index 3f77e388..dd585dfe 100644 --- a/packages/web/src/api/client.ts +++ b/packages/web/src/api/client.ts @@ -23,6 +23,7 @@ import type { UpdateMemberRequest, LiveKitTokenResponse, CreateDmRequest, + CreateGroupDmRequest, CreateDmMessageRequest, Friend, FriendRequest, @@ -135,6 +136,7 @@ export class BackspaceApiClient { readonly dm: { list: () => Promise; create: (data: CreateDmRequest) => Promise; + createGroup: (data: CreateGroupDmRequest) => Promise; close: (id: string) => Promise<{ success: boolean }>; messages: (id: string, before?: string, limit?: number) => Promise; messagesAround: (id: string, messageId: string) => Promise; @@ -480,6 +482,7 @@ export class BackspaceApiClient { this.dm = { list: () => request('GET', '/dm'), create: (data: CreateDmRequest) => request('POST', '/dm', data), + createGroup: (data: CreateGroupDmRequest) => request('POST', '/dm/group', data), close: (id: string) => request<{ success: boolean }>('DELETE', `/dm/${id}`), messages: (id: string, before?: string, limit = 50) => { const params = new URLSearchParams(); diff --git a/packages/web/src/components/modals/AddDmMemberModal.tsx b/packages/web/src/components/modals/AddDmMemberModal.tsx index 18441844..0b1eae17 100644 --- a/packages/web/src/components/modals/AddDmMemberModal.tsx +++ b/packages/web/src/components/modals/AddDmMemberModal.tsx @@ -1,8 +1,10 @@ import React, { useState, useRef, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Modal } from '../ui/Modal'; import { Avatar } from '../ui/Avatar'; import { useUIStore } from '../../stores/uiStore'; -import { useSpaceStore, getApiForOrigin } from '../../stores/spaceStore'; +import { useSpaceStore } from '../../stores/spaceStore'; +import { useAuthStore } from '../../stores/authStore'; import { api } from '../../api/client'; import type { User } from '@backspace/shared'; @@ -16,7 +18,8 @@ export function AddDmMemberModal() { const modalData = useUIStore((s) => s.modalData); const closeModal = useUIStore((s) => s.closeModal); const dmChannels = useSpaceStore((s) => s.dmChannels); - const channelOriginMap = useSpaceStore((s) => s.channelOriginMap); + const navigate = useNavigate(); + const myUserId = useAuthStore((s) => s.user?.id); const inputRef = useRef(null); const searchTimer = useRef>(); @@ -64,14 +67,24 @@ export function AddDmMemberModal() { }; const handleSelectUser = async (user: User) => { - if (!dmChannelId || isAdding) return; + if (!dmChannelId || !dmChannel || isAdding) return; setError(''); setIsAdding(true); try { - const origin = channelOriginMap.get(dmChannelId) || ''; - const targetApi = getApiForOrigin(origin); - await targetApi.dm.addMember(dmChannelId, { userId: user.id }); - closeModal(); + if (!dmChannel.ownerId) { + // 1-on-1 DM → create a new group DM with all 3 users + const otherMember = dmChannel.members.find(m => m.id !== myUserId); + if (!otherMember) return; + const newChannel = await api.dm.createGroup({ userIds: [otherMember.id, user.id] }); + closeModal(); + navigate(`/channels/@me/${newChannel.id}`); + } else { + // Group DM → add to existing group + // Always call the home instance — S2S federation handles relay. + // Only the owner can add members, and the owner is always on the home instance. + await api.dm.addMember(dmChannelId, { userId: user.id }); + closeModal(); + } } catch (err) { setError((err as Error).message || 'Failed to add member'); } finally {