Files
narlblog/frontend/src/pages/index.astro
T
2026-05-18 13:51:33 +02:00

142 lines
5.3 KiB
Plaintext

---
import Layout from '../layouts/Layout.astro';
import PostList from '../components/react/PostList';
import EditableText from '../components/react/EditableText';
import AssetsButton from '../components/react/AssetsButton';
import { getSiteMode, copy } from '../lib/siteMode';
const siteMode = getSiteMode();
const c = copy(siteMode);
const API_URL = process.env.PUBLIC_API_URL || 'http://localhost:3000';
interface CoverImage {
url: string;
alt: string;
w?: number;
h?: number;
}
interface Post {
slug: string;
date: string;
title?: string;
summary?: string;
excerpt?: string;
tags: string[];
draft: boolean;
reading_time: number;
cover_image?: CoverImage;
image_count: number;
}
let posts: Post[] = [];
let error = '';
let siteConfig = {
welcome_title: "Works on view",
welcome_subtitle: "An ongoing arrangement of pieces, sketches, and stray observations."
};
const cookieHeader = Astro.request.headers.get('cookie') ?? '';
const fetchHeaders: HeadersInit = cookieHeader ? { cookie: cookieHeader } : {};
try {
const [postsRes, configRes] = await Promise.all([
fetch(`${API_URL}/api/posts`, { headers: fetchHeaders }),
fetch(`${API_URL}/api/config`)
]);
if (postsRes.ok) {
posts = await postsRes.json();
} else {
error = 'Failed to fetch works';
}
if (configRes.ok) {
siteConfig = await configRes.json();
}
} catch (e) {
const cause = (e as any)?.cause;
error = `Could not connect to backend at ${API_URL}: ${e instanceof Error ? e.message : String(e)}${cause ? ' (Cause: ' + (cause.message || cause.code || JSON.stringify(cause)) + ')' : ''}`;
console.error(error);
}
const isAdmin = Astro.cookies.get('admin_session')?.value === '1';
---
<Layout title={c.indexTitle} description={siteConfig.welcome_subtitle}>
{posts[0]?.cover_image?.url && (
<Fragment slot="head">
<link rel="preload" as="image" href={posts[0].cover_image.url} fetchpriority="high" />
</Fragment>
)}
<section class="relative mb-16 md:mb-24">
<div class="max-w-2xl">
<h1 class="font-display italic font-semibold text-[var(--text)] text-5xl md:text-7xl lg:text-8xl leading-[1.08] tracking-tight mb-6">
{isAdmin ? (
<EditableText
client:visible
initial={siteConfig.welcome_title}
fieldKey="welcome_title"
isAdmin
ariaLabel="welcome title"
className="inline"
/>
) : siteConfig.welcome_title}
</h1>
<p class="font-sans text-lg md:text-xl text-[var(--subtext1)] leading-relaxed max-w-xl">
{isAdmin ? (
<EditableText
client:visible
initial={siteConfig.welcome_subtitle}
fieldKey="welcome_subtitle"
isAdmin
ariaLabel="welcome subtitle"
multiline
className="inline"
/>
) : siteConfig.welcome_subtitle}
</p>
{isAdmin && (
<div class="mt-8 flex flex-wrap items-center gap-3">
<a href="/admin/editor" class="btn btn--primary">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
New work
</a>
<AssetsButton client:idle className="btn btn--ghost" iconSize={14} />
<a href="/admin/messages" class="btn btn--ghost">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
Messages
</a>
<a href="/admin/settings" class="btn btn--ghost">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/></svg>
Settings
</a>
</div>
)}
</div>
</section>
{error && (
<div class="glass p-6 md:p-8 text-center mb-12 border-[var(--red)]/40">
<p class="font-display italic text-[var(--red)] text-lg">{error}</p>
</div>
)}
{posts.length === 0 && !error && (
<div class="glass p-12 md:p-20 text-center max-w-2xl mx-auto">
<div class="font-display italic text-[var(--subtext0)] text-sm tracking-[0.3em] uppercase mb-4">Nothing here yet</div>
<p class="font-display italic text-[var(--text)] text-2xl md:text-3xl leading-snug mb-2">
No posts to show.
</p>
<p class="font-sans text-[var(--subtext1)] mt-4">Check back soon.</p>
{isAdmin && (
<a href="/admin/editor" class="btn btn--primary mt-8">Create the first post</a>
)}
</div>
)}
{posts.length > 0 && <PostList posts={posts} isAdmin={isAdmin} mode={siteMode} client:idle />}
</Layout>