import { useState, useEffect, type FormEvent } from 'react'; interface BlogPost { id: number; title: string; content: string; author: string; created_at: string; published: boolean; tags: string[]; featured_image?: { url: string; alt_text: string; caption: string; }; post_images: Array<{ id: number; url: string; alt_text: string; caption: string; order: number; position?: number; }>; } interface BlogSummary { id: number; title: string; author: string; created_at: string; tags: string[]; excerpt: string; has_featured_image: boolean; featured_image_url?: string; post_image_count: number; } interface BlogResponse { posts: BlogSummary[]; total: number; limit: number; offset: number; has_more: boolean; } export default function App() { const [blogData, setBlogData] = useState(null); const [selectedPost, setSelectedPost] = useState(null); const [viewMode, setViewMode] = useState<'home' | 'blog'>('home'); const [currentPage, setCurrentPage] = useState(1); const [isDragging, setIsDragging] = useState(false); const [headerCollapsed, setHeaderCollapsed] = useState(false); const [lastScrollY, setLastScrollY] = useState(0); const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState('All'); const [isLoading, setIsLoading] = useState(false); const [searchResults, setSearchResults] = useState(null); const [isSearching, setIsSearching] = useState(false); const [searchTimer, setSearchTimer] = useState(null); const PAGE_SIZE = 6; const handlePageChange = (newPage: number) => { if (newPage >= 1 && (!blogData || newPage <= Math.ceil(blogData.total / PAGE_SIZE))) { setCurrentPage(newPage); fetchBlogPosts(newPage); } }; const [sliderVisible, setSliderVisible] = useState(false); const categories = ['All', 'Artificial Intelligence','Developers','AI Agents','Social','Movies']; // Fetch blog posts on component mount and page change (disabled when searching) useEffect(() => { if (!searchQuery) fetchBlogPosts(currentPage); }, [currentPage, searchQuery, selectedCategory]); // When category changes, reset page & clear search results (if any) useEffect(() => { setCurrentPage(1); if (!searchQuery) { fetchBlogPosts(1); } }, [selectedCategory]); // Handle scroll effect for blog header useEffect(() => { if (viewMode !== 'blog') return; const handleScroll = () => { const currentScrollY = window.scrollY; const scrollThreshold = 100; // Minimum scroll distance to trigger effect if (currentScrollY > scrollThreshold) { // Scrolling down - collapse header if (currentScrollY > lastScrollY && !headerCollapsed) { setHeaderCollapsed(true); } // Scrolling up - expand header else if (currentScrollY < lastScrollY && headerCollapsed) { setHeaderCollapsed(false); } } else { // Near top - always show full header setHeaderCollapsed(false); } setLastScrollY(currentScrollY); }; window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); }, [viewMode, lastScrollY, headerCollapsed]); async function fetchBlogPosts(page: number = 1) { setIsLoading(true); try { const params = new URLSearchParams({ page: String(page), limit: String(PAGE_SIZE) }); if (selectedCategory && selectedCategory !== 'All') params.append('category', selectedCategory); const res = await fetch(`/api/blog/posts?${params.toString()}`); if (res.ok) { const data = await res.json(); // Small delay to make loading visible await new Promise(resolve => setTimeout(resolve, 300)); setBlogData(data); } } catch (err) { console.error('Failed to fetch blog posts:', err); } finally { setIsLoading(false); } } async function runSearch(query: string) { const q = query.trim(); if (!q) { setSearchResults(null); return; } setIsSearching(true); try { const params = new URLSearchParams({ q }); if (selectedCategory && selectedCategory !== 'All') params.append('category', selectedCategory); const res = await fetch(`/api/blog/search?${params.toString()}`); if (res.ok) { const data = await res.json(); setSearchResults({ posts: data.posts, total: data.total, limit: data.posts.length, offset: 0, has_more: false } as BlogResponse); } } catch (e) { console.error('Search failed', e); } finally { setIsSearching(false); } } // Debounce search input useEffect(() => { if (searchTimer) window.clearTimeout(searchTimer); const handle = window.setTimeout(() => { runSearch(searchQuery); }, 300); setSearchTimer(handle); return () => window.clearTimeout(handle); }, [searchQuery]); async function fetchBlogPost(id: number) { setIsLoading(true); try { const res = await fetch(`/api/blog/posts/${id}`); if (res.ok) { const post = await res.json(); // Add artificial delay to make loading visible await new Promise(resolve => setTimeout(resolve, 300)); setSelectedPost(post); setViewMode('blog'); } } catch (err) { console.error('Failed to fetch blog post:', err); } finally { setIsLoading(false); } } function formatDate(dateString: string) { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } function renderBlogContent(content: string, images: BlogPost['post_images']) { const paragraphs = content.split('\n\n').filter(p => p.trim()); const elements: JSX.Element[] = []; paragraphs.forEach((paragraph, index) => { const paragraphNumber = index + 1; elements.push(

{paragraph}

); // Insert images that should appear after this paragraph const imagesForPosition = images.filter(img => img.position === paragraphNumber); imagesForPosition.forEach(image => { elements.push(
{image.alt_text} {image.caption &&
{image.caption}
}
); }); }); return elements; } // Toggle subtle separator only if content is scrollable or user scrolled useEffect(() => { function evaluate() { const header = document.querySelector('.compact-header'); if (!header) return; const scrollable = document.documentElement.scrollHeight > window.innerHeight + 4; const scrolled = window.scrollY > 4; if (scrollable || scrolled) header.classList.add('with-sep'); else header.classList.remove('with-sep'); } evaluate(); window.addEventListener('resize', evaluate); window.addEventListener('scroll', evaluate, { passive: true }); return () => { window.removeEventListener('resize', evaluate); window.removeEventListener('scroll', evaluate); }; }, []); if (viewMode === 'blog' && selectedPost) { return (
); } return (
); }