ClubMap is a map-centered campus discovery platform that brings student organizations, events, locations, RSVPs, event conversations, and check-in passes into one application.
The current interface uses ClubMap's original black and grayscale design, with the map serving as the primary discovery surface instead of a secondary panel.
- Full-screen Mapbox campus map using the dark map style
- Precise geographic markers anchored to each event's latitude and longitude
- Larger accessible marker hit areas without changing the mapped coordinate
- Hover and keyboard-focus previews with event, organization, time, and attendance information
- Direct marker click-through to
/events/[eventId] - Event and location search directly over the map
- Browser geolocation and standard Mapbox navigation controls
- Automatic map bounds based on the currently visible events
- Loading, empty, configuration, and map-error states
- Standalone event detail pages
- Event date, time, location, capacity, visibility, organizer, cover media, and attendee count
- Public, university-only, and organization-member visibility levels
- Authenticated RSVP creation and cancellation
- Database-backed My Events calendar
- Organizer event publishing with coordinates and Supabase media uploads
- Automatic group-chat creation when an event is published
- NextAuth credentials-based sessions
- Password hashing with Node's
scryptimplementation - Student registration and login at
/login - Dedicated organizer and administrator login at
/admin/login - Guest, student, organizer, and administrator access levels
- University-email verification support
- Organizer/admin checks on protected event and upload APIs
- Attendee and organizer access checks on event conversations
- Optional bootstrap administrator configured through the environment
- Conversations generated from actual events rather than static examples
- Chats available to RSVPed attendees, event organizers, and administrators
- Persisted sender identity, message content, and timestamps
- Latest-message conversation previews
- Automatic active-chat refresh
- Real loading, empty, access-denied, and error states
- 2,000-character server-side message limit
- Modern responsive QR pass interface
- Short-lived, signed check-in tokens tied to the authenticated user
- Five-minute token rotation and visible expiration countdown
- Manual pass refresh
- User identity and university-verification display
- Step-by-step event check-in instructions
- Role-restricted management navigation and dashboard
- Event creation with title, description, schedule, location, coordinates, capacity, visibility, and cover image
- Local file uploads during development
- Supabase Storage uploads in database-backed environments
- Organizer records and event-chat creation handled automatically
- Administrative overview and moderation-ready role infrastructure
- Next.js 15 App Router and React 19
- TypeScript
- Mapbox GL JS
- NextAuth 5
- PostgreSQL with Drizzle ORM and Drizzle Kit
- Supabase Storage
- Tailwind CSS and custom responsive CSS
next-qrcode- Lucide icons
- ESLint and Prettier
No PostgreSQL instance or migration is required for local testing.
npm install
npm run devOpen http://localhost:3000.
Development mode automatically uses a file-backed store. Local users, events, RSVPs, conversations, and messages persist in:
.clubmap/local-data.json
Local event images are stored under public/uploads/. Both locations are
excluded from Git.
A Mapbox token is still required to render the interactive map. Add it to
.env.local:
NEXT_PUBLIC_MAPBOX_TOKEN=pk.your-mapbox-token
AUTH_SECRET=your-local-random-secret| Access | Password | |
|---|---|---|
| Administrator | admin@clubmap.test |
Admin123! |
| Student | student@clubmap.test |
Student123! |
The initial local student is RSVPed to a starter event, allowing the map, event-detail, My Events, and conversation workflows to be tested immediately.
To reset local data, stop the development server, delete
.clubmap/local-data.json, and restart the app.
To explicitly test against PostgreSQL during development, set:
CLUBMAP_LOCAL_MODE=falseCopy .env.example to .env.local and configure the required services:
DATABASE_URL=postgresql://USER:PASSWORD@HOST:6543/postgres
CLUBMAP_LOCAL_MODE=false
AUTH_SECRET=replace-with-a-random-32-byte-secret
NEXT_PUBLIC_MAPBOX_TOKEN=pk.your-mapbox-token
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SERVICE_ROLE_KEY=your-service-role-key
UNIVERSITY_EMAIL_DOMAIN=.edu
CLUBMAP_BOOTSTRAP_ADMIN_EMAIL=admin@university.eduCreate a public Supabase Storage bucket named event-media, then apply the
schema:
npm run migrateThe migration adds:
- Password hashes and university-verification state
- Event locations, timestamps, visibility, capacity, and media URLs
- RSVP records with event/user uniqueness
- Notification records
- Group-chat and message defaults
- Chats for events that existed before the migration
The email configured as CLUBMAP_BOOTSTRAP_ADMIN_EMAIL receives the admin
role when that account is registered.
After migrating a non-production development database, the same test accounts can be inserted with:
npm run seed:test-usersThe seed script hashes both passwords, safely upserts the users, creates the
administrator's organizer record, and refuses to run when
NODE_ENV=production.
| Route | Purpose |
|---|---|
/ |
Public map discovery |
/login |
Student login and registration |
/admin/login |
Organizer and administrator login |
/events/[eventId] |
Individual event details and RSVP |
/user/[id]/MyMap |
Auth-aware map discovery |
/user/[id]/MyEvents |
The signed-in user's RSVPs |
/user/[id]/GroupChats |
Event conversations |
/user/[id]/QR |
Rotating digital campus pass |
/user/[id]/Profile |
Identity, verification, and sign-out |
/user/[id]/Admin |
Organizer and administrator dashboard |
| Endpoint | Methods | Purpose |
|---|---|---|
/api/auth/[...nextauth] |
NextAuth | Sessions and credentials authentication |
/api/auth/register |
POST |
Student registration |
/api/events |
GET, POST |
Discover or publish events |
/api/events/[eventId] |
GET |
Retrieve one authorized event |
/api/events/[eventId]/rsvp |
POST |
Toggle the current user's RSVP |
/api/chats |
GET |
Retrieve accessible event conversations |
/api/chats/[chatId]/messages |
GET, POST |
Read or send persisted messages |
/api/passes/me |
GET |
Generate a signed short-lived QR token |
/api/uploads |
POST |
Upload organizer event media |
| Command | Description |
|---|---|
npm run dev |
Start the Turbopack development server |
npm run build |
Create a production build and run type validation |
npm start |
Start the production server |
npm run lint |
Run ESLint over the repository |
npm run migrate |
Apply Drizzle migrations |
npm run generate |
Generate a Drizzle migration |
npm run seed:test-users |
Seed development-only test accounts |
For direct formatting checks:
npm exec prettier -- --check "app/**/*.{ts,tsx,css}" "lib/**/*.{ts,tsx}"Production uses PostgreSQL as the source of truth for users, organizers, events, RSVPs, chats, messages, and notifications. Server-side repositories apply visibility and membership rules before returning data to the browser.
Development uses the same API contracts through a JSON-backed adapter. This keeps local testing migration-free without introducing client-side hardcoded state or maintaining a separate UI implementation.
- Production should always provide a strong
AUTH_SECRET. SERVICE_ROLE_KEYis read only by the server-side upload route and must never use aNEXT_PUBLIC_prefix.- QR passes contain a signed, expiring payload rather than a reusable user ID.
- Test credentials are development-only and should not be seeded in production.
- API authorization is enforced server-side; hiding navigation items is not treated as a security boundary.