feat: blog pubblico con filtri, paginazione e articolo

This commit is contained in:
2026-07-02 01:31:59 +02:00
parent e9840105a3
commit f3fb2fe005
4 changed files with 146 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
---
import Base from '../../layouts/Base.astro';
import Sidebar from '../../components/blog/Sidebar.astro';
import { getDb } from '../../lib/db';
import { getPublishedBySlug } from '../../lib/posts';
export const prerender = false;
const post = getPublishedBySlug(getDb(), Astro.params.slug!);
if (!post) return Astro.redirect('/404');
const fmt = new Intl.DateTimeFormat('it-IT', { dateStyle: 'long' });
---
<Base title={post.title} description={post.excerpt}>
<section class="section">
<div class="container art">
<article>
{post.cover && <img class="art__cover" src={post.cover} alt="" />}
<p class="art__meta">{post.published_at && fmt.format(new Date(post.published_at))} · {post.category}</p>
<h1>{post.title}</h1>
<div class="art__body" set:html={post.body_html} />
</article>
<Sidebar />
</div>
</section>
</Base>
<style>
.art { display: grid; grid-template-columns: 2.4fr 1fr; gap: 60px; }
.art__cover { width: 100%; margin-bottom: 24px; }
.art__meta { font-size: .8rem; color: var(--c-text-light); }
.art__body :global(img) { margin-block: 20px; }
.art__body :global(blockquote) { border-left: 3px solid var(--c-accent); margin: 24px 0; padding: 6px 0 6px 20px; font-style: italic; color: var(--c-heading); }
@media (max-width: 991px) { .art { grid-template-columns: 1fr; } }
</style>
+81
View File
@@ -0,0 +1,81 @@
---
import Base from '../../layouts/Base.astro';
import PageHero from '../../components/PageHero.astro';
import { getDb } from '../../lib/db';
import { listPublished } from '../../lib/posts';
import { CATEGORIES } from '../../lib/blog-const';
export const prerender = false;
const PER_PAGE = 9;
const url = Astro.url;
const category = url.searchParams.get('categoria') ?? undefined;
const page = Math.max(1, Number(url.searchParams.get('pagina') ?? '1') || 1);
const { items, total } = listPublished(getDb(), {
category: category && (CATEGORIES as readonly string[]).includes(category) ? category : undefined,
page, perPage: PER_PAGE,
});
const pages = Math.max(1, Math.ceil(total / PER_PAGE));
const fmt = (d: string | null) => d ? new Intl.DateTimeFormat('it-IT', { dateStyle: 'long' }).format(new Date(d)) : '';
const linkFor = (cat?: string, p = 1) => {
const q = new URLSearchParams();
if (cat) q.set('categoria', cat);
if (p > 1) q.set('pagina', String(p));
const s = q.toString();
return `/blog${s ? `?${s}` : ''}`;
};
---
<Base title="Blog & Edugo" description="Edugo è lo spazio di InsanityLab dedicato alla conoscenza: articoli, guide e approfondimenti su allenamento, nutrizione e salute.">
<PageHero title="Blog & Edugo" />
<section class="section">
<div class="container">
<div class="section-heading">
<h2>Blog & Edugo</h2>
<p>Edugo è lo spazio di InsanityLab dedicato alla conoscenza: articoli, guide e approfondimenti su allenamento, nutrizione e salute per capire il corpo e migliorare il proprio benessere. Perché la conoscenza è il primo passo del cambiamento.</p>
</div>
<nav class="bfilters" aria-label="categorie">
<a class:list={['bfilters__item', { 'is-active': !category }]} href={linkFor()}>Mostra tutto</a>
{CATEGORIES.map((c) => (
<a class:list={['bfilters__item', { 'is-active': category === c }]} href={linkFor(c)}>{c}</a>
))}
</nav>
{items.length === 0 ? (
<p style="text-align:center">Nessun articolo pubblicato{category ? ' in questa categoria' : ''}, torna a trovarci presto.</p>
) : (
<div class="bgrid">
{items.map((post) => (
<article class="bcard">
{post.cover && <a href={`/blog/${post.slug}`}><img src={post.cover} alt="" loading="lazy" /></a>}
<p class="bcard__date">{fmt(post.published_at)}</p>
<h3><a href={`/blog/${post.slug}`}>{post.title}</a></h3>
<p>{post.excerpt}</p>
<a class="bcard__more" href={`/blog/${post.slug}`}>Per saperne di più →</a>
</article>
))}
</div>
)}
{pages > 1 && (
<nav class="bpages" aria-label="paginazione">
{Array.from({ length: pages }, (_, i) => (
<a class:list={['bpages__n', { 'is-active': page === i + 1 }]} href={linkFor(category, i + 1)}>{i + 1}</a>
))}
</nav>
)}
</div>
</section>
</Base>
<style>
.bfilters { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-bottom: 50px; }
.bfilters__item { font-family: var(--font-heading); font-size: .7rem; letter-spacing: .16em; text-transform: uppercase; text-decoration: none; color: var(--c-text); padding: 10px 16px; }
.bfilters__item.is-active { background: var(--c-accent); color: #fff; }
.bgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 50px 40px; }
.bcard img { aspect-ratio: 4/3; object-fit: cover; width: 100%; margin-bottom: 14px; }
.bcard__date { font-size: .78rem; color: var(--c-text-light); margin-bottom: 4px; }
.bcard h3 a { text-decoration: none; }
.bcard__more { font-family: var(--font-heading); font-size: .7rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); }
.bpages { display: flex; gap: 8px; justify-content: center; margin-top: 50px; }
.bpages__n { font-family: var(--font-heading); text-decoration: none; padding: 8px 14px; color: var(--c-text); }
.bpages__n.is-active { background: var(--c-dark); color: #fff; }
@media (max-width: 991px) { .bgrid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 600px) { .bgrid { grid-template-columns: 1fr; } }
</style>