5.6 KB
raw
import React from "react";
import PropTypes from "prop-types";
import styles from "@styles/pages/code.module.css";
import Page from "../components/page";
const GitHubIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
</svg>
);
};
const PROJECTS = [
{
name: "Taproot",
slug: "taproot",
description:
"Dotfiles, containers, and the configs that make a machine mine.",
tech: ["Docker", "Shell"],
},
{
name: "darkfurrow.com",
slug: "darkfurrow.com",
description:
"A living almanac of seasons, soil, and the quiet knowledge that used to be common.",
tech: ["Rust", "Axum"],
},
{
name: "Heartwood",
slug: "heartwood",
description:
"A minimal self-hosted git browser. Renders bare repos as a website with commits, diffs, syntax-highlighted blobs, and atom feeds, plus clone over HTTPS.",
tech: ["Rust", "Axum"],
},
{
name: "Analytics",
slug: "analytics",
description:
"A self-hostable analytics service with a straightforward API to track events from any source.",
tech: ["Rust", "Axum", "SQLite"],
},
{
name: "Status",
slug: "status",
description:
"A self-hosted uptime monitor and status page builder, with Lighthouse audits and PDF reports baked in.",
tech: ["Rust", "Axum", "SQLite"],
},
{
name: "blog.bythewood.me",
slug: "blog.bythewood.me",
description:
"A self-hostable markdown blog for developers, with code blocks, syntax highlighting, live search, great SEO, and a clean customizable UI.",
tech: ["Rust", "Axum"],
},
{
name: "Timelite",
slug: "timelite",
description:
"A simple time tracker that keeps everything local in your browser. No accounts, no sync, no server-side state.",
tech: ["Next.js", "JavaScript"],
},
{
name: "isaacbythewood.com",
slug: "isaacbythewood.com",
description: "The personal website you are looking at right now.",
tech: ["Next.js", "JavaScript"],
},
];
const Code = ({ commits }) => {
return (
<Page title="Code" description="Some of my most recent coding projects.">
<div className={styles.background} />
<h1 className={styles.heading}>Code</h1>
<p className={styles.paragraph}>
Outside of work I build self-hosted tools, personal websites, and
whatever else catches my interest. Side projects are where I experiment
with new technology and stay sharp since at work I stick to stable,
mature frameworks. There is plenty more to see on{" "}
<a
href="https://github.com/overshard"
rel="noopener noreferrer"
target="_blank"
>
my GitHub account
</a>
.
</p>
<div className={styles.projects}>
{PROJECTS.map((project, index) => (
<div
key={project.slug}
className={styles.project}
style={{ animationDelay: `${index * 100}ms` }}
>
<h2 className={styles.projectHeading}>{project.name}</h2>
<div className={styles.tags}>
{project.tech.map((tag) => (
<span key={tag} className={styles.tag}>
{tag}
</span>
))}
</div>
<p className={styles.projectParagraph}>{project.description}</p>
{commits[project.slug] && (
<pre className={styles.projectCommit}>
{JSON.stringify(commits[project.slug], null, 2)}
</pre>
)}
<a
className={styles.projectButton}
href={`https://github.com/overshard/${project.slug}`}
rel="noopener noreferrer"
target="_blank"
>
<GitHubIcon />
GitHub
</a>
</div>
))}
</div>
</Page>
);
};
export async function getStaticProps() {
const getCommit = async (repo) => {
try {
const res = await fetch(
`https://api.github.com/repos/overshard/${repo}/commits?per_page=1`
);
if (!res.ok) {
console.error(`GitHub API failed for ${repo}: ${res.status}`);
return null;
}
const commits = await res.json();
if (!Array.isArray(commits) || commits.length === 0) return null;
const commit = commits[0];
return {
sha: commit.sha ? commit.sha.substring(0, 7) : null,
message: commit.commit?.message || null,
date: commit.commit?.author?.date || null,
author: commit.commit?.author?.name || null,
};
} catch (err) {
console.error(`Failed to fetch commits for ${repo}:`, err);
return null;
}
};
const results = await Promise.all(
PROJECTS.map(async (project) => [
project.slug,
await getCommit(project.slug),
])
);
const commits = Object.fromEntries(results);
return {
props: { commits },
revalidate: 3600,
};
}
Code.propTypes = {
commits: PropTypes.object,
};
export default Code;