Studium przypadku
KanbanHub
Przestrzeń dla zespołów z tablicami w stylu Trello, śledzeniem czasu i aktualizacjami na żywo, pięcioma metodami logowania i sesjami dla każdego urządzenia, zbudowana i wdrożona samodzielnie od początku do końca.
- Rola
- Projekt samodzielny: produkt, projekt interfejsu, frontend, backend i wdrożenie
- Okres
- cze 2025 – paź 2026
- Technologie
- ReactZustandTailwind CSSNode.jsExpressSocket.IOMongoDBFirebase AuthWebAuthnZodPlaywright

- testów automatycznych
- 215
- testów automatycznych
- scenariuszy end-to-end w przeglądarce
- 28
- scenariuszy end-to-end w przeglądarce
- metod logowania, w tym passkeys
- 5
- metod logowania, w tym passkeys
- commitów od czerwca 2025
- 80
- commitów od czerwca 2025
Problem
Małe zespoły rozpraszają odpowiedzialność, postęp i czas pracy po czatach, arkuszach i tablicach, których nikt nie aktualizuje. KanbanHub trzyma je w jednym miejscu: każda karta ma osoby, terminy, checklistę i przepracowane godziny, a tablica aktualizuje się na żywo dla wszystkich jej członków.
Tablice mogą być prywatne, udostępnione w przestrzeni roboczej albo otwarte przez publiczny link, dlatego kontrola dostępu jest częścią każdej funkcji: sprawdzają ją API, połączenie na żywo, linki zaproszeń i powiadomienia e-mail.
Co zbudowałem
- Tablice w stylu Trello ze zdjęciem lub kolorem w tle; listy można kolorować, zwijać, sortować, kopiować i przenosić
- Karty z okładkami, etykietami, priorytetami, terminami, checklistami, załącznikami, obserwującymi i komentarzami
- Przeciąganie napisane od zera na zdarzeniach pointer, dla myszy i dotyku, z automatycznym przewijaniem przy krawędziach i animacjami FLIP
- Śledzenie czasu: osobny stoper dla każdej osoby, czas dodawany ręcznie, szacunki i sumy na osobę
- Aktualizacje i obecność na żywo przez Socket.IO, bez odświeżania strony
- Moje zadania, kalendarz z przeciąganiem terminów, widok zespołu z obciążeniem, filtr kart i wyszukiwanie ⌘K
- Udostępnianie przez e-mail z rolami, linki zaproszeń i publicznego podglądu oraz przekazanie własności
- Powiadomienia w aplikacji, na pulpicie i e-mailem: od razu albo jako cogodzinne podsumowanie
- Logowanie e-mailem i hasłem, przez Google, Microsoft, Apple oraz kluczem dostępu (passkey), z listą zalogowanych urządzeń
- Motyw jasny i ciemny; interfejs po angielsku, ukraińsku i rosyjsku
Ekrany
Tablica
Listy z etykietami, priorytetami, terminami, checklistami i działającym stoperem na karcie w toku. Zmiany synchronizują się dla wszystkich na tablicy.
Architektura
Aplikacja React (SPA) komunikuje się z API w Express przez REST i Socket.IO. Obie części są wdrożone na Render pod adresami kanbanhub.app i api.kanbanhub.app, a dane są w MongoDB Atlas.
01
Klient
- React + Vite, Tailwind CSS
- Stan w Zustand
- Własne przeciąganie na zdarzeniach pointer
- Klient Socket.IO
- Firebase Auth i SimpleWebAuthn
02
API
- REST API w Express z walidacją Zod
- Pokoje i obecność w Socket.IO
- JWT w ciasteczkach HttpOnly, sesja na każde urządzenie
- Helmet, ochrona CSRF, limity żądań
- Zadania w tle: przypomnienia o terminach i podsumowania e-mail
03
Dane i usługi
- MongoDB Atlas z transakcjami
- Firebase Admin do tokenów dostawców
- E-maile transakcyjne przez Brevo
- Cloudinary dla awatarów i obrazów
- Tła z Unsplash, Picsum i Wikimedia
Decyzje techniczne
01
Czas rzeczywisty z kontrolą dostępu
Połączenia Socket.IO uwierzytelniają się tym samym ciasteczkiem HttpOnly co REST API. Dołączenie do pokoju tablicy wymaga bycia właścicielem lub członkiem, a każdy heartbeat ponownie sprawdza sesję, więc odwołana sesja traci też aktualizacje na żywo. Po 45 sekundach bez heartbeatu użytkownik jest oznaczany jako nieobecny.
02
Sesja dla każdego urządzenia
Każde logowanie tworzy rekord sesji wskazany w ciasteczku, więc ustawienia pokazują wszystkie urządzenia i pozwalają wylogować jedno albo wszystkie pozostałe. Wylogowane urządzenie od razu traci połączenie na żywo. Zmiana hasła kończy wszystkie inne sesje i zostawia bieżącą.
03
Czas liczony tylko raz
Każda osoba ma jeden działający stoper: uruchomienie kolejnego zatrzymuje poprzedni. Stoper kończy się warunkową aktualizacją, więc dwa żądania zatrzymania w tej samej chwili zapisują czas tylko raz, a suma na karcie jest przeliczana z wpisów.
04
Bezpieczne przy równoległych żądaniach
Dwie rejestracje na ten sam e-mail albo dwa użycia jednego tokenu resetu hasła lub wyzwania passkey mogą się udać tylko raz. Usunięcie członka to jedna transakcja MongoDB: członkostwo, jego powiadomienia, przypisania do zadań i otwarte zaproszenia są usuwane razem.
05
Linki wygasające przy zmianie dostępu
Linki publiczne i zaproszeń są przechowywane jako hashe z wersją. Zmiana widoczności tablicy lub odwołanie linku podnosi wersję, więc stare linki przestają działać natychmiast.
06
Logowanie powiązane z kontem dostawcy
Logowanie przez Google, Microsoft i Apple jest powiązane z identyfikatorem konta u dostawcy, a nie z adresem e-mail, więc inne konto z tym samym adresem nie przejmie dostępu. Tokeny muszą być świeże, nieodwołane i dotyczyć zweryfikowanego e-maila, a połączenie konta wymaga aktualnego hasła.
07
E-maile bez spamu
Powiadomienia mogą przychodzić od razu, maksymalnie 30 e-maili na godzinę, albo jako cogodzinne podsumowanie pogrupowane według tablic. Podsumowanie czeka pięć minut, żeby zebrać serię zmian, a przed wysyłką jest rezerwowane dla użytkownika, więc dwa serwery nigdy nie wyślą go podwójnie.
08
Przeciąganie bez biblioteki
Karty i listy przesuwają się na zdarzeniach pointer, dla myszy i dotyku: uniesiona kopia karty, miejsce upuszczenia, automatyczne przewijanie przy krawędziach i animacje FLIP przy lądowaniu. Na ekranach dotykowych nic samo nie otwiera klawiatury.
Testy
152 testy integracyjne backendu działają na wbudowanym test runnerze Node.js z Supertest i MongoDB w pamięci, a 63 testy jednostkowe frontendu obejmują filtry kart, sortowanie i reguły wyświetlania. 28 scenariuszy Playwright steruje prawdziwą aplikacją w przeglądarce, od rejestracji po udostępnienie tablicy. Kilka przypadków z backendu:
- logs time only once when a timer is stopped twice at the same moment
- never signs in a different provider account that claims the same email
- ends every other session and keeps this device signed in
- issues passkey sign-in challenges without an email and accepts each one only once
- invalidates a general invite link after room visibility changes
- allows a reset token to be claimed only once under concurrency
Chcesz porozmawiać o tym projekcie?
Chętnie omówię kod, kompromisy i to, co zbudowałbym dalej.