feat(ui): add /dash route, shared nav, project dashboard with /v1/projects API

Restructure routes: / and /dash show a project overview dashboard,
/activity hosts the existing timeline, /cv remains. Shared Layout
component provides consistent nav header and footer across all routes.

New /v1/projects endpoint aggregates per-repo activity stats (commits,
issues, PRs, date range) from existing event data via SQL. Dashboard
ranks projects by weighted recency + volume score and renders a card
grid.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 15:19:49 +03:00
parent a71b4e6b84
commit a70fab4feb
11 changed files with 305 additions and 63 deletions

View File

@@ -1,8 +1,6 @@
import { useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import Col from 'react-bootstrap/Col';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import { VerticalTimeline } from 'react-vertical-timeline-component';
@@ -13,13 +11,6 @@ import { TimelineEntry } from '../components/TimelineEntry';
const RANGE_MIN = new Date('2010-01-01T00:00:00Z').getTime();
const RANGE_MAX = Date.now();
const externalLinks: { url: string; alt: string }[] = [
{ url: 'https://linkedin.com/in/thijssen/', alt: 'linkedin' },
{ url: 'https://stackoverflow.com/users/68115/grenade', alt: 'stackoverflow' },
{ url: 'https://github.com/grenade', alt: 'github' },
{ url: 'https://git.lair.cafe/grenade', alt: 'gitea' },
];
export function TimelineHome() {
const [enabledSources, setEnabledSources] = useState<Record<Source, boolean>>({
github: true,
@@ -61,38 +52,7 @@ export function TimelineHome() {
const events = eventsQ.data ?? [];
return (
<Container className="py-4">
<Row className="mb-3">
<Col>
<h1>hi, i'm rob</h1>
</Col>
<Col className="d-flex flex-wrap gap-3 justify-content-end align-items-center">
{externalLinks.map((el) => (
<a
key={el.url}
href={el.url}
title={el.alt}
target="_blank"
rel="noopener noreferrer"
>
{el.alt}
</a>
))}
</Col>
</Row>
<Row className="mb-4">
<Col>
<p>
i rarely say anything that warrants capital letters. if you're here
to see my resume, please go to{' '}
<Link className="hot-pink" to="/cv">
/cv
</Link>
. a peek into the projects i'm working on is below.
</p>
</Col>
</Row>
<>
<Filters
enabledSources={enabledSources}
onSourceToggle={(s, on) =>
@@ -123,6 +83,6 @@ export function TimelineHome() {
</VerticalTimeline>
</Col>
</Row>
</Container>
</>
);
}