SpiningProblems
Featured
Web Development

SpiningProblems

webdesignNext.jsreal-problem

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

// Jak to funguje

Nápad projektu

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.

INPUT

GitHub Issues

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

FILTER

Todo / In Progress

Labely GitHubu se mapují do jednoduchého stavu pro kolo.

CHOICE

Canvas roulette

Výherce se vybere a kolo se přesně dotočí na jeho výseč.

FALLBACK

Manual projects

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í.

// Jak to funguje

OAuth bez vystavení tokenu klientovi

GitHub→OAuth callback→httpOnly cookie→Server API→React UI

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.

// Kousek kódu

Bezpečné OAuth session

KÓD 01 · OAUTH + CSRF STATEapp/api/[[...path]]/route.js
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}`;
Co to děláPři startu OAuth vytvoří náhodný state, uloží ho do httpOnly cookie a callback ho musí vrátit shodně. Session token se potom uloží do httpOnly cookie.
Proč na tom záležíTady se řeší dvě různé věci najednou: ochrana OAuth flow proti CSRF a ochrana tokenu před přístupem JavaScriptem v prohlížeči.
// Kousek kódu

GitHub data se filtrují už na serveru

KÓD 02 · ISSUES MAPPINGapp/api/[[...path]]/route.js
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 };
  })
);
Co to děláOmezí počet repozitářů, stáhne jen otevřené issues, odstraní pull requesty a mapuje labely na stav todo/in-progress.
Proč na tom záležíKdyby se tahalo všechno, aplikace by zbytečně zatěžovala GitHub API. Tohle je konkrétní optimalizace kolem rate limitu i množství dat, které UI opravdu potřebuje.
// Kousek kódu

Kolo není jen CSS efekt

KÓD 03 · CANVAS SPINapp/page.js
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();
Co to děláVybere konkrétní issue, spočítá její středovou výseč a vypočítá rotaci tak, aby po několika otočeních skončila pod ukazatelem.
Proč na tom záležíVýznamná je deterministická matematika animace: vítěz je znám předem a animace pouze vizualizuje cestu k němu. Díky tomu může UI po skončení spolehlivě pracovat se stejným taskem.
// Jak to funguje

Vlastní animace místo další knihovny

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.

// Jak to funguje

Responsive layout

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.

// Jak to funguje

Co je na projektu nejcennější

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.

// Pod kapotou

Z čeho je projekt postavený

Žádná technologie tu není jen pro parádu. Tady je rychlý přehled, kdo za co odpovídá.

FRONTEND + API

Next.js 15

PŘIHLÁŠENÍ

GitHub OAuth

SESSION

httpOnly cookies

DATABÁZE

MongoDB

KOLO A CONFETTI

Canvas

UI

React

// Shrnutí

Co je na projektu podstatné?

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
Přihlášení