Skip to content

Repository files navigation

Kudeak

All-in-one gym management platform

Scheduling • Members • Payments • Analytics • Member Portal

Next.js 16 React 19 TypeScript Tailwind v4 Stripe


Overview

Kudeak is a multi-tenant SaaS platform that gives gym owners everything they need to run their business — class scheduling, member management, payment processing, analytics, and a self-service member portal — all from one place.

Each gym is a tenant. Users can own or manage multiple gyms, and members can belong to more than one gym. The platform includes both an admin dashboard (for staff) and a member portal (for gym-goers), plus a public marketing site and embeddable join pages for member registration.


Tech Stack

Layer Technology
Framework Next.js 16 (App Router)
Language TypeScript 5
UI shadcn/ui (New York) + Radix UI + Tailwind CSS v4
State TanStack Query + TanStack Table + TanStack Form
Database Drizzle ORMPGlite (dev) / Neon PostgreSQL (prod)
Auth Better Auth (email + password, session cookies)
Payments Stripe Connect (subscriptions, one-time, refunds)
Email Resend + React Email
Charts Recharts
Icons Lucide React
i18n Custom middleware (English + Spanish)
Toasts Sonner
Package Manager pnpm

Getting Started

Prerequisites

  • Node.js >= 20
  • pnpm >= 9

Installation

# Clone the repo
git clone https://github.com/your-org/kudeak.git
cd kudeak

# Install dependencies
pnpm install

# Set up environment variables
cp .env.example .env.local

# Create the data directory and seed the dev database
mkdir -p data/pglite
pnpm db:seed

# Start the dev server
pnpm dev

Open http://localhost:3000.

Dev Credentials

After seeding, you can log in with:

Role Email Password
Admin (owner) admin@mail.com password
Manager sarah.kim@mail.com password
Staff jake@mail.com password
Member (portal) sarah.johnson@email.com password

Admin login: localhost:3000/login Member portal: localhost:3000/portal/login


Scripts

Command Description
pnpm dev Start dev server (Turbopack)
pnpm build Production build
pnpm start Run production server
pnpm lint Run ESLint
pnpm db:seed Seed PGlite with sample data (2 gyms, 35 members, classes, payments)
pnpm db:generate Generate Drizzle migrations
pnpm db:migrate Run migrations
pnpm db:push Push schema to database
pnpm db:studio Open Drizzle Studio GUI
pnpm email:dev Preview email templates on port 3001

Architecture

Route Groups

The app is split into four route groups, each with its own layout:

src/app/
  (marketing)/     Marketing site + admin login
  (dashboard)/     Admin dashboard (protected)
  (portal)/        Member portal (protected)
  (portal-auth)/   Portal login (public)
  join/[gymSlug]/  Embeddable member registration (public)
  api/             40+ REST API endpoints

Multi-Tenancy

Every domain table has a gymId foreign key. The active gym is resolved server-side from a cookie (kudeak-active-gym-id), and client-side via GymProvider context.

User ──owns/manages──> Gym (via gym_members, role-based)
Gym ──has──> Members, Classes, Schedules, Bookings, Payments

Data Flow

Client Component
  └─ TanStack Query (useMembers, useClasses, etc.)
       └─ fetch("/api/members")
            └─ API Route (auth check → gym scope → Drizzle query)
                 └─ PGlite (dev) or Neon PostgreSQL (prod)

Database Schema

src/lib/db/schema/
  auth.ts              users, sessions, accounts, verifications
  gyms.ts              gyms, gym_members
  members.ts           members, membership_plans
  classes.ts           classes, class_schedules
  bookings.ts          bookings
  payments.ts          payments
  member-actions.ts    member_actions (onboarding tasks)
  member-signatures.ts member_signatures (waivers)
  email-logs.ts        email_logs
  webhook-events.ts    webhook_events

Key Patterns

  • API Routes + TanStack Query — no server actions. Every mutation goes through a REST endpoint.
  • Custom hooks per domainuse-members.ts, use-classes.ts, etc. Each scopes cache keys by gymId.
  • Cookie-based gym switching — both dashboard (kudeak-active-gym-id) and portal (kudeak-portal-gym-id).
  • Middleware — protects dashboard/portal routes, detects locale, handles iframe CSP for join pages.

Project Structure

