GitHub Issues
Repozitáře a jejich otevřené issues jsou hlavním zdrojem úkolů.

Taky znáte ten moment kdy máte spoustu nápadů, práce a zážitků které by jste chtěli stihnou? Každý to zná a v moment kdy to dojde do bodu, že jde tak zahlceni rozhodnutím, že se radši všeho vzdáte...přesně v ten moment přichází na řadu Spining Problems. Propojte GitHub, nebo napište vlastní projekty a úkoly, a nechte rozhodnutí do čeho se pustit na náhodě. v aktuální verzi zvládne aplikace nahrát cca 6 projektů z githubu, na jejich rozšíření pracuji
Když máš úkolů hodně, problém často není, že by jich bylo málo, ale že nevíš, do čeho se pustit. Proto tu není klasický kanban. Appka vezme existující issues a promění je v okamžik, kdy se za tebe prostě rozhodne.
Appka bere úkoly z několika míst a promění je v jeden okamžik, kdy se prostě vybere, do čeho se jde.
Repozitáře a jejich otevřené issues jsou hlavním zdrojem úkolů.
Labely GitHubu se mapují do jednoduchého stavu pro kolo.
Výherce se vybere a kolo se přesně dotočí na jeho výseč.
Projekt lze vytvořit i bez GitHubu.
Každý blok je jiná vrstva: data z GitHubu, filtr, samotné kolo a záložní ruční projekty. Hned potom přijde schéma přihlášení, na kterém celá appka stojí.
Nejdůležitější rozhodnutí je, že GitHub access token nikdy neopustí server. Prohlížeč dostane jen session cookie a volání GitHub API dělá serverová route.
const secureCookieBase = {
httpOnly: true, // nedostupné z JS v prohlížeči
secure: process.env.NODE_ENV === 'production', // posílá se jen přes HTTPS v produkci
sameSite: 'lax',
path: '/',
};
if (route === 'auth/github') {
if (!GH_CLIENT_ID || !GH_CLIENT_SECRET) {
return NextResponse.redirect(`${BASE_URL}/?error=not_configured`);
}
const state = crypto.randomBytes(16).toString('hex');
const redirectUri = `${BASE_URL}/api/auth/github/callback`;
const authorizeUrl =
`https://github.com/login/oauth/authorize` +
`?client_id=${encodeURIComponent(GH_CLIENT_ID)}` +
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
`&scope=${encodeURIComponent('repo read:user')}` +
`&state=${state}`;
const reposWithIssues = repos.filter((r) => r.open_issues_count > 0).slice(0, 15);
const projects = await Promise.all(
reposWithIssues.map(async (repo) => {
const issuesRes = await fetch(
`https://api.github.com/repos/${repo.full_name}/issues?state=open&per_page=30`,
{ headers: ghHeaders(token) }
);
const issues = await issuesRes.json();
const mapped = (Array.isArray(issues) ? issues : [])
.filter((i) => !i.pull_request)
.map((i) => ({
id: `gh-${i.id}`,
title: i.title,
state: (i.labels || []).some((l) => /progress|doing|wip/i.test(l.name || ''))
? 'in-progress'
: 'todo',
url: i.html_url,
}));
return { id: `ghrepo-${repo.id}`, name: repo.name, type: 'github', issues: mapped };
})
);
const spin = () => {
if (!issues || issues.length === 0 || isSpinning || disabled) return;
setIsSpinning(true);
const N = issues.length;
const slice = (2 * Math.PI) / N;
const winnerIndex = Math.floor(Math.random() * N);
const winner = issues[winnerIndex];
const winnerCenter = winnerIndex * slice + slice / 2;
const pointer = (3 * Math.PI) / 2;
const startRot = rotationRef.current;
const turns = 5;
let target = pointer - winnerCenter;
const min = startRot + turns * 2 * Math.PI;
target += Math.ceil((min - target) / (2 * Math.PI)) * 2 * Math.PI;
const duration = 4200;
const t0 = performance.now();
Kolo i confetti jsou napsané ručně na Canvasu. V komentářích v kódu je i důvod: míň závislostí a plná kontrola nad animací. Občas je menší vlastní řešení praktičtější než další knihovna.
Na mobilu je grid v jednom sloupci, na desktopu přibývají panely. Vlevo projekty, uprostřed úkoly, vpravo kolo. Když se panely schovají, kolo se může roztáhnout.
Je tu OAuth, práce s citlivým tokenem na serveru, GitHub API, filtrování dat a vlastní canvasová animace. Není to jen „fun wheel“: pod jednoduchým UI je několik samostatných problémů, které se musí potkat v jednom flow.
Žádná technologie tu není jen pro parádu. Tady je rychlý přehled, kdo za co odpovídá.
Nejde mi jen o to, jak to vypadá. Baví mě hlavně to, co je pod tím: kudy data tečou, co se validuje, co může selhat a proč jsem to udělala právě takhle.
Napiš mi