Rewrite as a Spring Boot app on the Bennett platform
build-and-publish / build (push) Successful in 1m38s

Restores the real backend lost with Supabase (posts, authors, comments, subscriptions, admin) in
Postgres, seeds the existing markdown posts, and rebuilds the site as a Vite/React SPA served by
Spring — keeping the original styling (serif + tan accent) and content.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-22 20:35:51 -05:00
co-authored by Claude Opus 4.8
parent ba054ab2eb
commit cbe8a764b0
128 changed files with 3997 additions and 7685 deletions
+79
View File
@@ -0,0 +1,79 @@
import { useState } from 'react';
import { useAsync } from '../lib/useAsync';
import { cn } from '../lib/utils';
interface Chapter {
title: string;
paragraphs: Record<string, string>;
}
interface Confession {
title: string;
chapters: Record<string, Chapter>;
}
// Served from public/ rather than bundled — it's a large static document.
const loadConfession = async (): Promise<Confession> => {
const res = await fetch('/data/1689-confession.json');
if (!res.ok) throw new Error('could not load the confession');
return res.json();
};
export default function ConfessionPage() {
const { data, loading, error } = useAsync(loadConfession, []);
const [selected, setSelected] = useState<string | null>(null);
if (loading) return <p className="text-primary-500">Loading the confession</p>;
if (error || !data) return <p className="text-primary-700">The confession could not be loaded.</p>;
const numbers = Object.keys(data.chapters).sort((a, b) => Number(a) - Number(b));
const current = selected ?? numbers[0];
const chapter = data.chapters[current];
return (
<div className="mx-auto max-w-6xl">
<h1 className="mb-6 text-3xl font-bold md:text-4xl">{data.title}</h1>
<div className="flex flex-col gap-8 md:flex-row">
<nav className="md:w-1/3">
<div className="card max-h-[70vh] overflow-y-auto">
<h2 className="mb-4 border-b border-primary-200 pb-2 text-xl font-bold">Chapters</h2>
<ol className="space-y-2">
{numbers.map((n) => (
<li key={n}>
<button
type="button"
onClick={() => setSelected(n)}
className={cn(
'w-full text-left text-sm transition-colors',
current === n ? 'font-bold text-accent-dark' : 'text-primary-700 hover:text-accent-dark',
)}
>
{n}. {data.chapters[n].title}
</button>
</li>
))}
</ol>
</div>
</nav>
<article className="md:w-2/3">
<div className="card">
<h2 className="mb-6 border-b border-primary-200 pb-2 text-2xl font-bold">
Chapter {current} {chapter.title}
</h2>
<ol className="space-y-5">
{Object.keys(chapter.paragraphs)
.sort((a, b) => Number(a) - Number(b))
.map((p) => (
<li key={p} className="leading-relaxed text-primary-700">
<span className="mr-2 font-bold text-accent-dark">{p}.</span>
{chapter.paragraphs[p]}
</li>
))}
</ol>
</div>
</article>
</div>
</div>
);
}