likwid/frontend/src/pages/communities.astro

371 lines
11 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
export const prerender = false;
import Layout from '../layouts/Layout.astro';
import { API_BASE as apiBase } from '../lib/api';
---
<Layout title="Communities">
<section class="ui-page">
<div class="ui-container">
<div class="ui-page-header">
<div class="ui-page-title">
<h1>Communities</h1>
<p class="ui-subtitle">Browse organizations and communities</p>
</div>
<div class="ui-actions">
<a href="/communities/new" class="ui-btn ui-btn-primary" id="create-btn" style="display: none;">Create Community</a>
</div>
</div>
<div class="communities-toolbar ui-toolbar ui-card ui-card-glass">
<div class="ui-toolbar-grid">
<div class="toolbar-search">
<input type="text" id="search-input" class="ui-input" placeholder="Search communities…" autocomplete="off" />
</div>
<div class="toolbar-sort">
<select id="sort-select" class="ui-select" aria-label="Sort communities">
<option value="name_asc">Name (AZ)</option>
<option value="name_desc">Name (ZA)</option>
<option value="slug_asc">Slug (AZ)</option>
</select>
</div>
</div>
<div class="ui-kpis" aria-label="Communities summary">
<div class="ui-kpi">
<div class="ui-kpi-value" id="kpi-total">—</div>
<div class="ui-kpi-label">Total</div>
</div>
<div class="ui-kpi">
<div class="ui-kpi-value" id="kpi-showing">—</div>
<div class="ui-kpi-label">Showing</div>
</div>
<div class="ui-kpi">
<div class="ui-kpi-value" id="kpi-query">All</div>
<div class="ui-kpi-label">Filter</div>
</div>
</div>
</div>
<div id="communities-list" class="communities-grid" aria-live="polite">
<div class="state-card ui-card"><p class="loading">Loading communities…</p></div>
</div>
<script is:inline define:vars={{ apiBase }}>
(function() {
var API_BASE = apiBase;
var allCommunities = [];
var currentQuery = '';
var currentSort = 'name_asc';
function escapeHtml(value) {
return String(value || '').replace(/[&<>\"']/g, function(ch) {
switch (ch) {
case '&': return '&amp;';
case '<': return '&lt;';
case '>': return '&gt;';
case '"': return '&quot;';
case "'": return '&#39;';
default: return ch;
}
});
}
function updateKpis(total, showing, query) {
var totalEl = document.getElementById('kpi-total');
var showingEl = document.getElementById('kpi-showing');
var queryEl = document.getElementById('kpi-query');
if (totalEl) totalEl.textContent = String(total);
if (showingEl) showingEl.textContent = String(showing);
if (queryEl) queryEl.textContent = query ? '“' + query + '”' : 'All';
}
function renderSkeleton(count) {
var container = document.getElementById('communities-list');
if (!container) return;
var html = '';
for (var i = 0; i < count; i++) {
html += '<div class="community-tile ui-card is-skeleton" aria-hidden="true">' +
'<div class="tile-top">' +
'<div class="skel skel-title"></div>' +
'<div class="skel skel-slug"></div>' +
'</div>' +
'<div class="skel skel-desc"></div>' +
'<div class="skel skel-desc short"></div>' +
'</div>';
}
container.innerHTML = html;
}
function sortCommunities(list) {
var sorted = list.slice();
sorted.sort(function(a, b) {
var an = (a && a.name ? String(a.name) : '').toLowerCase();
var bn = (b && b.name ? String(b.name) : '').toLowerCase();
var as = (a && a.slug ? String(a.slug) : '').toLowerCase();
var bs = (b && b.slug ? String(b.slug) : '').toLowerCase();
if (currentSort === 'name_desc') {
if (an < bn) return 1;
if (an > bn) return -1;
return 0;
}
if (currentSort === 'slug_asc') {
if (as < bs) return -1;
if (as > bs) return 1;
return 0;
}
if (an < bn) return -1;
if (an > bn) return 1;
return 0;
});
return sorted;
}
function renderCommunities(communities) {
var container = document.getElementById('communities-list');
if (!container) return;
if (!communities || communities.length === 0) {
container.innerHTML = '<div class="state-card ui-card"><p>No communities found.</p></div>';
return;
}
var html = '';
for (var i = 0; i < communities.length; i++) {
var c = communities[i];
var name = escapeHtml(c && c.name ? c.name : 'Community');
var slug = escapeHtml(c && c.slug ? c.slug : '');
var desc = escapeHtml(c && c.description ? c.description : 'No description');
html += '<a href="/communities/' + slug + '" class="community-tile ui-card">' +
'<div class="tile-top">' +
'<h3 class="tile-title">' + name + '</h3>' +
'<div class="tile-slug">/' + slug + '</div>' +
'</div>' +
'<p class="tile-desc">' + desc + '</p>' +
'<div class="tile-meta">' +
'<span class="meta-pill">Open community</span>' +
'</div>' +
'</a>';
}
container.innerHTML = html;
}
function applyFilterSortAndRender() {
var q = String(currentQuery || '').toLowerCase().trim();
var filtered = allCommunities;
if (q) {
filtered = allCommunities.filter(function(c) {
var name = (c && c.name ? String(c.name) : '').toLowerCase();
var slug = (c && c.slug ? String(c.slug) : '').toLowerCase();
var desc = (c && c.description ? String(c.description) : '').toLowerCase();
return name.indexOf(q) >= 0 || slug.indexOf(q) >= 0 || desc.indexOf(q) >= 0;
});
}
var sorted = sortCommunities(filtered);
updateKpis(allCommunities.length, sorted.length, currentQuery);
renderCommunities(sorted);
}
function loadCommunities() {
var container = document.getElementById('communities-list');
if (!container) return;
renderSkeleton(8);
updateKpis('—', '—', '');
fetch(API_BASE + '/api/communities')
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(data) {
allCommunities = Array.isArray(data) ? data : (data.value || data.communities || []);
applyFilterSortAndRender();
})
.catch(function(error) {
console.error('Failed to load communities:', error);
container.innerHTML = '<div class="state-card ui-card"><p class="error">Failed to load communities.</p><p class="hint">Please try again later.</p></div>';
});
}
// Initialize
loadCommunities();
// Setup search
var searchInput = document.getElementById('search-input');
if (searchInput) {
searchInput.addEventListener('input', function(e) {
currentQuery = e.target.value;
applyFilterSortAndRender();
});
}
var sortSelect = document.getElementById('sort-select');
if (sortSelect) {
sortSelect.addEventListener('change', function(e) {
currentSort = e.target.value || 'name_asc';
applyFilterSortAndRender();
});
}
// Show create button if logged in
var token = localStorage.getItem('token');
var createBtn = document.getElementById('create-btn');
if (token && createBtn) {
createBtn.style.display = 'block';
}
})();
</script>
</div>
</section>
</Layout>
<style>
.ui-page {
--ui-state-card-pad: 2.5rem 1.5rem;
}
.communities-toolbar {
--ui-toolbar-pad: 1rem 1.25rem;
--ui-toolbar-mb: 1.25rem;
--ui-toolbar-cols: 1fr 220px;
--ui-toolbar-align: center;
}
.communities-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
@media (max-width: 1024px) {
.communities-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.communities-toolbar .ui-toolbar-grid {
grid-template-columns: 1fr;
}
.communities-grid {
grid-template-columns: 1fr;
}
}
.community-tile {
display: flex;
flex-direction: column;
padding: 1.25rem 1.25rem 1rem;
color: var(--color-text);
position: relative;
overflow: hidden;
transition: transform var(--motion-normal) var(--easing-standard), box-shadow var(--motion-normal) var(--easing-standard), border-color var(--motion-fast) var(--easing-standard);
}
@media (max-width: 640px) {
.community-tile {
padding: 1rem;
}
}
.community-tile::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(700px 220px at 15% 0%, rgba(129, 140, 248, 0.12), transparent 60%);
opacity: 0;
transition: opacity var(--motion-normal) var(--easing-standard);
pointer-events: none;
}
.community-tile:hover {
border-color: var(--color-border-hover);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.community-tile:hover::before {
opacity: 1;
}
.tile-top {
display: grid;
gap: 0.25rem;
margin-bottom: 0.75rem;
}
.tile-title {
font-size: 1.05rem;
font-weight: 650;
letter-spacing: -0.01em;
margin: 0;
}
@media (max-width: 640px) {
.tile-title {
font-size: 1rem;
}
}
.tile-slug {
font-family: 'SF Mono', Monaco, 'Cascadia Code', ui-monospace, monospace;
font-size: 0.8125rem;
color: var(--color-primary);
opacity: 0.9;
}
.tile-desc {
color: var(--color-text-muted);
font-size: 0.9375rem;
line-height: 1.55;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
margin: 0;
min-height: calc(0.9375rem * 1.55 * 2);
}
@media (max-width: 640px) {
.tile-desc {
font-size: 0.9rem;
}
}
.tile-meta {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
@media (max-width: 640px) {
.tile-meta {
margin-top: 0.75rem;
}
}
.meta-pill {
display: inline-flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.10);
background: rgba(255, 255, 255, 0.04);
color: rgba(255, 255, 255, 0.78);
}
.skel-title { height: 18px; width: 68%; }
.skel-slug { height: 12px; width: 36%; }
.skel-desc { height: 12px; width: 100%; margin-top: 0.75rem; }
.skel-desc.short { width: 78%; margin-top: 0.5rem; }
</style>