Dugout Value
Baseball research on pitching, hitting, and projections, plus tools for comparing players, contracts, and trade value.
Lightweight pickleball league tool for scheduling sessions, tracking match history, and managing player rankings. Create leagues, organize games, and track your DUPR ratings.
Sports & fitnessTypeScript · JavaScriptGitHub @consumedsoulOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Pickled Citizens
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Lightweight web app for running casual pickleball leagues: create leagues, add
players, schedule game sessions, auto-generate balanced doubles matchups, and track
match results with lifetime statistics.Read the full README ↗Lightweight web app for running casual pickleball leagues: create leagues, add players, schedule game sessions, auto-generate balanced doubles matchups, and track match results with lifetime statistics.
Live site: pickledcitizens.com
Last updated: 2026-10-30
docs/dupr-sync.md./admin/events (audit log),
/admin/users (create a user with name, DUPR, dupr.com link and league
picks, edit or delete one), and /admin/leagues.| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router, React Server Components), React 18, TypeScript 5.6 |
| Styling | Tailwind CSS 3.4 — editorial B&W design tokens, monospace labels |
| Database | Cloudflare D1 (SQLite) accessed via Drizzle ORM |
| Auth | Clerk (password + magic link), Clerk-hosted UI |
| Hosting | Cloudflare (SSR) via OpenNext for Cloudflare (@opennextjs/cloudflare) |
| CLI / migrations | Wrangler 4 |
| Tests | Vitest |
Note: This project was migrated off Supabase (Postgres + Auth + RLS) to Cloudflare D1 + Clerk. The live schema is the Drizzle definition in
src/lib/db/schema.tswith migrations underdrizzle/.
app/ Next.js App Router pages + API routes
api/
admin/users/ POST/PATCH/DELETE — admin user mgmt (Clerk + D1)
clerkwebhook/ Clerk user lifecycle webhook (svix-verified)
admin/ Super-admin pages (events, users, leagues)
auth/ Clerk SignIn/SignUp + /auth/complete profile finisher
leagues/, sessions/ League + session pages
sitemap.ts Dynamic sitemap (/sitemap.xml)
src/
components/ui/ Reusable UI library (Button, Input, Modal, SectionLabel)
components/sessions/ CreateSessionForm, SessionsList, GuestModal
lib/
db/schema.ts Drizzle schema — source of truth for D1
db/client.ts getDbAsync() — Drizzle client over the Worker DB binding
db/queries/ Per-domain queries with explicit auth checks
actions/ 'use server' actions called from client components
teamGeneration.ts Snaking team-balance algorithm (unit-tested)
standings.ts League board: win % + rivals, derived from results
dupr.ts DUPR formatting, range and profile-link validation
clerk.ts Clerk Backend API client + name length bound
middleware.ts Clerk middleware — CSP nonce + /admin gating
drizzle/ Drizzle-generated D1 migrations
__tests__/ Vitest tests (team generation, authorization, standings, DUPR, actions, admin users, webhook)
docs/dupr-sync.md How the weekly official-DUPR sync runs
Path alias: @/* maps to ./src/*.
npm install
npm run dev # Next.js dev server — NOTE: the D1 binding is NOT available here
npm run preview # OpenNext build + Cloudflare local preview (D1 + Clerk env required)
npm run lint
npm test
For any database work use npm run preview (bindings available) or the Wrangler
local D1 emulator.
Public (committed in wrangler.toml [vars]):
NEXT_PUBLIC_SITE_URL — public site URL (default https://pickledcitizens.com)NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY — Clerk client key (safe to commit)NEXT_PUBLIC_CLERK_SIGN_IN_URL / _SIGN_UP_URL / fallback redirect URLsServer-only (set as Cloudflare secrets — never commit):
CLERK_SECRET_KEY — Clerk Backend API keyCLERK_WEBHOOK_SIGNING_SECRET — verifies the Clerk user lifecycle webhookDISPLAY_TIMEZONE — IANA timezone for session metadata (default America/Los_Angeles)Copy
.env.exampleto.env.localand fill in the values above to run locally. Local.env.localfiles are git-ignored.
D1 is SQLite. The schema lives in src/lib/db/schema.ts (Drizzle). To change it:
# 1. Edit src/lib/db/schema.ts
npx drizzle-kit generate --name <description>
# 2. Apply
wrangler d1 migrations apply pickled-citizens --local # local
wrangler d1 migrations apply pickled-citizens --remote # production
D1 has no row-level security — all authorization is enforced in TypeScript, but not uniformly at the query layer:
src/lib/db/queries/ take the calling user's ID explicitly
and check ownership/membership themselves (canManageSession,
isLeagueOwner).getSessionById, listLeagues, listMembersOfLeague,
listAllProfiles, listAdminEvents and others — take no caller ID and
perform no check. Their caller is responsible for the gate.So when adding a read path, the authorization check belongs in the server action or page, not in the query module. Server actions are POST-reachable RPC endpoints: "the page only renders for members" is not a defense.
Tables: profiles, leagues, league_members, game_sessions, matches, match_players, match_results, session_guests,
admin_events.
The app is built with OpenNext for Cloudflare and deployed via Cloudflare's
Pages GitHub integration on push to main. wrangler.toml declares
pages_build_output_dir = ".open-next/pages" and the D1 binding.
The CI workflow (.github/workflows/cloudflare-deploy.yml) runs typecheck +
lint + tests as a gate; it does not itself deploy.
npm run pages:build # OpenNext build assembled for Cloudflare Pages output
A single super-admin email ([email protected]) is defined in src/lib/constants.ts
as ADMIN_EMAIL and enforced in middleware.ts, src/lib/db/auth-helpers.ts,
and src/components/AdminFooterLinks.tsx. Changing it is a one-line edit — no DB
migration needed.
Proprietary — personal/league use. Reach out via GitHub if you would like to use this codebase for your own league.