aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/explore-page.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-31 01:06:06 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-31 01:06:06 +0200
commit0c2754d2d4cb1905d5e324f56fbc64e4afae526f (patch)
treec081859830e017af09af9d2f73c6ff8442aacce4 /packages/meshbay-hub/src/meshbay_hub/static/explore-page.js
parentf6e80064ff8c4869a6ba2cef908cc54326948463 (diff)
downloadmeshbay-0c2754d2d4cb1905d5e324f56fbc64e4afae526f.tar.gz
refactor(ui): extract Explore, Login, Register and CreateGroup from app.js
ExplorePage → explore-page.js (static import), LoginPage/RegisterPage/ FirstRunPage → auth-page.js (static import, LoginPage receives onLogin as a prop), CreateGroupPage/wizard → create-group-page.js (lazy-loaded via dynamic import(), same pattern as AdminPage/NodePage). app.js goes from 1803 to 914 lines. webapp.py _ASSETS extended with the three new files and the previously missing extracted pages. Test fixtures updated to follow the moved components. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/explore-page.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/explore-page.js100
1 files changed, 100 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/explore-page.js b/packages/meshbay-hub/src/meshbay_hub/static/explore-page.js
new file mode 100644
index 0000000..486c1e6
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/explore-page.js
@@ -0,0 +1,100 @@
+import {
+ html, useState, useEffect, useCallback,
+} from './vendor/htm-preact.js';
+import { t } from './i18n.js';
+import { hubFetch, navigate } from './hub-client.js';
+import * as platform from './platform.js';
+import { GroupName } from './group-name.js';
+import { Icon } from './icon.js';
+
+export function ExplorePage({ token, myGroupIds, allowPublicGroups = true }) {
+ const [groups, setGroups] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [search, setSearch] = useState('');
+ const [joining, setJoining] = useState(null);
+
+ const doSearch = useCallback((q) => {
+ setLoading(true);
+ const url = q ? `/v1/groups?q=${encodeURIComponent(q)}` : '/v1/groups';
+ hubFetch(url, { token })
+ .then(data => setGroups(data.groups || []))
+ .catch(() => {})
+ .finally(() => setLoading(false));
+ }, [token]);
+
+ useEffect(() => { doSearch(''); }, [token]);
+
+ const onSearch = useCallback((e) => {
+ const q = e.target.value;
+ setSearch(q);
+ doSearch(q);
+ }, [doSearch]);
+
+ const joinGroup = useCallback(async (gid) => {
+ setJoining(gid);
+ try {
+ await hubFetch(`/v1/groups/${gid}/join`, { method: 'POST', token });
+ navigate(`/group/${gid}`);
+ setTimeout(() => window.location.reload(), 100);
+ } catch (err) {
+ if (err.message.includes('Already a member')) {
+ navigate(`/group/${gid}`);
+ } else {
+ alert(err.message);
+ }
+ } finally {
+ setJoining(null);
+ }
+ }, [token]);
+
+ const isMember = (gid) => myGroupIds && myGroupIds.includes(gid);
+
+ if (!allowPublicGroups) {
+ return html`<div><p class="page-message">${t('explore.disabled')}</p></div>`;
+ }
+
+ return html`
+ <div>
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
+ <h2 style="margin:0">${t('explore.title')}</h2>
+ ${platform.node.available && html`
+ <a class="admin-btn" href="#/create-group">${t('explore.create_group')}</a>
+ `}
+ </div>
+ <div class="file-toolbar" style="margin-bottom:16px">
+ <input type="text" class="admin-search" placeholder="${t('explore.search')}"
+ value=${search} onInput=${onSearch} />
+ </div>
+ ${loading
+ ? html`<p class="page-message">${t('explore.loading')}</p>`
+ : groups.length === 0
+ ? html`<p class="page-message">${t('explore.empty')}</p>`
+ : html`
+ <div class="group-grid">
+ ${groups.map(g => html`
+ <div key=${g.id} class="group-card">
+ <a href="#/group/${g.id}" style="text-decoration:none;color:inherit">
+ <h3><${GroupName} name=${g.name}
+ owner=${g.source && g.source !== 'local' ? g.source : g.owner_username} /></h3>
+ ${g.description && html`<p class="group-card-desc">${g.description}</p>`}
+ </a>
+ <span class="badge">${g.join_policy}</span>
+ ${' '}
+ ${isMember(g.id)
+ ? html`<span class="badge">${t('explore.member')}</span>`
+ : g.join_policy === 'open' && html`
+ <button class="admin-btn" style="margin-top:8px"
+ disabled=${joining === g.id}
+ onClick=${() => joinGroup(g.id)}>
+ ${joining === g.id ? '...' : t('explore.join')}
+ </button>
+ `
+ }
+ </div>
+ `)}
+ </div>
+ `
+ }
+ </div>
+ `;
+}