"use client"; import { useState, useEffect, useMemo } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { format } from "date-fns"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import Head from "next/head"; import Link from "next/link"; interface Message { id: string; date: string; content: string; timestamp: number; tags?: string[]; } export default function BlogPage() { const router = useRouter(); const searchParams = useSearchParams(); const selectedTag = searchParams.get("tag"); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(""); useEffect(() => { fetchMessages(); }, []); async function fetchMessages() { try { const res = await fetch("/api/messages"); const data = await res.json(); setMessages(data); } catch (err) { console.error("Failed to fetch:", err); } finally { setLoading(false); } } // Get all unique tags const allTags = useMemo(() => { const tags = new Set(); messages.forEach((m) => m.tags?.forEach((t) => tags.add(t))); return Array.from(tags).sort(); }, [messages]); // Filter messages const filteredMessages = useMemo(() => { let filtered = messages; if (selectedTag) { filtered = filtered.filter((m) => m.tags?.includes(selectedTag)); } if (searchQuery) { const query = searchQuery.toLowerCase(); filtered = filtered.filter( (m) => m.content.toLowerCase().includes(query) || m.tags?.some((t) => t.toLowerCase().includes(query)) ); } return filtered.sort((a, b) => b.timestamp - a.timestamp); }, [messages, selectedTag, searchQuery]); // Get featured post (most recent) const featuredPost = filteredMessages[0]; const regularPosts = filteredMessages.slice(1); // Parse title from content function getTitle(content: string): string { const lines = content.split("\n"); const titleLine = lines.find((l) => l.startsWith("# ") || l.startsWith("## ")); return titleLine?.replace(/#{1,2}\s/, "") || "Daily Update"; } // Get excerpt function getExcerpt(content: string, maxLength: number = 150): string { const plainText = content .replace(/#{1,6}\s/g, "") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/\*/g, "") .replace(/\n/g, " "); if (plainText.length <= maxLength) return plainText; return plainText.substring(0, maxLength).trim() + "..."; } if (loading) { return (
); } return ( <> Daily Digest | OpenClaw Blog
{/* Header */}
📓
Daily Digest
{/* Main Content */}
{/* Page Title */}

{selectedTag ? `Posts tagged "${selectedTag}"` : "Latest Posts"}

{filteredMessages.length} post{filteredMessages.length !== 1 ? "s" : ""} {selectedTag && ( )}

{/* Featured Post */} {featuredPost && !selectedTag && !searchQuery && (
{featuredPost.tags && featuredPost.tags.length > 0 && (
{featuredPost.tags.slice(0, 3).map((tag) => ( {tag} ))}
)} Featured Post

{getTitle(featuredPost.content)}

{getExcerpt(featuredPost.content, 200)}

{format(new Date(featuredPost.date), "MMMM d, yyyy")} · 5 min read
)} {/* Post List */}
{regularPosts.map((post) => (
{/* Date Column */}
{format(new Date(post.date), "d")}
{format(new Date(post.date), "MMM")}
{/* Content */}
{post.tags && post.tags.length > 0 && (
{post.tags.slice(0, 2).map((tag) => ( ))}
)}

{getTitle(post.content)}

{getExcerpt(post.content)}

{format(new Date(post.date), "MMMM d, yyyy")} · 3 min read
))}
{filteredMessages.length === 0 && (

No posts found.

{(selectedTag || searchQuery) && ( )}
)}
{/* Sidebar */}
{/* Search */}
setSearchQuery(e.target.value)} placeholder="Search posts..." className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
{/* Tags */}

Tags

{allTags.map((tag) => ( ))}
{/* About */}

About

Daily curated digests covering AI coding assistants, iOS development, OpenClaw updates, and digital entrepreneurship.

{/* Stats */}

Stats

Total posts {messages.length}
Tags {allTags.length}
{/* Footer */}
); }