NBA Feed
팀 뉴스와 경기 정보를 제공, 점수는 직접 확인할 때까지 숨겨져 있습니다
선수 순위·라인업 최적화·매치업 분석·스트리밍 추천까지 제공하는 고급 판타지 농구 분석
Advanced fantasy basketball analytics. Player rankings, lineup optimization, matchup analysis, and streaming recommendations to help you win your league.
스포츠·피트니스TypeScript · CSSGitHub @court-vision온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Next.js frontend for the Court Vision website.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
The Next.js web application for Court Vision, a fantasy basketball analytics platform. Provides player rankings, lineup optimization, live matchup scoring, streaming recommendations, and a Bloomberg Terminal-inspired analytics interface.
bash
cd frontend
bun install
bash
bun run dev # Start dev server at http://lREADME 전문 보기 ↗The Next.js web application for Court Vision, a fantasy basketball analytics platform. Provides player rankings, lineup optimization, live matchup scoring, streaming recommendations, and a Bloomberg Terminal-inspired analytics interface.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript 5 |
| UI | React 19, Tailwind CSS 3, shadcn/ui (Radix UI primitives) |
| Data fetching | TanStack Query v5 |
| State | Zustand 4 (with persist middleware) |
| Auth | Clerk (@clerk/nextjs) |
| Charts | Recharts |
| Drag & Drop | dnd-kit |
| Animations | Framer Motion |
| Forms | React Hook Form + Zod |
| Package manager | Bun |
| Analytics | Vercel Analytics + Speed Insights |
src/
├── app/ # Next.js App Router pages
│ ├── layout.tsx # Root layout (Clerk, TanStack Query, Theme, Toaster)
│ ├── page.tsx # Home – WelcomeView (signed out) or DashboardView
│ ├── rankings/ # Player rankings table (public)
│ ├── matchup/ # Current-week matchup analysis (auth required)
│ ├── streamers/ # Streaming pickup recommendations (auth required)
│ ├── lineup-generation/ # Lineup optimizer UI (auth required)
│ ├── manage-lineups/ # Saved lineup management (auth required)
│ ├── manage-teams/ # Add/edit/remove fantasy teams (auth required)
│ ├── your-teams/ # Team overview dashboard (auth required)
│ ├── terminal/ # Analytics terminal (public)
│ ├── query-builder/ # SQL-style query builder (auth required)
│ ├── developer/ # API key management (auth required)
│ ├── settings/ # User settings (auth required)
│ ├── account/ # Account management / email verification
│ ├── sign-in/ # Clerk sign-in page
│ └── sign-up/ # Clerk sign-up page
│
├── components/
│ ├── ui/ # shadcn/ui primitives (Button, Dialog, etc.)
│ ├── Base.tsx # App shell – sidebar nav + page wrapper
│ ├── dashboard/ # Home dashboard widgets (GameScoreTicker, StatCard, etc.)
│ ├── terminal/ # Analytics terminal components
│ │ ├── TerminalLayout.tsx # Three-column resizable shell
│ │ ├── TerminalCommandBar.tsx # Player/team search with autocomplete
│ │ ├── TerminalStatusBar.tsx # Status bar with team carousel
│ │ ├── core/ # PanelRegistry, PanelContainer, PanelToolbar
│ │ └── panels/ # One file per panel type (20+ panels)
│ ├── rankings-components/ # Rankings table and filters
│ ├── matchup-components/ # Matchup score display
│ ├── streamers-components/ # Streamer cards and filters
│ ├── lineup-components/ # Lineup generation forms and results
│ ├── teams-components/ # Team management modals
│ └── settings/ # Notification preferences UI
│
├── hooks/ # TanStack Query hooks (one file per domain)
│ ├── useTeams.ts # useTeamsQuery, useTeamInsightsQuery, mutations
│ ├── useMatchup.ts # useMatchupQuery, useLiveMatchupQuery, useWeeklyMatchupQuery
│ ├── useRankings.ts
│ ├── useStreamers.ts
│ ├── useLineups.ts
│ ├── usePlayer.ts
│ ├── useLiveStats.ts
│ ├── useGames.ts
│ └── ...
│
├── stores/ # Zustand stores
│ ├── useUIStore.ts # Selected team, provider, modal states, sidebar
│ ├── useTerminalStore.ts # Terminal layout, focused player/team, watchlist
│ └── useDashboardStore.ts
│
├── lib/
│ ├── api.ts # ApiClient class – all backend calls
│ ├── auth.ts # buildAuthHeaders helper (Clerk token -> Bearer)
│ └── utils.ts # cn(), getTodayET(), etc.
│
├── providers/
│ ├── QueryProvider.tsx # TanStack Query client + devtools
│ └── CommandPaletteProvider.tsx
│
├── types/ # TypeScript types per domain
│ ├── team.ts, player.ts, matchup.ts, lineup.ts, streamer.ts ...
│ └── terminal.ts # PanelDefinition, TerminalState, etc.
│
├── endpoints.tsx # Central API URL constants
└── middleware.ts # Clerk auth middleware + robots header control
Prerequisites: Node.js 20+ and Bun installed.
cd frontend
bun install
bun run dev # Start dev server at http://localhost:3000
bun run build # Production build
bun run start # Serve production build
bun run lint # ESLint
bun run typecheck # tsc --noEmit
bun test # unit tests for src/lib (bun's built-in runner)
Create a .env.local file in frontend/:
# Clerk authentication (required)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
# Clerk redirect paths (optional, defaults shown)
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/your-teams
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/manage-teams
No other environment variables are required. The backend URL is controlled by a compile-time flag (see below).
The API base URL is set in src/endpoints.tsx:
export const API_BASE =
true // <- flip this boolean
? PROD_BACKEND_ENDPOINT // "https://api.courtvision.dev"
: LOCAL_BACKEND_ENDPOINT // "http://127.0.0.1:8000"
Change true to false to point at a local backend instance running on port 8000.
| Route | Auth | Description |
|---|---|---|
/ | Optional | Dashboard (DashboardView if signed in, WelcomeView if not) |
/rankings | No | Player rankings table with model selector |
/terminal | No | Analytics terminal (three-column panel layout) |
/matchup | Yes | Current-week matchup analysis vs. opponent |
/streamers | Yes | Streaming pickup recommendations + breakout candidates |
/lineup-generation | Yes | AI-assisted lineup optimizer |
/manage-lineups | Yes | View and manage saved lineups |
/manage-teams | Yes | Connect ESPN/Yahoo teams via league ID |
/your-teams | Yes | Per-team roster and schedule overview |
/query-builder | Yes | SQL-style query interface for player data |
/developer | Yes | API key management for public API access |
/settings | Yes | Notification preferences |
/account | Yes | Account info and email verification |
Route protection is enforced in middleware.ts via Clerk's createRouteMatcher. Public routes (/, /rankings, /terminal) also have their X-Robots-Tag: noindex header removed so search engines can index them.
src/lib/api.ts)A singleton ApiClient class wraps all backend calls. Two internal approaches handle the two auth tiers:
authenticatedRequest – attaches a Clerk JWT (Authorization: Bearer <token>) for internal/user routes.fetch calls – used for public routes (rankings, players, live stats) that need no auth.All responses follow BaseApiResponse<T> with { status, message, data }. The client unwraps data and throws on status !== "success".
import { apiClient } from "@/lib/api";
// Authenticated
const teams = await apiClient.getTeams(getToken);
// Public
const rankings = await apiClient.getRankings();
src/hooks/)Each domain has its own hook file exporting TanStack Query hooks and mutations. All authenticated hooks:
useAuth() from Clerk to get getToken and isSignedIn.getToken directly to apiClient methods.enabled: isSignedIn === true so queries never fire before auth resolves.Live-data hooks use refetchInterval to poll (e.g. useLiveMatchupQuery polls every 60 seconds, matching the backend pipeline cadence). The useWeeklyMatchupQuery hook seeds per-day cache entries after loading so useDailyMatchupQuery gets instant cache hits.
Query key factories follow the TanStack Query recommended pattern:
export const teamsKeys = {
all: ["teams"] as const,
lists: () => [...teamsKeys.all, "list"] as const,
detail: (id: number) => [...teamsKeys.all, "detail", id] as const,
};
src/stores/)| Store | Persisted fields | Purpose |
|---|---|---|
useUIStore | selectedTeam, selectedProvider, selectedRankingModel, selectedLineupWeek | Cross-page UI selections |
useTerminalStore | watchlist, recentlyViewed, layout, statWindow, lastFocusedTeamId | Terminal panel layout and player/team focus |
useDashboardStore | — | Dashboard widget state |
All stores use Zustand's persist middleware with partialize to persist only the fields that should survive a page reload.
/terminal)The terminal is a three-column resizable layout built with react-resizable-panels. It supports three modes:
focusedPlayerId set; left/right panels show player-specific datafocusedTeamId set; panels show roster, live matchup score, category strengthsPanels are registered in src/components/terminal/core/PanelRegistry.ts. Adding a new panel type means:
PanelDefinition entry to PANEL_REGISTRYsrc/components/terminal/panels/case to PanelContainer's render switchAvailable panel categories:
| Category | Panels |
|---|---|
| player | Player Focus, Advanced Stats, Performance Chart, Game Log, Team Schedule, Matchup Context |
| team | Roster Overview, Matchup, Category Strengths, Score History, Daily Breakdown, Lineup Optimizer, Team Streamers |
| comparison | Comparison |
| market | Watchlist, Trending, Today's Leaders, Streamers |
| schedule | Schedule |
The command bar (TerminalCommandBar) merges team and player search results (teams shown first). The status bar (TerminalStatusBar) shows a team carousel ({/} keys cycle teams).
Keyboard shortcuts:
[ / ] – toggle left/right panels, / . / < / > – resize panelsEscape – clears focus (Player Mode → Team Mode → Overview)The root layout composes providers in this order (outermost to innermost):
ClerkProvider
QueryProvider (TanStack Query)
TeamsProvider (React context – team list for nav)
LineupProvider (React context – active lineup state)
ThemeProvider (next-themes, forced dark)
CommandPaletteProvider
Layout (Base.tsx – sidebar shell)
Two player ID systems coexist throughout the codebase:
Terminal panels require NBA IDs. When navigating from an ESPN-sourced component to a terminal panel, the NBA ID must be resolved first. Backend responses for ESPN-sourced data expose an nba_player_id field for this purpose.
cron-runner (Go)
| triggers pipelines on schedule
v
data-platform (FastAPI, port 8001)
| ETL: fetches NBA/ESPN data, writes to PostgreSQL
v
backend (FastAPI, port 8000)
| user-facing REST API
v
frontend (Next.js, port 3000) <-- this repo
| reads backend for user data
| reads public endpoints without auth (rankings, players, live)
| sends lineup generation requests -> features service (Go, port 8080)
The frontend never talks to the data platform or cron-runner directly. All requests go through the backend REST API (https://api.courtvision.dev in production).
Public endpoints (/v1/rankings, /v1/players, /v1/live, /v1/games) require no authentication and are called directly via fetch. Authenticated endpoints (/v1/internal/...) require a Clerk JWT sent as Authorization: Bearer <token>.