diff --git a/blog/test.json b/blog/test.json new file mode 100644 index 0000000..53b15cd --- /dev/null +++ b/blog/test.json @@ -0,0 +1,10 @@ +{ + "title": "My Post", + "description": "This is a short description", + "author": "John Doe", + "date": "2025-12-03", + "readingTime": 1, + "tags": ["typescript", "nextjs"], + "image": "/images/my-post.jpg", + "category": "guides" +} diff --git a/blog/test.md b/blog/test.md new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/blog/test.md @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/src/app/api/blog/[slug]/route.ts b/src/app/api/blog/[slug]/route.ts new file mode 100644 index 0000000..8072b76 --- /dev/null +++ b/src/app/api/blog/[slug]/route.ts @@ -0,0 +1,45 @@ +// app/api/blog/[slug]/route.ts +import { NextRequest, NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; +import { PostPreview } from "@/lib/posts"; + +const BLOG_DIR = path.join(process.cwd(), "blog"); + +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ slug: string }> } +) { + try { + const { slug } = await params; + + // Read JSON metadata + const jsonPath = path.join(BLOG_DIR, `${slug}.json`); + if (!fs.existsSync(jsonPath)) { + return NextResponse.json({ error: "Post not found" }, { status: 404 }); + } + const rawData = fs.readFileSync(jsonPath, "utf-8"); + const data: Omit = JSON.parse(rawData); + + // Read Markdown content + const mdPath = path.join(BLOG_DIR, `${slug}.md`); + let content = ""; + if (fs.existsSync(mdPath)) { + content = fs.readFileSync(mdPath, "utf-8"); + } + + return NextResponse.json({ + meta: { + ...data, + url: `/blog/${slug}`, + }, + content, + }); + } catch (err) { + console.error(err); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/blog/route.ts b/src/app/api/blog/route.ts new file mode 100644 index 0000000..be6bdda --- /dev/null +++ b/src/app/api/blog/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; +import { PostPreview } from "@/lib/posts"; + +const BLOG_DIR = path.join(process.cwd(), "blog"); + +export function getAllBlogPreviews(): PostPreview[] { + const files = fs.readdirSync(BLOG_DIR).filter((f) => f.endsWith(".json")); + + return files.map((file) => { + const filePath = path.join(BLOG_DIR, file); + const rawData = fs.readFileSync(filePath, "utf-8"); + const data = JSON.parse(rawData); + + // Generate the URL based on filename + const slug = file.replace(/\.json$/, ""); + + return { + ...data, + url: `/blog/${slug}`, + } as PostPreview; + }); +} + +export function GET(req: NextRequest): NextResponse { + return NextResponse.json(getAllBlogPreviews()); +} diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..0e3472c --- /dev/null +++ b/src/app/blog/[slug]/page.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { motion } from "framer-motion"; +import { Sparkles } from "lucide-react"; +import WebStuff from "@/components/Web"; +import { useParams } from "next/navigation"; +import { PostPreview } from "@/lib/posts"; + +export default function BlogPost() { + const [post, setPost] = useState<(PostPreview & { content: string }) | null>( + null + ); + const [mounted, setMounted] = useState(false); + const { slug } = useParams(); + + useEffect(() => { + setMounted(true); + }, []); + + useEffect(() => { + async function fetchPost() { + const res = await fetch(`/api/blog/${slug}`); + const data = await res.json(); + setPost(data); + } + + fetchPost(); + }, [slug]); + + if (!post) { + return ( + +
+ Loading post... +
+
+ ); + } + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { staggerChildren: 0.1 } }, + }; + + const itemVariants = { + hidden: { y: 20, opacity: 0 }, + visible: { y: 0, opacity: 1, transition: { duration: 0.5 } }, + }; + + return ( + + Hero Section + + + + + {post.category} + + + + + + {post.title} + + + + + {post.author} + • + {new Date(post.date).toLocaleDateString()} + {post.readingTime && ( + <> + • + {post.readingTime} min read + + )} + {post.tags?.map((tag, idx) => ( + + {tag} + + ))} + + + {/* Optional Featured Image */} + {post.image && ( + + {post.title} + + )} + + {/* Post Content */} + + + + ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx index 7ffbb4b..a320e63 100644 --- a/src/app/blog/page.tsx +++ b/src/app/blog/page.tsx @@ -2,39 +2,27 @@ import { useState, useEffect } from "react"; import { motion } from "framer-motion"; -import { - Github, - MessageSquare, - Sparkles, - ExternalLink, - Star, - GitFork, -} from "lucide-react"; +import { Sparkles } from "lucide-react"; import WebStuff from "@/components/Web"; -import projects from "@/lib/posts"; -import formatNumber from "@/lib/formatNumber"; +import { PostPreview } from "@/lib/posts"; export default function Blog() { const [mounted, setMounted] = useState(false); const [activeFilter, setActiveFilter] = useState("all"); - const [orgStats, setOrgStats] = useState({ - contributors: 0, - stars: 0, - forks: 0, - }); + const [projects, setProjects] = useState([]); useEffect(() => { setMounted(true); }, []); useEffect(() => { - async function fetchStats() { - const res = await fetch(`/api/stats`); + async function fetchPosts() { + const res = await fetch(`/api/blog`); - setOrgStats(await res.json()); + setProjects(await res.json()); } - fetchStats(); + fetchPosts(); }, []); const filters = [ diff --git a/src/lib/posts.ts b/src/lib/posts.ts new file mode 100644 index 0000000..7b5257f --- /dev/null +++ b/src/lib/posts.ts @@ -0,0 +1,11 @@ +export type PostPreview = { + title: string; + description: string; + author: string; + date: string; + readingTime: number; + tags: string[]; + image: string; + url: string; + category: "news" | "guides" | "journal" | "research"; +}; diff --git a/src/lib/posts.tsx b/src/lib/posts.tsx deleted file mode 100644 index cb48051..0000000 --- a/src/lib/posts.tsx +++ /dev/null @@ -1,81 +0,0 @@ -type PostPreview = { - title: string; - description: string; - author: string; - date: string; - readingTime: number; - tags: string[]; - image: string; - url: string; - category: "news" | "guides" | "journal" | "research"; -}; - -const posts: PostPreview[] = [ - { - title: "Understanding React Server Components", - description: - "Server components in React allow you to offload work to the server while keeping your UI interactive. Learn how to integrate them into your Next.js app.", - author: "Jane Doe", - date: "2025-11-28", - readingTime: 7, - tags: ["React", "Next.js", "Server"], - image: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAS0AAACnCAMAAABzYfrWAAABiVBMVEUfICbtHpAyxf////8Akf/6YwD8VQD/agAgJi0gKS8jPUUAAAAiNDz/7Of6VwAUx80pYm4e3LUd2rcc17sVycsXzcYAGicJsOcXHyZRLSN5OR+XQhsa078p85ob1b0Y0MMOutwk6aYq+Jkh4q4Wy8grxZAm7KInTV8oUlkAIBoRwdQj56gMtuHv///WHoMi5KwbmMMgFADxQqAOd9ARwNUNuN4KsuUGsO3/hVUn75/yUKX+aBkTxdAAjv9utv8jLz8AAA0tlaTz9/8vmp/o/PVlIEYTFRzZ2dpJIDj/7/huz/DFxcYzzP//3u3N9P9F0tjP//AUZa5paWzQ+vu1/+KBgYNs8bw7PEDr6+vQ0NEfGBu0+uez8+8tk6eenqB4eHqWlphSU1YeAAAor38jo4wgmZexsbJCQ0cjoY4bialP6LuX/tAmaYYmqYWw5P//n4H/0L0ofKAmZIAXVY8totJpNCBAKCQiRVgor3xc87Cr+dHM/OPC6f+W3f+4/Nd99rr/3ND/sZ4sgolrgGENAAALFklEQVR4nO2diX/cRhWA1VVSVp4lV5M4QNwcbR2TBuK0OTBQMhh3hkYmaQWypIXqMEVLcWJwMATnhP7lzBtpL10r7a51xPPlt5d2Vrv6/N7oaXREkgQCgUAgEBwShFGw/WH9lPpj9BiGhDMbDQ1hQ9N6eG6+GqYeURkwbZzRSHFdnS8VkVTe2stqXQCiuW5vPrMqhdCWLIMugsOoITj4mwcTiMEEIf6GGbb2yKA1e4D20vCODOYSNhhMik5BTL4z+sU1B2zpPZ8JQETSfReiBhuu7/bgiWf7roHbNrMFMYB9eKJZskyRhDXf19sEwqOt+Tohuq1h4riuodkQiUTRfV9jc3Fdh+j8GczPkfhHJM122SzYt/sOm2D7uoErFZELsKUhzGwRjweOj7EuB0+Qz584Cn/QiIQgqhAhpmkq2AqmQiMbmkOO+ohNhU9RZoC/b2GJvbKCGftBGkNEwUfMYB4UBRGr46plTCSILfjt8JthkfU2BBB74TlsKjjowZ3Kkg+0YfgQQjzMYDpXwBcX7ggsPyS3C38A1YQMH7zH5kdt9gxsBY3ga6jrspbsJlftYjKDfsv1WCBomilT3PM8iDPIOB2xxXDAH0tUCfovLPE1GWYNDIgkDWIriFA2SWFTXPiMxdRYiHd4MrRk7/Vgfhr7w3gqNLIhFNUwOqmueQ3o7vu2bKSH2mTkB5kBnUqPYBZHvb4liBD2wHTCUx5gOtxgqb2+LQ97EDEQVhBXzJZJwGuvv4qAj2hYC2052Aj/XrhiF5PhUcF+vYpYMKiO7jgaJIyjBrGlIc2ytP46kScrQh4kIIiDhY3bcmBWlg7zlMZsQdKzL3AMmDl2BrYQ0n0armjrDbcl8Z8KHTDLQJWliAOlgsbCw9ShAwNbLmsMPY9sWjwU2b0K4WjEbPHP+BAw4Fwd2PLYjG1o7o3ZUuGlDl/YEFvQh1C44wnohN2yg4PUURF/AsuCwzYWJr1+BvvQCY3ZknlPFTY1yMBWWK1R1Lel8vRXvXCmtZfFSihKPUIsdo801TRVDyOXUt+hVCPEpiZlK3bSs6ipwcLwNlRHULL61LRY8WFT6iB210MqpW3e75uqwcoNxzKprxCJUjX8GgzzY72ZDS80yp6yD5oq6rFZURdV7SIHrNcgvCaA8hrzmpo/8MnwGNbr/foc4X6pzqbxNtCS3fFnfC2JwoqdPWIJ5o37X8PewP0XJJiO+aZAMP2owW3VP6NqAvZNU9jKTVO2jwUCQb3odre3u91u1T+jCXS3O4/3Vlf39p52t4WwbLrS3mJrc3OT3/avCV9ZbD9ubbaGbO4/2676J9WWrrQ/6or72hO6kul27kZlMV2rTdJ14cfTcGGar+ouxlw1LLoufP7TafjtFLq2Y2kY6nramK7+wufvTMMUtrpPk2UxhK0Y24l5GORigq5bhSjJd2m2uo9TQyspuG599WkR/hmdw4U5UoGt7f10WQk9161PjxXhq1sRWT+YIxXYUtJltVrxKmJGW+e/N0fKt9W9lpGIrcW5x9YP50gFtrK6rVZLmbMt6fwcqcDWXqathTnbOv+jOVI3W5vPoql4xPutkjOxaAmRbylKs5VeyTPuxtrPbOtQKK2C6GSF1n7lFcS/ci1QedVp6oYP67Yez7uCKNzL/zvXQpS35ZPRzW/Guq3SK4h8C1HeVrWULitpQLDYVnU53VaJo4EZwXV//ovVeLp3U0KrQYOn5dFNXi1uxleIAgm2rJNkxbeoBZzus1ZsD9l+GbLa8ZVuA+i2V8d0bbYel5GGyGro8YDbz/Zbm4Ex9rBXzqEjSG2oLam7rTxd3V9s7a/uXSvrMJvm2pJAGKe8Q5Iabat0hK0iCFtFELaKIGwVQdgqgrBVBGErN4ZhSKrstOFRMAHSP1k6uP6GIBN+8nB49nrVv6UBoH5wNeCU++oZBJfo5/MQBpcIrVwEwWVW/TOaAr/+gQitnPCrIYkVYl5YzyVCKzes5xIrxPwQsZlYBCFLIBAcAu3mUoEr5f7h0DlsFKVsX22ls/DkP3+O8SSFbxL4a5y/pPObCL+O8HWEb1PZW+go5epqd1bWrjKW+qz1WQ65wvk44CbngyGn+5wJeI/zPnAx4FLIOeDDgLOMkwG/DLjOud3n8oA7nBsBdwMWOffuLa50yrWl3H8yZirQlWXqZpKq06OuRm2NuTr34bm+q7Mnz464itq6fHlc140BY7bu7S2Ue3xY59lHQ1tLSwfPX7x4/WZ5XNfHV0Zs3Uy0dSbN1sWpbN1OszUeW/dWFzplymqP2XqzIb/cffVyXX69NmZrLLjy2rqYYOvciK2TcVu3c9oKfIGtclOx3bk2tPVcfhUeObi+caXcTLyebevOjYSOq1pbz+XhwZa7cjmZmKYrautOQipWautAHj0y9eWXayVk4qOfTMWDym1d3Xg1dsyzvFVCJj764pPivPO76m0ty+NHiO++GAZXX1g0F2fOxEdfTHOuUw1s7Twct/VAXlu+cgBsAX9LZHImXsrOxKbaev4ycvYB6+e35PVM5NkzsaG2Xu9GbV1Z3lr/LOPUxM+OybP08jPbWqxbbOW1dTq133pLY+sg0su/YiVEtq1jxzIzcVK/1WhbS/J4BbG+Mym2jlWciZXa2hkLrt2N5QK2psnERq8Tl5ZerI/koXwQ2MriKGci6Opv++zKW2vc1q8yOcKZyLerH756sPlgV9444IX81sYEjnAmwuDpzgvm4PXWcPQ0Om4zvu1zlDNxqT/QnDzSHB+ymWkM4i2wFdmDkTC+9UGSrekz8efF+aSuthJGbJLHt6bNxKaOb022dQiZmDYunz3OXI9x+fIzMW13YgPG5SvIxML7E6N7yOprq8RMnLQ/8Yhm4kRbd5oaW3XIxBt1tpW4h0xk4ihZEZYuLFQ29HUxSVfyETbX0/a9ju55HSkeql8nvvlydn4/PQ+LUL2tnb8f/0UzOP6PGzWwdeJ4MzhRC1vvNoU62Jo08peX6TaUU0na61t9v7W0FK8kchx5Gl0nnskuuCLrxFwHnSYdc1q9rTlXp0m1/FtVnc4vtt6LlqdRWWdnia3qR2zm1m8dSidWt35LrBOL2RL1ViFbUCc3gDrYmnU7cYZtxAZuJ6aPQSQdelrmGEQd14kzjZ0mjW9NcZ5Pg8ZO4+eRZdSoqYPNqcOBl6L1VqOr053vl4g829hp9ZnI6q0TZfHuf5ueiWXWW2m2GpSJ//tZeeSPrZpm4sEf0vljHv5UhKZnYqyKyFVuZZ1X/X683HqrRmyy9ycWPD/xEPcnVp+Jojqd2da8x07nfc5+vWwdZiY2/Sx0kYmz2RKZWMSWWCdOYUtkYkjn2TcN3p/47Uq5V2ZRFk59dDUgVs/nGd/KcZWk+OBp2u7qpNhKOIRrGFqnFsq9pFS7s3Lqu+glsaYkeiWt2fk6djWuUb47VfYVpSSls7ByqpmsLHRK/w8HFearmXSUCv53xnZbaSYVXGVRIKg/hCg49c2eRCTx//oMIR61rLaEscRuhGBmDxMpvGGVuEjXCQ4nH3mwaSDXJbZv9GzfcX1s66qHNdXBnupiV5NtTZN8v+25vrhCv2RYSCLI1z3qmYrpqZrleqZBDcsze45CJSoxfY5maVQxRUq2KSLtHiWIaiqi2NWtNjIdH+m2pqoSJRTZOn/XR6p35HMRqb7HVEBsWYgSZsv2qEHblmcbqkOJ2WOxpWvCVgBxfQ1L0G/pyJU0z9L9HtZ8h91Yv0U06LdsX/EcrItMlPj6j68TCfxD1BiuE2ECY/CuIAqu+gcIBAKB4Ijyf7eXQMaS1/yXAAAAAElFTkSuQmCC", - url: "/blog/react-server-components", - category: "guides", - }, - { - title: "A Deep Dive into Tailwind CSS 4.0", - description: - "Tailwind CSS keeps evolving. Discover the new features in version 4.0 and how they can streamline your workflow.", - author: "John Smith", - date: "2025-11-20", - readingTime: 5, - tags: ["CSS", "Tailwind", "Frontend"], - image: - "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQt89y2XtPE7GhSgcohDk9qdcaf8t5--qwPTg&s", - url: "/blog/tailwind-css-4", - category: "guides", - }, - { - title: "Optimizing Next.js for Performance", - description: - "Performance is key in web apps. Learn practical techniques to make your Next.js application lightning-fast.", - author: "Alice Johnson", - date: "2025-11-15", - readingTime: 8, - tags: ["Next.js", "Performance", "WebDev"], - image: - "https://bs-uploads.toptal.io/blackfish-uploads/components/blog_post_page/6389919/cover_image/regular_1708x683/Next-js-Improving-Page-Speed-of-a-Server-Side-React-App-Blog-7d4de23630c897d49e1c5be730ad5695.png", - url: "/blog/nextjs-performance", - category: "guides", - }, - { - title: "State Management in Modern React", - description: - "From Context API to Zustand and Redux Toolkit, we explore modern strategies for managing state in React apps.", - author: "Robert Lee", - date: "2025-11-10", - readingTime: 6, - tags: ["React", "State Management", "Frontend"], - image: - "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQEm17yo2o8rIW-5nNXHk2J2MhKaTSi4cKZvA&s", - url: "/blog/react-state-management", - category: "guides", - }, - { - title: "Getting Started with TypeScript 6", - description: - "TypeScript 6 brings exciting new type features. This guide helps you get started with the latest version.", - author: "Emma Watson", - date: "2025-11-05", - readingTime: 7, - tags: ["TypeScript", "JavaScript", "Programming"], - image: - "data:image/jpeg;base64,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", - url: "/blog/typescript-6-intro", - category: "guides", - }, -]; - -export default posts; diff --git a/src/lib/projects.tsx b/src/lib/projects.ts similarity index 100% rename from src/lib/projects.tsx rename to src/lib/projects.ts