diff --git a/.eslintrc.json b/.eslintrc.json
index 4505427..9490889 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,10 +1,53 @@
{
"extends": ["next/core-web-vitals", "next/typescript"],
+ "plugins": ["check-file"],
+ "rules": {
+ "max-lines": ["warn", { "max": 300, "skipBlankLines": true, "skipComments": true }],
+ "check-file/filename-naming-convention": [
+ "error",
+ { "**/*.{js,ts,jsx,tsx}": "KEBAB_CASE" },
+ { "ignoreMiddleExtensions": true }
+ ],
+ "check-file/folder-naming-convention": [
+ "error",
+ { "src/**/!(*\\[*\\]|*\\(*\\))": "KEBAB_CASE", "app/**/!(*\\[*\\]|*\\(*\\))": "KEBAB_CASE" }
+ ],
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
+ "indent": ["error", 2, { "SwitchCase": 1 }],
+ "@typescript-eslint/no-explicit-any": "warn",
+ "@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports" }],
+ "react/self-closing-comp": ["error", { "component": true, "html": true }],
+ "react/jsx-boolean-value": ["error", "never"],
+ "react/jsx-curly-brace-presence": ["error", { "props": "never", "children": "never" }],
+ "react/hook-use-state": "error",
+ "react-hooks/exhaustive-deps": "warn",
+ "react/forbid-component-props": ["error", {
+ "forbid": [{
+ "propName": "className",
+ "allowedFor": ["div", "span", "button", "input", "a", "img", "ul", "li", "ol", "p", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "nav", "header", "footer", "main", "form", "label", "table", "thead", "tbody", "tr", "td", "th", "svg", "path", "textarea", "select", "option", "pre", "code", "blockquote", "hr", "br", "strong", "em", "small", "sup", "sub", "cite", "Link", "Image", "motion.div", "motion.span", "motion.section", "motion.article", "motion.ul", "motion.li", "motion.p", "motion.button", "motion.a", "motion.nav", "motion.header", "ArrowLeft", "Check", "CheckSquare", "Copy", "Search", "Square", "Sun", "Moon", "Database", "Skeleton"],
+ "message": "Use variant props instead of className on design system components"
+ }]
+ }]
+ },
"overrides": [
{
"files": ["next-env.d.ts"],
"rules": {
- "@typescript-eslint/triple-slash-reference": "off"
+ "@typescript-eslint/triple-slash-reference": "off",
+ "check-file/filename-naming-convention": "off"
+ }
+ },
+ {
+ "files": ["components/**/*.tsx"],
+ "rules": {
+ "react/forbid-component-props": "off"
+ }
+ },
+ {
+ "files": ["lib/notion.ts"],
+ "rules": {
+ "max-lines": "off",
+ "@typescript-eslint/no-explicit-any": "off"
}
}
]
diff --git a/.github/workflows/check-quotes.yml b/.github/workflows/check-quotes.yml
deleted file mode 100644
index 2e98bd3..0000000
--- a/.github/workflows/check-quotes.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-name: Check for Quote Updates
-
-on:
- schedule:
- - cron: '0 15-23,0-3 * * *' # Every hour from 8am to 8pm PDT
- workflow_dispatch:
-
-permissions:
- actions: write
- contents: write
-
-jobs:
- check-quotes:
- runs-on: ubuntu-latest
- environment:
- name: github-pages
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Setup Bun
- uses: oven-sh/setup-bun@v2
- with:
- bun-version: latest
-
- - name: Install dependencies
- run: bun install
-
- - name: Restore quotes hash cache
- uses: actions/cache@v4
- with:
- path: quotes-hash.txt
- key: quotes-hash-${{ github.sha }}
- restore-keys: quotes-hash-
-
- - name: Check if quotes changed
- id: check
- env:
- NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
- QUOTES_DATABASE_ID: ${{ secrets.QUOTES_DATABASE_ID }}
- run: bun run check-quotes
- continue-on-error: true # Don't fail the workflow on exit code 1 or 2
-
- - name: Save updated hash
- if: steps.check.outcome == 'success'
- uses: actions/cache/save@v4
- with:
- path: quotes-hash.txt
- key: quotes-hash-${{ github.sha }}-${{ github.run_id }}
-
- - name: Trigger build workflow
- if: steps.check.outcome == 'success'
- uses: actions/github-script@v7
- with:
- script: |
- await github.rest.actions.createWorkflowDispatch({
- owner: context.repo.owner,
- repo: context.repo.repo,
- workflow_id: 'nextjs.yml',
- ref: 'main'
- })
- console.log('Build workflow (workflow_dispatch) triggered!')
-
- - name: Log result
- run: |
- if [ "${{ steps.check.outcome }}" = "success" ]; then
- echo "Quotes changed - build triggered"
- elif [ "${{ steps.check.outcome }}" = "failure" ] && [ "${{ steps.check.conclusion }}" = "success" ]; then
- echo "No changes - no build needed"
- else
- echo "Error occurred - build may be needed"
- fi
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..23cb445
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,28 @@
+name: CI
+
+on:
+ pull_request:
+ branches: [main, dev]
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Lint
+ run: bun run lint
+
+ - name: Type check
+ run: bun run tsc
+
+ - name: Build
+ run: bun run build
diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml
deleted file mode 100644
index 7375f75..0000000
--- a/.github/workflows/nextjs.yml
+++ /dev/null
@@ -1,86 +0,0 @@
-# Sample workflow for building and deploying a Next.js site to GitHub Pages
-#
-# To get started with Next.js see: https://nextjs.org/docs/getting-started
-#
-name: Deploy Next.js site to Pages
-
-on:
- # Runs on pushes targeting the default branch
- push:
- branches: ["main"]
-
- # Triggered when quotes change
- repository_dispatch:
- types: [quotes-changed]
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
-permissions:
- contents: read
- pages: write
- id-token: write
-
-# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
-concurrency:
- group: "pages"
- cancel-in-progress: false
-
-jobs:
- # Build job
- build:
- runs-on: ubuntu-latest
- environment:
- name: github-pages
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Setup Bun
- uses: oven-sh/setup-bun@v2
- with:
- bun-version: latest
- - name: Setup Pages
- uses: actions/configure-pages@v5
- with:
- # Automatically inject basePath in your Next.js configuration file and disable
- # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
- #
- # You may remove this line if you want to manage the configuration yourself.
- static_site_generator: next
- - name: Restore cache
- uses: actions/cache@v4
- with:
- path: |
- .next/cache
- ~/.bun/install/cache
- # Generate a new cache whenever packages or source files change.
- key: ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
- # If source files changed but packages didn't, rebuild from a prior cache.
- restore-keys: |
- ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}-
- - name: Install dependencies
- run: bun install
- - name: Create .env.local
- run: |
- echo "NOTION_TOKEN=${{ secrets.NOTION_TOKEN }}" >> .env.local
- echo "QUOTES_DATABASE_ID=${{ secrets.QUOTES_DATABASE_ID }}" >> .env.local
- - name: Build with Next.js
- run: bun run build
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: ./out
-
- # Deployment job
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- needs: build
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
index 35f4826..a82e909 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,9 +6,6 @@
.pnp.js
.yarn/install-state.gz
-# scripts generated files
-quotes-hash.txt
-
# testing
/coverage
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..9dd0801
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,55 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Commands
+
+```bash
+bun run dev # Start development server
+bun run build # Production build
+bun run lint # Run ESLint with --fix
+```
+
+Always use Bun, not Node.js/npm/pnpm. Bun automatically loads `.env` files.
+
+## Architecture
+
+**Stack**: Next.js 16 (App Router) + TypeScript + Tailwind CSS + Shadcn UI + Framer Motion
+
+**Deployed on Vercel** with ISR (Incremental Static Regeneration). Notion content auto-refreshes hourly via `revalidate: 3600` in fetch calls.
+
+### Content Sources
+- **Quotes**: Fetched from Notion API, cached 1 hour, client-side searchable
+- **Blogs**: Markdown files in `/content/blogs/`, parsed with gray-matter
+- **Timeline**: Static TypeScript data in `/content/timeline-data.ts`
+
+### Key Files
+- `lib/notion.ts` - All Notion API integration (exempted from max-lines rule)
+- `lib/quotes.ts` - Quote fetching entry point
+- `lib/blogs.ts` - Markdown blog loading with search
+- `app/layout.tsx` - Root layout with Vercel Analytics
+
+### Server vs Client Components
+- Pages are server components by default
+- `"use client"` used for interactive components (TopNav, search features)
+
+## Code Standards
+
+### Naming (enforced via ESLint)
+- Files: `kebab-case` (e.g., `top-nav.tsx`)
+- Folders: `kebab-case` (except `[slug]` and `(groups)` for Next.js routing)
+- Max 300 lines per file (exceptions: `lib/notion.ts`)
+
+### TypeScript
+- Use `type` imports: `import type { Foo } from './bar'`
+- Unused vars must be prefixed with `_`
+
+### React/JSX
+- Self-closing components: `` not ``
+- No unnecessary boolean values: `disabled` not `disabled={true}`
+- No unnecessary curly braces: `prop="value"` not `prop={"value"}`
+
+### Styling
+- Use variant props on Shadcn UI components, not `className`
+- `className` allowed only on HTML primitives, Link, Image, and motion.* components
+- Component files in `/components/` are exempt from this rule
diff --git a/README.md b/README.md
index 91080f5..b2a19bd 100644
--- a/README.md
+++ b/README.md
@@ -1,109 +1,30 @@
# Personal Website
-This is my personal website and blog built with a modern component-based architecture. This README explains the technical decisions and architecture for those curious about how it works.
+Personal website and blog built with Next.js App Router, deployed on Vercel.
## Tech Stack
-The project uses a modern tech stack focused on developer experience, performance, and best practices:
+- **Runtime**: [Bun](https://bun.sh/)
+- **Framework**: [Next.js](https://nextjs.org/) (App Router)
+- **Language**: [TypeScript](https://www.typescriptlang.org/)
+- **Styling**: [Tailwind CSS](https://tailwindcss.com/)
+- **UI Components**: [Shadcn UI](https://ui.shadcn.com/)
+- **Animations**: [Framer Motion](https://www.framer.com/motion/)
+- **CMS**: [Notion API](https://developers.notion.com/) for quotes and blog content
+- **Deployment**: [Vercel](https://vercel.com/) with ISR
-- **Runtime**: [Bun](https://bun.sh/) - Fast JavaScript runtime and package manager
-- **Framework**: [Next.js](https://nextjs.org/) (App Router)
-- **Language**: [TypeScript](https://www.typescriptlang.org/)
-- **Styling**: [Tailwind CSS](https://tailwindcss.com/)
-- **UI Components**: [Shadcn UI](https://ui.shadcn.com/)
-- **Animations**: [Framer Motion](https://www.framer.com/motion/)
-- **Icons**: [Lucide React](https://lucide.dev/)
-- **Theming**: [next-themes](https://github.com/pacocoursey/next-themes) for light/dark mode.
-- **Markdown/MDX**: `react-markdown` with `remark` and `rehype` for rendering blog posts.
-- **CMS**: [Notion API](https://developers.notion.com/) for managing quotes content.
+## Development
-## Project Structure
-
-The project follows a structure that separates concerns while taking advantage of Next.js App Router features:
-
-- `app/`: Contains all the routes and pages for the application. Each folder represents a URL segment.
- - `app/layout.tsx`: The root layout of the application.
- - `app/(home)/page.tsx`: The entry point for the homepage.
- - `app/(routes)`: Subdirectories for each page (e.g., `blogs`, `projects`), containing their specific components and logic.
-- `components/`: Contains reusable components shared across the application.
- - `components/ui`: UI primitives from Shadcn UI.
- - `components/layout`: Components that define the structure of the site, like the `Sidebar`.
- - `components/providers`: Wrapper components that provide context to the application (e.g., `ThemeProvider`).
-- `content/`: Stores static data and content, such as blog posts or timeline information.
-- `lib/`: Utility functions and helper scripts.
-- `public/`: Static assets like images and fonts.
-
-## How It Works
-
-The website is built using **Bun** as the runtime and package manager for faster development and builds. The development server runs with `bun run dev` and leverages Next.js's App Router for routing and server-side rendering.
-
-## Dynamic Quotes with Notion Integration
-
-The quotes page uses a custom Notion integration to dynamically fetch and display quotes from a personal Notion database. This creates a seamless CMS experience where I can add new quotes directly in Notion and they appear on the website.
-
-### Technical Implementation
-
-Instead of using the official Notion SDK, the integration uses direct REST API calls to `https://api.notion.com/v1/databases/{database_id}/query`.
-
-
-### Smart Text Processing
-
-The system handles Notion's rich text formatting by:
-- Converting literal `\n` and `\t` characters to actual newlines and tabs
-- Preserving formatting while extracting plain text
-- Creating markdown-style links when author links are provided
-
-### Static Site + Dynamic Content Challenge
-
-Since this site is deployed to **GitHub Pages** (static hosting), traditional server-side features like Next.js's `revalidate` don't work - there's no running server to refresh cached content. This creates a challenge: how do you get fresh content from Notion without manual deployments?
-
-### Clever GitHub Actions Solution
-
-I built a smart automation system that works around static hosting limitations:
-
-#### **Smart Change Detection**
-- A scheduled GitHub Action runs hourly during active hours (8am to 8pm PDT)
-- Fetches fresh quotes from Notion API
-- Creates a SHA-256 hash of the content for comparison
-- Only triggers a rebuild if quotes have actually changed
-
-#### **Smart Caching**
-- Uses GitHub Actions cache to store content hashes between runs
-- Prevents unnecessary builds when quotes haven't changed
-- Saves ~95% of build time on unchanged content
-
-#### **Two-Workflow Architecture**
-1. **Quote Checker** (`check-quotes.yml`): Lightweight check (~30 seconds)
-2. **Main Build** (`nextjs.yml`): Full rebuild only when needed (~5 minutes)
-
-```yaml
-# Runs hourly during active hours, only builds when quotes change
-on:
- schedule:
- - cron: '0 15-23,0-3 * * *' # 8am to 8pm PDT
+```bash
+bun install
+bun run dev
```
-#### **TypeScript Build Script**
-- Custom `scripts/check-quotes.ts` handles the smart comparison
-- Exit codes control workflow behavior:
- - `0`: Changes detected → Trigger build
- - `1`: No changes → Skip build
- - `2`: Error → Fail safe and build anyway
-
-### Performance Benefits
-
-- **Automatic updates**: Fresh quotes appear within an hour of Notion changes
-- **Resource efficient**: No unnecessary rebuilds when content is unchanged
-- **Zero maintenance**: Runs completely automated once configured
-- **Client-side optimization**: Quotes are shuffled and filtered locally for smooth interactions
-- **Real-time search**: Filter through quotes and authors without additional API calls
-
-This approach gives you **dynamic content** on a **static site** - the best of both worlds! And, free! :)
+## Notion Integration
-### Markdown Support
+Quotes and blogs are fetched from Notion databases via the REST API. Content is cached and auto-refreshes hourly using Next.js ISR (`revalidate: 3600`).
-The frontend includes a custom markdown renderer that handles:
-- **Bold** and *italic* text formatting
-- Automatic URL linking
-- Line breaks and paragraph formatting
-- Clickable author links when available
+The integration handles Notion's rich text formatting:
+- Converts literal `\n` and `\t` to actual newlines/tabs
+- Preserves formatting while extracting plain text
+- Creates markdown-style links for authors
diff --git a/app/(home)/components/AboutSection.tsx b/app/(home)/components/AboutSection.tsx
deleted file mode 100644
index d48dca9..0000000
--- a/app/(home)/components/AboutSection.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-"use client";
-
-import React from "react";
-import { motion } from "framer-motion";
-import { Card, CardContent } from "@/components/ui/card";
-import EmphasisText from "@/components/text/EmphasisText";
-
-export default function AboutSection(): React.ReactNode {
- return (
-
-
-
-
-
- About Me
-
-
-
- {"I build AI systems that solve real problems –– be it saving people hours of work, "}
- {"catching issues before they become expensive mistakes, or bringing in new revenue. "}
- {"While my primary focus is on "}
- AI or ML engineering
- {" (depending on the project), I also emphasize "}
- full-stack development
- {" because I believe that the best AI is the kind that actually gets used –– whether or not people know it's AI."}
-
-
Some keyword spam (sorry):
-
-
- {"My tech stack is always evolving, but my current language stack is "}
- Python, SQL, JavaScript, and TypeScript.
-
- {/*
- {"I've been working on mastering some more low-level languages like "}
- C++ and Rust.
-
*/}
-
- {"My current go-to systems and frameworks for frontend are "}
- React, Next.js
- {", Tailwind CSS, and Shadcn UI."}
-
-
{"I like to use FastAPI for my backend."}
-
{"I've worked with databases like PostgreSQL, Databricks, and MS SQL."}
-
{"I use Docker for containerization."}
-
{"I have experience with cloud platforms such as AWS, GCP, and Azure."}
-
{"I've worked with both GitHub and Bitbucket for version control and building CI/CD pipelines."}
-
{"As far as AI/ML APIs, I've worked with OpenAI, Anthropic, Google, and Hugging Face."}
-
- {"Model development is obviously in "}
- PyTorch
- {" :)"}
-
-
-
- {"More on traditional "}
- Machine Learning:
- {" My foundations are rooted in my academic background. I've been working towards an AI graduate certificate at "}
- Stanford
- {" maintaining a "}
- 4.0 GPA
- {" through some of the most challenging courses in the field. I've also completed an undergraduate degree in math, CS, and business data analytics (and music lol), where I graduated as the "}
- outstanding Mathematics graduate.
- {" This background enables me to approach ML challenges with deep fundamentals understanding, leading to strong models that actually make an impact on the business."}
-
-
- {"Outside my day job, my non-extracurricular time is spent either "}
- consulting
- {" local businesses for AI solutions, or as a "}
- course facilitator
- {" for Stanford's Machine Learning and AI Principles courses."}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/app/(home)/components/CtaSection.tsx b/app/(home)/components/CtaSection.tsx
deleted file mode 100644
index 1033424..0000000
--- a/app/(home)/components/CtaSection.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-"use client";
-
-import React from "react";
-import { motion } from "framer-motion";
-import { Card, CardContent } from "@/components/ui/card";
-
-export default function CtaSection(): React.ReactNode {
- return (
-
-
-
-
- Let's Build Something Amazing
-
-
- I'm always open to collaborating on creative and challenging projects.
- Whether you're looking to build an AI-powered application or explore innovative solutions, let's connect.
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/app/(home)/components/HeroSection.tsx b/app/(home)/components/HeroSection.tsx
deleted file mode 100644
index f392598..0000000
--- a/app/(home)/components/HeroSection.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-"use client";
-
-import React from "react";
-import { motion } from "framer-motion";
-
-export default function HeroSection(): React.ReactNode {
- return (
-
-
-
-
- Always open to new opportunities :)
-
-
- I'm Jake, a mathematician turned{" "}
- ML Engineer
- and
- Full Stack Developer
- with a keen eye for
- design.
-
-
- {/* Building cool stuff that both look good and solve real problems, from algorithms to web & iOS apps... and some totally unnecessary but fun side projects too! */}
- {"Build fast, ship fast, and fix fast, but it has to look good throughout. Optimizing every element from the user's perspective, be it frontend interfaces, backends, or machine learning models, is key to an end product that's actually useful."}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/app/(home)/components/InfoGrid.tsx b/app/(home)/components/InfoGrid.tsx
deleted file mode 100644
index f8c38e3..0000000
--- a/app/(home)/components/InfoGrid.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-"use client";
-
-import React from "react";
-import { motion } from "framer-motion";
-import { Card, CardContent } from "@/components/ui/card";
-
-export default function InfoGrid(): React.ReactNode {
- return (
-
-
-
-
-
-
- What I'm Building
-
-
-
-
- Autonomous agents that actually save businesses time and money
-
-
- Teaching Stanford professional students how to train and devleop ML models
-
-
-
-
-
-
-
-
-
-
- Beyond Code
-
-
-
-
- Serving at Agape Christian Church
-
-
-
- Soccer enthusiast (Força Barça!)
-
-
-
- Music composition & performance
-
-
-
- Nature and beach lover
-
-
-
- Music composition & performance
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx
index aca2acf..db84b63 100644
--- a/app/(home)/page.tsx
+++ b/app/(home)/page.tsx
@@ -1,23 +1,71 @@
-"use client";
+"use client"
-import React from "react";
-import HeroSection from "./components/HeroSection";
-import AboutSection from "./components/AboutSection";
-import CtaSection from "./components/CtaSection";
+import Link from "next/link"
+import { PageTitle } from "@/components/layout/page-title"
-export default function HomePage(): React.ReactNode {
+function TypographyParagraph({ children }: { children: React.ReactNode }) {
return (
-
-
-
+ <>
+
+ {children}
+
+
+ >
+ )
+}
- {/* Main Content Grid */}
-
-
- {/* */}
-
-
+export default function HomePage() {
+ return (
+
+
+ jake bodea
+
+
+ resumes are boring, so i made this website to showcase myself. i have a{" "}
+
+ timeline
+ {" "}
+ for more details, but i prefer to{" "}
+
+ show
+ {" "}
+ my accomplishments
+
+
+
+ i'm an all-around engineer with a math background. i majored in
+ math with minors in cs, business data analytics, and music. currently
+ studying ai at stanford. i love working on product and i like making
+ music
+
+
+ my tech stack is pretty much "i'll learn whatever you need me to learn",
+ but i have strong experience with TypeScript, Python, and SQL. also
+ dabbling in Rust :)
+
+
+ in my free time, you'll find me volunteering at church, working on
+ side projects (technical or musical), or exploring new adventures
+ with my wife
+
+
+
+ thanks for checking out my website, and please please please {" "}
+
+ say hi
+
+ {" "}!
+
- );
-}
\ No newline at end of file
+ )
+}
diff --git a/app/blogs/[slug]/page.tsx b/app/blogs/[slug]/page.tsx
index f58c933..4e378b6 100644
--- a/app/blogs/[slug]/page.tsx
+++ b/app/blogs/[slug]/page.tsx
@@ -10,7 +10,8 @@ import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeMathjax from 'rehype-mathjax'
import { CodeBlock } from '@/components/ui/code-block'
-import { CopyMarkdownButton } from '@/app/blogs/components/CopyMarkdownButton'
+import { CopyMarkdownButton } from '@/components/common/copy-markdown-button'
+import { BlogPostTitle } from '@/components/layout/blog-post-title'
interface PageProps {
slug: string
@@ -51,9 +52,7 @@ export default async function BlogPostPage({ params }: { params: Promise
-
- {title}
-
+ {title}
{new Date(date + 'T00:00:00').toLocaleDateString(undefined, {
weekday: 'long',
diff --git a/app/blogs/BlogsClient.tsx b/app/blogs/blogs-client.tsx
similarity index 61%
rename from app/blogs/BlogsClient.tsx
rename to app/blogs/blogs-client.tsx
index ecb47ba..6be75bf 100644
--- a/app/blogs/BlogsClient.tsx
+++ b/app/blogs/blogs-client.tsx
@@ -1,9 +1,10 @@
'use client'
-import { BlogData } from '@/lib/blogs'
+import type { BlogData } from '@/lib/blogs'
import { useState, useEffect } from 'react'
-import PostList from './components/PostList'
-import SearchBar from './components/SearchBar'
+import { PostList } from '@/components/common/post-list'
+import { SearchInput } from '@/components/common/search-input'
+import { PageWrapper } from '@/components/layout/page-wrapper'
interface BlogsPageProps {
initialPosts: BlogData[]
@@ -28,14 +29,9 @@ export default function BlogsPageClient({ initialPosts }: BlogsPageProps) {
}, [searchQuery, initialPosts])
return (
-
-
-
Blogs
-
-
-
-
-
-
+
+
+
+
)
}
\ No newline at end of file
diff --git a/app/blogs/components/PostList.tsx b/app/blogs/components/PostList.tsx
deleted file mode 100644
index e32366c..0000000
--- a/app/blogs/components/PostList.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-'use client'
-
-import Link from 'next/link'
-import { BlogData } from '@/lib/blogs'
-
-interface PostListProps {
- posts: BlogData[];
- searchQuery: string;
-}
-
-export default function PostList({ posts, searchQuery }: PostListProps) {
- return (
-
- I'm working on putting together some projects I can share publicly (most of my work is internal-only, so bear with me!).
- In the meantime, check out my experience timeline for project overviews, or explore my GitHub for contribution history.
-
+ i'm working on putting together some projects i can share publicly (most of my work is internal-only, so bear with me!).
+ in the meantime, check out my experience timeline for project overviews, or explore my github for contribution history.
+