Files
elegalsoftware/apps/web/src/pages/blog/BlogPostPage.tsx
T
Leon SerfatyandClaude Fable 5 1f249dc126
CI / build-and-test (push) Has been cancelled
SEO prerender pipeline + register AI routes and tighten CSP
- Static prerender + sitemap/robots for marketing, blog, and tool pages
  (entry-server, Seo component, prerender/sitemap scripts, NotFoundPage)
- Register aiRoutes and extend CSP for Turnstile + Spaces image hosts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 13:37:05 -04:00

128 lines
4.4 KiB
TypeScript

import { Link, useParams } from 'react-router-dom';
import { ArrowLeft, Clock, Info, AlertTriangle } from 'lucide-react';
import { PublicLayout } from '@/components/public/PublicLayout';
import { getPost, type Block } from '@/content/posts';
import { formatDate } from '@/lib/format';
import { cn } from '@/lib/cn';
export default function BlogPostPage() {
const { slug } = useParams<{ slug: string }>();
const post = slug ? getPost(slug) : undefined;
if (!post) {
return (
<PublicLayout>
<section className="container py-24 max-w-2xl text-center">
<h1 className="text-2xl font-bold text-ink-950 font-display">Post not found</h1>
<Link to="/blog" className="mt-4 inline-block text-brand-600 hover:text-brand-700">
Back to blog
</Link>
</section>
</PublicLayout>
);
}
return (
<PublicLayout>
<article className="container py-12 max-w-3xl">
<Link to="/blog" className="inline-flex items-center gap-1.5 text-sm text-ink-500 hover:text-ink-800 mb-6">
<ArrowLeft className="h-4 w-4" />
All posts
</Link>
<header className="mb-10">
<p className="text-xs uppercase tracking-wider text-brand-600 font-semibold">Blog</p>
<h1 className="mt-3 text-3xl md:text-5xl font-bold text-ink-950 font-display leading-tight">
{post.title}
</h1>
<p className="mt-4 text-lg text-ink-600 leading-relaxed">{post.description}</p>
<div className="mt-6 flex items-center gap-3 text-sm text-ink-500">
<span>{post.author}</span>
<span className="text-ink-300">·</span>
<time dateTime={post.publishedAt}>{formatDate(post.publishedAt)}</time>
<span className="text-ink-300">·</span>
<span className="inline-flex items-center gap-1.5">
<Clock className="h-3.5 w-3.5" />
{post.readMinutes} min read
</span>
</div>
</header>
{post.coverImage && (
<img
src={post.coverImage}
alt=""
className="mb-10 aspect-[16/9] w-full rounded-2xl border border-ink-100 object-cover"
/>
)}
<div className="space-y-5">
{post.body.map((b, i) => (
<RenderBlock key={i} block={b} />
))}
</div>
<footer className="mt-16 border-t border-ink-100 pt-8 flex items-center justify-between">
<Link to="/blog" className="text-sm text-brand-600 hover:text-brand-700">
All posts
</Link>
<Link to="/signup" className="btn-primary text-sm">
Try eLegal Software free
</Link>
</footer>
</article>
</PublicLayout>
);
}
function RenderBlock({ block }: { block: Block }) {
switch (block.type) {
case 'p':
return <p className="text-ink-800 leading-relaxed">{block.text}</p>;
case 'h2':
return <h2 className="mt-10 text-2xl md:text-3xl font-bold text-ink-950 font-display">{block.text}</h2>;
case 'h3':
return <h3 className="mt-8 text-xl font-semibold text-ink-900 font-display">{block.text}</h3>;
case 'ul':
return (
<ul className="list-disc pl-6 space-y-2 text-ink-800">
{block.items.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
);
case 'ol':
return (
<ol className="list-decimal pl-6 space-y-2 text-ink-800">
{block.items.map((item, i) => (
<li key={i}>{item}</li>
))}
</ol>
);
case 'quote':
return (
<blockquote className="border-l-4 border-brand-500 pl-4 italic text-ink-700">{block.text}</blockquote>
);
case 'callout': {
const tone = block.tone;
const Icon = tone === 'amber' ? AlertTriangle : Info;
return (
<div
className={cn(
'rounded-2xl p-5 my-2 flex items-start gap-3',
tone === 'amber'
? 'border border-amber-200 bg-amber-50/50 text-amber-900'
: 'border border-brand-200 bg-brand-50/50 text-ink-800',
)}
>
<Icon className={cn('h-5 w-5 flex-none mt-0.5', tone === 'amber' ? 'text-amber-600' : 'text-brand-600')} />
<div>
<p className="font-semibold">{block.title}</p>
<p className="mt-1 text-sm leading-relaxed">{block.text}</p>
</div>
</div>
);
}
}
}