ux: improve proposals states

This commit is contained in:
Marco Allegretti 2026-02-05 13:48:06 +01:00
parent a946fc6b85
commit 5b5a11a96d
2 changed files with 111 additions and 6 deletions

View file

@ -44,7 +44,7 @@ import { API_BASE as apiBase } from '../lib/api';
</div> </div>
</div> </div>
<div id="proposals-list" class="list"> <div id="proposals-list" class="list" aria-live="polite">
<div class="state-card ui-card"><p class="loading">Loading proposals...</p></div> <div class="state-card ui-card"><p class="loading">Loading proposals...</p></div>
</div> </div>
</div> </div>
@ -78,6 +78,62 @@ import { API_BASE as apiBase } from '../lib/api';
container.innerHTML = html; container.innerHTML = html;
} }
function renderErrorState(message) {
const container = document.getElementById('proposals-list');
if (!container) return;
container.innerHTML = `
<div class="state-card ui-card">
<p class="error">${message}</p>
<p class="hint">Check your connection and try again.</p>
<div class="state-actions">
<button type="button" class="ui-btn ui-btn-primary" id="retry-load">Retry</button>
<a class="ui-btn ui-btn-secondary" href="/communities">Browse communities</a>
</div>
</div>
`;
document.getElementById('retry-load')?.addEventListener('click', loadProposals);
}
function renderEmptyState(isFiltered) {
const container = document.getElementById('proposals-list');
if (!container) return;
container.innerHTML = `
<div class="state-card ui-card">
<p class="empty">${isFiltered ? 'No proposals match your filters.' : 'No proposals found.'}</p>
<p class="hint">Try adjusting search, status, or sort.</p>
<div class="state-actions">
<button type="button" class="ui-btn ui-btn-secondary" id="reset-filters">Reset filters</button>
<a class="ui-btn ui-btn-primary" href="/communities">Go to communities</a>
</div>
</div>
`;
document.getElementById('reset-filters')?.addEventListener('click', () => {
const searchInput = document.getElementById('search-input');
const statusFilter = document.getElementById('status-filter');
const sortFilter = document.getElementById('sort-filter');
if (searchInput) searchInput.value = '';
if (statusFilter) statusFilter.value = '';
if (sortFilter) sortFilter.value = 'newest';
filterProposals();
});
}
function hasActiveFilters() {
const searchInput = document.getElementById('search-input');
const statusFilter = document.getElementById('status-filter');
const sortFilter = document.getElementById('sort-filter');
const query = searchInput?.value.toLowerCase().trim() || '';
const status = statusFilter?.value || '';
const sort = sortFilter?.value || 'newest';
return Boolean(query || status || sort !== 'newest');
}
async function loadProposals() { async function loadProposals() {
const container = document.getElementById('proposals-list'); const container = document.getElementById('proposals-list');
if (!container) return; if (!container) return;
@ -88,7 +144,7 @@ import { API_BASE as apiBase } from '../lib/api';
allProposals = await res.json(); allProposals = await res.json();
renderProposals(allProposals); renderProposals(allProposals);
} catch (error) { } catch (error) {
container.innerHTML = '<div class="error"><p>Failed to load proposals.</p></div>'; renderErrorState('Failed to load proposals.');
} }
} }
@ -97,7 +153,7 @@ import { API_BASE as apiBase } from '../lib/api';
if (!container) return; if (!container) return;
if (proposals.length === 0) { if (proposals.length === 0) {
container.innerHTML = '<div class="state-card ui-card"><p class="empty">No proposals found.</p></div>'; renderEmptyState(hasActiveFilters());
return; return;
} }
@ -171,6 +227,14 @@ import { API_BASE as apiBase } from '../lib/api';
gap: 1rem; gap: 1rem;
} }
.state-actions {
margin-top: 1.25rem;
display: flex;
gap: 0.75rem;
justify-content: center;
flex-wrap: wrap;
}
.proposal-card { .proposal-card {
display: block; display: block;
padding: 1.1rem; padding: 1.1rem;

View file

@ -28,6 +28,24 @@ const proposalId = id ?? '';
let quadraticAllocations = {}; let quadraticAllocations = {};
let starRatings = {}; let starRatings = {};
function renderProposalErrorState(message) {
const container = document.getElementById('proposal-content');
if (!container) return;
container.innerHTML = `
<div class="state-card ui-card">
<p class="error">${escapeHtml(message)}</p>
<p class="hint">It may have been deleted, or you may not have access.</p>
<div class="state-actions">
<button type="button" class="ui-btn ui-btn-primary" id="retry-proposal">Retry</button>
<a class="ui-btn ui-btn-secondary" href="/proposals">Back to proposals</a>
</div>
</div>
`;
document.getElementById('retry-proposal')?.addEventListener('click', loadProposal);
}
function escapeHtml(value) { function escapeHtml(value) {
return String(value || '').replace(/[&<>"']/g, function(ch) { return String(value || '').replace(/[&<>"']/g, function(ch) {
switch (ch) { switch (ch) {
@ -407,7 +425,7 @@ const proposalId = id ?? '';
} }
} catch (error) { } catch (error) {
container.innerHTML = '<div class="error">Proposal not found</div>'; renderProposalErrorState('Proposal not found.');
} }
} }
@ -600,7 +618,12 @@ const proposalId = id ?? '';
const comments = await res.json(); const comments = await res.json();
if (comments.length === 0) { if (comments.length === 0) {
container.innerHTML = '<p class="empty-comments">No comments yet. Be the first to share your thoughts!</p>'; container.innerHTML = `
<div class="state-card ui-card ui-card-soft">
<p class="empty">No comments yet.</p>
<p class="hint">Be the first to share your thoughts.</p>
</div>
`;
return; return;
} }
@ -619,7 +642,17 @@ const proposalId = id ?? '';
`; `;
}).join(''); }).join('');
} catch (e) { } catch (e) {
container.innerHTML = '<p class="error-small">Failed to load comments</p>'; container.innerHTML = `
<div class="state-card ui-card ui-card-soft">
<p class="error">Failed to load comments.</p>
<p class="hint">Try again in a moment.</p>
<div class="state-actions">
<button type="button" class="ui-btn ui-btn-secondary" id="retry-comments">Retry</button>
</div>
</div>
`;
document.getElementById('retry-comments')?.addEventListener('click', loadComments);
} }
} }
@ -659,6 +692,14 @@ const proposalId = id ?? '';
max-width: 880px; max-width: 880px;
} }
.state-actions {
margin-top: 1.25rem;
display: flex;
gap: 0.75rem;
justify-content: center;
flex-wrap: wrap;
}
.results-chart-host { .results-chart-host {
display: none; display: none;
} }