import { useEffect, useMemo, useState } from "react"; const API_BASE = import.meta.env.VITE_API_BASE || ""; import { Link } from "react-router-dom"; import Layout from "@/components/Layout"; import SEO from "@/components/SEO"; import LoadingScreen from "@/components/LoadingScreen"; import { useAethexToast } from "@/hooks/use-aethex-toast"; import BlogHero from "@/components/blog/BlogHero"; import BlogTrendingRail from "@/components/blog/BlogTrendingRail"; import BlogCategoryChips from "@/components/blog/BlogCategoryChips"; import BlogPostGrid from "@/components/blog/BlogPostGrid"; import BlogNewsletterSection from "@/components/blog/BlogNewsletterSection"; import BlogCTASection from "@/components/blog/BlogCTASection"; import { blogSeedPosts } from "@/data/blogSeed"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { ArrowRight, Layers, ListFilter, Newspaper } from "lucide-react"; import type { BlogCategory, BlogPost } from "@/components/blog/types"; const buildSlug = (post: BlogPost): string => post.slug || post.id?.toString() || "article"; const normalizeCategory = (value?: string | null) => (value || "general") .toString() .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "-"); const Blog = () => { const toast = useAethexToast(); const [isLoading, setIsLoading] = useState(true); const [posts, setPosts] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); const staticPosts = useMemo(() => blogSeedPosts, []); useEffect(() => { let cancelled = false; (async () => { try { // Use relative path for API calls to work in both dev and prod const res = await fetch(`/api/blog?limit=50`); let data: any = []; try { if (res.ok) { data = await res.json(); } } catch (error) { console.warn( "Failed to parse blog API response, falling back to Supabase", error, ); } if ( (!Array.isArray(data) || !data.length) && import.meta.env.VITE_SUPABASE_URL && import.meta.env.VITE_SUPABASE_ANON_KEY ) { try { const sbUrl = import.meta.env.VITE_SUPABASE_URL.replace(/\/$/, ""); const url = `${sbUrl}/rest/v1/blog_posts?select=slug,title,excerpt,author,date,read_time,category,image,likes,comments,published_at&order=published_at.desc&limit=50`; const fallbackRes = await fetch(url, { headers: { apikey: import.meta.env.VITE_SUPABASE_ANON_KEY, Authorization: `Bearer ${import.meta.env.VITE_SUPABASE_ANON_KEY}`, }, }); if (fallbackRes.ok) { data = await fallbackRes.json(); } } catch (error) { console.warn("Supabase fallback failed", error); } } if (!cancelled && Array.isArray(data)) { const mapped: BlogPost[] = data.map((record: any) => ({ id: record.id ?? record.slug, slug: record.slug, title: record.title, excerpt: record.excerpt ?? record.summary ?? null, author: record.author ?? "AeThex Team", date: record.date ?? record.published_at, readTime: record.read_time ?? record.readTime ?? null, category: record.category ?? "General", image: record.image ?? null, likes: typeof record.likes === "number" ? record.likes : null, comments: typeof record.comments === "number" ? record.comments : null, trending: Boolean(record.trending) || (typeof record.likes === "number" && record.likes > 250), body: record.body_html ?? record.body ?? null, })); setPosts(mapped); } } catch (error) { console.warn("Blog fetch failed", error); toast.system("Loaded curated AeThex articles"); } finally { if (!cancelled) { setIsLoading(false); } } })(); return () => { cancelled = true; }; }, [toast]); const dataset = posts.length ? posts : staticPosts; const filteredPosts = useMemo(() => { const query = searchQuery.trim().toLowerCase(); return dataset.filter((post) => { const matchesCategory = selectedCategory === "all" || normalizeCategory(post.category) === selectedCategory; if (!matchesCategory) return false; if (!query) return true; const haystack = [post.title, post.excerpt, post.author] .filter(Boolean) .map((value) => value!.toLowerCase()) .join(" "); return haystack.includes(query); }); }, [dataset, selectedCategory, searchQuery]); const featuredPost = useMemo(() => { if (!filteredPosts.length) { return dataset.find((post) => post.trending) ?? dataset[0] ?? null; } return ( filteredPosts.find((post) => post.trending) ?? filteredPosts[0] ?? null ); }, [dataset, filteredPosts]); const displayedPosts = useMemo(() => { if (!featuredPost) return filteredPosts; return filteredPosts.filter( (post) => buildSlug(post) !== buildSlug(featuredPost), ); }, [filteredPosts, featuredPost]); const trendingPosts = useMemo(() => { const sorted = [...dataset] .filter((post) => post.trending || (post.likes ?? 0) >= 200) .sort((a, b) => (b.likes ?? 0) - (a.likes ?? 0)); return sorted.slice(0, 3); }, [dataset]); const categories: BlogCategory[] = useMemo(() => { const counts = new Map(); dataset.forEach((post) => { const id = normalizeCategory(post.category); const name = post.category || "General"; counts.set(id, { id, name, count: (counts.get(id)?.count ?? 0) + 1, }); }); const ordered = [ { id: "all", name: "All posts", count: dataset.length }, ...Array.from(counts.values()).sort((a, b) => b.count - a.count), ]; return ordered; }, [dataset]); const insights = useMemo( () => [ { label: "Teams publishing", value: new Set( dataset.map((post) => (post.author || "AeThex Team").split(" ")[0]), ).size, helper: "Active contributors this month", icon: , }, { label: "Focus areas", value: new Set(dataset.map((post) => post.category || "General")).size, helper: "Distinct categories covered", icon: , }, { label: "Stories published", value: dataset.length, helper: "All-time AeThex blog posts", icon: , }, ], [dataset], ); if (isLoading) { return ; } const handleResetFilters = () => { setSelectedCategory("all"); setSearchQuery(""); }; return ( <>

Filter by track

Navigate the AeThex knowledge graph

{insights.map((insight) => ( {insight.icon}

{insight.label}

{insight.value}

{insight.helper}

))}

Latest updates

Fresh from the AeThex ship room

Explore more

Dive into AeThex documentation

Looking for implementation guides, deployment recipes, or program onboarding materials? Visit our documentation hub for developer tutorials, platform references, and community playbooks.

); }; export default Blog;