import Link from 'next/link'; import Image from 'next/image'; import type { Metadata } from 'next'; import { type JournalEntrySummary } from '@/lib/journal'; import { listJournalEntries } from '@/lib/admin/journal-service'; import { isAdminAuthenticated } from '@/lib/admin/auth'; import { immich } from '@/lib/immich'; import { imageUrl, assetPlaceholder } from '@/lib/urls'; import { BackLink } from '@/components/BackLink'; import { IconBook } from '@/components/Icons'; import './journal.css'; export const dynamic = 'force-dynamic'; export const metadata: Metadata = { title: 'Journal', description: 'Photo essays, travel stories, and behind-the-scenes journals.', }; interface EnrichedJournalEntry extends JournalEntrySummary { coverUrl?: string; blurDataURL?: string; dominantColor?: string; } export default async function JournalIndexPage() { const isAuthedAdmin = await isAdminAuthenticated(); const allEntries = await listJournalEntries(); // Non-admins only see published entries const visibleEntries = isAuthedAdmin ? allEntries : allEntries.filter((e) => !e.frontmatter.draft); // Fetch cover image placeholders and URLs const enrichedEntries: EnrichedJournalEntry[] = await Promise.all( visibleEntries.map(async (entry) => { if (!entry.frontmatter.coverAssetId) { return entry; } try { const asset = await immich.getAssetInfo(entry.frontmatter.coverAssetId); const ph = asset ? assetPlaceholder(asset) : null; return { ...entry, coverUrl: imageUrl(entry.frontmatter.coverAssetId, 'preview'), blurDataURL: ph?.blurDataURL, dominantColor: ph?.dominantColor, }; } catch { return { ...entry, coverUrl: imageUrl(entry.frontmatter.coverAssetId, 'preview'), }; } }), ); return (
Photo essays, visual stories, and field notes.
No journal entries published yet.
{entry.frontmatter.subtitle}
)} {/* extractExcerpt() falls back to the subtitle when there is one, so rendering both printed the same sentence twice. */} {entry.excerpt && entry.excerpt !== entry.frontmatter.subtitle && ({entry.excerpt}
)}