mirror of
				https://github.com/jakejarvis/jarv.is.git
				synced 2025-10-31 00:16:02 -04:00 
			
		
		
		
	a bit more cleanup
This commit is contained in:
		
							
								
								
									
										25
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								.env.example
									
									
									
									
									
								
							| @@ -1,25 +1,32 @@ | ||||
| # required. storage for hit counter endpoints at /api/count and /api/hits. | ||||
| # currently uses Postgres, but this can be changed in prisma/schema.prisma. | ||||
| # required. storage for hit counter's server component at app/notes/[slug]/counter.tsx and API endpoint at /api/hits. | ||||
| # currently set automatically by Vercel's Neon integration, but this can be changed in prisma/schema.prisma. | ||||
| # https://www.prisma.io/docs/postgres/overview | ||||
| # https://vercel.com/marketplace/neon | ||||
| DATABASE_URL= | ||||
|  | ||||
| # requred. used for /projects grid, built with SSG. only needs the "public_repo" scope since we don't need/want to | ||||
| # required. used for /projects grid, built with ISR. only needs the "public_repo" scope since we don't need/want to | ||||
| # showcase any private repositories, obviously. | ||||
| # https://github.com/settings/tokens/new?scopes=public_repo | ||||
| GH_PUBLIC_TOKEN= | ||||
| GITHUB_TOKEN= | ||||
|  | ||||
| # required for production. sends contact form submissions via /api/contact. | ||||
| # optional. enables comments on blog posts via GitHub discussions. | ||||
| # https://giscus.app/ | ||||
| NEXT_PUBLIC_GISCUS_REPO_ID= | ||||
| NEXT_PUBLIC_GISCUS_CATEGORY_ID= | ||||
|  | ||||
| # required for production. sends contact form submissions via a server action (see app/contact/actions.ts). | ||||
| # https://resend.com/api-keys | ||||
| # currently set automatically by Vercel's Resend integration. | ||||
| # https://vercel.com/integrations/resend | ||||
| RESEND_API_KEY= | ||||
| # optional. send submissions from noreply@{RESEND_DOMAIN}; defaults to onboarding@resend.dev. sender's real email is | ||||
| # passed via a Reply-To header. setting this makes zero difference to the user. | ||||
| # https://resend.com/docs/send-with-nodemailer-smtp | ||||
| RESEND_DOMAIN= | ||||
|  | ||||
| # required for production. falls back to testing keys if not set or in dev environment: | ||||
| # required for production. site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the captcha widget. | ||||
| # falls back to testing keys if not set or in dev environment: | ||||
| # https://developers.cloudflare.com/turnstile/troubleshooting/testing/ | ||||
| # site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the actual captcha widget. | ||||
| # https://developers.cloudflare.com/turnstile/ | ||||
| NEXT_PUBLIC_TURNSTILE_SITE_KEY= | ||||
| # used only for backend validation of contact form submissions on /api/contact. | ||||
| # used for backend validation of turnstile result. | ||||
| TURNSTILE_SECRET_KEY= | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| import { headers } from "next/headers"; | ||||
| import { z } from "zod"; | ||||
| import { Resend } from "resend"; | ||||
| import config from "../../lib/config"; | ||||
| import config from "../../lib/config/constants"; | ||||
|  | ||||
| const schema = z.object({ | ||||
|   name: z.string().min(1, { message: "Name is required" }), | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { buildFeed } from "../../lib/helpers/build-feed"; | ||||
| export const dynamic = "force-static"; | ||||
|  | ||||
| export const GET = async () => { | ||||
|   return new Response(await buildFeed({ type: "atom" }), { | ||||
|   return new Response((await buildFeed()).atom1(), { | ||||
|     headers: { | ||||
|       "content-type": "application/atom+xml; charset=utf-8", | ||||
|     }, | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { buildFeed } from "../../lib/helpers/build-feed"; | ||||
| export const dynamic = "force-static"; | ||||
|  | ||||
| export const GET = async () => { | ||||
|   return new Response(await buildFeed({ type: "rss" }), { | ||||
|   return new Response((await buildFeed()).rss2(), { | ||||
|     headers: { | ||||
|       "content-type": "application/rss+xml; charset=utf-8", | ||||
|     }, | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import { ThemeProvider } from "../contexts/ThemeContext"; | ||||
| import Header from "../components/Header"; | ||||
| import Footer from "../components/Footer"; | ||||
| import { SkipToContentLink, SkipToContentTarget } from "../components/SkipToContent"; | ||||
| import config from "../lib/config"; | ||||
| import config from "../lib/config/constants"; | ||||
| import type { Metadata } from "next"; | ||||
| import type { Person, WithContext } from "schema-dts"; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import config from "../lib/config"; | ||||
| import config from "../lib/config/constants"; | ||||
| import type { MetadataRoute } from "next"; | ||||
|  | ||||
| const manifest = (): MetadataRoute.Manifest => { | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import Loading from "../../../components/Loading"; | ||||
| import HitCounter from "./counter"; | ||||
| import { getPostSlugs, getFrontMatter } from "../../../lib/helpers/posts"; | ||||
| import { metadata as defaultMetadata } from "../../layout"; | ||||
| import config from "../../../lib/config"; | ||||
| import config from "../../../lib/config/constants"; | ||||
| import { FiCalendar, FiTag, FiEdit, FiEye } from "react-icons/fi"; | ||||
| import type { Metadata, Route } from "next"; | ||||
| import type { Article, WithContext } from "schema-dts"; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import Content from "../../components/Content"; | ||||
| import Link from "../../components/Link"; | ||||
| import Time from "../../components/Time"; | ||||
| import { getAllPosts } from "../../lib/helpers/posts"; | ||||
| import config from "../../lib/config"; | ||||
| import config from "../../lib/config/constants"; | ||||
| import { metadata as defaultMetadata } from "../layout"; | ||||
| import type { ReactElement } from "react"; | ||||
| import type { Metadata, Route } from "next"; | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import PageTitle from "../../components/PageTitle"; | ||||
| import Link from "../../components/Link"; | ||||
| import RelativeTime from "../../components/RelativeTime"; | ||||
| import commaNumber from "comma-number"; | ||||
| import config from "../../lib/config"; | ||||
| import config from "../../lib/config/constants"; | ||||
| import { metadata as defaultMetadata } from "../layout"; | ||||
| import { GoStar, GoRepoForked } from "react-icons/go"; | ||||
| import { SiGithub } from "react-icons/si"; | ||||
| @@ -43,8 +43,8 @@ type Project = { | ||||
|  | ||||
| async function getRepos(): Promise<Project[] | null> { | ||||
|   // don't fail the entire site build if the required API key for this page is missing | ||||
|   if (!process.env.GH_PUBLIC_TOKEN || process.env.GH_PUBLIC_TOKEN === "") { | ||||
|     console.warn(`ERROR: I can't fetch any GitHub projects without "GH_PUBLIC_TOKEN" set! Skipping for now...`); | ||||
|   if (!process.env.GITHUB_TOKEN || process.env.GITHUB_TOKEN === "") { | ||||
|     console.warn(`ERROR: I can't fetch any GitHub projects without "GITHUB_TOKEN" set! Skipping for now...`); | ||||
|  | ||||
|     return null; | ||||
|   } | ||||
| @@ -86,7 +86,7 @@ async function getRepos(): Promise<Project[] | null> { | ||||
|       limit: 12, | ||||
|       headers: { | ||||
|         accept: "application/vnd.github.v3+json", | ||||
|         authorization: `token ${process.env.GH_PUBLIC_TOKEN}`, | ||||
|         authorization: `token ${process.env.GITHUB_TOKEN}`, | ||||
|       }, | ||||
|     } | ||||
|   ); | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import config from "../lib/config"; | ||||
| import config from "../lib/config/constants"; | ||||
| import type { MetadataRoute } from "next"; | ||||
|  | ||||
| export const dynamic = "force-static"; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import path from "path"; | ||||
| import glob from "fast-glob"; | ||||
| import { getAllPosts } from "../lib/helpers/posts"; | ||||
| import config from "../lib/config"; | ||||
| import config from "../lib/config/constants"; | ||||
| import type { MetadataRoute } from "next"; | ||||
|  | ||||
| export const dynamic = "force-static"; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| "use client"; | ||||
|  | ||||
| import Giscus from "@giscus/react"; | ||||
| import config from "../../lib/config"; | ||||
| import config from "../../lib/config/constants"; | ||||
| import type { GiscusProps } from "@giscus/react"; | ||||
|  | ||||
| export type CommentsProps = { | ||||
| @@ -10,9 +10,9 @@ export type CommentsProps = { | ||||
|  | ||||
| const Comments = ({ title }: CommentsProps) => { | ||||
|   // fail silently if giscus isn't configured | ||||
|   if (!config.giscusConfig) { | ||||
|   if (!process.env.NEXT_PUBLIC_GISCUS_REPO_ID || !process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID) { | ||||
|     console.warn( | ||||
|       "[giscus] not configured, ensure giscusConfig.repoId and giscusConfig.categoryId are set in lib/config/index.js" | ||||
|       "[giscus] not configured, ensure 'NEXT_PUBLIC_GISCUS_REPO_ID' and 'NEXT_PUBLIC_GISCUS_CATEGORY_ID' environment variables are set." | ||||
|     ); | ||||
|  | ||||
|     return null; | ||||
| @@ -22,14 +22,15 @@ const Comments = ({ title }: CommentsProps) => { | ||||
|   return ( | ||||
|     <Giscus | ||||
|       repo={config.githubRepo as GiscusProps["repo"]} | ||||
|       repoId={config.giscusConfig.repoId} | ||||
|       repoId={process.env.NEXT_PUBLIC_GISCUS_REPO_ID} | ||||
|       term={title} | ||||
|       category="Comments" | ||||
|       categoryId={config.giscusConfig.categoryId} | ||||
|       categoryId={process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID} | ||||
|       mapping="specific" | ||||
|       reactionsEnabled="1" | ||||
|       emitMetadata="0" | ||||
|       inputPosition="top" | ||||
|       theme="preferred_color_scheme" | ||||
|       loading="lazy" | ||||
|     /> | ||||
|   ); | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import clsx from "clsx"; | ||||
| import Link from "../Link"; | ||||
| import { GoHeartFill } from "react-icons/go"; | ||||
| import { SiNextdotjs } from "react-icons/si"; | ||||
| import config from "../../lib/config"; | ||||
| import config from "../../lib/config/constants"; | ||||
| import type { ComponentPropsWithoutRef } from "react"; | ||||
|  | ||||
| import styles from "./Footer.module.css"; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import clsx from "clsx"; | ||||
| import Link from "../Link"; | ||||
| import Image from "../Image"; | ||||
| import Menu from "../Menu"; | ||||
| import config from "../../lib/config"; | ||||
| import config from "../../lib/config/constants"; | ||||
| import type { ComponentPropsWithoutRef } from "react"; | ||||
|  | ||||
| import styles from "./Header.module.css"; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import NextLink from "next/link"; | ||||
| import clsx from "clsx"; | ||||
| import objStr from "obj-str"; | ||||
| import config from "../../lib/config"; | ||||
| import config from "../../lib/config/constants"; | ||||
| import type { ComponentPropsWithoutRef } from "react"; | ||||
|  | ||||
| import styles from "./Link.module.css"; | ||||
|   | ||||
| @@ -35,10 +35,11 @@ const Video = ({ src, poster, autoplay = false, responsive = true, className, .. | ||||
|       > | ||||
|         {src.map((file) => { | ||||
|           const extension = file.split(".").pop(); | ||||
|  | ||||
|           if (extension === "vtt") { | ||||
|             return <track key={file} kind="subtitles" src={file} srcLang="en" label="English" default />; | ||||
|           } else { | ||||
|             return <source key={file} src={file} type={`video/${file.split(".").pop()}`} />; | ||||
|             return <source key={file} src={file} type={`video/${extension}`} />; | ||||
|           } | ||||
|         })} | ||||
|       </video> | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| // @ts-check
 | ||||
| 
 | ||||
| const config = { | ||||
| const constants = { | ||||
|   // Site info
 | ||||
|   siteName: "Jake Jarvis", | ||||
|   siteLocale: "en-US", | ||||
| @@ -20,11 +18,6 @@ const config = { | ||||
|   licenseUrl: "https://creativecommons.org/licenses/by/4.0/", | ||||
|   copyrightYearStart: 2001, | ||||
|   githubRepo: "jakejarvis/jarv.is", | ||||
|   giscusConfig: { | ||||
|     // https://github.com/giscus/giscus-component/tree/main/packages/react#readme
 | ||||
|     repoId: "MDEwOlJlcG9zaXRvcnk1MzM0MDgxMQ==", | ||||
|     categoryId: "DIC_kwDOAy3qi84CAsjS", | ||||
|   }, | ||||
| 
 | ||||
|   // Me info
 | ||||
|   authorName: "Jake Jarvis", | ||||
| @@ -42,4 +35,4 @@ const config = { | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export default config; | ||||
| export default constants; | ||||
| @@ -1,10 +1,10 @@ | ||||
| import { Feed } from "feed"; | ||||
| import { getAllPosts } from "./posts"; | ||||
| import config from "../config"; | ||||
| import config from "../config/constants"; | ||||
|  | ||||
| import meJpg from "../../app/me.jpg"; | ||||
|  | ||||
| export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Promise<string> => { | ||||
| export const buildFeed = async (): Promise<Feed> => { | ||||
|   // https://github.com/jpmonette/feed#example | ||||
|   const feed = new Feed({ | ||||
|     id: config.baseUrl, | ||||
| @@ -43,15 +43,5 @@ export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Pro | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   if (options.type === "rss") { | ||||
|     return feed.rss2(); | ||||
|   } else if (options.type === "atom") { | ||||
|     return feed.atom1(); | ||||
|   } else if (options.type === "json") { | ||||
|     // rare but including as an option because why not... | ||||
|     // https://www.jsonfeed.org/ | ||||
|     return feed.json1(); | ||||
|   } else { | ||||
|     throw new TypeError(`Invalid feed type "${options.type}", must be "rss", "atom", or "json".`); | ||||
|   } | ||||
|   return feed; | ||||
| }; | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import dayjsRelativeTime from "dayjs/plugin/relativeTime"; | ||||
| import dayjsLocalizedFormat from "dayjs/plugin/localizedFormat"; | ||||
| import dayjsAdvancedFormat from "dayjs/plugin/advancedFormat"; | ||||
| import "dayjs/locale/en"; | ||||
| import config from "../config"; | ||||
| import config from "../config/constants"; | ||||
|  | ||||
| const IsomorphicDayJs = (date?: dayjs.ConfigType): dayjs.Dayjs => { | ||||
|   // plugins | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import glob from "fast-glob"; | ||||
| import pMap from "p-map"; | ||||
| import pMemoize from "p-memoize"; | ||||
| import { formatDate } from "./format-date"; | ||||
| import config from "../config"; | ||||
| import config from "../config/constants"; | ||||
|  | ||||
| // path to directory with .mdx files, relative to project root | ||||
| const POSTS_DIR = "notes"; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { NextResponse } from "next/server"; | ||||
| import type { NextRequest } from "next/server"; | ||||
|  | ||||
| import siteConfig from "./lib/config"; | ||||
| import siteConfig from "./lib/config/constants"; | ||||
|  | ||||
| export function middleware(request: NextRequest) { | ||||
|   const response = NextResponse.next(); | ||||
|   | ||||
| @@ -28,7 +28,7 @@ const nextConfig: NextConfig = { | ||||
|   }, | ||||
|   eslint: { | ||||
|     // https://nextjs.org/docs/basic-features/eslint#linting-custom-directories-and-files | ||||
|     dirs: ["app", "components", "contexts", "hooks", "lib"], | ||||
|     dirs: ["app", "components", "contexts", "hooks", "lib", "notes"], | ||||
|   }, | ||||
|   headers: async () => [ | ||||
|     { | ||||
|   | ||||
| @@ -101,7 +101,7 @@ | ||||
|   "engines": { | ||||
|     "node": ">=20.x" | ||||
|   }, | ||||
|   "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b", | ||||
|   "packageManager": "pnpm@10.6.1+sha512.40ee09af407fa9fbb5fbfb8e1cb40fbb74c0af0c3e10e9224d7b53c7658528615b2c92450e74cfad91e3a2dcafe3ce4050d80bda71d757756d2ce2b66213e9a3", | ||||
|   "cacheDirectories": [ | ||||
|     "node_modules", | ||||
|     ".next/cache" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user