BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
Find a pickup game, pay your share, and play.
Sports & fitnessTypeScript · JavaScriptGitHub @gregjhaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
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
Loading the full README. The saved excerpt is shown below.
Wager is a rec sports meetup app: find a pickup match, pay the entry fee, lock your spot on the roster. Hosts get paid out through Stripe Connect. This repo is a Bun-workspaces monorepo.
REST API contract: docs/api.md.
Mobile (separate terminal): cd apps/mobile && bunx expo start. See apps/mobile/README.md.Read the full README ↗Wager is a rec sports meetup app: find a pickup match, pay the entry fee, lock your spot on the roster. Hosts get paid out through Stripe Connect. This repo is a Bun-workspaces monorepo.
apps/
web/ # Next.js 16 — App Router, REST /api, Better Auth, Server Actions
mobile/ # Expo SDK 55 — TanStack Query + REST against web /api
packages/
domain/ # @wager/domain — server-only match, entry, and payment logic
payments/ # @wager/payments — Stripe Connect, Checkout, refunds, webhooks (server-only)
shared/ # @wager/shared — Zod API schemas, DTOs, api-client, query keys
db/ # @wager/db — Prisma
auth/ # @wager/auth — Better Auth
REST API contract: docs/api.md.
bun install
cp apps/web/.env.example apps/web/.env # fill in values
bun run --filter @wager/db prisma:migrate:dev
bun run dev # web on :3000
bun run stripe:listen # forwards webhooks; copy the whsec_ into .env
| Command | What it does |
|---|---|
bun run dev | Next.js web only (:3000) |
bun run build | web production build |
bun run lint | web lint |
bun run prisma-sanity | validate + generate Prisma artifacts |
bun run test:domain | domain integration tests (needs DATABASE_URL pointing at a disposable DB — the tests truncate tables) |
Mobile (separate terminal): cd apps/mobile && bunx expo start. See apps/mobile/README.md.
/host/payouts. Paid matches can't be created until charges_enabled and details_submitted are true (account.updated webhook, with a direct check on return).POST /api/matches/:id/entry locks the match row (SELECT … FOR UPDATE), counts confirmed entries plus unexpired holds, and either confirms (free) or writes a Pending entry with holdExpiresAt ≈ 31 min and returns a Checkout URL.PLATFORM_FEE_BPS (5%). Checkout expires just inside the hold window.checkout.session.completed confirms the seat. If the hold lapsed and the seat was taken, or the match was cancelled meanwhile, the payment is refunded automatically. Events are recorded in StripeEvent after success, so failures are retried by Stripe.refundCutoffHours; seat released without refund after.@wager/domain for server reads; Server Actions for match create/update/cancel and payout onboarding; TanStack Query for lists and join/leave.@wager/shared api-client. Paid joins use WebBrowser.openAuthSessionAsync; /checkout/return bounces Stripe's https redirect to wager://.@wager/db; business logic in @wager/domain (server-only)./api/auth/* (Google), Redis secondary storage.@wager/payments.Single Vercel project with Root Directory apps/web.
| Variable | Purpose |
|---|---|
DATABASE_URL | Postgres |
REDIS_URL | Session secondary storage |
AUTH_SECRET, BETTER_AUTH_URL, OAuth client IDs/secrets | Better Auth |
STRIPE_SECRET_KEY | Stripe API |
STRIPE_WEBHOOK_SECRET | Signing secret for /api/webhooks/stripe |
Create the webhook endpoint at {origin}/api/webhooks/stripe with "Listen to events on Connected accounts" enabled (needed for account.updated) and subscribe to: checkout.session.completed, checkout.session.expired, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, account.updated, charge.refunded.
OAuth redirects: {origin}/api/auth/callback/google.
Entry fees go to the host to cover court time, refs, and gear. Pooling fees into a prize paid to winners turns this into a paid-entry contest, which is regulated state by state and restricted under Stripe's terms — review both before adding winner payouts.