diff --git a/frontend/src/pages/dashboard.astro b/frontend/src/pages/dashboard.astro index 15f5265..dcc73cc 100644 --- a/frontend/src/pages/dashboard.astro +++ b/frontend/src/pages/dashboard.astro @@ -26,6 +26,11 @@ import { API_BASE as apiBase } from '../lib/api'; } const user = JSON.parse(userStr || '{}'); + const dashboardState = { + communities: null, + proposals: null, + activities: null, + }; function escapeHtml(value) { return String(value || '').replace(/[&<>"']/g, function(ch) { @@ -59,6 +64,84 @@ import { API_BASE as apiBase } from '../lib/api'; el.setAttribute('aria-label', str); } + function pluralize(label, count) { + return count === 1 ? label : `${label}s`; + } + + function renderInsights() { + const container = document.getElementById('dashboard-insights'); + if (!container) return; + + const communities = dashboardState.communities; + const proposals = dashboardState.proposals; + const activities = dashboardState.activities; + + if (!communities || !proposals || !activities) { + container.innerHTML = '

Loading...

'; + return; + } + + const communityCount = communities.length; + const proposalCount = proposals.length; + const activityCount = activities.length; + + const statusCounts = { draft: 0, discussion: 0, voting: 0, closed: 0 }; + proposals.forEach((p) => { + const key = normalizeStatus(p.status); + statusCounts[key] = (statusCounts[key] || 0) + 1; + }); + + const bullets = []; + if (communityCount === 0) { + bullets.push('You are not in any communities yet.'); + } else { + bullets.push(`You are active in ${communityCount} ${pluralize('community', communityCount)}.`); + } + + if (proposalCount === 0) { + bullets.push('You have not created any proposals yet.'); + } else if (statusCounts.voting > 0) { + bullets.push(`${statusCounts.voting} ${pluralize('proposal', statusCounts.voting)} ${statusCounts.voting === 1 ? 'is' : 'are'} currently in voting.`); + } else if (statusCounts.discussion > 0) { + bullets.push(`${statusCounts.discussion} ${pluralize('proposal', statusCounts.discussion)} ${statusCounts.discussion === 1 ? 'is' : 'are'} in discussion.`); + } else if (statusCounts.draft > 0) { + bullets.push(`${statusCounts.draft} ${pluralize('draft', statusCounts.draft)} ${statusCounts.draft === 1 ? 'is' : 'are'} waiting to be published.`); + } else { + bullets.push(`${statusCounts.closed} closed ${pluralize('proposal', statusCounts.closed)} in your list.`); + } + + if (activityCount === 0) { + bullets.push('No recent activity was recorded.'); + } else { + bullets.push(`${activityCount} recent ${pluralize('event', activityCount)} across your communities.`); + } + + const actions = []; + if (communityCount === 0) { + actions.push({ href: '/communities', label: 'Browse Communities', kind: 'secondary' }); + actions.push({ href: '/communities/new', label: 'Create Community', kind: 'primary' }); + } else { + actions.push({ href: '/communities', label: 'Browse', kind: 'secondary' }); + actions.push({ href: '/proposals', label: 'View Proposals', kind: 'secondary' }); + } + + actions.push({ href: '/delegations', label: 'Review Delegations', kind: 'secondary' }); + + setBadge('badge-insights', bullets.length); + + container.innerHTML = ` +
+
What stands out
+ +
+ ${actions.map((a) => `${escapeHtml(a.label)}`).join('')} +
+
+ `; + } + async function loadDashboard() { const container = document.getElementById('dashboard-content'); if (!container) return; @@ -99,6 +182,20 @@ import { API_BASE as apiBase } from '../lib/api';
+
+ + Insights + + + + +
+
+

Loading...

+
+
+
+
My Communities @@ -159,6 +256,8 @@ import { API_BASE as apiBase } from '../lib/api'; window.location.href = '/'; }); + renderInsights(); + loadMyCommunities(); loadMyProposals(); loadRecentActivity(); @@ -171,10 +270,13 @@ import { API_BASE as apiBase } from '../lib/api'; try { const res = await fetch(`${apiBase}/api/activity/recent`); const activities = await res.json(); + dashboardState.activities = activities; setText('kpi-recent', activities.length); setBadge('badge-recent', activities.length); + renderInsights(); + if (activities.length === 0) { container.innerHTML = '

No recent activity

'; return; @@ -208,10 +310,13 @@ import { API_BASE as apiBase } from '../lib/api'; headers: { 'Authorization': `Bearer ${token}` }, }); const proposals = await res.json(); + dashboardState.proposals = proposals; setText('kpi-my-proposals', proposals.length); setBadge('badge-my-proposals', proposals.length); + renderInsights(); + if (proposals.length === 0) { container.innerHTML = '

You haven\'t created any proposals yet

'; return; @@ -242,10 +347,13 @@ import { API_BASE as apiBase } from '../lib/api'; headers: { 'Authorization': `Bearer ${token}` }, }); const communities = await res.json(); + dashboardState.communities = communities; setText('kpi-my-communities', communities.length); setBadge('badge-my-communities', communities.length); + renderInsights(); + if (communities.length === 0) { container.innerHTML = `

You haven't joined any communities yet

@@ -349,6 +457,26 @@ import { API_BASE as apiBase } from '../lib/api'; font-size: 0.875rem; } + .insights-body { + display: grid; + gap: 0.75rem; + } + + .insights-title { + font-weight: 700; + letter-spacing: -0.01em; + } + + .insights-list { + margin: 0; + padding-left: 1.1rem; + color: var(--color-text-muted); + font-size: 0.9375rem; + line-height: 1.5; + display: grid; + gap: 0.35rem; + } + .activity-item { display: flex; align-items: center;