kudeak/
├── src/
│   ├── app/                    # Next.js App Router
│   │   ├── (marketing)/        # Landing page, admin login
│   │   ├── (dashboard)/        # Dashboard, members, schedule, payments, analytics, settings
│   │   ├── (portal)/           # Member portal (classes, actions, documents)
│   │   ├── (portal-auth)/      # Portal login
│   │   ├── join/[gymSlug]/     # Public member registration (embeddable)
│   │   └── api/                # REST API routes
│   ├── components/
│   │   ├── ui/                 # shadcn/ui primitives
│   │   ├── layout/             # Sidebar, header, gym-switcher, mobile-nav
│   │   ├── dashboard/          # Metric cards, daily overview, activity feed
│   │   ├── members/            # Table, filters, forms, wizard
│   │   ├── schedule/           # Calendar grid, class cards, detail panel
│   │   ├── payments/           # Revenue chart, transactions, payouts
│   │   ├── analytics/          # Growth, forecasts, top classes, at-risk
│   │   ├── settings/           # Account, billing, branding, team, waiver tabs
│   │   ├── portal/             # Portal header, nav, waiver modal
│   │   └── docs/               # In-app documentation components
│   ├── lib/
│   │   ├── db/                 # Drizzle connection + schema definitions
│   │   ├── stripe/             # Stripe helpers (resolve-gym, webhook handlers)
│   │   ├── auth.ts             # Better Auth server config
│   │   ├── auth-client.ts      # Client-side auth
│   │   ├── auth-utils.ts       # getAuthSession()
│   │   ├── gym-utils.ts        # getActiveGym()
│   │   └── portal-utils.ts     # getPortalMember()
│   ├── hooks/                  # TanStack Query hooks (per domain)
│   ├── providers/              # React context (query, gym, portal, locale)
│   ├── i18n/                   # Internationalization (en, es)
│   ├── emails/                 # React Email templates
│   ├── content/docs/           # MDX documentation content
│   └── middleware.ts           # Auth guard, locale detection, iframe CSP
├── scripts/
│   └── seed.ts                 # Database seeding (2 gyms, full sample data)
├── docs/                       # Architecture & implementation docs
├── design/                     # UI design references (PNG + HTML)
└── data/pglite/                # Local PGlite database (gitignored)

Features

Admin Dashboard

  • Overview — active members, monthly revenue, today's attendance, upcoming classes
  • Member Management — searchable directory, filters by status/plan, multi-step add wizard, profile detail view
  • Scheduling — weekly calendar grid, create/edit classes, manage schedules, view bookings per class
  • Payments — revenue chart, transactions table, Stripe payout tracking, refund processing
  • Analytics — member growth trends, revenue forecasts, top-performing classes, at-risk member alerts
  • Settings — gym profile, account, team management (roles: owner/admin/manager/staff), branding (accent color + logo), waiver editor, notifications, billing
  • Documentation — in-app searchable help center (MDX-powered)

Member Portal

  • Dashboard — personalized greeting, next class, quick actions
  • Class Browsing — view schedule, book/cancel classes
  • Actions — pending onboarding tasks (sign waiver, complete profile)
  • Documents — view signed waivers and documents
  • Multi-gym — members at 2+ gyms get a gym switcher in the header

Gym Branding

  • Admins set an accent color and logo in Settings > Branding
  • Applied to the public join page, portal header, and buttons
  • Live preview in settings before saving

Embeddable Join Page

  • Public URL: /join/[gym-slug] — gym-branded, dark-themed registration
  • Can be embedded in external gym websites via <iframe>
  • Middleware sets frame-ancestors * CSP for /join routes

Internationalization

  • English (default) and Spanish
  • Auto-detected from cookie > IP country > Accept-Language header
  • Locale switcher in the dashboard

Stripe Integration

  • Stripe Connect — each gym connects their own Stripe account
  • Subscriptions, one-time payments, manual charges
  • Webhook handling for payment events
  • Customer portal for self-service billing
  • Plan syncing between Kudeak and Stripe

Environment Variables

# Database — PGlite for dev (zero setup), Neon/PostgreSQL for prod
DATABASE_URL=pglite://./data/pglite

# Better Auth
BETTER_AUTH_SECRET=your-secret-here-at-least-32-characters
BETTER_AUTH_URL=http://localhost:3000

# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

# Resend (email)
RESEND_API_KEY=re_your_api_key_here

For production, set DATABASE_URL to your PostgreSQL connection string:

DATABASE_URL=postgresql://user:password@host:5432/dbname

Dev Tips

Issue Fix
PGlite WASM error on login rm -rf data/pglite && mkdir -p data/pglite && pnpm db:seed
tsx / esbuild breaks Delete node_modules/.pnpm/tsx@* and run pnpm install
Middleware deprecation warning Harmless — Next.js 16 shows this but middleware still works
Build-time PGlite RuntimeError Harmless — static generation can't load WASM, API routes work at runtime

Design Tokens

Token Value
Primary #257bf4 (blue)
Marketing Accent #fbbf24 (amber)
Font Inter
Icons Lucide React
Sidebar 264px, white background
UI Style shadcn/ui New York

License

Private — not open source.

About

Management tool for small gyms

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages