From 3b091653f26a8a3d33184a9cea0669115b249f90 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Thu, 29 Jan 2026 11:38:43 +0100 Subject: [PATCH] demo: add brochure gated mode --- compose/demo.yml | 1 + frontend/src/layouts/Layout.astro | 10 +++- frontend/src/layouts/PublicLayout.astro | 10 +++- frontend/src/middleware.ts | 65 +++++++++++++++++++++++++ frontend/src/pages/communities.astro | 1 + frontend/src/pages/demo.astro | 39 ++++++--------- frontend/src/pages/login.astro | 20 ++++++-- frontend/src/pages/proposals.astro | 1 + frontend/src/pages/register.astro | 1 + 9 files changed, 118 insertions(+), 30 deletions(-) create mode 100644 frontend/src/middleware.ts diff --git a/compose/demo.yml b/compose/demo.yml index 2955c1c..bd036b4 100644 --- a/compose/demo.yml +++ b/compose/demo.yml @@ -57,6 +57,7 @@ services: environment: INTERNAL_API_BASE: http://backend:3000 API_BASE: ${API_BASE:-http://localhost:3001} + PUBLIC_DEMO_SITE: ${PUBLIC_DEMO_SITE:-false} depends_on: - backend diff --git a/frontend/src/layouts/Layout.astro b/frontend/src/layouts/Layout.astro index 9630c5a..839bcdd 100644 --- a/frontend/src/layouts/Layout.astro +++ b/frontend/src/layouts/Layout.astro @@ -7,8 +7,16 @@ import { DEFAULT_THEME, themes as themeRegistry } from '../lib/themes'; import { API_BASE as apiBase } from '../lib/api'; import VotingIcons from '../components/icons/VotingIcons.astro'; +function isEnabled(v: string | undefined): boolean { + if (!v) return false; + const n = v.trim().toLowerCase(); + return n === '1' || n === 'true' || n === 'yes' || n === 'on'; +} + const { title } = Astro.props; +const publicDemoSite = isEnabled((globalThis as any).process?.env?.PUBLIC_DEMO_SITE); + const themes = Object.fromEntries( Object.entries(themeRegistry).map(([id, t]) => [id, { isDark: t.isDark, colors: t.colors }]), ); @@ -84,7 +92,7 @@ const { title } = Astro.props; diff --git a/frontend/src/layouts/PublicLayout.astro b/frontend/src/layouts/PublicLayout.astro index de1f65a..e1b1404 100644 --- a/frontend/src/layouts/PublicLayout.astro +++ b/frontend/src/layouts/PublicLayout.astro @@ -6,8 +6,16 @@ interface Props { import { DEFAULT_THEME, themes as themeRegistry } from '../lib/themes'; +function isEnabled(v: string | undefined): boolean { + if (!v) return false; + const n = v.trim().toLowerCase(); + return n === '1' || n === 'true' || n === 'yes' || n === 'on'; +} + const { title, description = "Likwid is a modular governance engine for distributed organizations, open source communities, and civic movements." } = Astro.props; +const publicDemoSite = isEnabled((globalThis as any).process?.env?.PUBLIC_DEMO_SITE); + const themes = Object.fromEntries( Object.entries(themeRegistry).map(([id, t]) => [id, { isDark: t.isDark, colors: t.colors }]), ); @@ -83,7 +91,7 @@ const defaultTheme = DEFAULT_THEME; diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts new file mode 100644 index 0000000..aa03b18 --- /dev/null +++ b/frontend/src/middleware.ts @@ -0,0 +1,65 @@ +import { defineMiddleware } from 'astro:middleware'; + +function isEnabled(v: string | undefined): boolean { + if (!v) return false; + const n = v.trim().toLowerCase(); + return n === '1' || n === 'true' || n === 'yes' || n === 'on'; +} + +const DEMO_COOKIE = 'likwid_demo_unlocked'; + +export const onRequest = defineMiddleware(async (context, next) => { + const publicDemoSite = isEnabled((globalThis as any).process?.env?.PUBLIC_DEMO_SITE); + if (!publicDemoSite) { + return next(); + } + + const url = new URL(context.request.url); + const path = url.pathname; + + if (path === '/demo' && url.searchParams.get('enter') === '1') { + let nextPath = url.searchParams.get('next') || '/communities'; + if (!nextPath.startsWith('/')) { + nextPath = '/communities'; + } + + context.cookies.set(DEMO_COOKIE, '1', { + path: '/', + httpOnly: true, + sameSite: 'lax', + maxAge: 60 * 60 * 12, + }); + + return context.redirect(nextPath); + } + + if (path.startsWith('/_astro') || path.startsWith('/favicon') || path.startsWith('/robots')) { + return next(); + } + + if (path === '/setup' || path === '/register') { + return context.redirect('/demo'); + } + + const isProtected = + path === '/login' || + path === '/dashboard' || + path === '/delegations' || + path === '/notifications' || + path === '/settings' || + path.startsWith('/admin') || + path.startsWith('/communities') || + path.startsWith('/proposals') || + path.startsWith('/users'); + + if (!isProtected) { + return next(); + } + + const unlocked = context.cookies.get(DEMO_COOKIE)?.value === '1'; + if (!unlocked) { + return context.redirect('/demo'); + } + + return next(); +}); diff --git a/frontend/src/pages/communities.astro b/frontend/src/pages/communities.astro index d9220a9..6c21d97 100644 --- a/frontend/src/pages/communities.astro +++ b/frontend/src/pages/communities.astro @@ -1,4 +1,5 @@ --- +export const prerender = false; import Layout from '../layouts/Layout.astro'; import { API_BASE as apiBase } from '../lib/api'; --- diff --git a/frontend/src/pages/demo.astro b/frontend/src/pages/demo.astro index 6b3a326..96f1d52 100644 --- a/frontend/src/pages/demo.astro +++ b/frontend/src/pages/demo.astro @@ -1,4 +1,5 @@ --- +export const prerender = false; import PublicLayout from '../layouts/PublicLayout.astro'; import { API_BASE } from '../lib/api'; --- @@ -55,7 +56,7 @@ import { API_BASE } from '../lib/api';
Loading...
- Explore Aurora → + Explore Aurora →
@@ -71,7 +72,7 @@ import { API_BASE } from '../lib/api';
Loading...
- Explore Civic Commons → + Explore Civic Commons →
@@ -87,7 +88,7 @@ import { API_BASE } from '../lib/api';
Loading...
- Explore Makers → + Explore Makers →
@@ -103,7 +104,7 @@ import { API_BASE } from '../lib/api'; Navigate communities, read proposals, view voting results, and explore delegation networks without any account. Full read access is public.

