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:
|
environment:
|
||||||
INTERNAL_API_BASE: http://backend:3000
|
INTERNAL_API_BASE: http://backend:3000
|
||||||
API_BASE: ${API_BASE:-http://localhost:3001}
|
API_BASE: ${API_BASE:-http://localhost:3001}
|
||||||
|
PUBLIC_DEMO_SITE: ${PUBLIC_DEMO_SITE:-false}
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,16 @@ import { DEFAULT_THEME, themes as themeRegistry } from '../lib/themes';
|
||||||
import { API_BASE as apiBase } from '../lib/api';
|
import { API_BASE as apiBase } from '../lib/api';
|
||||||
import VotingIcons from '../components/icons/VotingIcons.astro';
|
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 { title } = Astro.props;
|
||||||
|
|
||||||
|
const publicDemoSite = isEnabled((globalThis as any).process?.env?.PUBLIC_DEMO_SITE);
|
||||||
|
|
||||||
const themes = Object.fromEntries(
|
const themes = Object.fromEntries(
|
||||||
Object.entries(themeRegistry).map(([id, t]) => [id, { isDark: t.isDark, colors: t.colors }]),
|
Object.entries(themeRegistry).map(([id, t]) => [id, { isDark: t.isDark, colors: t.colors }]),
|
||||||
);
|
);
|
||||||
|
|
@ -84,7 +92,7 @@ const { title } = Astro.props;
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-auth" id="nav-auth">
|
<div class="nav-auth" id="nav-auth">
|
||||||
<a href="/login">Login</a>
|
<a href="/login">Login</a>
|
||||||
<a href="/register" class="btn-register">Register</a>
|
{!publicDemoSite ? <a href="/register" class="btn-register">Register</a> : null}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,16 @@ interface Props {
|
||||||
|
|
||||||
import { DEFAULT_THEME, themes as themeRegistry } from '../lib/themes';
|
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 { 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(
|
const themes = Object.fromEntries(
|
||||||
Object.entries(themeRegistry).map(([id, t]) => [id, { isDark: t.isDark, colors: t.colors }]),
|
Object.entries(themeRegistry).map(([id, t]) => [id, { isDark: t.isDark, colors: t.colors }]),
|
||||||
);
|
);
|
||||||
|
|
@ -83,7 +91,7 @@ const defaultTheme = DEFAULT_THEME;
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-actions">
|
<div class="nav-actions">
|
||||||
<a href="/demo" class="btn-demo">Explore Demo</a>
|
<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>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</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 Layout from '../layouts/Layout.astro';
|
||||||
import { API_BASE as apiBase } from '../lib/api';
|
import { API_BASE as apiBase } from '../lib/api';
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
export const prerender = false;
|
||||||
import PublicLayout from '../layouts/PublicLayout.astro';
|
import PublicLayout from '../layouts/PublicLayout.astro';
|
||||||
import { API_BASE } from '../lib/api';
|
import { API_BASE } from '../lib/api';
|
||||||
---
|
---
|
||||||
|
|
@ -55,7 +56,7 @@ import { API_BASE } from '../lib/api';
|
||||||
<div class="card-stats" id="stats-aurora">
|
<div class="card-stats" id="stats-aurora">
|
||||||
<span class="loading-text">Loading...</span>
|
<span class="loading-text">Loading...</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="community-card">
|
<div class="community-card">
|
||||||
|
|
@ -71,7 +72,7 @@ import { API_BASE } from '../lib/api';
|
||||||
<div class="card-stats" id="stats-civic-commons">
|
<div class="card-stats" id="stats-civic-commons">
|
||||||
<span class="loading-text">Loading...</span>
|
<span class="loading-text">Loading...</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="community-card">
|
<div class="community-card">
|
||||||
|
|
@ -87,7 +88,7 @@ import { API_BASE } from '../lib/api';
|
||||||
<div class="card-stats" id="stats-makers">
|
<div class="card-stats" id="stats-makers">
|
||||||
<span class="loading-text">Loading...</span>
|
<span class="loading-text">Loading...</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -103,7 +104,7 @@ import { API_BASE } from '../lib/api';
|
||||||
Navigate communities, read proposals, view voting results, and explore
|
Navigate communities, read proposals, view voting results, and explore
|
||||||
delegation networks without any account. Full read access is public.
|
delegation networks without any account. Full read access is public.
|
||||||
</p>
|
</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>
|
||||||
|
|
||||||
<div class="access-mode">
|
<div class="access-mode">
|
||||||
|
|
@ -199,8 +200,8 @@ import { API_BASE } from '../lib/api';
|
||||||
Use demo accounts to participate.
|
Use demo accounts to participate.
|
||||||
</p>
|
</p>
|
||||||
<div class="cta-buttons">
|
<div class="cta-buttons">
|
||||||
<a href="/communities" class="btn-primary btn-large">Enter the Demo</a>
|
<a href="/demo?enter=1&next=/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=/login" class="btn-secondary">Sign In with Demo Account</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -787,7 +788,7 @@ import { API_BASE } from '../lib/api';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Demo login handler
|
// Demo login handler
|
||||||
async function handleDemoLogin(username) {
|
async function loginDemoUser(username) {
|
||||||
const btn = document.querySelector('[data-username="' + username + '"]');
|
const btn = document.querySelector('[data-username="' + username + '"]');
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.classList.add('loading');
|
btn.classList.add('loading');
|
||||||
|
|
@ -810,22 +811,10 @@ import { API_BASE } from '../lib/api';
|
||||||
throw new Error('Login failed');
|
throw new Error('Login failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const result = await response.json();
|
||||||
|
localStorage.setItem('token', result.token);
|
||||||
// Store token in localStorage (use 'token' to match rest of app)
|
localStorage.setItem('user', JSON.stringify(result.user));
|
||||||
localStorage.setItem('token', data.token);
|
window.location.href = '/demo?enter=1&next=/communities';
|
||||||
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);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Demo login failed:', error);
|
console.error('Demo login failed:', error);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
|
|
@ -841,6 +830,8 @@ import { API_BASE } from '../lib/api';
|
||||||
btn.querySelector('.btn-info span').textContent = texts[username] || '';
|
btn.querySelector('.btn-info span').textContent = texts[username] || '';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alert('Demo login failed. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -852,7 +843,7 @@ import { API_BASE } from '../lib/api';
|
||||||
btn.addEventListener('click', function() {
|
btn.addEventListener('click', function() {
|
||||||
var username = btn.dataset.username;
|
var username = btn.dataset.username;
|
||||||
if (username) {
|
if (username) {
|
||||||
handleDemoLogin(username);
|
loginDemoUser(username);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})(buttons[i]);
|
})(buttons[i]);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
---
|
---
|
||||||
|
export const prerender = false;
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
import { API_BASE as apiBase } from '../lib/api';
|
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">
|
<Layout title="Login">
|
||||||
|
|
@ -19,9 +28,11 @@ import { API_BASE as apiBase } from '../lib/api';
|
||||||
<div id="error" class="error"></div>
|
<div id="error" class="error"></div>
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</form>
|
</form>
|
||||||
|
{!publicDemoSite ? (
|
||||||
<p class="alt-action">
|
<p class="alt-action">
|
||||||
Don't have an account? <a href="/register">Register</a>
|
Don't have an account? <a href="/register">Register</a>
|
||||||
</p>
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
@ -56,7 +67,8 @@ import { API_BASE as apiBase } from '../lib/api';
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
localStorage.setItem('token', result.token);
|
localStorage.setItem('token', result.token);
|
||||||
localStorage.setItem('user', JSON.stringify(result.user));
|
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) {
|
} catch (err) {
|
||||||
errorEl.textContent = 'Connection error. Is the backend running?';
|
errorEl.textContent = 'Connection error. Is the backend running?';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
export const prerender = false;
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
import { API_BASE as apiBase } from '../lib/api';
|
import { API_BASE as apiBase } from '../lib/api';
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
export const prerender = false;
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
import { API_BASE as apiBase } from '../lib/api';
|
import { API_BASE as apiBase } from '../lib/api';
|
||||||
---
|
---
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue