import { useSeoMeta } from '@unhead/react'; import { MainLayout } from '@/components/layout/MainLayout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { RelaySelector } from '@/components/RelaySelector'; import { useBlogPosts, type BlogPost } from '@/hooks/useBlogPosts'; import { BookOpen, Calendar, Clock, ArrowRight, FileText } from 'lucide-react'; import { Link } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; const Blog = () => { useSeoMeta({ title: 'Blog - Patrick Ulrich', description: 'Read Patrick\'s thoughts on Bitcoin, Nostr, and digital sovereignty.', }); const { data: blogPosts, isLoading, error } = useBlogPosts(); const formatDate = (timestamp: number) => { return new Date(timestamp * 1000).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); }; const estimateReadTime = (content: string) => { // Rough estimate: 200 words per minute const words = content.trim().split(/\s+/).length; const minutes = Math.ceil(words / 200); return `${minutes} min read`; }; const getExcerpt = (content: string, maxLength = 200) => { // Remove markdown formatting for a cleaner excerpt const cleaned = content .replace(/#{1,6}\s/g, '') // Remove headers .replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold .replace(/\*(.*?)\*/g, '$1') // Remove italic .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links, keep text .replace(/`([^`]+)`/g, '$1') // Remove inline code .trim(); if (cleaned.length <= maxLength) return cleaned; // Find the last complete word within the limit const truncated = cleaned.substring(0, maxLength); const lastSpace = truncated.lastIndexOf(' '); return (lastSpace > 0 ? truncated.substring(0, lastSpace) : truncated) + '...'; }; const getArticleUrl = (post: BlogPost) => { const naddr = nip19.naddrEncode({ identifier: post.dTag, pubkey: post.pubkey, kind: 30023, }); return `/blog/${naddr}`; }; // Get featured post (most recent or first with image) const featuredPost = blogPosts?.find(post => post.image) || blogPosts?.[0]; const otherPosts = blogPosts?.filter(post => post.id !== featuredPost?.id) || []; return (

Blog

Long-form thoughts on Bitcoin, Nostr, and digital sovereignty. All articles are published on Nostr using NIP-23.

{isLoading ? (
{/* Featured Post Skeleton */}
{/* Other Posts Skeleton */}
{Array.from({ length: 6 }).map((_, i) => (
))}
) : error ? (

Failed to load blog posts

) : !blogPosts?.length ? (

No blog posts found. Try another relay?

) : (
{/* Featured Post */} {featuredPost && (

Featured Article

{featuredPost.image && (
{featuredPost.title
)} Featured {featuredPost.title || `Untitled Post #${featuredPost.dTag}`}

{featuredPost.summary || getExcerpt(featuredPost.content, 300)}

{formatDate(featuredPost.publishedAt || featuredPost.createdAt)} {estimateReadTime(featuredPost.content)}
{featuredPost.hashtags.length > 0 && (
{featuredPost.hashtags.slice(0, 3).map((tag) => ( #{tag} ))}
)}
)} {/* Other Posts Grid */} {otherPosts.length > 0 && (

Recent Articles

{otherPosts.map((post) => ( {post.title || `Untitled Post #${post.dTag}`}
{formatDate(post.publishedAt || post.createdAt)} {estimateReadTime(post.content)}

{post.summary || getExcerpt(post.content)}

{post.hashtags.length > 0 && (
{post.hashtags.slice(0, 2).map((tag) => ( #{tag} ))}
)}
))}
)}
)}
); }; export default Blog;