From 0c2754d2d4cb1905d5e324f56fbc64e4afae526f Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 31 Aug 2026 01:06:06 +0200 Subject: refactor(ui): extract Explore, Login, Register and CreateGroup from app.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/meshbay_hub/static/explore-page.js | 100 +++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 packages/meshbay-hub/src/meshbay_hub/static/explore-page.js (limited to 'packages/meshbay-hub/src/meshbay_hub/static/explore-page.js') 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`

${t('explore.disabled')}

`; + } + + return html` +
+
+

${t('explore.title')}

+ ${platform.node.available && html` + ${t('explore.create_group')} + `} +
+
+ +
+ ${loading + ? html`

${t('explore.loading')}

` + : groups.length === 0 + ? html`

${t('explore.empty')}

` + : html` +
+ ${groups.map(g => html` +
+ +

<${GroupName} name=${g.name} + owner=${g.source && g.source !== 'local' ? g.source : g.owner_username} />

+ ${g.description && html`

${g.description}

`} +
+ ${g.join_policy} + ${' '} + ${isMember(g.id) + ? html`${t('explore.member')}` + : g.join_policy === 'open' && html` + + ` + } +
+ `)} +
+ ` + } +
+ `; +} -- cgit v1.2.3