Location-based QR code attendance system for general club/classes. Built with Next.js 16, Supabase, and deployed on Vercel.
Maintainers rotate as students and contributors change. This section is updated regularly—reach out if you need help, or open a PR to add yourself.
- Kyle Zhang — kyle100@wharton.upenn.edu
- QR Code Check-In: Generate unique QR codes for each event
- Location Verification: Verify attendees are within 50m of event location using GPS
- Time Windows: Registration opens 15-30 min before and closes 15-30 min after events
- Duplicate Prevention: 24-hour cookie-based duplicate check-in prevention
- Admin Portal: Create, edit, delete events and view attendee data
- Search & Filter: Search attendees by name or email across all events
- Countdown Timer: Shows countdown before registration opens
- Mobile-Responsive: Optimized for mobile devices (primary use case)
- Credits Section: Home screen shows contributors across Claude Builders Clubs
- Framework: Next.js 16+ (App Router) with TypeScript
- Database: Supabase (PostgreSQL)
- Auth: NextAuth.js (Credentials) with multi-tenant organizations and JWT sessions
- Styling: Tailwind CSS
- QR Codes: qrcode.react
- Maps: Leaflet + OpenStreetMap (leaflet, react-leaflet, leaflet-geosearch)
- Deployment: Vercel
See the project changelog for recent updates and breaking changes: CHANGELOG.md
- Node.js 18+ installed
- Supabase account
- Google Maps API key (optional, for location picker)
cd attendance-location-for-groups
npm install- Create a new Supabase project at supabase.com
- Go to SQL Editor and run the schema from supabase/schema.sql
- Get your Supabase URL and keys from Project Settings > API
Copy .env.example to .env.local and fill in your values:
cp .env.example .env.localEdit .env.local:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# NextAuth
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-here # Generate with: openssl rand -base64 32
# SMTP (required for per-org SMTP password encryption)
SMTP_SECRET_KEY=your-secret-here # Generate with: openssl rand -base64 32
# App
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# Note: No Google Maps key needed (Leaflet + OSM)This app supports multiple organizations (clubs). Run the migration to create the organizations table and add org IDs to events/attendees. The migration may also seed an initial sample organization for development.
Requirements:
- Ensure these env vars are set in
.env.local:NEXT_PUBLIC_SUPABASE_URLandSUPABASE_SERVICE_ROLE_KEY
Run:
npm run migrateIf you see a message that direct SQL execution is unavailable, the script will write a ready-to-run SQL file at:
Open your Supabase SQL Editor, paste the contents of that file, and execute it.
Run the timezone migration so events store their IANA timezone:
- Execute supabase/migrations/004_add_event_timezone.sql in the Supabase SQL Editor.
Note: If you haven't applied this migration yet, the app falls back to the viewer's browser timezone.
If you want each organization to send from its own SMTP credentials, apply:
- supabase/migrations/006_add_organization_smtp.sql
- supabase/migrations/007_add_organization_smtp_encryption.sql
npm run devEach organization can configure its own SMTP settings under /:username/settings.
Passwords are encrypted at rest using SMTP_SECRET_KEY.
- If you don't have an organization yet, go to
/registerto create one. - Then go to
/loginand sign in with the credentials you created. You'll be redirected to your organization routes under/:username, e.g./yourorg/dashboard. - Create a new event:
- Enter event title
- Set start and end times (ET timezone)
- Pick a location on the map (Leaflet + OpenStreetMap) or enter coordinates
- Download the generated QR code
- Share QR code for your event
- Scan the event QR code with phone camera
- Wait for registration window to open (countdown shows time remaining)
- Allow location permission when prompted
- System verifies you're within 50m of event location
- Enter your name and email
- Submit to complete check-in
- Per Event: Click "View Attendees" on an event in the dashboard
- Global Search:
/:username/attendees(e.g./penncbc/attendees) shows all attendees across events
├── app/
│ ├── [username]/ # Multi-tenant org routes (dashboard, events, attendees)
│ ├── admin/ # Legacy routes (redirected by middleware)
│ ├── api/ # API routes (server-side, service role)
│ │ ├── attendance/ # Check-in API
│ │ ├── attendees/ # Attendee search API (org-scoped)
│ │ ├── auth/ # NextAuth
│ │ └── events/ # Event CRUD API (org-scoped)
│ ├── event/[eventId]/ # Public check-in page
│ └── login/ # Admin login page
├── components/
│ ├── admin/ # Admin components
│ └── event/ # Public check-in components
├── lib/
│ ├── cookies/ # Cookie management
│ ├── geolocation/ # Location verification
│ ├── supabase/ # Supabase clients
│ └── utils/ # Helper functions
├── supabase/
│ ├── schema.sql # Base database schema
│ └── migrations/ # Incremental migrations
│ ├── 002_add_multi_tenant_support.sql
│ └── 004_add_event_timezone.sql
└── types/ # TypeScript type definitions
- supabase/schema.sql - Base database schema
- supabase/migrations/002_add_multi_tenant_support.sql - Multi-tenant migration (script will produce a ready version)
- supabase/migrations/004_add_event_timezone.sql - Event timezone column
- supabase/migrations/006_add_organization_smtp.sql - Per-org SMTP columns
- supabase/migrations/007_add_organization_smtp_encryption.sql - SMTP password encryption fields
- middleware.ts - Org route protection and redirects
- app/api/attendance/route.ts - Check-in API with validations
- app/api/attendees/route.ts - Organization-scoped attendee API
- app/event/[eventId]/page.tsx - Public check-in flow
- components/admin/AttendeeTable.tsx - Admin attendee table and filters
- lib/geolocation/verification.ts - Haversine distance calculation
- Push your code to GitHub
- Import your repository in Vercel
- Add environment variables in Vercel dashboard:
- Add all variables from
.env.local - Keep
SUPABASE_SERVICE_ROLE_KEYin Server-side env only (never exposed to browser) - Set
NEXT_PUBLIC_BASE_URLto your production domain
- Add all variables from
- Deploy!
Default: 30 minutes before and after event times. Modify in database or when creating events.
Default: 50 meters. Modify per-event in the database.
Credentials are stored in the organizations table in Supabase and are managed by the app (registration and login). Use the Register page to create an organization. To rotate or reset a password, update it securely via your own admin flow or directly in the database.
- Ensure user grants location permission
- Verify GPS is enabled on device
- HTTPS required in production for geolocation API
- Verify API key is correct
- Check that required APIs are enabled
- Ensure API key isn't restricted to wrong domain
- Fallback: System works with manual lat/lng entry if Maps API unavailable
- Verify Supabase credentials in
.env.local - Ensure schema.sql has been run in Supabase SQL Editor
- Check RLS policies are enabled
- Ensure you are signed in (401 responses are surfaced as fetch errors)
- Apply the multi-tenant migration and the timezone migration:
- The API now gracefully handles missing timezone by falling back to the browser timezone, but applying the migration is recommended
- Run
npm installto ensure dev dependencies (includingtsx) are installed - Make sure you are using a recent Node.js (v18+)
- Verify
NEXT_PUBLIC_SUPABASE_URLandSUPABASE_SERVICE_ROLE_KEYare set
Contributions are welcome. Please:
- Open an issue to discuss significant changes
- Submit focused pull requests with clear descriptions
- Follow existing code style and TypeScript strictness
- Do not include secrets or production keys in commits
Add yourself to Credits by editing lib/contributors.ts with your club/name, affiliation, and link. The list renders on the home screen.
AGPL-3.0-or-later. See LICENSE.
For issues or questions, contact Penn Claude Builders Club.