Initial commit — eLegal Software monorepo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-04-26 02:42:42 -04:00
co-authored by Claude Sonnet 4.6
commit 0700d54225
160 changed files with 22771 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
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>
<span>{formatDate(post.publishedAt)}</span>
<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>
<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>
);
}
}
}