From 8466324a59746bb223a7fa169607fe0be0668314 Mon Sep 17 00:00:00 2001
From: badbl0cks <4161747+badbl0cks@users.noreply.github.com>
Date: Mon, 20 Jul 2026 12:10:32 -0700
Subject: [PATCH] Add Projects page backed by the Forgejo API, with caching
---
src/components/ProjectCard.astro | 73 ++++++++++++++++++++++++++++++
src/layouts/BaseLayout.astro | 3 +-
src/lib/ForgejoClient.ts | 52 +++++++++++++++++++++
src/lib/ProjectsCache.ts | 78 ++++++++++++++++++++++++++++++++
src/pages/projects.astro | 64 ++++++++++++++++++++++++++
5 files changed, 269 insertions(+), 1 deletion(-)
create mode 100644 src/components/ProjectCard.astro
create mode 100644 src/lib/ForgejoClient.ts
create mode 100644 src/lib/ProjectsCache.ts
create mode 100644 src/pages/projects.astro
diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro
new file mode 100644
index 0000000..92afeec
--- /dev/null
+++ b/src/components/ProjectCard.astro
@@ -0,0 +1,73 @@
+---
+import type { Project } from "@lib/ProjectsCache";
+interface Props { project: Project }
+const { project } = Astro.props;
+// Space instead of "T" gives the browser somewhere to wrap; the datetime
+// attribute keeps the real ISO value.
+const committedAtLabel = project.lastCommitAt.replace("T", " ");
+---
+
+
+
+
+ {project.description && {project.description}
}
+ {project.lastCommitMessage && (
+
+ {project.lastCommitSha} {project.lastCommitMessage}
+
+ )}
+
+ {project.languages.length > 0 && (
+ `${l.name} ${l.percent}%`).join(", ")}`}>
+ {project.languages.map((l) => (
+
+ ))}
+
+
+ {project.languages.slice(0, 4).map((l) => {l.name} {l.percent}%)}
+
+ )}
+
+ {project.topics.length > 0 && (
+
+ {project.topics.map((t) => - #{t}
)}
+
+ )}
+
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index 9f3eadd..fbbfc67 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -44,6 +44,7 @@
@@ -106,7 +107,7 @@
/>
- Made from scratch with BAHz: Bun, Astro, and Htmz!
+ Made from scratch with BAAHz: Bun, Astro, Alpine.js, and Htmz!