"use client"; import React, { useEffect, useState } from "react"; interface HeatmapData { timestamp: number; contributions: number; } export default function ContributionHeatmap() { const [weeks, setWeeks] = useState<{ date: string; contribs: number; level: number }[][]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [totalContribs, setTotalContribs] = useState(0); const [activities, setActivities] = useState([]); useEffect(() => { async function fetchHeatmap() { try { const res = await fetch("/api/heatmap"); if (!res.ok) { throw new Error("Failed to fetch"); } const data: HeatmapData[] = await res.json(); const map = new Map(); let total = 0; data.forEach((item) => { const d = new Date(item.timestamp * 1000); const dateStr = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0"); map.set(dateStr, item.contributions); total += item.contributions; }); const endDate = new Date(); endDate.setHours(0, 0, 0, 0); const startDate = new Date(endDate); startDate.setFullYear(startDate.getFullYear() - 1); startDate.setDate(startDate.getDate() - startDate.getDay()); const newWeeks = []; let currentWeek = []; for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { const dateStr = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0"); const contribs = map.get(dateStr) || 0; let level = 0; if (contribs > 0) level = 1; if (contribs >= 3) level = 2; if (contribs >= 6) level = 3; if (contribs >= 10) level = 4; currentWeek.push({ date: dateStr, contribs, level }); if (currentWeek.length === 7) { newWeeks.push(currentWeek); currentWeek = []; } } if (currentWeek.length > 0) { while (currentWeek.length < 7) { currentWeek.push({ date: "", contribs: -1, level: -1 }); } newWeeks.push(currentWeek); } setWeeks(newWeeks); setTotalContribs(total); setLoading(false); } catch (err) { console.error("Failed to load heatmap:", err); setError(true); setLoading(false); } } async function fetchActivities() { try { const res = await fetch("/api/activities"); if (res.ok) { const data = await res.json(); // Filter to just standard push/commit events const valid = data.filter((d: any) => d.op_type === "commit_repo" || d.op_type === "push_repo").slice(0, 3); setActivities(valid); } } catch (err) { console.error("Failed to load activities:", err); } } fetchHeatmap(); fetchActivities(); }, []); if (loading) { return
Loading contributions...
; } if (error) { return
Failed to load contributions.
; } return (
martin@git.dvorakmortonxd.dev:~$ echo "{totalContribs} contributions in the last year"
{weeks.map((week, wIdx) => (
{week.map((day, dIdx) => (
= 0 ? day.level : undefined} style={{ visibility: day.level === -1 ? "hidden" : "visible" }} title={day.level >= 0 ? `${day.contribs} contributions on ${day.date}` : undefined} >
))}
))}
Less
More
{activities.length > 0 && (
martin@git.dvorakmortonxd.dev:~$ git log -n 3 #displays my 3 latest contributions
{activities.map((act, idx) => { let msg = "No message"; try { if (act.content) { const parsed = JSON.parse(act.content); if (parsed.Commits && parsed.Commits.length > 0) { msg = parsed.Commits[0].Message.split("\n")[0]; } else if (parsed.HeadCommit) { msg = parsed.HeadCommit.Message.split("\n")[0]; } } } catch (e) {} return (
repo: {act.repo?.full_name || "unknown"} {new Date(act.created).toLocaleDateString()}-{new Date(act.created).toLocaleTimeString()}
{msg}
); })}
)}
); }