Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
105 lines
4.2 KiB
JavaScript
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>`}
|
|
<//>`;
|
|
}
|