Replace Astro hub build with standalone hub/index.html
This commit is contained in:
+279
@@ -0,0 +1,279 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it" style="background:#09090b">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Bincio</title>
|
||||
<script>
|
||||
(function () {
|
||||
var palettes = {
|
||||
default: { accent: '#60a5fa', dim: 'rgba(96,165,250,0.15)' },
|
||||
giro: { accent: '#f472b6', dim: 'rgba(244,114,182,0.15)' },
|
||||
tour: { accent: '#facc15', dim: 'rgba(250,204,21,0.15)' },
|
||||
vuelta: { accent: '#ef4444', dim: 'rgba(239,68,68,0.15)' },
|
||||
};
|
||||
var races = [
|
||||
{ key: 'giro', start: [4, 8], end: [5, 1] },
|
||||
{ key: 'tour', start: [5, 27], end: [6, 19] },
|
||||
{ key: 'vuelta', start: [7, 15], end: [8, 6] },
|
||||
];
|
||||
function autoKey() {
|
||||
var now = new Date(), y = now.getFullYear();
|
||||
for (var i = 0; i < races.length; i++) {
|
||||
var r = races[i];
|
||||
if (now >= new Date(y, r.start[0], r.start[1]) &&
|
||||
now < new Date(y, r.end[0], r.end[1] + 1)) return r.key;
|
||||
}
|
||||
return 'default';
|
||||
}
|
||||
var p = palettes[autoKey()];
|
||||
document.documentElement.style.setProperty('--accent', p.accent);
|
||||
document.documentElement.style.setProperty('--accent-dim', p.dim);
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--accent: #60a5fa;
|
||||
--accent-dim: rgba(96,165,250,0.15);
|
||||
--bg: #09090b;
|
||||
--bg-card: #18181b;
|
||||
--border: #27272a;
|
||||
--border-hover: #52525b;
|
||||
--text: #ffffff;
|
||||
--text-muted: #71717a;
|
||||
--text-dim: #3f3f46;
|
||||
--red: #f87171;
|
||||
}
|
||||
html, body {
|
||||
min-height: 100vh;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
body { visibility: hidden; }
|
||||
|
||||
.wrap {
|
||||
max-width: 384px;
|
||||
margin: 0 auto;
|
||||
padding: 4rem 1rem 2rem;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
text-align: center;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.875rem;
|
||||
font-style: italic;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
input[type=text], input[type=password] {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
input[type=text]:focus, input[type=password]:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.field { margin-bottom: 1rem; }
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.btn-primary:hover { opacity: 0.85; }
|
||||
|
||||
.error {
|
||||
font-size: 0.875rem;
|
||||
color: var(--red);
|
||||
margin-bottom: 0.75rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.forgot {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.forgot a {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.forgot a:hover { color: var(--text-dim); }
|
||||
|
||||
/* App cards */
|
||||
.greeting {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.cards { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.card {
|
||||
display: block;
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
text-decoration: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.card:hover { border-color: var(--border-hover); }
|
||||
.card-title { font-weight: 600; color: var(--text); }
|
||||
.card-sub { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.125rem; }
|
||||
|
||||
.logout-wrap { text-align: center; margin-top: 2rem; }
|
||||
.btn-logout {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-dim);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.btn-logout:hover { color: var(--text-muted); }
|
||||
|
||||
#hub-apps { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
<div id="hub-login">
|
||||
<p class="tagline">mangia<br>bevi<br>stai calmo<br>non strappare</p>
|
||||
<h1>Bincio</h1>
|
||||
<form id="login-form">
|
||||
<div class="field">
|
||||
<label for="handle">Handle</label>
|
||||
<input id="handle" name="handle" type="text" autocomplete="username" placeholder="your handle" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<p id="login-error" class="error"></p>
|
||||
<button type="submit" class="btn-primary">Sign in</button>
|
||||
</form>
|
||||
<p class="forgot">
|
||||
<a href="https://activity.bincio.org/reset-password/">Forgot password?</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="hub-apps">
|
||||
<p id="hub-greeting" class="greeting"></p>
|
||||
<p class="tagline">mangia<br>bevi<br>stai calmo<br>non strappare</p>
|
||||
<div id="hub-cards" class="cards"></div>
|
||||
<div class="logout-wrap">
|
||||
<button id="hub-logout" class="btn-logout">Log out</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ACTIVITY_URL = 'https://activity.bincio.org';
|
||||
const WIKI_URL = 'https://wiki.bincio.org';
|
||||
|
||||
const loginDiv = document.getElementById('hub-login');
|
||||
const appsDiv = document.getElementById('hub-apps');
|
||||
const greeting = document.getElementById('hub-greeting');
|
||||
const cardsDiv = document.getElementById('hub-cards');
|
||||
const form = document.getElementById('login-form');
|
||||
const errEl = document.getElementById('login-error');
|
||||
|
||||
function appCard(title, sub, href) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'card';
|
||||
a.href = href;
|
||||
a.innerHTML = `<p class="card-title">${title}</p><p class="card-sub">${sub}</p>`;
|
||||
return a;
|
||||
}
|
||||
|
||||
function showApps(user) {
|
||||
loginDiv.style.display = 'none';
|
||||
appsDiv.style.display = '';
|
||||
greeting.textContent = 'Ciao ' + (user.display_name || user.handle);
|
||||
cardsDiv.innerHTML = '';
|
||||
if (user.activity_access)
|
||||
cardsDiv.appendChild(appCard('BincioActivity', 'Tracks, strade e numeri', ACTIVITY_URL));
|
||||
if (user.wiki_access)
|
||||
cardsDiv.appendChild(appCard('BincioWiki', 'La memoria collettiva del gruppo', WIKI_URL));
|
||||
}
|
||||
|
||||
function showLogin() {
|
||||
appsDiv.style.display = 'none';
|
||||
loginDiv.style.display = '';
|
||||
}
|
||||
|
||||
fetch('/api/me', { credentials: 'include' })
|
||||
.then(async r => {
|
||||
document.body.style.visibility = '';
|
||||
if (r.ok) showApps(await r.json());
|
||||
})
|
||||
.catch(() => { document.body.style.visibility = ''; });
|
||||
|
||||
form.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
errEl.style.display = 'none';
|
||||
const handle = form.querySelector('#handle').value.trim();
|
||||
const password = form.querySelector('#password').value;
|
||||
try {
|
||||
const r = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ handle, password }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
const d = await r.json().catch(() => ({}));
|
||||
errEl.textContent = d.detail ?? 'Invalid credentials';
|
||||
errEl.style.display = '';
|
||||
return;
|
||||
}
|
||||
showApps(await r.json());
|
||||
} catch {
|
||||
errEl.textContent = 'Could not reach server';
|
||||
errEl.style.display = '';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('hub-logout').addEventListener('click', async () => {
|
||||
try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }); } catch (_) {}
|
||||
form.querySelector('#handle').value = '';
|
||||
form.querySelector('#password').value = '';
|
||||
showLogin();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user