diff options
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 | 37 |
1 files changed, 26 insertions, 11 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 529a33d..a510e63 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -897,10 +897,11 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, // ── Invitation links (docs/MESHBAY_DESIGN.md §3.4) ────────────────── // // Two halves, in the order that leaves nothing half-made: the node's code - // first, then the hub's ticket bound to the address; a ticket the hub then - // refuses takes the code back with it, since a code nobody can reach the node - // with only occupies one of the group's twenty places. The code reaches the - // hub only when the box asks the hub to write the mail. + // first, then the hub's ticket; a ticket the hub then refuses takes the code + // back with it, since a code nobody can reach the node with only occupies one + // of the group's twenty places. The address is optional and binds nothing: + // the link is for whoever opens it first, so it can go by any messaging app. + // The code reaches the hub only when the box asks the hub to write the mail. const [linkEmail, setLinkEmail] = useState(''); const [linking, setLinking] = useState(false); const [linkError, setLinkError] = useState(''); @@ -932,7 +933,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, const doCreateLink = useCallback(async (e) => { e.preventDefault(); const email = linkEmail.trim(); - if (!email) return; + const mailIt = inviteByEmail && Boolean(email); setLinking(true); setLinkError(''); setNewLink(null); @@ -951,8 +952,8 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, method: 'POST', token, body: { email, expires_at: node.expires_at, node_invite_id: node.invite_id, - send_email: inviteByEmail, - ...(inviteByEmail ? { node_pk: n, code: node.code } : {}), + send_email: mailIt, + ...(mailIt ? { node_pk: n, code: node.code } : {}), }, }); } catch (err) { @@ -960,7 +961,6 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, throw err; } setNewLink({ - email, link: inviteLinkHere({ g: groupId, t: ticket.ticket, n, c: node.code }), emailStatus: ticket.email_status, }); @@ -1004,6 +1004,16 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, } catch { /* the field is selectable */ } }, [newLink]); + // The system share sheet, where there is one (phones mostly): the way a link + // reaches a messaging app without a round trip through the clipboard. + const canShare = typeof navigator !== 'undefined' && typeof navigator.share === 'function'; + const shareLink = useCallback(async () => { + if (!newLink) return; + try { + await navigator.share({ title: t('members.link_share_title'), url: newLink.link }); + } catch { /* dismissed; the field and Copy are still there */ } + }, [newLink]); + if (loading) return html`<p class="page-message">${t('explore.loading')}</p>`; const isOwner = Boolean(isAdmin); @@ -1067,13 +1077,17 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, <form onSubmit=${doCreateLink}> ${newLink && html` <div class="success-msg" style="margin-bottom:8px"> - <p>${t('members.link_ready', { email: newLink.email })}</p> + <p>${t('members.link_ready')}</p> <div class="form-row"> <input type="text" readonly value=${newLink.link} onFocus=${e => e.target.select()} /> <button class="admin-btn" type="button" onClick=${copyLink}> ${linkCopied ? t('members.link_copied') : t('members.link_copy')} </button> + ${canShare && html` + <button class="admin-btn" type="button" onClick=${shareLink}> + ${t('members.link_share')} + </button>`} </div> ${newLink.emailStatus === 'sent' ? html`<p style="color:var(--success)">${t('members.link_email_sent')}</p>` @@ -1085,7 +1099,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, <div class="form-row"> <input type="email" placeholder=${t('members.link_email_placeholder')} value=${linkEmail} onInput=${e => setLinkEmail(e.target.value)} - disabled=${!connected || !operatorPaired} required /> + disabled=${!connected || !operatorPaired} /> <button class="admin-btn" type="submit" disabled=${linking || !connected || !operatorPaired}> ${linking ? '...' : t('members.link_btn')} @@ -1104,7 +1118,8 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, ${links.map(l => html` <li key=${l.link_id} style="display:flex;gap:8px;align-items:center; flex-wrap:wrap;word-break:break-word;margin:4px 0"> - <span>${l.email}</span> + <span>${l.email || t('members.link_unlabelled', + { date: new Date(l.created_at).toLocaleDateString() })}</span> <span style="color:var(--text-dim)"> ${l.status === 'expired' ? t('members.link_status_expired') |