diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-23 18:05:14 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-23 18:05:14 +0200 |
| commit | 35a7764db3f58a93c32206cb3ce74bb2f03967e7 (patch) | |
| tree | d2acb2a07ee6dc2f4241fcc785c2860d57263892 /packages/meshbay-hub/src/meshbay_hub/static/app.js | |
| parent | 998f9c69308ee88fac36cfb77dfb6d07c6fa926a (diff) | |
| download | meshbay-35a7764db3f58a93c32206cb3ce74bb2f03967e7.tar.gz | |
feat(hub): open, create and join invitation links in the interface
#/invite takes the link out of the address on load and keeps it in the
tab through registration and sign-in; joining is one click, only the
ticket goes to the hub, and the code goes only to the node the link
names once it has signed its challenge. Members tab gains "Invite by
link" (shared e-mail box, pending list, cancel both halves); home page
takes a pasted link. Browser probe drives the real app, signed out and in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 20 |
1 files changed, 19 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index fbfb942..a757e61 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -1,3 +1,6 @@ +// First, on purpose: loading it takes an invitation link out of the address +// before anything else here can read, log or route on it (invite-link.js). +import { clearPending, loadPending } from './invite-link.js'; import { html, render, useState, useEffect, useLayoutEffect, useCallback, useRef, createContext, useContext, @@ -29,6 +32,7 @@ import { ProfilePage } from './profile-page.js'; import { ExplorePage } from './explore-page.js'; import { GroupName } from './group-name.js'; import { FirstRunPage, LoginPage, RegisterPage, ResetPasswordPage } from './auth-page.js'; +import { InvitePage, JoinByLink } from './invite-page.js'; // ── Constants ──────────────────────────────────────────────────────────────── @@ -536,6 +540,7 @@ function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroup <h2>${t('home.welcome')}</h2> <${NotificationFeed} notifications=${notifications} onMarkRead=${onMarkRead} onPurge=${onPurge} /> + <${JoinByLink} hubOrigin=${platform.hubOrigin()} /> <p class="page-message"> ${t('home.no_groups')} ${' '}${allowPublicGroups @@ -568,6 +573,7 @@ function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroup </a> `)} </div> + <${JoinByLink} hubOrigin=${platform.hubOrigin()} /> </div> `; } @@ -680,7 +686,8 @@ function App() { // flow signs in half-way and still has its progress and result to show. const onAuthForm = route === '/login' || route === '/register'; useEffect(() => { - if (user && onAuthForm) window.location.replace('#/'); + // An invitation waiting in this tab is where a sign-in was headed. + if (user && onAuthForm) window.location.replace(loadPending() ? '#/invite' : '#/'); }, [user, onAuthForm]); // A desktop build with a remembered device signs in without asking. Null // until it has tried, so nothing renders a sign-in form the user is about to @@ -1091,6 +1098,8 @@ function App() { // Revoked on the hub too, so a copy of the refresh token is worth // nothing. Read before the next line clears it. logoutOnHub(); + // An invitation belongs to whoever was about to use it, not to the tab. + clearPending(); setAuth(null); setUser(null); setGroups([]); @@ -1136,6 +1145,15 @@ function App() { : route === '/reset' ? html`<${ResetPasswordPage} onLogin=${authCtx.login} />` : html`<${LoginPage} onLogin=${authCtx.login} />`; + } else if (route === '/invite') { + // Signed in or not: signed out, it explains and sends to register or sign + // in; signed in, it asks the hub what the ticket is for. + page = html`<${InvitePage} user=${user} onJoined=${async () => { + try { + const data = await hubFetch('/v1/groups/mine', { token: user.token }); + setGroups(data.groups || []); + } catch { /* the group page fetches what it needs */ } + }} />`; } else if (!user) { page = html`<${LoginPage} onLogin=${authCtx.login} />`; } else if (route === '/search') { |