This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/sw/surfaces/chat/channel-members-panel.js
gobha b7746c3004 Changeset 0.37.14 (#226)
Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
2026-03-23 16:47:48 +00:00

105 lines
4.2 KiB
JavaScript

// ==========================================
// Chat Surface — Channel Members Panel
// ==========================================
// Drawer showing channel participants with role management.
// Uses Drawer primitive + channels.participants API.
import { Drawer } from '../../primitives/drawer.js';
import { UserPicker } from '../../primitives/user-picker.js';
const { html } = window;
const { useState, useEffect, useCallback } = hooks;
const ROLES = ['owner', 'member', 'observer'];
function _initials(name) {
if (!name) return '?';
return name.split(/\s+/).map(w => w[0]).join('').toUpperCase().slice(0, 2);
}
/**
* @param {{ open: boolean, channelId: string, onClose: () => void }} props
*/
export function ChannelMembersPanel({ open, channelId, onClose }) {
const [members, setMembers] = useState([]);
const [loading, setLoading] = useState(false);
const [adding, setAdding] = useState(false);
const load = useCallback(async () => {
if (!channelId) return;
setLoading(true);
try {
const data = await sw.api.channels.participants(channelId);
setMembers(data || []);
} catch (e) { sw.toast(e.message, 'error'); }
finally { setLoading(false); }
}, [channelId]);
useEffect(() => {
if (open && channelId) load();
}, [open, channelId, load]);
async function updateRole(pid, role) {
try {
await sw.api.channels.updateParticipant(channelId, pid, { role });
sw.toast('Role updated', 'success');
load();
} catch (e) { sw.toast(e.message, 'error'); }
}
async function remove(pid) {
const ok = await sw.confirm('Remove this participant?', true);
if (!ok) return;
try {
await sw.api.channels.removeParticipant(channelId, pid);
sw.toast('Participant removed', 'success');
load();
} catch (e) { sw.toast(e.message, 'error'); }
}
const _onUserSelect = useCallback(async (user) => {
setAdding(true);
try {
await sw.api.channels.addParticipant(channelId, {
participant_type: 'user',
participant_id: user.id,
});
sw.toast('Participant added', 'success');
load();
} catch (e) { sw.toast(e.message, 'error'); }
finally { setAdding(false); }
}, [channelId, load]);
if (!open) return null;
return html`
<${Drawer} open=${open} side="right" width="340px" title="Members" onClose=${onClose}>
${loading && !members.length
? html`<div class="sw-panel-loading">Loading\u2026</div>`
: html`
<div class="sw-members-list">
${members.map(m => html`
<div class="sw-members-row" key=${m.id}>
<span class="sw-members-avatar">${_initials(m.display_name || m.participant_id)}</span>
<div class="sw-members-info">
<span class="sw-members-name">${m.display_name || m.participant_id}</span>
<span class="sw-members-type">${m.participant_type}</span>
</div>
<select class="sw-members-role" value=${m.role || 'member'}
onChange=${e => updateRole(m.id, e.target.value)}>
${ROLES.map(r => html`<option key=${r} value=${r}>${r}</option>`)}
</select>
<button class="sw-members-remove" onClick=${() => remove(m.id)}
title="Remove">\u00d7</button>
</div>`)}
${members.length === 0 && html`
<div class="sw-panel-empty">No participants</div>`}
</div>
<div class="sw-members-add">
<${UserPicker}
onSelect=${_onUserSelect}
placeholder=${adding ? 'Adding\u2026' : 'Add participant\u2026'} />
</div>`}
<//>`;
}