aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js74
1 files changed, 73 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 875b1aa..3a85bf7 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -528,7 +528,66 @@ function NotificationFeed({ notifications, onMarkRead, onPurge }) {
`;
}
-function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroups = true }) {
+/**
+ * Groups somebody added this account to, waiting for an answer.
+ *
+ * Being added is an invitation, not a membership (the hub's `group_invitations`):
+ * until it is accepted the group is not in the sidebar, not dialled, not in
+ * Search and not named in any token this browser hands a node. Accepting is
+ * what makes it a group of ours; declining forgets it.
+ */
+function PendingInvitations({ token, onAccepted }) {
+ const [items, setItems] = useState([]);
+ const [busy, setBusy] = useState('');
+ const [error, setError] = useState('');
+ const load = useCallback(() => {
+ if (!token) return;
+ hubFetch('/v1/groups/invitations', { token })
+ .then(data => setItems(data.invitations || []))
+ .catch(() => setItems([]));
+ }, [token]);
+ useEffect(() => { load(); }, [load]);
+
+ const answer = async (inv, verb) => {
+ setBusy(inv.group_id); setError('');
+ try {
+ await hubFetch(`/v1/groups/${inv.group_id}/invitation/${verb}`,
+ { method: 'POST', token });
+ setItems(prev => prev.filter(i => i.group_id !== inv.group_id));
+ if (verb === 'accept' && onAccepted) onAccepted(inv.group_id);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setBusy('');
+ }
+ };
+
+ if (!items.length) return null;
+ return html`
+ <div class="settings-section">
+ <h3 class="settings-heading">${t('home.invitations')}</h3>
+ <p class="settings-hint">${t('home.invitation_hint')}</p>
+ ${error && html`<p class="error-msg">${error}</p>`}
+ <ul class="invite-links">
+ ${items.map(inv => html`
+ <li key=${inv.group_id} style="display:flex;gap:8px;align-items:center;
+ flex-wrap:wrap;word-break:break-word;margin:4px 0">
+ <strong><${GroupName} name=${inv.name} owner=${inv.owner_username} /></strong>
+ ${inv.invited_by && html`<span style="color:var(--text-dim)">
+ ${t('home.invited_by', { name: inv.invited_by })}</span>`}
+ <button class="admin-btn" type="button" disabled=${busy === inv.group_id}
+ onClick=${() => answer(inv, 'accept')}>${t('home.accept')}</button>
+ <button class="admin-btn" type="button" disabled=${busy === inv.group_id}
+ onClick=${() => answer(inv, 'decline')}>${t('home.decline')}</button>
+ </li>
+ `)}
+ </ul>
+ </div>
+ `;
+}
+
+function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroups = true,
+ token, onGroupsChanged }) {
const [setupDismissed, setSetupDismissed] = useState(false);
if (groups.length === 0) {
@@ -539,6 +598,7 @@ function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroup
return html`
<div>
<h2>${t('home.welcome')}</h2>
+ <${PendingInvitations} token=${token} onAccepted=${onGroupsChanged} />
<${NotificationFeed} notifications=${notifications}
onMarkRead=${onMarkRead} onPurge=${onPurge} />
<${JoinByLink} hubOrigin=${platform.hubOrigin()} />
@@ -555,6 +615,7 @@ function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroup
return html`
<div>
<h2>${t('home.my_groups')}</h2>
+ <${PendingInvitations} token=${token} onAccepted=${onGroupsChanged} />
<${NotificationFeed} notifications=${notifications}
onMarkRead=${onMarkRead} onPurge=${onPurge} />
<div class="group-grid">
@@ -958,6 +1019,15 @@ function App() {
fetchNotifications();
}, [user]);
+ // After an invitation is accepted: the group is ours now, and only the hub
+ // knows its row the way `/mine` answers it.
+ const reloadGroups = useCallback(() => {
+ if (!user) return;
+ hubFetch('/v1/groups/mine', { token: user.token })
+ .then(data => setGroups(data.groups || []))
+ .catch(() => {});
+ }, [user]);
+
// The group list lives here, so an edit made three components down has to come
// back up rather than be re-fetched: a reload would drop the WebRTC connection
// the page is holding.
@@ -1216,6 +1286,7 @@ function App() {
? html`<${LazyAdminPage} token=${user.token} role=${user.role} />`
: html`<${HomePage} groups=${groups} notifications=${notifications}
allowPublicGroups=${allowPublicGroups}
+ token=${user.token} onGroupsChanged=${reloadGroups}
onMarkRead=${markRead} onPurge=${purgeNotifications} />`;
} else if (route === '/settings') {
page = html`<${SettingsPage} user=${user} theme=${theme}
@@ -1233,6 +1304,7 @@ function App() {
} else {
page = html`<${HomePage} groups=${groups} notifications=${notifications}
allowPublicGroups=${allowPublicGroups}
+ token=${user.token} onGroupsChanged=${reloadGroups}
onMarkRead=${markRead} onPurge=${purgeNotifications} />`;
}