Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanpoland committed Dec 11, 2024
0 parents commit 17b0e57
Show file tree
Hide file tree
Showing 79 changed files with 14,461 additions and 0 deletions.
94 changes: 94 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# 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"]

# 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
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Detect package manager
id: detect-package-manager
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=ci" >> $GITHUB_OUTPUT
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: ${{ steps.detect-package-manager.outputs.manager }}

- name: Setup Pages
uses: actions/configure-pages@v4

- name: Restore cache
uses: actions/cache@v4
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} --force

- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next 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
40 changes: 40 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# env files (can opt-in for commiting if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
180 changes: 180 additions & 0 deletions app/blog/[slug]/blog-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"use client"

import { useEffect, useState } from 'react'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
import { serialize } from 'next-mdx-remote/serialize'
import rehypePrism from 'rehype-prism-plus'
import remarkGfm from 'remark-gfm'
import { notFound } from 'next/navigation'
import { Clock, User } from 'lucide-react'
import { type BlogPost } from '@/lib/blog-data'

interface BlogContentProps {
initialPost: BlogPost
slug: string
}

export default function BlogContent({ initialPost, slug }: BlogContentProps) {
const [mdxSource, setMdxSource] = useState<MDXRemoteSerializeResult | null>(null)
const [post] = useState<BlogPost | null>(initialPost)
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
async function loadPost() {
try {
const response = await fetch(`/blogs/${slug}.md`)
if (!response.ok) {
throw new Error('Blog post not found')
}

const text = await response.text()
const mdxSource = await serialize(text, {
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypePrism],
}
})

setMdxSource(mdxSource)
} catch (error) {
console.error('Failed to load blog post:', error)
notFound()
} finally {
setIsLoading(false)
}
}

loadPost()
}, [slug])

if (isLoading) {
return <BlogSkeleton />
}

if (!post || !mdxSource) {
return notFound()
}

const components = {
pre: ({ className, ...props }: React.HTMLAttributes<HTMLPreElement>) => (
<pre className={`${className} p-4 rounded-lg bg-neutral-950 overflow-x-auto`} {...props} />
),
code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (
<code className={`${className} px-1 py-0.5 rounded-md text-orange-400 bg-neutral-950`} {...props} />
),
h1: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h1 className={`${className} text-3xl font-bold mt-8 mb-4`} {...props} />
),
h2: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h2 className={`${className} text-2xl font-semibold mt-6 mb-3`} {...props} />
),
h3: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h3 className={`${className} text-xl font-semibold mt-4 mb-2`} {...props} />
),
p: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (
<p className={`${className} my-4 leading-7`} {...props} />
),
ul: ({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) => (
<ul className={`${className} list-disc list-inside my-4 space-y-2`} {...props} />
),
ol: ({ className, ...props }: React.HTMLAttributes<HTMLOListElement>) => (
<ol className={`${className} list-decimal list-inside my-4 space-y-2`} {...props} />
),
li: ({ className, ...props }: React.HTMLAttributes<HTMLLIElement>) => (
<li className={`${className} ml-4`} {...props} />
),
a: ({ className, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
<a className={`${className} text-blue-400 hover:text-blue-300 underline`} {...props} />
),
blockquote: ({ className, ...props }: React.HTMLAttributes<HTMLQuoteElement>) => (
<blockquote className={`${className} border-l-4 border-neutral-700 pl-4 my-4 italic`} {...props} />
),
table: ({ className, ...props }: React.TableHTMLAttributes<HTMLTableElement>) => (
<div className="overflow-x-auto my-4">
<table className={`${className} min-w-full border-collapse`} {...props} />
</div>
),
th: ({ className, ...props }: React.ThHTMLAttributes<HTMLTableHeaderCellElement>) => (
<th className={`${className} border border-neutral-800 px-4 py-2 bg-neutral-900`} {...props} />
),
td: ({ className, ...props }: React.TdHTMLAttributes<HTMLTableDataCellElement>) => (
<td className={`${className} border border-neutral-800 px-4 py-2`} {...props} />
),
}

return (
<div className="min-h-screen bg-black text-white">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div className="mb-8">
<h1 className="text-4xl font-bold text-neutral-100 mb-4">
{post.title}
</h1>

<div className="flex items-center gap-6 mb-6 text-neutral-400">
<div className="flex items-center gap-2">
<User className="w-4 h-4" />
<span>{post.author}</span>
</div>
<div className="flex items-center gap-2">
<Clock className="w-4 h-4" />
<span>{post.readingTime} min read</span>
</div>
<time>
{new Date(post.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
</div>

<div className="flex gap-2 flex-wrap mb-4">
{post.categories.map(category => (
<span
key={category}
className="px-3 py-1 text-sm rounded-full bg-blue-500/10 text-blue-400"
>
{category}
</span>
))}
</div>

<p className="text-lg text-neutral-400">
{post.excerpt}
</p>
</div>

<div className="prose prose-invert prose-neutral max-w-none">
<MDXRemote {...mdxSource} components={components} />
</div>
</div>
</div>
)
}

function BlogSkeleton() {
return (
<div className="min-h-screen bg-black text-white">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div className="animate-pulse">
<div className="h-10 w-2/3 bg-neutral-800 rounded mb-4" />
<div className="flex gap-4 mb-6">
<div className="h-5 w-32 bg-neutral-800 rounded" />
<div className="h-5 w-24 bg-neutral-800 rounded" />
<div className="h-5 w-32 bg-neutral-800 rounded" />
</div>
<div className="flex gap-2 mb-4">
<div className="h-6 w-20 bg-neutral-800 rounded-full" />
<div className="h-6 w-24 bg-neutral-800 rounded-full" />
</div>
<div className="h-6 w-2/3 bg-neutral-800 rounded mb-8" />
<div className="space-y-4">
<div className="h-4 bg-neutral-800 rounded w-full" />
<div className="h-4 bg-neutral-800 rounded w-5/6" />
<div className="h-4 bg-neutral-800 rounded w-4/6" />
</div>
</div>
</div>
</div>
)
}
22 changes: 22 additions & 0 deletions app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { blogData } from '@/lib/blog-data'
import BlogContent from './blog-content'

export default function BlogPage({
params,
}: {
params: { slug: string }
searchParams: Record<string, string | string[] | undefined>
}) {
const post = blogData.find((p) => p.slug === params.slug)

if (!post) {
return null
}

return <BlogContent initialPost={post} slug={params.slug} />
}

export const generateStaticParams = () =>
blogData.map((post) => ({
slug: post.slug,
}))
Loading

0 comments on commit 17b0e57

Please sign in to comment.