From f24030844b7f85b39bf3fddef5faaef1b8aecfc1 Mon Sep 17 00:00:00 2001 From: Nick Oates <58091943+n1ckoates@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:33:16 -0700 Subject: [PATCH 01/13] slop --- README.md | 9 -- app/about/page.tsx | 125 +++++++++++++----- app/blog/page-client.tsx | 94 +++++++------ app/blog/page.tsx | 4 +- app/components/Menu.tsx | 32 ++--- app/components/Navbar.tsx | 18 +-- app/components/NewsletterForm.tsx | 63 --------- app/components/Orb.tsx | 2 +- app/components/Prose.tsx | 17 ++- app/components/Sidebar.tsx | 26 ++-- app/components/SidebarLinks.tsx | 13 +- app/components/SocialIcons.tsx | 4 +- app/components/TextInput.tsx | 2 +- app/components/ThemeSwitch.tsx | 31 ----- app/components/typography.tsx | 31 ++--- app/feed.xml/route.ts | 2 +- app/globals.css | 13 +- app/layout.tsx | 21 ++- app/lib/actions.ts | 47 ------- app/lib/mergeMetadata.ts | 5 +- app/not-found.tsx | 8 +- app/page.tsx | 106 +++++++-------- app/projects/page.tsx | 71 ++++++---- bun.lockb | Bin 307973 -> 307508 bytes .../artificial-intelligence-and-copyright.mdx | 2 - package.json | 1 - 26 files changed, 333 insertions(+), 414 deletions(-) delete mode 100644 app/components/NewsletterForm.tsx delete mode 100644 app/components/ThemeSwitch.tsx delete mode 100644 app/lib/actions.ts diff --git a/README.md b/README.md index 5a999a2..630787b 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,3 @@ ![Screenshot of nickoates.com](https://repository-images.githubusercontent.com/622056687/77b6322b-5d3d-4bc2-bf35-18f2ba1c809a) This is my website, blog, and portfolio. I designed and built it myself using Next.js, Tailwind CSS, TypeScript, and MDX. For more information on the tech stack, see [my initial blog post](https://nickoates.com/blog/rebuilding-my-website). Parts of the design were inspired by [Lee Robinson's website](https://leerob.io) and [Apple's newsroom](https://www.apple.com/newsroom). - -## Setting up newsletter subscriptions - -I use [Resend](https://resend.com) to manage my newsletter. To setup your own newsletter, create a Resend account (if you haven't already), then add an audience for your newsletter. Generate an API key with full access to your account, and add both the key and audience ID to your `.env.local` file: - -```bash -RESEND_API_KEY=re_yourApiKey -RESEND_AUDIENCE_ID=your-audience-id -``` diff --git a/app/about/page.tsx b/app/about/page.tsx index c481ffe..e3a83ae 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -4,46 +4,99 @@ import Image from "next/image"; import me from "../../public/images/nick-oates.webp"; import { Orb } from "@/components/Orb"; import { OrbContainer } from "@/components/Orb"; -import { S, CustomLink } from "@/components/typography"; +import { CustomLink } from "@/components/typography"; import { ViewTransition } from "react"; export const metadata = mergeMetadata({ - title: "About", + title: "About", }); export default function About() { - return ( - - - - - - -

About Nick Oates

- -
- Nick Oates standing in front of of a tree -
Photo of me by AJ Dimke
-
- - -

- Hey, I'm Nick Oates, a software engineer with a passion for designing and building cool things on the web. I love obsessing over the small details of my work, and I'm always looking for new things to learn and ways to improve my skills. -

-
- -

- I'm a strong believer in internet freedom & privacy, and I believe those values are essential to a healthy, open web. I'm also a big fan of open-source software, reflected in much of my work. -

- -

- Outside of tech, I'm an editor of my high school's newspaper, The Benson Orbit, which I helped start in 2024. I cover school news and culture, and handle our digital presence. I'm also the Chief of Music at KBPS, the oldest high school radio station in America, where I help manage the music library, DJ on-air, and write and announce news. I have a lot of fun playing video games, especially with friends, with my favorites being Portal 2, Minecraft, and Zelda: Skyward Sword, in that order. -

- -

- You can follow me on Twitter or check out my code on GitHub to see what I'm working on, or if you'd like to get in touch, send me an email at hey@nickoates.com. -

- -
- ) + return ( + + + + + + +

+ About Nick Oates +

+ +
+ Nick Oates standing in front of a tree +
+ Photo by{" "} + + AJ Dimke + +
+
+ + +

+ I'm a software engineer at Vercel, where I work on the{" "} + AI SDK. I care + about thoughtful developer tools, open-source software, and the small + details that make technology feel good to use. +

+
+ +

+ I'm a strong believer in internet freedom and privacy, and I + believe those values are essential to a healthy, open web. They're + reflected in much of my work. +

+ +

+ I graduated from Benson Polytechnic High School in 2026, where I studied + radio broadcasting and gave the valedictorian speech. While I was there, + I helped start the school newspaper,{" "} + + The Benson Orbit + + , and served as Chief of Music at{" "} + KBPS, the oldest high + school radio station in America. +

+ +

+ Outside of work, I like writing and playing video games with friends. My + favorites are{" "} + + Portal 2 + + ,{" "} + + Minecraft + + , and{" "} + + Zelda: Skyward Sword + + , in that order. +

+ +

+ You can follow me on{" "} + Twitter, see what + I'm building on{" "} + GitHub, or + email me at{" "} + + hey@nickoates.com + + . +

+
+ ); } diff --git a/app/blog/page-client.tsx b/app/blog/page-client.tsx index b06519e..cee8b71 100644 --- a/app/blog/page-client.tsx +++ b/app/blog/page-client.tsx @@ -20,9 +20,7 @@ type PartialBlogPost = { blurDataURL: string; }; -export function BlogClient(props: { - posts: PartialBlogPost[]; -}) { +export function BlogClient(props: { posts: PartialBlogPost[] }) { const [queryText, setQueryText] = useState(""); const [animationParent] = useAutoAnimate(); @@ -35,70 +33,82 @@ export function BlogClient(props: { return ( <> -
+
-

Blog Posts

+
+

+ Blog +

+

+ Notes on software, the web, and things I'm learning. +

+
-
+
setQueryText(e.target.value)} /> - +
-
+
{!filteredPosts.length && ( -

No posts were found with that search term.

+

+ No posts were found with that search term. +

)} {filteredPosts.map(({ post }) => ( - {post.coverAlt} +
+ {post.coverAlt} +
-
- -
- {" "} + • {post.readingTime} min read +

-
- -

- {post.title} -

-
-

- {post.summary} -

-
+ +

+ {post.title} +

+
+ +

+ {post.summary} +

))} diff --git a/app/blog/page.tsx b/app/blog/page.tsx index 890cf07..4664607 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -1,8 +1,8 @@ -import { BlogClient } from "./page-client" +import { BlogClient } from "./page-client"; import allPosts from "@/lib/posts"; import mergeMetadata from "@/lib/mergeMetadata"; -export const metadata = mergeMetadata({ title: "Blog Posts" }); +export const metadata = mergeMetadata({ title: "Blog" }); export default function Blog() { // Remove unneeded fields from posts before sending them to the client diff --git a/app/components/Menu.tsx b/app/components/Menu.tsx index 03449c5..51eb397 100644 --- a/app/components/Menu.tsx +++ b/app/components/Menu.tsx @@ -13,18 +13,16 @@ export default function Menu({ open: boolean; setOpen: (open: boolean) => void; }) { - if (typeof window !== "undefined") { - const main = document.getElementById("main") as HTMLElement; - const body = document.body; + useEffect(() => { + const main = document.getElementById("main"); + main?.toggleAttribute("inert", open); + document.body.classList.toggle("overflow-hidden", open); - if (open) { - main.inert = true; - body.classList.add("overflow-hidden"); - } else { - main.inert = false; - body.classList.remove("overflow-hidden"); - } - } + return () => { + main?.removeAttribute("inert"); + document.body.classList.remove("overflow-hidden"); + }; + }, [open]); useEffect(() => { function handleResize() { @@ -41,7 +39,7 @@ export default function Menu({ aria-label="Toggle navigation" aria-expanded={open} onClick={() => setOpen(!open)} - className="grid size-10 items-center justify-center gap-1.5 rounded-md p-4 transition-colors hover:bg-zinc-300/50 dark:hover:bg-zinc-700/50" + className="grid size-8 items-center justify-center gap-1.5 rounded-md p-2 transition-colors hover:bg-zinc-200/70 dark:hover:bg-zinc-800/70" type="button" >
-
+
{links.map(({ title, href }) => ( setOpen(false)} >
-
+
diff --git a/app/components/Navbar.tsx b/app/components/Navbar.tsx index 98853f7..9fc8b86 100644 --- a/app/components/Navbar.tsx +++ b/app/components/Navbar.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from "react"; import Menu from "./Menu"; -import ThemeSwitch from "./ThemeSwitch"; import Link from "next/link"; import clsx from "clsx"; @@ -12,21 +11,18 @@ export default function Navbar() { useEffect(() => { function handleScroll() { - if (window.scrollY >= 20) { - setSolid(true); - } else { - setSolid(false); - } + setSolid(window.scrollY >= 20); } - window.addEventListener("scroll", handleScroll); + handleScroll(); + window.addEventListener("scroll", handleScroll, { passive: true }); return () => window.removeEventListener("scroll", handleScroll); }, []); return ( ); } diff --git a/app/components/NewsletterForm.tsx b/app/components/NewsletterForm.tsx deleted file mode 100644 index d683df9..0000000 --- a/app/components/NewsletterForm.tsx +++ /dev/null @@ -1,63 +0,0 @@ -"use client"; - -import { IconLoader2, IconMail } from "@tabler/icons-react"; -import clsx from "clsx"; -import { subscribe, type State } from "@/lib/actions"; -import { useActionState } from "react"; -import TextInput from "@/components/TextInput"; - -export default function NewsletterForm({ - title = "Subscribe to my newsletter", -}: { - title?: string; -}) { - const [{ message, status }, formAction, isPending] = useActionState< - State, - FormData - >(subscribe, { - status: "idle", - message: title, - }); - - return ( -
- - {message} - - -
- - - - -
- ); -} diff --git a/app/components/Orb.tsx b/app/components/Orb.tsx index b078b36..4780939 100644 --- a/app/components/Orb.tsx +++ b/app/components/Orb.tsx @@ -11,7 +11,7 @@ export function OrbContainer({ children }: { children: React.ReactNode }) { export function Orb({ className }: { className?: string }) { return (
); } diff --git a/app/components/Prose.tsx b/app/components/Prose.tsx index 7491490..b8556b3 100644 --- a/app/components/Prose.tsx +++ b/app/components/Prose.tsx @@ -1,4 +1,13 @@ -export function Prose(props: React.HTMLAttributes & { as?: React.ElementType }) { - const Component = props.as || "article"; - return -} \ No newline at end of file +export function Prose({ + as, + ...props +}: React.HTMLAttributes & { as?: React.ElementType }) { + const Component = as || "article"; + + return ( + + ); +} diff --git a/app/components/Sidebar.tsx b/app/components/Sidebar.tsx index e598605..faa1321 100644 --- a/app/components/Sidebar.tsx +++ b/app/components/Sidebar.tsx @@ -1,26 +1,24 @@ import Link from "next/link"; -import ThemeSwitch from "./ThemeSwitch"; import SidebarLinks from "@/components/SidebarLinks"; import SocialIcons from "@/components/SocialIcons"; export default function Sidebar() { return ( -