demo: add guided start here journeys

This commit is contained in:
Marco Allegretti 2026-02-02 18:27:02 +01:00
parent a98895d935
commit 49579e9286

View file

@ -32,6 +32,18 @@ const nextQuery = nextParam ? `&next=${encodeURIComponent(nextParam)}` : '';
<p>Loading demo status...</p> <p>Loading demo status...</p>
</div> </div>
<section class="demo-start" aria-labelledby="demo-start-title">
<h2 id="demo-start-title">Start here</h2>
<p class="section-intro">
Pick a short journey. Each link drops you into a real community or proposal, so you can learn by doing.
</p>
<div class="start-grid" id="demo-start-grid" aria-live="polite">
<div class="start-card ui-card ui-card-pad-lg">
<p class="loading-text">Loading suggested journeys…</p>
</div>
</div>
</section>
<section class="demo-intro"> <section class="demo-intro">
<div class="intro-content"> <div class="intro-content">
<h2>Why a Demo Instance?</h2> <h2>Why a Demo Instance?</h2>
@ -283,6 +295,60 @@ const nextQuery = nextParam ? `&next=${encodeURIComponent(nextParam)}` : '';
line-height: 1.6; line-height: 1.6;
} }
.demo-start {
padding: 3rem 0;
border-bottom: 1px solid var(--color-border);
}
.demo-start h2 {
font-size: 1.75rem;
text-align: center;
margin-bottom: 0.75rem;
}
.start-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.25rem;
margin-top: 2rem;
}
@media (max-width: 900px) {
.start-grid {
grid-template-columns: 1fr;
}
}
.start-card {
display: flex;
flex-direction: column;
gap: 0.75rem;
text-decoration: none;
color: inherit;
}
.start-card:hover {
color: inherit;
}
.start-card h3 {
margin: 0;
font-size: 1.125rem;
}
.start-card p {
margin: 0;
color: var(--color-text-muted);
font-size: 0.9375rem;
line-height: 1.55;
}
.start-link {
margin-top: auto;
font-weight: 600;
color: var(--color-primary);
}
/* Intro Section */ /* Intro Section */
.demo-intro { .demo-intro {
padding: 3rem 0; padding: 3rem 0;
@ -701,6 +767,176 @@ const nextQuery = nextParam ? `&next=${encodeURIComponent(nextParam)}` : '';
const nextParam = currentUrl.searchParams.get('next') || ''; const nextParam = currentUrl.searchParams.get('next') || '';
const nextPath = nextParam && nextParam.startsWith('/') ? nextParam : ''; const nextPath = nextParam && nextParam.startsWith('/') ? nextParam : '';
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 normalizeStatus(status) {
const s = String(status || '').toLowerCase();
if (s === 'draft' || s === 'discussion' || s === 'voting' || s === 'closed') return s;
return 'draft';
}
function renderStartGrid(cards) {
const grid = document.getElementById('demo-start-grid');
if (!grid) return;
if (!cards || !cards.length) {
grid.innerHTML = '<div class="start-card ui-card ui-card-pad-lg"><p class="loading-text">No journeys available.</p></div>';
return;
}
grid.innerHTML = cards.map(function(c) {
return (
'<a href="' + c.href + '" class="start-card ui-card ui-card-pad-lg ui-card-interactive">' +
'<h3>' + escapeHtml(c.title) + '</h3>' +
'<p>' + escapeHtml(c.body) + '</p>' +
'<span class="start-link">' + escapeHtml(c.cta) + '</span>' +
'</a>'
);
}).join('');
}
async function loadSuggestedJourneys() {
const grid = document.getElementById('demo-start-grid');
if (!grid) return;
grid.innerHTML = '<div class="start-card ui-card ui-card-pad-lg"><p class="loading-text">Loading suggested journeys…</p></div>';
const fallback = function() {
renderStartGrid([
{
title: 'Explore a community overview',
body: 'See members, recent proposals, and moderation history in context.',
cta: 'Open Aurora →',
href: '/demo?enter=1&next=%2Fcommunities%2Faurora'
},
{
title: 'Browse proposals in one community',
body: 'Jump straight to a proposal list and pick a closed or voting item.',
cta: 'Open proposals →',
href: '/demo?enter=1&next=%2Fcommunities%2Faurora%2Fproposals'
},
{
title: 'Log in and participate',
body: 'Use a demo account to vote and see the member experience.',
cta: 'Log in →',
href: '/demo?enter=1&next=%2Flogin'
}
]);
};
try {
const statusRes = await fetch(API_BASE + '/api/demo/status');
const status = await statusRes.json();
if (!status || !status.demo_mode) {
fallback();
return;
}
const demoCommRes = await fetch(API_BASE + '/api/demo/communities');
const demoCommData = await demoCommRes.json();
const slugs = (demoCommData && demoCommData.communities ? demoCommData.communities.map(function(c) { return c.slug; }) : [])
.filter(Boolean);
const knownSlugs = slugs.length ? slugs : ['aurora', 'civic-commons', 'makers'];
const commRes = await fetch(API_BASE + '/api/communities');
const communities = await commRes.json();
const commBySlug = {};
for (var i = 0; i < knownSlugs.length; i++) {
var s = knownSlugs[i];
commBySlug[s] = (communities || []).find(function(c) { return c.slug === s; }) || null;
}
const proposalFetches = knownSlugs.map(function(s) {
const comm = commBySlug[s];
if (!comm || !comm.id) return Promise.resolve([]);
return fetch(API_BASE + '/api/communities/' + encodeURIComponent(String(comm.id)) + '/proposals')
.then(function(r) { return r.ok ? r.json() : []; })
.then(function(ps) {
if (!Array.isArray(ps)) return [];
return ps.map(function(p) {
return Object.assign({}, p, { community_slug: s, community_name: comm.name || s });
});
})
.catch(function() { return []; });
});
const lists = await Promise.all(proposalFetches);
const all = [].concat.apply([], lists);
const pickByStatus = function(target) {
for (var j = 0; j < all.length; j++) {
if (normalizeStatus(all[j].status) === target) return all[j];
}
return null;
};
const voting = pickByStatus('voting');
const closed = pickByStatus('closed');
const discussion = pickByStatus('discussion');
const cards = [];
cards.push({
title: 'Explore a community overview',
body: 'Start with a full picture: members, proposals, and governance activity in one place.',
cta: 'Open Aurora →',
href: '/demo?enter=1&next=%2Fcommunities%2Faurora'
});
if (voting && voting.id) {
cards.push({
title: 'Vote on an active decision',
body: 'Open a proposal that is currently in voting. Log in as Contributor to cast a ballot.',
cta: 'Open voting proposal →',
href: '/demo?enter=1&next=' + encodeURIComponent('/proposals/' + String(voting.id))
});
} else if (discussion && discussion.id) {
cards.push({
title: 'Read an ongoing discussion',
body: 'See how proposals evolve before the vote begins.',
cta: 'Open discussion proposal →',
href: '/demo?enter=1&next=' + encodeURIComponent('/proposals/' + String(discussion.id))
});
} else {
cards.push({
title: 'Browse proposals',
body: 'Pick a community and explore drafts, discussions, and votes.',
cta: 'Open proposals →',
href: '/demo?enter=1&next=%2Fcommunities%2Faurora%2Fproposals'
});
}
if (closed && closed.id) {
cards.push({
title: 'See a completed decision',
body: 'Review a closed proposal and inspect the outcome and voting results.',
cta: 'Open closed proposal →',
href: '/demo?enter=1&next=' + encodeURIComponent('/proposals/' + String(closed.id))
});
} else {
cards.push({
title: 'Log in and participate',
body: 'Use a demo account to vote, comment, and explore member-only areas.',
cta: 'Log in →',
href: '/demo?enter=1&next=%2Flogin'
});
}
renderStartGrid(cards.slice(0, 3));
} catch (e) {
fallback();
}
}
async function loadDemoData() { async function loadDemoData() {
try { try {
// Fetch demo status // Fetch demo status
@ -818,10 +1054,12 @@ const nextQuery = nextParam ? `&next=${encodeURIComponent(nextParam)}` : '';
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
loadDemoData(); loadDemoData();
loadSuggestedJourneys();
setupDemoLoginButtons(); setupDemoLoginButtons();
}); });
} else { } else {
loadDemoData(); loadDemoData();
loadSuggestedJourneys();
setupDemoLoginButtons(); setupDemoLoginButtons();
} }
</script> </script>