diff options
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.js | 100 |
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> + `; +} |