Basquetebol português no teu bolso
App gratuita e open-source para acompanhar todos os clubes e competições da Federação Portuguesa de Basquetebol
v9 — 🛡️ Painel admin · 🔐 Client Trust 2FA · ✉️ Verificação email · ⚡ Speed Insights · CSP completo · dribly.pt
📱 Screenshots em breve — visita dribly.pt para ver a app em ação.
O basquetebol português tem centenas de clubes e dezenas de competições, mas as plataformas disponíveis ou são pagas, ou desktop-only, ou limitadas a uma competição. A Dribly preenche essa lacuna com uma app 100% gratuita, mobile-first e open source.
| Funcionalidade | Dribly | FPB.pt | Swish | TugaBasket |
|---|---|---|---|---|
| Mobile-first | ✅ | ✅ | ✅ | ❌ |
| App instalável | ✅ | ❌ | ✅ | ❌ |
| Open source | ✅ | ❌ | ❌ | ❌ |
| 100% gratuito | ✅ | ✅ | ❌ | ✅ |
| Multi-clube | ✅ | ✅ | ❌ | |
| Multi-escalão | ✅ | ✅ | ❌ | |
| Offline parcial | ✅ | ❌ | ❌ | ❌ |
| Ficha de jogo detalhada | ✅ | ✅ | ✅ | ❌ |
| Estatísticas individuais | ✅ | ✅ | ✅ | ❌ |
| Contas / Seguir clubes | ✅ | ❌ | ✅ | ❌ |
| Perfil + Segurança | ✅ | ❌ | ✅ | ❌ |
| Mapa de pavilhões | ✅ | ❌ | ❌ | ❌ |
| Pavilhões (detalhe) | ✅ | ✅ | ❌ | ❌ |
| Funcionalidade | v | Descrição |
|---|---|---|
| 🔍 Pesquisa de clubes | 1.0 | 281 clubes, cores, logos, pesquisa fuzzy com acrónimos |
| ❤️ Seguir clubes/ligas | 3.4 | Página "Seguidos", botão Heart, sync localStorage↔Supabase |
| 🎨 Tema dinâmico por clube | 2.9 | Accent color própria de cada clube |
| 🎯 Tour onboarding | 3.3 | Guiado ao criar conta |
| 💡 Sugestões pós-registo | 3.3 | Clubes e ligas recomendados na 1ª visita |
| Funcionalidade | v | Descrição |
|---|---|---|
| 📅 Jogos e agenda | 1.0 | Calendário, resultados, fichas de jogo detalhadas |
| 🏆 Classificações | 3.3 | Tabelas com J, V, D, PM, PS, DIF, PTS |
| 📊 Estatísticas individuais | 3.4 | 22 campos — PTS, REB, AST, VAL, %L2, %L3, %LL |
| 🗺️ Mapa de pavilhões | 6.0 | Leaflet interativo com 400+ pavilhões, clustering, geolocalização |
| ⚡ Pre-warming de cache | 7.12 | Dados pré-carregados antes do pico de utilização |
| 🛠️ Scraper automático | 7.2 | GitHub Action diária para dados sempre frescos |
| Funcionalidade | v | Descrição |
|---|---|---|
| 🌓 Modo claro/escuro | 1.0 | Transição suave, segue prefers-color-scheme |
| 📱 App instalável | 1.2 | App nativa no telemóvel (PWA) |
| 🔌 Offline parcial | 3.0 | Service Worker + cache inteligente |
| 🔐 Contas email/password | 5.0 | Login + registo com username único (Clerk) |
| ✉️ Verificação de email | 9.0 | Código de 6 dígitos enviado por email |
| 🔑 Recuperar password | 5.0 | Email com código de 6 dígitos |
| 🔐 Client Trust 2FA | 9.0 | Código email em novos dispositivos (Clerk) |
| 👤 Perfil completo | 5.0 | Editar username, nome, bio, mudar password |
| 🔒 Sessões ativas | 5.0 | Ver e terminar sessões remotas |
| 🗑️ Apagar conta | 5.0 | Auto-remoção com confirmação |
| 🛡️ Painel admin | 9.0 | Gerir clubes, users, jogos e competições |
| 🌍 Domínio próprio | 5.0 | dribly.pt |
| Funcionalidade | v | Descrição |
|---|---|---|
| 🧹 ESLint 0 erros | 8.0 | Qualidade de código, zero any types |
| 🔒 Content-Security-Policy | 8.0 | Headers CSP no vercel.json contra XSS |
| ⚡ Speed Insights | 9.0 | Core Web Vitals reais em produção (Vercel) |
| 🎭 Testes E2E Playwright | 8.0 | Smoke tests: Landing, Clubes, Mapa |
| 🧪 Testes unitários | 7.6 | Vitest, 111 testes, gate no build |
| 🔍 SEO dinâmico | 7.15 | Meta tags por página, Open Graph, sitemap |
| ♿ Acessibilidade | 7.16 | aria-label, role="dialog", breadcrumbs |
| 🛡️ ErrorBoundary global | 7.9 | Fallback amigável em vez de ecrã branco |
git clone https://github.com/mefrraz/dribly.git
cd dribly/web
npm install
npm run dev # → http://localhost:5173Cria um ficheiro web/.env:
VITE_SUPABASE_URL=https://[project].supabase.co
VITE_SUPABASE_ANON_KEY=[anon public key]
VITE_CLERK_PUBLISHABLE_KEY=pk_test_... # clerk.com → API Keysnpm test # 111 testes
npm run lint:fix # ESLint auto-fix
npm run build # tsc + vitest + vite build| Camada | Tecnologia |
|---|---|
| Frontend | React 18 + TypeScript (strict: true) |
| Build | Vite 6 |
| Estilos | Tailwind CSS 3 |
| Auth | Clerk (email/password, username único, password recovery) |
| Base de dados | Supabase (PostgreSQL + RLS via Clerk JWT) |
| Deploy | Vercel (Edge Functions, auto-deploy) |
| Cache | localStorage + Service Worker (Workbox) |
| Dados | FPB + TugaBasket (scraping HTML + WordPress AJAX) |
| Testes | Vitest + Playwright + jsdom |
A Dribly é uma SPA sem backend própria. Toda a lógica vive no browser ou em Edge Functions serverless.
Browser (React SPA)
│
├── Clerk SDK ────────────► Clerk (Auth + sessões)
│
├── Supabase SDK ─────────► Supabase (DB + follows + RLS)
│
├── /api/* (Edge Funcs) ──► FPB / TugaBasket (scraping)
│
└── Service Worker ───────► Cache local (PWA offline)
| Fonte | Método | Conteúdo |
|---|---|---|
FPB (fpb.pt) |
HTML scraping + WordPress AJAX | Clubes, jogos, classificações, estatísticas |
| TugaBasket | HTML scraping | Estatísticas complementares |
| Supabase | PostgreSQL + RLS | Follows, cache, pavilhões, logos |
| Local | Mapeamento manual | Logos e cores de 281 clubes |
- Registo com username único validado pelo Clerk
- Password recovery com código de 6 dígitos por email
- Perfil completo: editar nome/bio/username, mudar password, ver/terminar sessões, apagar conta
- Tour onboarding só no sign-up (não no login)
- JWT integrado com Supabase RLS (
auth.uid()::text)
web/
├── src/
│ ├── lib/ # Lógica pura: APIs, contexts, utils
│ │ ├── fpbApi.ts # Parser HTML FPB (clubes)
│ │ ├── fpbCompetitionsApi.ts # Parser WordPress AJAX (competições)
│ │ ├── tugabasketApi.ts # Parser TugaBasket
│ │ ├── ClubContext.tsx # Estado global dos clubes
│ │ ├── AuthContext.tsx # Clerk auth (useAuth, TokenProvider)
│ │ └── supabase.ts # Cliente Supabase (DB + RLS via JWT)
│ ├── hooks/ # Hooks React com cache
│ │ ├── useGames.ts
│ │ ├── useStandings.ts
│ │ └── useFollows.ts
│ ├── components/ # Componentes reutilizáveis
│ │ ├── AuthModal.tsx # Login / Criar conta / Recuperar password
│ │ ├── OnboardingTour.tsx # Tour guiado pós-registo
│ │ ├── GameCard.tsx # Cartão de jogo
│ │ └── SegmentControl.tsx # Navegação por tabs
│ └── pages/ # Páginas (route-level)
│ ├── Landing.tsx
│ ├── CompetitionDetail.tsx
│ ├── Game.tsx
│ ├── Following.tsx
│ ├── ProfilePage.tsx
│ └── club/ # Nested routes: /clube/:slug/*
├── api/ # Vercel Edge Functions
│ ├── fpb.ts # Proxy FPB
│ ├── tugabasket.ts # Proxy TugaBasket
│ └── admin.ts # Painel admin (Clerk + Supabase)
└── public/
├── logo.svg
└── logo.png # PWA maskable icon
database/
└── migrations/ # 16 migrações numeradas
├── 01 create_clubs_table.sql
├── 03 create_user_follows.sql
├── 04 add_clerk_auth.sql
├── 11 add_pavilions.sql
└── ...
scrapers/ # Scripts Node.js independentes
└── (scraper FPB + TugaBasket)
.github/workflows/ # 4 workflows
├── scrape-daily.yml # Diário: scraper + sitemap
├── discover-competitions.yml # Semanal: novas competições
├── e2e-smoke.yml # Manual: Playwright E2E
└── fpb-manual.yml # Manual: seed FPB
111 testes em 14 ficheiros. Os testes correm em cada build — se falharem, o deploy não publica.
cd web
npm test # unitários (Vitest + jsdom)
npm run test:e2e # end-to-end (Playwright)| Área | Ficheiros | Testes |
|---|---|---|
| Parsers FPB/TugaBasket | fpbApi.test.ts, fpbCompetitionsApi.test.ts, tugabasketApi.test.ts, fetchStandings.test.ts |
33 |
| Utilitários | fpbUtils.test.ts, matchUtils.test.ts, clubSearch.test.ts |
46 |
| Hooks React | useGames.test.ts, useFollows.test.ts |
7 |
| Componentes | GameCard.test.tsx, ErrorBoundary.test.tsx, StandingsTable.test.tsx, Toast.test.tsx |
20 |
| Contexto | ClubContext.test.tsx |
5 |
PRs são bem-vindos! Lê o CONTRIBUTING.md para um guia completo (explica o que são PRs, issues, forks — mesmo que nunca tenhas contribuído para open source).
Resumo rápido:
- Escolhe uma issue ou cria uma nova
- Faz fork, clone, branch — mexe no que quiseres
npm run lint:fixpara alinhar com ESLint- Adiciona testes se mexeres em lógica de parsing, hooks ou componentes
npm run buildtem de passar — faz tsc + testes + build- Abre o PR — eu revejo em 1-2 dias
Não sabes o que é um PR? Sem stress — o CONTRIBUTING.md explica tudo, com glossário e passo a passo.
GNU AGPLv3 — código aberto, copyleft para serviços web. Vê o ficheiro LICENSE.