feat(messenger): group settings UI — rename, member list, add/remove
- messenger-api: useGroupSettings(threadId) — members query + rename/add/ remove commands + isAdmin (from the member roles); live + mock - messenger: a settings (gear) button on group thread headers opens a GroupSettingsModal — editable name (admin), member list with role pills, admin-gated remove, and add-from-directory search - controls are admin-gated in the UI; IIOS/OPA re-enforces server-side Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -87,12 +87,26 @@ export interface ThreadData {
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
export interface UiMember { userId: string; displayName: string; role: string }
|
||||
export interface GroupSettingsData {
|
||||
loading: boolean; error: string | null;
|
||||
members: UiMember[];
|
||||
isAdmin: boolean;
|
||||
rename: (subject: string) => Promise<void>;
|
||||
addMember: (userId: string) => Promise<void>;
|
||||
removeMember: (userId: string) => Promise<void>;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
export function useMessengerData(): MessengerData {
|
||||
return SHELL ? useLiveMessenger() : useMockMessenger();
|
||||
}
|
||||
export function useThread(threadId: string): ThreadData {
|
||||
return SHELL ? useLiveThread(threadId) : useMockThread(threadId);
|
||||
}
|
||||
export function useGroupSettings(threadId: string): GroupSettingsData {
|
||||
return SHELL ? useLiveGroupSettings(threadId) : useMockGroupSettings(threadId);
|
||||
}
|
||||
|
||||
/* ======================================================================== */
|
||||
/* Live implementation (be-crm data door + IIOS socket) */
|
||||
@@ -259,6 +273,29 @@ function useLiveThread(threadId: string): ThreadData {
|
||||
};
|
||||
}
|
||||
|
||||
function useLiveGroupSettings(threadId: string): GroupSettingsData {
|
||||
const { sdk } = useAppShell();
|
||||
const { user } = useAuth();
|
||||
const q = useQuery<UiMember[]>("crm.messenger.members", { threadId });
|
||||
const members = useMemo(() => q.data ?? [], [q.data]);
|
||||
const isAdmin = useMemo(() => members.some((m) => m.userId === user?.id && m.role === "ADMIN"), [members, user?.id]);
|
||||
|
||||
const rename = useCallback(async (subject: string) => {
|
||||
await sdk.command("crm.messenger.group.rename", { threadId, subject });
|
||||
q.refetch();
|
||||
}, [sdk, threadId, q]);
|
||||
const addMember = useCallback(async (userId: string) => {
|
||||
await sdk.command("crm.messenger.participant.add", { threadId, userId });
|
||||
q.refetch();
|
||||
}, [sdk, threadId, q]);
|
||||
const removeMember = useCallback(async (userId: string) => {
|
||||
await sdk.command("crm.messenger.participant.remove", { threadId, userId });
|
||||
q.refetch();
|
||||
}, [sdk, threadId, q]);
|
||||
|
||||
return { loading: q.loading, error: q.error?.message ?? null, members, isAdmin, rename, addMember, removeMember, refetch: q.refetch };
|
||||
}
|
||||
|
||||
function shape(
|
||||
c: ConversationDTO,
|
||||
nameOf: (id: string) => string,
|
||||
@@ -372,3 +409,27 @@ function useMockThread(threadId: string): ThreadData {
|
||||
typingUserIds: [], seenIds: new Set(), refetch: notifyMock,
|
||||
};
|
||||
}
|
||||
|
||||
function useMockGroupSettings(threadId: string): GroupSettingsData {
|
||||
useMockSubscription();
|
||||
const nameById = useMemo(() => Object.fromEntries(MOCK_PEOPLE.map((p) => [p.id, p.name])), []);
|
||||
const t = MOCK_STORE.get(threadId);
|
||||
const members: UiMember[] = (t?.participants ?? []).map((id) => ({
|
||||
userId: id,
|
||||
displayName: id === "me" ? "You" : (nameById[id] ?? `User ${shortId(id)}`),
|
||||
role: id === "me" ? "ADMIN" : "MEMBER",
|
||||
}));
|
||||
const rename = useCallback(async (subject: string) => {
|
||||
const th = MOCK_STORE.get(threadId);
|
||||
if (th) { th.subject = subject; notifyMock(); }
|
||||
}, [threadId]);
|
||||
const addMember = useCallback(async (userId: string) => {
|
||||
const th = MOCK_STORE.get(threadId);
|
||||
if (th && !th.participants.includes(userId)) { th.participants = [...th.participants, userId]; notifyMock(); }
|
||||
}, [threadId]);
|
||||
const removeMember = useCallback(async (userId: string) => {
|
||||
const th = MOCK_STORE.get(threadId);
|
||||
if (th) { th.participants = th.participants.filter((p) => p !== userId); notifyMock(); }
|
||||
}, [threadId]);
|
||||
return { loading: false, error: null, members, isAdmin: true, rename, addMember, removeMember, refetch: notifyMock };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user