尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Tracking your commutes from SF to G/A/PA/MTV/FB/ETC
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A competitive cycling commute tracker for the SF2G community. Connect your Strava account, sync your rides, and see how you stack up across seven Bay Area corridors.
Live at sf2ging.com
| Layer | Technology |
|---|---|
| Framework | TanStack Start (full-stack React) |
| UI | React 19, TanStack Table, TanStack Virtual |
| Data | TanStack Query |
| Backend | Supabase (PostgreSQL + REST) |
| Auth | Strava OAuth 2.0 |
| Charts | Recharts |
| Maps | Leaflet |
| Analytics | PostHog |
| Styling | Vanilla CSS (custom properties, glassmorphism) |
| Testing | Vitest |
| Bundler | Vite |
| Deploy | Cloudflare Workers (Free plan) |
| Package Manager | pnpm |
packageManager)brew install colima docker && colima start)brew install supabase/tap/supabase)localhost in its Authorization Callback Domain (only needed to log in locally)git clone https://github.com/pmaier983/sf2g.git
cd sf2g
pnpm install
# Local database (never point local dev at production)
pnpm db:start # starts Postgres + PostgREST in Docker
pnpm db:reset # applies supabase/migrations/* and supabase/seed/*
cp .env.example .env.local # local Supabase defaults are pre-filled
pnpm dev # http://localhost:5173
| Command | Description |
|---|---|
pnpm dev | Start Vite dev server (localhost:5173) |
pnpm build | Production build for Cloudflare |
pnpm preview | Preview production build locally |
pnpm deploy | Build and deploy to Cloudflare Workers |
pnpm typecheck | Run TypeScript type checking |
pnpm test | Run tests once |
pnpm test:watch | Run tests in watch mode |
pnpm test:db | DB tests against the local fixture DB (resets it when migrations or fixtures differ) |
pnpm e2e | Playwright smoke tests (reuses a running pnpm dev) |
pnpm db:start / db:stop | Start / stop the local Supabase stack |
pnpm db:reset | Re-apply all migrations and seed data locally |
pnpm db:types | Regenerate app/lib/database.types.ts from the local DB |
app/
├── routes/ # File-based routing
│ ├── __root.tsx # Root layout (navbar, theme, toasts)
│ ├── index.tsx # Landing page
│ ├── leaderboard.tsx # Full leaderboard with filters
│ ├── routes.tsx # Route corridor documentation
│ ├── auth/ # OAuth login & callback
│ └── profile/ # Rider profiles ($userId)
├── components/ # Shared components
│ ├── LeaderboardTable.tsx # Virtualized leaderboard
│ ├── LeaderboardColumns.tsx # Column definitions & sub-tables
│ ├── LeaderboardFilters.tsx # Route/time filter chips
│ ├── InteractiveMap.tsx # Leaflet route map
│ ├── GatewayMap.tsx # Gateway checkpoint map
│ ├── ProfileRideStats.tsx # Rider stat cards
│ ├── ProfileRidesTable.tsx # Per-rider ride history
│ ├── CommunityPieCharts.tsx # Route distribution charts
│ ├── GrowthChart.tsx # Cumulative ride growth
│ ├── RideFrequencyChart.tsx # Ride frequency over time
│ ├── RouteSpeedTable.tsx # Speed stats by route
│ ├── WindIndicator.tsx # Tailwind/headwind display
│ ├── FilterChips.tsx # Chip-based filter UI
│ ├── UnitToggle.tsx # Metric/imperial toggle
│ ├── ThemeToggle.tsx # Dark/light mode toggle
│ ├── NavBar.tsx # Navigation bar
│ ├── Toast.tsx # Sonner toast wrapper
│ └── DevToolsPanel.tsx # Admin/dev tools panel
├── lib/ # Utilities, types, classifiers
│ ├── route-classifier.ts # GPS gateway route classification
│ ├── destination-classifier.ts # Office/destination detection
│ ├── office-locations.ts # Known office coordinates
│ ├── constants.ts # Gateways, colors, labels
│ ├── database.types.ts # Auto-generated Supabase types
│ ├── session.ts # Cookie session management
│ ├── strava-oauth.ts # Strava OAuth flow
│ ├── strava.ts # Strava API helpers
│ ├── wind.ts # Wind calculation utilities
│ ├── rate-limiter.ts # API rate limiting
│ ├── analytics.ts # PostHog analytics
│ ├── supabase.ts # Supabase client factory
│ └── useUnit.ts # Unit preference hook
├── queries/ # TanStack Query option factories
│ ├── leaderboard.ts
│ ├── rides.ts
│ └── user.ts
├── server/ # Server-side logic (createServerFn)
│ ├── auth.ts # OAuth login/logout/callback
│ ├── sync.ts # Strava ride sync
│ ├── leaderboard.ts # Leaderboard queries
│ ├── rides.ts # Ride CRUD operations
│ ├── reclassify.ts # Bulk route reclassification
│ ├── wind-enrichment.ts # Wind data enrichment
│ ├── weather.ts # Weather API integration
│ ├── cron.ts # Maintenance + admin "run one tick"
│ ├── sync-drain.ts # Sync job queue drain (Cron Trigger)
│ └── users.ts # User management
└── styles/ # CSS files
├── global.css # Theme variables, base styles
├── components.css # Component-specific styles
├── leaderboard.css # Leaderboard-specific styles
├── maps.css # Map component styles
├── routes.css # Routes page styles
└── wind.css # Wind indicator styles
Routes are classified using GPS gateway checkpoints — specific lat/lng coordinates that a ride's decoded polyline must pass through. This avoids dependency on Strava segment IDs and works reliably across different GPS devices.
| Route | Description | Gateway North | Gateway South |
|---|---|---|---|
| Bayway | Bay shore / Foster City | (37.684, -122.390) | (37.579, -122.311) |
| Skyline | Mountain ridge / Skyline Blvd | (37.668, -122.485) | (37.489, -122.320) |
| HMBW | Coastal / Devil's Slide | (37.578, -122.512) | (37.449, -122.429) |
| Royale | El Camino Real corridor | (37.611, -122.403) | (37.521, -122.277) |
| Fleaway | Flat El Camino / Bayshore | (37.624, -122.408) | (37.492, -122.266) |
| MEBW | Middle East Bay | (37.683, -122.178) | (37.509, -122.114) |
| FEBW | Far East Bay | (37.814, -122.144) | (37.669, -122.001) |
Start point: Peet's Coffee, PPR (37.773, -122.439) Intercept: JD/Skyline (37.698, -122.495) Gateway radius: 500 meters
Browser ──► Cloudflare Worker (TanStack Start, Free plan: 10 ms CPU, 50 subrequests)
│ every page = one SQL function call (get_leaderboard, get_rides, …)
▼
Supabase Postgres ◄── sync_jobs queue ◄── Strava webhook (records only)
▲ │
└── Cron Trigger */5 ────┘ drains the queue in budget-sized steps
app/lib/table-query.ts) and every table renders through
app/components/table/DataTable.tsx.wrangler.jsonc)
drains sync_jobs every 5 minutes; a 12-hour sweep catches anything a
webhook missed. See docs/ops/sync.md.app/lib/dates.ts).Copy .env.example to .env.local (local values) — production values are
Cloudflare secrets/vars.
| Variable | Description |
|---|---|
SUPABASE_URL, SUPABASE_ANON_KEY | Supabase project (local: pnpm db:start) |
SUPABASE_SERVICE_ROLE_KEY | Server only — sync, queue, admin |
STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET | Strava API app |
STRAVA_WEBHOOK_VERIFY_TOKEN | Echoed back when Strava verifies the webhook |
STRAVA_WEBHOOK_SUBSCRIPTION_ID | Optional — ignore events for other subscriptions |
SESSION_SECRET | ≥ 32 chars, signs session cookies |
ADMIN_USER_IDS | Comma-separated user ids allowed to use admin actions |
CRON_SECRET | Bearer token for POST /api/cron |
APP_URL | Public app URL |
VITE_MAP_TILE_URL, VITE_MAP_TILE_URL_DARK, VITE_MAP_TILE_ATTRIBUTION | Optional map tile provider (default: OpenStreetMap) |
Private — not yet licensed for public use.