The all-in-one workspace for student clubs (and the teams that grow out of them): tasks, availability polls and RSVPs, notes and files, databases, the club budget, and the org chart, for every member, in one multi-tenant app.
The marketing site (/), sign-in, sign-up, onboarding and checkout follow the brand design
(src/app/brand.css, src/components/brand/); each club's workspace takes its own theme, with
the Bananasplit preset as the default. See docs/features/themes.md.
Next.js (App Router) + React + TypeScript, Postgres via Prisma, Auth.js (Google OAuth + email/ password), Tailwind CSS + shadcn/ui. See the project spec for the full rationale.
Sign-in supports both Google OAuth and email/password (bcrypt-hashed, /sign-up to register).
Email/password is a deliberate addition beyond the original spec, which scoped v1 to Google-only.
"Continue with Google" (on /sign-in, /sign-up and /invite/[token]) appears only when AUTH_GOOGLE_ID
and AUTH_GOOGLE_SECRET are both set (src/lib/auth/google-sign-in.ts); without them the pages
offer email and password only. Google Calendar sync (Settings > Integrations) is a separate OAuth
client.
-
Install dependencies
pnpm install
-
Configure environment variables
cp .env.example .env
Fill in
DATABASE_URL(a local Postgres instance; production runs on Supabase, seeRUNBOOK.md) andAUTH_SECRET(npx auth secret). The app also connects at runtime as three non-owner, row-level-security-scoped roles (app_user/app_service/app_auth, seedocs/ARCHITECTURE.md) instead of the owner; locally, setAPP_DB_PASSWORD,SERVICE_DB_PASSWORDandAUTH_DB_PASSWORDtotest—pnpm db:local-roles(next step) gives the roles that same password. GenerateSECRETS_KEK_V1andSECRETS_FINGERPRINT_KEYtoo (base64 of 32 random bytes each:node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"), which the secrets-at-rest keyring (integrations settings,/api/health) needs.AUTH_GOOGLE_ID/AUTH_GOOGLE_SECRETneed a real Google Cloud OAuth client (spec section 11, item 1) — request onlyopenid email profile, withhttp://localhost:3000as an authorized JavaScript origin andhttp://localhost:3000/api/auth/callback/googleas a redirect URI. Leave both empty unless you have one: "Continue with Google" only appears when both are set, so placeholder values would show a button that fails. You don't need Google for local development: every seeded user (see below) signs in with email/password. See.env.examplefor what each variable is for. -
Set up the database (from Phase 0.3 onward)
pnpm prisma migrate deploy pnpm db:local-roles pnpm db:seed
migrate deployapplies the committed migrations without prompting. (pnpm db:migrate, i.e.prisma migrate dev, is for authoring a new migration when you changeprisma/schema.prisma— see CONTRIBUTING.md — and can hang on an interactive prompt if run here instead.)db:local-rolesgives theapp_user/app_service/app_authroles (createdNOLOGINby the migrations) the local password set above; see RUNBOOK.md for how production provisions them instead. Every seeded user (e.g.alice@example.edu, seeprisma/seed.tsfor the full roster and roles) can sign in at/sign-inwith passwordpassword123. -
Run the dev server
pnpm dev
Open http://localhost:3000.
| Command | Purpose |
|---|---|
pnpm dev |
Start the dev server |
pnpm build |
Production build |
pnpm start |
Start a production build (after pnpm build) |
pnpm lint |
ESLint |
pnpm typecheck |
next typegen && tsc --noEmit |
pnpm format |
Format with Prettier |
pnpm format:check |
Check formatting without writing |
pnpm test |
Unit/integration tests (Vitest) |
pnpm test:rls |
Row-level-security regression + attack suite (spins up its own throwaway Postgres DB) |
pnpm test:e2e |
End-to-end browser tests (Playwright) |
pnpm db:migrate |
prisma migrate dev — author/apply a migration while changing prisma/schema.prisma |
pnpm db:seed |
Seed the local database (prisma/seed.ts) |
pnpm db:local-roles |
Give the RLS runtime roles a local login password |
pnpm db:studio |
Prisma Studio |
pnpm jobs:drain |
Run due background jobs locally (mail, reminders, syncs, maintenance) |
pnpm secrets:rotate-kek |
Rewrap stored secrets under a new key-encryption key (see RUNBOOK.md) |
CONTRIBUTING.md— conventions for making a change.docs/ARCHITECTURE.md— the multi-tenant model and how authorization is enforced.docs/features/— one doc per feature area (calendar, tasks, org chart, databases, reports, settings, themes, onboarding, profiles, search, platform services).docs/EBOARD-HANDBOOK.md— for the club's e-board, not developers: treasurer handoff, ongoing habits, continuity.RUNBOOK.md— deploying for the first time and restoring from backup.
Following the phased build-out in the project spec. Phases 0–6 (foundation
through hardening: notifications, an authorization audit, accessibility
fixes, performance work, E2E tests, and deployment/docs prep) are done.
Phase 7's Google Calendar sync (src/server/google-calendar/),
the public events feed (src/server/public-events/,
/api/public/[orgSlug]/events), and optional notices to event attendees are
built too — see docs/features/calendar.md.
Real-time collaborative editing is not: per the spec's own sequencing it
stays intentionally deferred until v1 has run with a real e-board for a
month.