mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-02-09 21:13:09 +00:00
demo: add brochure gated mode
This commit is contained in:
parent
593e53a846
commit
3b091653f2
9 changed files with 118 additions and 30 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
</div>
|
||||
<div class="nav-auth" id="nav-auth">
|
||||
<a href="/login">Login</a>
|
||||
<a href="/register" class="btn-register">Register</a>
|
||||
{!publicDemoSite ? <a href="/register" class="btn-register">Register</a> : null}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
</div>
|
||||
<div class="nav-actions">
|
||||
<a href="/demo" class="btn-demo">Explore Demo</a>
|
||||
<a href="/login" class="btn-login">Sign In</a>
|
||||
{!publicDemoSite ? <a href="/login" class="btn-login">Sign In</a> : null}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
|||
65
frontend/src/middleware.ts
Normal file
65
frontend/src/middleware.ts
Normal file
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
export const prerender = false;
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import { API_BASE as apiBase } from '../lib/api';
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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';
|
|||
<div class="card-stats" id="stats-aurora">
|
||||
<span class="loading-text">Loading...</span>
|
||||
</div>
|
||||
<a href="/communities/aurora" class="card-link">Explore Aurora →</a>
|
||||
<a href="/demo?enter=1&next=/communities/aurora" class="card-link">Explore Aurora →</a>
|
||||
</div>
|
||||
|
||||
<div class="community-card">
|
||||
|
|
@ -71,7 +72,7 @@ import { API_BASE } from '../lib/api';
|
|||
<div class="card-stats" id="stats-civic-commons">
|
||||
<span class="loading-text">Loading...</span>
|
||||
</div>
|
||||
<a href="/communities/civic-commons" class="card-link">Explore Civic Commons →</a>
|
||||
<a href="/demo?enter=1&next=/communities/civic-commons" class="card-link">Explore Civic Commons →</a>
|
||||
</div>
|
||||
|
||||
<div class="community-card">
|
||||
|
|
@ -87,7 +88,7 @@ import { API_BASE } from '../lib/api';
|
|||
<div class="card-stats" id="stats-makers">
|
||||
<span class="loading-text">Loading...</span>
|
||||
</div>
|
||||
<a href="/communities/makers" class="card-link">Explore Makers →</a>
|
||||
<a href="/demo?enter=1&next=/communities/makers" class="card-link">Explore Makers →</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -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.
|
||||
</p>
|
||||
<a href="/communities" class="mode-link">Browse Communities →</a>
|
||||
<a href="/demo?enter=1&next=/communities" class="mode-link">Browse Communities →</a>
|
||||
</div>
|
||||
|
||||
<div class="access-mode">
|
||||
|
|
@ -199,8 +200,8 @@ import { API_BASE } from '../lib/api';
|
|||
Use demo accounts to participate.
|
||||
</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="/communities" class="btn-primary btn-large">Enter the Demo</a>
|
||||
<a href="/login" class="btn-secondary">Sign In with Demo Account</a>
|
||||
<a href="/demo?enter=1&next=/communities" class="btn-primary btn-large">Enter the Demo</a>
|
||||
<a href="/demo?enter=1&next=/login" class="btn-secondary">Sign In with Demo Account</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
---
|
||||
|
||||
<Layout title="Login">
|
||||
|
|
@ -19,9 +28,11 @@ import { API_BASE as apiBase } from '../lib/api';
|
|||
<div id="error" class="error"></div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
{!publicDemoSite ? (
|
||||
<p class="alt-action">
|
||||
Don't have an account? <a href="/register">Register</a>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
|
@ -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?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
export const prerender = false;
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import { API_BASE as apiBase } from '../lib/api';
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
export const prerender = false;
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import { API_BASE as apiBase } from '../lib/api';
|
||||
---
|
||||
|
|
|
|||
Loading…
Reference in a new issue