- Browse Communities → + Browse Communities →
@@ -199,8 +200,8 @@ import { API_BASE } from '../lib/api'; Use demo accounts to participate.

@@ -787,7 +788,7 @@ import { API_BASE } from '../lib/api'; } // Demo login handler - async function handleDemoLogin(username) { + async function loginDemoUser(username) { const btn = document.querySelector('[data-username="' + username + '"]'); if (btn) { btn.classList.add('loading'); @@ -810,22 +811,10 @@ import { API_BASE } from '../lib/api'; throw new Error('Login failed'); } - const data = await response.json(); - - // Store token in localStorage (use 'token' to match rest of app) - localStorage.setItem('token', data.token); - localStorage.setItem('user', JSON.stringify(data.user)); - - // Update button to show success - if (btn) { - btn.querySelector('.btn-info span').textContent = 'Success! Redirecting...'; - } - - // Redirect to dashboard or communities - setTimeout(() => { - window.location.href = '/communities'; - }, 500); - + const result = await response.json(); + localStorage.setItem('token', result.token); + localStorage.setItem('user', JSON.stringify(result.user)); + window.location.href = '/demo?enter=1&next=/communities'; } catch (error) { console.error('Demo login failed:', error); if (btn) { @@ -841,6 +830,8 @@ import { API_BASE } from '../lib/api'; btn.querySelector('.btn-info span').textContent = texts[username] || ''; }, 2000); } + + alert('Demo login failed. Please try again.'); } } @@ -852,7 +843,7 @@ import { API_BASE } from '../lib/api'; btn.addEventListener('click', function() { var username = btn.dataset.username; if (username) { - handleDemoLogin(username); + loginDemoUser(username); } }); })(buttons[i]); diff --git a/frontend/src/pages/login.astro b/frontend/src/pages/login.astro index fd52219..adeb555 100644 --- a/frontend/src/pages/login.astro +++ b/frontend/src/pages/login.astro @@ -1,6 +1,15 @@ --- +export const prerender = false; import Layout from '../layouts/Layout.astro'; import { API_BASE as apiBase } from '../lib/api'; + +function isEnabled(v: string | undefined): boolean { + if (!v) return false; + const n = v.trim().toLowerCase(); + return n === '1' || n === 'true' || n === 'yes' || n === 'on'; +} + +const publicDemoSite = isEnabled((globalThis as any).process?.env?.PUBLIC_DEMO_SITE); --- @@ -19,9 +28,11 @@ import { API_BASE as apiBase } from '../lib/api';
-

- Don't have an account? Register -

+ {!publicDemoSite ? ( +

+ Don't have an account? Register +

+ ) : null}
@@ -56,7 +67,8 @@ import { API_BASE as apiBase } from '../lib/api'; const result = await res.json(); localStorage.setItem('token', result.token); localStorage.setItem('user', JSON.stringify(result.user)); - window.location.href = '/'; + const next = new URLSearchParams(window.location.search).get('next') || '/communities'; + window.location.href = next; } catch (err) { errorEl.textContent = 'Connection error. Is the backend running?'; } diff --git a/frontend/src/pages/proposals.astro b/frontend/src/pages/proposals.astro index 9ef33e3..4dc32ee 100644 --- a/frontend/src/pages/proposals.astro +++ b/frontend/src/pages/proposals.astro @@ -1,4 +1,5 @@ --- +export const prerender = false; import Layout from '../layouts/Layout.astro'; import { API_BASE as apiBase } from '../lib/api'; --- diff --git a/frontend/src/pages/register.astro b/frontend/src/pages/register.astro index bd85763..8857bfa 100644 --- a/frontend/src/pages/register.astro +++ b/frontend/src/pages/register.astro @@ -1,4 +1,5 @@ --- +export const prerender = false; import Layout from '../layouts/Layout.astro'; import { API_BASE as apiBase } from '../lib/api'; ---