From 95cec0e0bbc28e930f297f44bbd3dbf4d63f0bc2 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 23 Sep 2026 19:30:47 +0200 Subject: fix(hub): a redeemed invitation link leaves the owner's list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list under "Invite by link" answered every ticket the group had ever minted, so a link that somebody had already used sat there saying "used by " for the thirty days of KEEP_REDEEMED — beside the member row it had just produced, and above the links that still wait for somebody, which are the only ones there is anything to do about. The node's own `member list` had never shown them: it selects `used_at IS NULL`. The listing now selects `redeemed_by IS NULL`, and drops the `redeemed` status and the `redeemed_by` field with it. The row itself still lives for KEEP_REDEEMED, which is what lets a reload or a second tab of the invitation page be answered rather than refused; its comment says that now instead of naming a list it is no longer in. The SPA filters too, because the desktop client's copy of this interface can be newer than the hub it is signed into. Co-Authored-By: Claude Opus 5 --- .../meshbay-hub/src/meshbay_hub/static/group-settings.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-settings.js') 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 121c51b..529a33d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -916,10 +916,14 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, : null; }, [transportRef]); + // A redeemed link is not shown: whoever used it is in the members list above, + // and what belongs here is what the owner can still cancel. The hub leaves + // them out already; the filter is for the desktop client, whose copy of this + // interface can be newer than the hub it is signed into. const loadLinks = useCallback(() => { if (!(group && group.is_admin)) return; hubFetch(`/v1/groups/${groupId}/invite-links`, { token }) - .then(data => setLinks(data.links || [])) + .then(data => setLinks((data.links || []).filter(l => l.status !== 'redeemed'))) .catch(() => {}); }, [groupId, token, group]); @@ -1102,12 +1106,10 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, flex-wrap:wrap;word-break:break-word;margin:4px 0"> ${l.email} - ${l.status === 'redeemed' - ? t('members.link_status_redeemed', { user: l.redeemed_by || '' }) - : l.status === 'expired' - ? t('members.link_status_expired') - : t('members.link_expires', - { date: new Date(l.expires_at).toLocaleDateString() })} + ${l.status === 'expired' + ? t('members.link_status_expired') + : t('members.link_expires', + { date: new Date(l.expires_at).toLocaleDateString() })} ${l.status === 'pending' && html`