A production-ready conference ticketing and workshop management platform built with Next.js, Supabase, and Stripe.
- 🎫 Ticket Sales: Multiple ticket types with dynamic pricing stages
- 🎓 Workshops: Workshop catalog with registration and capacity management
- 👤 User Accounts: Profile management, ticket history, workshop registrations
- 💳 Secure Payments: Stripe integration with webhook handling
- 🔐 Authentication: Supabase Auth with role-based access control
- 🔒 Row Level Security: Database-level security with RLS policies
- 📧 Email Notifications: Automated confirmation emails with React Email
- 🎨 Modern UI: Tailwind CSS with Atomic Design components
- 📱 Responsive: Mobile-first design approach
- Framework: Next.js 15 (Pages Router)
- Database & Auth: Supabase
- Payments: Stripe
- Email: Resend with React Email
- Styling: Tailwind CSS v4
- State Management: React Query, Context API
- Type Safety: TypeScript (strict mode)
- Code Quality: ESLint, Husky, lint-staged
src/
├── pages/ # Next.js pages and API routes
│ ├── api/ # Backend API endpoints
│ ├── auth/ # Authentication pages (login, signup)
│ ├── account/ # User account pages
│ ├── workshops/ # Workshop catalog and details
│ └── admin/ # Admin interface
├── lib/ # Domain logic and infrastructure
│ ├── supabase/ # Supabase client and auth
│ ├── stripe/ # Stripe integration
│ ├── users/ # User management
│ ├── tickets/ # Ticket operations
│ ├── workshops/ # Workshop management
│ ├── roles/ # Role-based access control
│ └── types/ # TypeScript types
├── components/ # React components (Atomic Design)
│ ├── atoms/ # Basic building blocks
│ ├── molecules/ # Composite components
│ └── organisms/ # Complex components
├── config/ # Configuration files
├── emails/ # Email templates
└── data/ # Static data
supabase/
├── migrations/ # Database migrations (SQL)
└── config.toml # Supabase configuration
docs/
├── architecture.md # System architecture
├── IMPLEMENTATION_STATUS.md # Implementation progress
└── [other docs] # Additional documentation
- Node.js 20+
- npm or yarn
- Supabase account
- Stripe account
- Resend account (for emails)
-
Clone and install dependencies
git clone <repo-url> cd zurichjs-conf npm install
-
Set up environment variables
cp .env.example .env.local
Fill in your credentials in
.env.local:- Supabase URL and keys
- Stripe keys and webhook secret
- Resend API key
-
Set up Supabase
# Install Supabase CLI npm install -g supabase # Link to your project supabase link --project-ref your-project-ref # Apply migrations supabase db push
-
Configure Stripe
- Create products and prices in Stripe Dashboard
- Set up webhook endpoint:
https://your-domain.com/api/webhooks/stripe - Add webhook secret to
.env.local
-
Run development server
npm run dev
-
Test webhooks locally (optional)
stripe listen --forward-to localhost:3000/api/webhooks/stripe
- Documentation Index - All project documentation
- Analytics & Logging - PostHog integration and structured logging
- Supabase Branching - Testing migrations on a staging branch before production
- CFP Improvements - Roadmap for CFP system
- CLAUDE.md - AI assistant quick reference
- .cursorrules - Detailed coding standards
npm run dev # Start development server
npm run build # Build for production
npm run start # Start production server
npm run lint # Run ESLint
npm run typecheck # Run TypeScript type checking
npm run email:dev # Preview email templates- Pre-commit hooks run linting and type checking
- Strict TypeScript configuration
- ESLint with Next.js recommended rules
The codebase is organized by domain (users, tickets, workshops, roles) rather than by technical layer. Each domain exposes clean, typed interfaces.
Full TypeScript coverage with:
- Database schema types
- API request/response types
- Domain model types
- Strict null checking
- Row Level Security (RLS) on all tables
- Role-based access control (attendee, speaker, admin)
- Secure webhook signature verification
- Environment variable validation
All write operations are idempotent:
- Ticket creation checks for existing records by session ID
- User profile creation is upsert-based
- Webhook handlers can be safely retried
- Attendee: Can purchase tickets and register for workshops
- Speaker: Attendee permissions + can manage own workshops
- Admin: Full access to all resources
See deployment.md for detailed instructions.
Quick deploy to Vercel:
Important:
- Set all environment variables in Vercel
- Apply database migrations to production Supabase
- Configure Stripe webhook URL to production endpoint
This is a private conference platform. For questions or issues, contact the development team.
Private - All rights reserved
The dedicated /admin/badges panel reviews confirmed VIP attendees, other
confirmed attendees, the exact public speaker lineup, manually entered sponsor
representatives, and organizers. Hidden CFP applicants are never included, and
the commercial sponsor list is not imported into badge exports. Each category
can be reviewed independently, rows can be excluded from a particular export,
and persistent manual rows can be added or edited for sponsor representatives
and organizers.
Imported attendee and public-speaker names, roles, and companies can be edited
temporarily for print corrections. These overrides live only in browser
session storage, are sent directly to the export renderer, and never update the
source attendee or speaker records. Normal exports are read-only; missing share
IDs or badge QR codes must be created explicitly with Generate missing codes.
The export dropdown can download a ZIP containing one two-page PDF per person
for the active tab, the active tab's complete data ZIP, PDFs for every tab, or
the complete data ZIP for every tab. All four choices respect row exclusions; full-data exports include
CSV, QR images, relevant sponsor logo assets, manifests, and warnings.
The deployed export produces print-ready two-page PDFs from the approved vector
templates under assets/badges/templates/. Text is rendered with the bundled
Figtree fonts, each person receives their own 30 mm QR, and sponsor default/light
logos are contained in the configured logo box. The ZIP also retains CSV, QR
PNG, and logo assets for auditing or Illustrator use.
Sponsor badges work like organizer badges: create one manual row for every
physical badge, enter that representative's details and optional networking
links, and upload the sponsor's default light/white logo for the black badge
background. Multiple people can use the same company name and logo. Only add
sponsors who will attend in person. The uploaded logo is persistent and is used
by later exports and the public share page.
Any stored logo that cannot be inspected or is under 500 px wide is
also listed in WARNINGS.txt and manifest.json inside the export ZIP.
The equivalent local command is:
pnpm badges:export -- --provision-share-ids --output badge-exportAfter share IDs have been provisioned, the deployed read-only endpoint can also be scripted with the admin read-only API key:
curl --fail \
--header "Authorization: Bearer $ADMIN_READONLY_API_KEY" \
https://conf.zurichjs.com/api/admin/badges/export \
--output zurichjs-badges.zipThe script reads NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SECRET_KEY (or the legacy
SUPABASE_SERVICE_ROLE_KEY), and NEXT_PUBLIC_BASE_URL from the environment,
.env, or .env.local. Pass --base-url https://conf.zurichjs.com to override
the URL encoded in the QR images.
The command is read-only by default. If an attendee has never set a networking
preference, or a subject has no managed badge QR token, it exits and
asks for --provision-share-ids. That flag inserts missing disabled networking
rows and missing badge-code rows; it never changes existing visibility or
contact settings and is safe to rerun.
Printed badge tokens redirect to the existing stable networking share page.
Replacing a QR in /admin/badges destructively rotates only the badge token:
the old printed QR stops resolving immediately, while the attendee/manual-badge
share ID and speaker slug remain unchanged. The UI requires confirmation that the old
code has not been printed.
The ignored badge-export/ directory contains vip.csv, attendee.csv,
speaker.csv, sponsor.csv, organizer.csv, a sparse badges.csv,
pdf/<category>/*.pdf, qr/*.png, uploaded default sponsor logos, and
manifest.json. CSV image fields contain absolute local paths
for local exports. Every file under pdf/<category>/ contains the front and
back pages for exactly one person.
QR and sponsor-logo CSV headers use Illustrator's required @ linked-file
prefix. Illustrator removes the prefix in the Variables panel, so @speaker_qr
is displayed and bound as speaker_qr.
Generate five non-production fixture PDFs for template review with:
pnpm badges:sampleWith local Supabase running, exercise temporary manual organizer and sponsor rows, persistent sponsor-logo storage, stable QR rotation, public share-page resolution, and per-person PDF rendering with:
pnpm badges:verify-localThe verifier refuses non-local Supabase credentials and removes its temporary rows, QR codes, and logo asset when it finishes.