diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-05 11:27:20 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-05 11:27:20 +0200 |
| commit | 28752696f376eb11feb686580a435b166750a723 (patch) | |
| tree | a879254f126d6d83096c7a7c3a181a151e3be7f2 /packages/meshbay-hub/src/meshbay_hub/static/group-settings.js | |
| parent | 6cdc6016d72dcfb7530ac38a8fa92232418ac305 (diff) | |
| download | meshbay-28752696f376eb11feb686580a435b166750a723.tar.gz | |
fix: list as members only the accounts the node has admitted
The Members list showed the hub's membership, which an account gains when it
accepts the invitation or redeems a link, before it has presented its code to
the node. The node's roster is the authority (MESHBAY_DESIGN.md §3.4), so
the list now crosses the hub's members with the sealed group roster the node
already sends every connected member. An account the node has not admitted
yet is shown to the owner alone, as waiting for its code, with the Remove
button; other members do not see it. When the roster cannot be read, the
hub's list is shown as before.
groupRoster() takes { fresh: true } so the page sees who joined since the
connection opened.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-settings.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/group-settings.js | 42 |
1 files changed, 39 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js index 29aa7eb..3ffa524 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -783,6 +783,27 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, useEffect(() => { loadMembers(); }, [loadMembers]); + // Who the node has admitted to this group. The hub counts someone a member + // once they accept the invitation; the node, once they present their code. + // Until then they are not in the group, and are listed as waiting, to the + // owner only. Null when the roster cannot be read: the hub's list is shown. + const [admitted, setAdmitted] = useState(null); + useEffect(() => { + const transport = transportRef.current; + if (!connected || !transport || !transport.connected) { setAdmitted(null); return; } + let cancelled = false; + transport.groupRoster({ fresh: true }) + .then((roster) => { if (!cancelled) setAdmitted(new Set(roster.byAccount.keys())); }) + .catch(() => { if (!cancelled) setAdmitted(null); }); + return () => { cancelled = true; }; + }, [connected, transportRef, members]); + const joined = admitted + ? members.filter(m => m.user_id === adminId || admitted.has(m.user_id)) + : members; + const awaitingCode = admitted + ? members.filter(m => m.user_id !== adminId && !admitted.has(m.user_id)) + : []; + const ownsGroup = Boolean(group && group.is_admin); const loadHosts = useCallback(() => { if (!ownsGroup) return; @@ -1340,7 +1361,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, </${CollapsibleSection}> `} - <${CollapsibleSection} title=${`${t('group.tab_members')} (${members.length})`}> + <${CollapsibleSection} title=${`${t('group.tab_members')} (${joined.length})`}> <table class="admin-table"> <thead> <tr> @@ -1350,7 +1371,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, </tr> </thead> <tbody> - ${members.map(m => html` + ${joined.map(m => html` <tr key=${m.user_id}> <td>${m.username}</td> <td> @@ -1372,6 +1393,21 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, </td> </tr> `)} + ${isAdmin && awaitingCode.map(m => html` + <tr key=${m.user_id}> + <td>${m.username}</td> + <td><span class="badge">${t('members.awaiting_code')}</span></td> + <td class="admin-actions"> + <button class="admin-btn danger" disabled=${removing === m.user_id} + onClick=${async () => { + if (!await ask(t('members.remove_confirm', { user: m.username }))) return; + removeMember(m); + }}> + ${removing === m.user_id ? '...' : t('members.remove')} + </button> + </td> + </tr> + `)} ${invited.map(m => html` <tr key=${m.user_id}> <td>${m.username}</td> @@ -1391,7 +1427,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, `)} </tbody> </table> - ${isAdmin && members.length > 1 && html` + ${isAdmin && joined.length > 1 && html` <p class="settings-hint">${t('members.remove_hint')}</p> `} </${CollapsibleSection}> |