Simple event registration / admissions sales site
Front-end: Vite + React + Material-UI
Hosting: Firebase Hosting
Database: Firebase Firestore
Serverless functions: Firebase Functions
Logging: Google Cloud Logging
Secrets management: Doppler & Google Cloud Secret Manager
Address autocomplete: Google Places API
Email: Amazon SES
Payment: Stripe or PayPal
IaC: Terraform
- Prerequisites
- Generate Repository
- Bootstrap Projects
- Spreadsheet Setup
- Email Setup
- Deploy Infrastructure
- Grant Spreadsheet Access
- Payment Setup
- Site Configuration
- Development
- Deployment
- Post-Deployment
Important
Run commands from a Unix-compatible CLI (e.g. Terminal on Mac or Git Bash on Windows).
✅ GitHub - Code Repository
✅ Firebase - Hosting, Database, Backend
✅ Google Cloud - Infrastructure (billing required)
✅ Doppler - Secrets Management
✅ Amazon SES - Email Delivery
✅ Stripe or PayPal - Payment Processing
| Tool | Authentication |
|---|---|
| Node.js | - |
| Terraform | - |
| GitHub CLI | gh auth login |
| Firebase CLI | firebase login |
| Doppler CLI | doppler login |
| Google Cloud CLI | gcloud auth login + gcloud auth application-default login |
| Stripe CLI (optional) | stripe login |
- Generate repository from template
- Clone:
git clone <REPO_URL> - Navigate to project:
cd <your-repo-name>
Note
What this does: Creates production & staging Google Cloud projects linked to your billing account, enables APIs, initializes Terraform, generates .firebaserc file, creates Doppler projects (see scripts/bootstrap/README.md for details)
Tip
PROJECT_ID format: Use lowercase letters, numbers, hyphens. Must be globally unique.
Example: my-dance-event-2025 → Complimentary site will be at https://my-dance-event-2025.web.app
Tip
If the Google Cloud projects already exist (i.e. redeploying an existing site), add skip-project-creation as a second argument.
npm run bootstrap <PROJECT_ID> [skip-project-creation]- Create spreadsheet from template
- Rename spreadsheet to desired name
- Update columns as desired
- Add URL to
terraform/shared.auto.tfvars:
spreadsheet_url = "YOUR_SPREADSHEET_URL"Important
You must verify the domain of your email_from_address in Amazon SES.
- Go to SES Identities
- Create Identity → Domain → Enter your domain (e.g.
yourdomain.com) - Check "Use a custom MAIL FROM domain" → Enter
amazonses(this is not visible to recipients but improves deliverability) - Advanced DKIM settings → "Easy DKIM" with a 2048-bit key
- Uncheck "Publish DNS records to Route53" in both spots on the page (unless using Route53 for DNS)
- Leave "DKIM Signatures" checked
- Click "Create Identity"
- Publish the provided DNS records (likely 6 records total, from 3 sections - DKIM, MAIL FROM, and DMARC)
- Wait and check back until all 3 sections show as verified
- Go to Amazon SES SMTP
- Add credentials to
terraform/shared.auto.tfvars:
email_amazonses_smtp_user = "YOUR_SMTP_USER"
email_amazonses_smtp_password = "YOUR_SMTP_PASSWORD"Fill in email settings in terraform/shared.auto.tfvars:
email_from_name = "Example Dance Weekend"
email_from_address = "someone@yourdomain.com"
email_admin_notifications = "admin@yourdomain.com"
# Optional: if different from email_from_address
email_reply_to = ""
# Optional: domains to skip for email confirmation & data validation
email_test_domains = "example.com,test.com,testing.com"
# Required only if domain was verified in an aws region other than us-east-2
email_amazonses_email_endpoint = ""Important
Ensure all required values are set in terraform/shared.auto.tfvars
Tip
Leave frontend_domain blank if you don't plan to have a custom domain for your website
npm run terraform-stg # deploys staging infrastructure
npm run terraform-prd # deploys production infrastructureShare your spreadsheet (edit permissions) with:
sheets@<PROJECT_ID>.iam.gserviceaccount.comsheets@<PROJECT_ID>-stg.iam.gserviceaccount.com
Note
Staging & Dev mode webhooks are optional (only needed for testing webhook functionality)
Option A: Stripe
- Disable all payment methods except: Cards, Apple Pay, Google Pay
- Apple Pay requires Stripe domain verification
- Create 2 sandbox accounts - dev & stg
Create webhooks for payment_intent.succeeded event only:
| Environment | Endpoint URL |
|---|---|
| Production | https://<REGION>-<PROJECT_ID>.cloudfunctions.net/stripeWebhook |
| Stg (optional) | https://<REGION>-<PROJECT_ID>-stg.cloudfunctions.net/stripeWebhook |
| Dev (optional) | https://<localtunnel-url>/<PROJECT_ID>-stg/<REGION>/stripeWebhook (requires using localtunnel, e.g. lt -p 5001 -s <PROJECT_ID>) |
Run for each environment to set webhook secret and publishable + secret keys:
npm run set-payment-secrets <PROJECT_ID> stripe dev
npm run set-payment-secrets <PROJECT_ID> stripe stg
npm run set-payment-secrets <PROJECT_ID> stripe prdOption B: PayPal
- Don't want Venmo? Comment out the venmo line in
configPaypal.jsx
[!IMPORTANT] You must enable the "Transaction search" feature on each REST API app to facilitate the the payment matching script.
- Create 2 sandbox business accounts - dev & stg
- Create a REST API apps within each sandbox account
- Enable "Transaction search" feature on each app
- Also create production REST API app if it doesn't yet exist
Create webhooks for payment capture completed event only:
| Environment | Endpoint URL |
|---|---|
| Production | https://<REGION>-<PROJECT_ID>.cloudfunctions.net/paypalWebhook |
| Stg (optional) | https://<REGION>-<PROJECT_ID>-stg.cloudfunctions.net/paypalWebhook |
| Dev (optional) | https://<localtunnel-url>/<PROJECT_ID>-stg/<REGION>/paypalWebhook (requires using localtunnel, e.g. lt -p 5001 -s <PROJECT_ID>) |
Run for each environment to set webhook id and client id + client secret:
npm run set-payment-secrets <PROJECT_ID> paypal dev
npm run set-payment-secrets <PROJECT_ID> paypal stg
npm run set-payment-secrets <PROJECT_ID> paypal prd| File | About |
|---|---|
functions/config/userConfig.js |
Backend config |
src/config/ |
Frontend config - event, fields, order-summary, theme |
src/templates/ |
Email receipt templates |
src/components/Static/ |
Static pages (e.g. Home, About, Contact) |
src/components/IntroHeader.jsx |
Registration form header |
src/components/layouts/Navbar.jsx |
Navbar |
index.html |
Site title, metadata description, og:image |
public/logo.png |
Optional Navbar logo (≤80px height recommended) |
public/ |
favicon files - use a generator, e.g. favicon-generator |
npm install && npm install --prefix functions
git checkout -b stagingnpm run emulator # Start Firebase emulators
npm run dev # Start frontend dev serverSee scripts/README.md for database and payment processor query tools.
Note
GitHub Actions automatically handles:
- Frontend deployment to Firebase Hosting
- Backend deployment to Firebase Functions
- Doppler secrets sync to Google Cloud Secret Manager
Important
You must redeploy after any changes to Doppler secrets!
git push origin staging# 1. Ensure staging branch is up to date on GitHub
git push origin staging
# 2. Create and merge the pull request from staging to main
gh pr create --base main --head staging --fill --title "<SQUASH_COMMIT_MESSAGE>"
gh pr merge staging --auto --squash --delete-branch
# 3. Recreate staging branch from main
git checkout -b staging- Add custom domain in Firebase Console
- Confirm Stripe/PayPal production secrets are set in Doppler
- prd_frontend:
VITE_STRIPE_PUBLISHABLE_KEYorVITE_PAYPAL_CLIENT_ID - prd_backend:
STRIPE_SECRET_KEY+STRIPE_WEBHOOK_SECRETorPAYPAL_CLIENT_ID+PAYPAL_CLIENT_SECRET+PAYPAL_WEBHOOK_ID
- prd_frontend:
- Update registration link on homepage (if applicable)
- Update
robots.txtto allow indexing (if sharing direct link) - Set prd.live to true in
configEvent.jsx - Clear spreadsheet data
- Clear staging Firestore data
- Clear production Firestore data if needed
- Redeploy after any updates to Doppler secrets or source code
- Toggle registration.waitlist_mode flag in both
src/config/configEventandfunctions/config/userConfig.
For inactive projects:
npm run disable-apis # npm run enable-apis to wake upImportant
This will run terraform destroy, delete firebase functions, and delete firestore database for both staging and production projects.
It will not delete the Google Cloud or Doppler projects themselves.
- Necessary access for another developer to take over in a 'hit-by-the-bus' scenario: GitHub, Doppler, GCP, domain registration, spreadsheet
npm run shutdown <PROJECT_ID>