b913a2bf90
Refs gitea:Adriano/AI-OS#310 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
172 lines
8.5 KiB
Diff
172 lines
8.5 KiB
Diff
# Review package: 6b85872..71cd162
|
|
|
|
## Commits
|
|
71cd162 feat(header): identità utente SSR, link Accedi / menu utente
|
|
|
|
## Files changed
|
|
src/components/Header.astro | 33 +++++++++++++++++++++++++++++++++
|
|
src/layouts/Base.astro | 15 +++++++--------
|
|
2 files changed, 40 insertions(+), 8 deletions(-)
|
|
|
|
## Diff
|
|
diff --git a/src/components/Header.astro b/src/components/Header.astro
|
|
index 33a0b4c..eeb2cf3 100644
|
|
--- a/src/components/Header.astro
|
|
+++ b/src/components/Header.astro
|
|
@@ -1,23 +1,47 @@
|
|
---
|
|
import { site } from '../data/site';
|
|
import { t, contentImageUrl } from '../lib/content';
|
|
+interface Props {
|
|
+ user: { username: string; role: string } | null;
|
|
+ showTags: boolean;
|
|
+ canTags: boolean;
|
|
+}
|
|
+const { user, canTags } = Astro.props;
|
|
const path = Astro.url.pathname;
|
|
+const loginHref = `/login?next=${encodeURIComponent(path)}`;
|
|
+const areaHref = user?.role === 'superuser' ? '/admin/content' : '/admin';
|
|
---
|
|
<header class="hdr" id="site-header">
|
|
<div class="container hdr__in">
|
|
<a href="/" class="hdr__logo"><img src={contentImageUrl('header.logo.image', '/img/logo-dark.png')} alt={t('header.logo.alt')} width="150" /></a>
|
|
<nav class="hdr__nav" id="site-nav" aria-label="principale">
|
|
{site.navigation.map((item, i) => (
|
|
<a href={item.href} class:list={['hdr__link', { 'is-active': path === item.href || (item.href !== '/' && path.startsWith(item.href)) }]}>{t(`global.nav.${i + 1}.label`)}</a>
|
|
))}
|
|
</nav>
|
|
+ <div class="hdr__user">
|
|
+ {user ? (
|
|
+ <details class="umenu">
|
|
+ <summary class="umenu__btn">{user.username} ▾</summary>
|
|
+ <div class="umenu__panel">
|
|
+ <a href={areaHref}>Area riservata</a>
|
|
+ {canTags && (
|
|
+ <button type="button" class="umenu__tags" id="toggle-tags" data-slot="tags">Mostra tag</button>
|
|
+ )}
|
|
+ <a href="/admin/logout">Esci</a>
|
|
+ </div>
|
|
+ </details>
|
|
+ ) : (
|
|
+ <a class="hdr__login" href={loginHref}>Accedi</a>
|
|
+ )}
|
|
+ </div>
|
|
<button class="hdr__burger" id="nav-toggle" aria-label="Apri menu" aria-expanded="false">
|
|
<span></span><span></span><span></span>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<style>
|
|
.hdr { position: sticky; top: 0; z-index: 100; background: #fff; transition: box-shadow .2s; }
|
|
.hdr.is-scrolled { box-shadow: 0 2px 14px rgba(0,0,0,.08); }
|
|
.hdr__in { display: flex; align-items: center; justify-content: space-between; height: var(--header-h); }
|
|
@@ -25,20 +49,29 @@ const path = Astro.url.pathname;
|
|
.hdr__link { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); transition: color .3s ease; }
|
|
.hdr__link:hover, .hdr__link.is-active { color: var(--c-accent-dark); }
|
|
.hdr__burger { display: none; background: none; border: 0; cursor: pointer; padding: 8px; }
|
|
.hdr__burger span { display: block; width: 22px; height: 2px; background: var(--c-heading); margin: 5px 0; }
|
|
@media (max-width: 991px) {
|
|
.hdr__nav { display: none; position: absolute; top: var(--header-h); left: 0; right: 0; background: #fff; flex-direction: column; gap: 0; padding: 10px 20px 20px; box-shadow: 0 10px 20px rgba(0,0,0,.08); }
|
|
.hdr__nav.is-open { display: flex; }
|
|
.hdr__link { padding: 12px 0; }
|
|
.hdr__burger { display: block; }
|
|
}
|
|
+ .hdr__user { display: flex; align-items: center; }
|
|
+ .hdr__login { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); }
|
|
+ .hdr__login:hover { color: var(--c-accent-dark); }
|
|
+ .umenu { position: relative; }
|
|
+ .umenu__btn { cursor: pointer; list-style: none; font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--c-heading); }
|
|
+ .umenu__btn::-webkit-details-marker { display: none; }
|
|
+ .umenu__panel { position: absolute; right: 0; top: calc(100% + 8px); background: #fff; box-shadow: 0 8px 22px rgba(0,0,0,.12); min-width: 180px; display: flex; flex-direction: column; padding: 8px 0; z-index: 200; }
|
|
+ .umenu__panel a, .umenu__tags { padding: 10px 16px; text-align: left; background: none; border: 0; cursor: pointer; font: inherit; font-size: .8rem; text-decoration: none; color: var(--c-heading); }
|
|
+ .umenu__panel a:hover, .umenu__tags:hover { background: #f4f2ef; }
|
|
</style>
|
|
|
|
<script>
|
|
const header = document.getElementById('site-header')!;
|
|
const toggle = document.getElementById('nav-toggle')!;
|
|
const nav = document.getElementById('site-nav')!;
|
|
addEventListener('scroll', () => header.classList.toggle('is-scrolled', scrollY > 10), { passive: true });
|
|
toggle.addEventListener('click', () => {
|
|
const open = nav.classList.toggle('is-open');
|
|
toggle.setAttribute('aria-expanded', String(open));
|
|
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
|
|
index 365aabf..8a386dc 100644
|
|
--- a/src/layouts/Base.astro
|
|
+++ b/src/layouts/Base.astro
|
|
@@ -7,44 +7,43 @@ import '@fontsource/open-sans/400.css';
|
|
import '@fontsource/open-sans/600.css';
|
|
import '../styles/global.css';
|
|
import Header from '../components/Header.astro';
|
|
import Footer from '../components/Footer.astro';
|
|
import { getDb } from '../lib/db';
|
|
import { getSessionUser, SESSION_COOKIE } from '../lib/auth';
|
|
|
|
interface Props { title: string; description?: string }
|
|
const { title, description = 'InsanityLab — Performance. Balance. Longevity. Allenamento personalizzato, piccoli gruppi e benessere a Bologna.' } = Astro.props;
|
|
|
|
-// Overlay annotate: badge coi tag contenuto, solo per utenti loggati con ?annotate=1.
|
|
-let annotate = false;
|
|
-if (Astro.url.searchParams.get('annotate') === '1') {
|
|
- const tok = Astro.cookies.get(SESSION_COOKIE)?.value;
|
|
- annotate = Boolean(tok && getSessionUser(getDb(), tok));
|
|
-}
|
|
+const tok = Astro.cookies.get(SESSION_COOKIE)?.value;
|
|
+const user = tok ? getSessionUser(getDb(), tok) : null;
|
|
+const canTags = !!user && (user.role === 'superuser' || user.role === 'admin');
|
|
+const wantTags = Astro.cookies.get('showtags')?.value === '1' || Astro.url.searchParams.get('annotate') === '1';
|
|
+const showTags = canTags && wantTags;
|
|
---
|
|
<!doctype html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>{title} — InsanityLab</title>
|
|
<meta name="description" content={description} />
|
|
<meta property="og:title" content={`${title} — InsanityLab`} />
|
|
<meta property="og:description" content={description} />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:image" content={new URL('/img/og.jpg', Astro.site)} />
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
<link rel="sitemap" href="/sitemap.xml" />
|
|
<script is:inline>document.documentElement.classList.add('js');</script>
|
|
</head>
|
|
<body>
|
|
- <Header />
|
|
+ <Header user={user} showTags={showTags} canTags={canTags} />
|
|
<main><slot /></main>
|
|
<Footer />
|
|
<script>
|
|
// Reveal allo scroll: trigger a -100px dal viewport come il template Prowess.
|
|
const io = new IntersectionObserver((entries) => {
|
|
for (const e of entries) {
|
|
if (e.isIntersecting) {
|
|
e.target.classList.add('is-inview');
|
|
io.unobserve(e.target);
|
|
// A reveal concluso l'attributo va tolto, così la transition del reveal
|
|
@@ -67,21 +66,21 @@ if (Astro.url.searchParams.get('annotate') === '1') {
|
|
const r = el.getBoundingClientRect();
|
|
// Clamp entro il margine di sfondo (inset -120px) per non scoprire i bordi
|
|
const y = Math.max(-110, Math.min(110, (r.top + r.height / 2 - innerHeight / 2) * -0.3));
|
|
el.style.setProperty('--parallax-y', `${y.toFixed(1)}px`);
|
|
}
|
|
};
|
|
addEventListener('scroll', () => { if (!raf) raf = requestAnimationFrame(update); }, { passive: true });
|
|
update();
|
|
}
|
|
</script>
|
|
- {annotate && (
|
|
+ {showTags && (
|
|
<style is:global>
|
|
[data-tag] { outline: 1px dashed #b8a98c; outline-offset: 2px; }
|
|
.tag-badge { position: absolute; z-index: 9999; background: #3a2929; color: #fff;
|
|
font: 600 10px/1.6 monospace; padding: 0 6px; text-decoration: none; opacity: .85; }
|
|
.tag-badge:hover { opacity: 1; background: #9c8b70; }
|
|
</style>
|
|
<script is:inline>
|
|
document.querySelectorAll('[data-tag]').forEach((el) => {
|
|
const a = document.createElement('a');
|
|
a.className = 'tag-badge';
|