Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI

on:
pull_request:
push:
branches:
- main
- develop

permissions:
contents: read

jobs:
checks:
name: Lint, format, test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Check formatting
run: npm run format:check

- name: Test
run: npm test
91 changes: 57 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,56 @@

<img src="https://img.shields.io/badge/Next.js-000000?style=for-the-badge&logo=next.js&logoColor=white" />
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" />
<img src="https://img.shields.io/badge/Recharts-22B5BF?style=for-the-badge" />
<img src="https://img.shields.io/badge/Tailwind_CSS-06B6D4?style=for-the-badge&logo=tailwindcss&logoColor=white" />
<img src="https://img.shields.io/badge/GitHub_API-181717?style=for-the-badge&logo=github&logoColor=white" />
<a href="https://github.com/brandonperfetti/github-commit-dashboard/actions/workflows/ci.yml"><img src="https://github.com/brandonperfetti/github-commit-dashboard/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>

**A clean, focused operating dashboard for your GitHub activity.**
**Build turns a GitHub account into an engineering dashboard: 30-day contribution signal, PR and issue flow health, repository momentum and risk, and release cadence.**

[Live Demo](https://github-commit-dashboard.vercel.app) · [Report Bug](https://github.com/brandonperfetti/github-commit-dashboard/issues)
[Live Demo](https://github.brandonperfetti.com) · [Report Bug](https://github.com/brandonperfetti/github-commit-dashboard/issues)

</div>

---
![Build dashboard overview: 30-day contribution trend, language share, and featured repositories](public/og-image.png)

## Overview
## What it shows

GitHub Commit Dashboard transforms raw GitHub API signals into a polished, scannable UI. It surfaces your recent contribution history, top repositories, and language breakdown — without the noise of the standard GitHub profile UI.
| Route | Charts and panels |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/` | 30-day contribution trend · language share · pinned and recently shipped repositories |
| `/activity` | 30-day contribution heatmap with weekly totals and streaks · PR cycle time (median open-to-merge hours) · PR flow health (merge and reopen rates) · issue flow health (opened vs closed, net backlog) · commit timing heatmap (weekday × hour, timezone-aware) · PR throughput · daily breakdown |
| `/repos` | pinned vs non-pinned momentum · weekly commit cadence · repository risk panel (hot / active / stale / dormant) · browsable repository inventory |
| `/featured` | featured relevance score breakdown (pinned boost + stars + recency + 30-day commits) · monthly release cadence · featured project cards |

Built with **Next.js App Router** (server components for real-time data), **Tailwind CSS**, and a dark/light theme system, it's a practical tool for developers who want a cleaner lens into their own productivity.

---

## Features

- 📊 **Contribution Activity** — Visual heatmap-style contribution feed over the past 30 days
- 🏆 **Featured Repositories** — Top repos surfaced by star count and recent push activity
- 🗂️ **Repository Browser** — Full list of public repos with language, stars, and last-updated indicators
- 🌐 **Language Breakdown** — At-a-glance view of your most-used languages across repositories
- 🌙 **Dark / Light Mode** — System-aware theme toggle via `next-themes`
- ⚡ **Live GitHub Signal** — Server-side data fetching ensures fresh data on every page load
Every chart is a [Recharts](https://recharts.org/) component fed by server-side data shaping in [`lib/github.ts`](lib/github.ts). Data comes from the GitHub REST and GraphQL APIs, fetched in server components and cached with a five-minute revalidation window plus an on-demand revalidation endpoint. It is not real-time: there are no WebSockets or streaming updates.

---

## Tech Stack

| Technology | Purpose |
| --------------------------------------------------------- | -------------------------------------- |
| [Next.js](https://nextjs.org/) (App Router) | React framework with server components |
| [TypeScript](https://www.typescriptlang.org/) | Type safety throughout |
| [Tailwind CSS](https://tailwindcss.com/) | Utility-first styling |
| [next-themes](https://github.com/pacocoursey/next-themes) | Dark/light mode |
| [GitHub REST API](https://docs.github.com/en/rest) | Repository and activity data |
| [Lucide React](https://lucide.dev/) | Icon library |
| Technology | Purpose |
| ------------------------------------------------------------ | ----------------------------------------------------------- |
| [Next.js](https://nextjs.org/) 16 (App Router) | React 19 server components, `"use cache"` route caching |
| [TypeScript](https://www.typescriptlang.org/) | Type safety throughout |
| [Recharts](https://recharts.org/) | Charts, with a theme-aware palette from CSS variables |
| [Tailwind CSS](https://tailwindcss.com/) v4 | Utility-first styling |
| [GSAP](https://gsap.com/) | Headline and scroll-reveal motion (respects reduced motion) |
| [GitHub REST + GraphQL API](https://docs.github.com/en/rest) | Repositories, pinned items, commits, PRs, issues, releases |
| [Lucide React](https://lucide.dev/) | Icon library |
| [Vitest](https://vitest.dev/) | Unit and smoke tests |

Dark and light mode use a small custom theme provider (cookie plus `localStorage`, system-aware) rather than a library.

---

## Getting Started

### Prerequisites

- [Node.js](https://nodejs.org/) v18+
- [Node.js](https://nodejs.org/) v20.9+ (CI runs on 24)
- [npm](https://www.npmjs.com/) or compatible package manager
- A [GitHub Personal Access Token](https://github.com/settings/tokens) (classic, with `public_repo` scope)
- A [GitHub token](https://github.com/settings/tokens) (fine-grained with repository read access is preferred; classic with `public_repo` also works)

### Installation

Expand All @@ -69,14 +69,14 @@ Create a `.env.local` file in the project root:

```env
GITHUB_TOKEN=your_github_personal_access_token
GITHUB_USERNAME=your_github_username
NEXT_PUBLIC_SITE_URL=http://localhost:3000
REVALIDATE_SECRET=replace_with_a_long_random_secret
```

> **Note:** The `GITHUB_TOKEN` is used server-side only and is never exposed to the client.
> **Note:** `NEXT_PUBLIC_SITE_URL` is required for production builds and metadata URLs.
> **Note:** `REVALIDATE_SECRET` secures on-demand cache invalidation (`POST /api/revalidate`).
> **Note:** The dashboard's GitHub username is the `USERNAME` constant in `lib/github.ts`; change it there to point the dashboard at another account.

### Development

Expand All @@ -102,8 +102,16 @@ Notes:
- Keep it server-side only (do not prefix with `NEXT_PUBLIC_`).
- `ALLOWED_DEV_ORIGINS` accepts a comma-separated list for local device testing (for example, `192.168.1.156,192.168.1.200`).
- Fine-grained token with read access to repositories is preferred.
- With a token present, the app uses authenticated `/user/repos` calls and can include private repositories you can access.
- Without a token, it falls back to public `/users/{username}/repos`.
- With a token present, the app uses authenticated `/user/repos` calls and can include private repositories you can access, and it reads your pinned repositories over GraphQL.
- Without a token, it falls back to public `/users/{username}/repos` and skips pinned repositories.

### Testing

```bash
npm test
```

The suite runs on [Vitest](https://vitest.dev/) with no network access: every GitHub request goes through a fetch router in `tests/helpers`, and any unrouted request fails the test. It covers the data shaping behind the charts (streaks, calendar weeks, weekly and monthly windows, cycle-time medians, merge rates, timezone bucketing, risk buckets), the GitHub client's pagination and fallbacks, the URL and timezone helpers, and a smoke render of the home route from stubbed API responses, including the case where every upstream call fails. `npm run test:watch` keeps it running while you work.

### Production Build

Expand Down Expand Up @@ -172,29 +180,44 @@ You can also run it manually from Actions and override:
npm run lint
```

### Continuous Integration

- `CI` (`.github/workflows/ci.yml`) runs lint, the Prettier check, and the test suite on every pull request and on pushes to `main` and `develop`.
- `Env Guard` (`.github/workflows/env-guard.yml`) validates the production environment variables.

---

## Project Structure

```
github-commit-dashboard/
├── app/
│ ├── activity/ # Contribution activity page
│ ├── components/ # Shared UI components
│ ├── activity/ # Contribution, PR/issue flow, and commit timing page
│ ├── api/
│ │ ├── activity/ # Commit timing heatmap endpoint (timezone query)
│ │ └── revalidate/ # Secret-guarded on-demand cache invalidation
│ ├── components/
│ │ ├── charts/ # Recharts components + theme-aware color hook
│ │ ├── motion/ # GSAP headline and scroll-reveal wrappers
│ │ └── ui/ # Badge, button, card primitives
│ ├── featured/ # Featured repositories page
│ ├── repos/ # Repository browser page
│ ├── layout.tsx # Root layout with theme provider
│ └── page.tsx # Dashboard home
├── lib/
│ └── github.ts # GitHub API utilities
│ ├── github.ts # GitHub API client and all chart data shaping
│ ├── site-config.ts # Public site URL resolution
│ ├── timezone.ts # IANA timezone validation
│ └── url.ts # Safe http(s) URL normalisation
├── tests/ # Vitest suite (fetch-stubbed, no network)
└── public/ # Static assets
```

---

## Deployment

This project is optimized for deployment on [Vercel](https://vercel.com/). Add your `GITHUB_TOKEN` and `GITHUB_USERNAME` as environment variables in the Vercel project settings.
This project is optimized for deployment on [Vercel](https://vercel.com/). Add `GITHUB_TOKEN`, `NEXT_PUBLIC_SITE_URL`, and `REVALIDATE_SECRET` as environment variables in the Vercel project settings.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/brandonperfetti/github-commit-dashboard)

Expand Down
4 changes: 2 additions & 2 deletions app/featured/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const metadata: Metadata = {
type: "website",
images: [
{
url: `${siteUrl}/globe.svg`,
url: `${siteUrl}/og-image.png`,
alt: "Build featured project overview",
},
],
Expand All @@ -59,7 +59,7 @@ export const metadata: Metadata = {
card: "summary_large_image",
title: "Featured · Build",
description: featuredDescription,
images: [`${siteUrl}/globe.svg`],
images: [`${siteUrl}/og-image.png`],
},
};

Expand Down
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const metadata: Metadata = {
type: "website",
images: [
{
url: `${siteUrl}/globe.svg`,
url: `${siteUrl}/og-image.png`,
alt: "Build GitHub activity dashboard overview",
},
],
Expand All @@ -54,7 +54,7 @@ export const metadata: Metadata = {
card: "summary_large_image",
title: "Build · GitHub Activity Dashboard",
description: homeDescription,
images: [`${siteUrl}/globe.svg`],
images: [`${siteUrl}/og-image.png`],
creator: "@brandonperfetti",
},
};
Expand Down
4 changes: 2 additions & 2 deletions app/repos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const metadata: Metadata = {
type: "website",
images: [
{
url: `${siteUrl}/globe.svg`,
url: `${siteUrl}/og-image.png`,
alt: "Build repository surface area overview",
},
],
Expand All @@ -46,7 +46,7 @@ export const metadata: Metadata = {
card: "summary_large_image",
title: "Repos · Build",
description: reposDescription,
images: [`${siteUrl}/globe.svg`],
images: [`${siteUrl}/og-image.png`],
},
};

Expand Down
Loading
Loading