- NextAuth.js v5 — email/password and Google OAuth, database sessions, role-based middleware on
/dashboard,/org/*,/admin/* - Recurring event signup with four scopes: single, all, until date, pick specific dates — one
VolunteerSignuprow per occurrence, generated at signup time viarruleexpansion - Availability grid (7 days × 3 time buckets) in
VolunteerAvailabilitytable — queried before sending new-event notifications to filter by day and time bucket - Add to Calendar on signup confirmation and each dashboard row — Google, Apple, Microsoft 365, Outlook via
add-to-calendar-button-react, client-side only, no API key - QR self-check-in via HMAC-SHA256 signed URL, server-validated against
CHECKIN_SECRET, registered users only — unregistered volunteers checked in manually
- Recurring event RRULE builder — occurrences generated up to 12 months at save time, extended by monthly Cloud Scheduler cron at
/api/cron/generate-occurrences - Unified roster —
VolunteerSignupandUnregisteredSignupin one table, one-click check-in on both record types /org/volunteers— two-tab directory, signed claim token on invite, migratesUnregisteredSignuptoVolunteerSignupon account creation, setsclaimedAtandclaimedByUserId- CSV bulk import for events and volunteers — row-level server validation with preview before confirm, separate API routes for template, validate, and confirm
- Resend notifications —
sendEmailToUser(respectsemailNotificationsflag) andsendEmail(direct address for unregistered) — availability filtering on Trigger 1 only, Triggers 4 and 5 reach both record types - Cloud Run (scales to zero) + Cloud SQL
db-f1-micro+ Cloud Storage — Cloud Build CI/CD from GitHub, secrets in Secret Manager, Cloud Scheduler cron for reminders (every 5 min) and occurrence generation (1st of month)
- Expo/React Native apps for iOS and Android — calls Phase 1 Next.js API routes directly, distributed via App Store and Google Play
- Push notifications via Expo Notifications — FCM for Android, APNs for iOS, covers new events, reminders, and emergency
EventFeedbackmodel — post-event survey triggered 24h after check-in, results visible on org roster- Birthday field:
birthdayMonth+birthdayDayonUserandUnregisteredVolunteer— no year stored, visible to orgs for recognition only - Richer profiles: hours history, skill endorsements, in-app notification bell with unread count
- Twilio SMS for emergency and time-sensitive broadcasts
- In-app QR scanner for org staff — replaces printed code, built into native app
VolunteerGroupandVolunteerGroupMembermodels — supports registered and unregistered members, group signup creates individual records, group messaging sends to all members with email on file- Filtered export with AND logic: skills, day-of-week availability, specific-date with time bucket, prior check-in on a specific recurring event
- Resource Library — 10 skill categories, public (no login), markdown body, admin CMS at
/admin/resources - Phase 2 migration: reverse relations added to
User,UnregisteredVolunteer,Organization, andEvent— no destructive changes to Phase 1 schema
2 – 4 hrs/month routine — dependency patches, small fixes, content updates
4 – 8 hrs per major framework update (typically 1 – 2 times per year)
$11 – $22/month — Cloud Run, Cloud SQL, Cloud Storage, Resend (Phase 1)
Adds ~$15 – $30/month for Twilio SMS (Phase 2 only)
The manual includes a complete Prisma schema, 13 Claude Code prompting stages each with a copy-paste prompt and a verification checklist, and page-by-page specs for every route. Below is a sample: two schema models from Phase 1, a partial Stage 7 checklist, and a selection of issues caught in review before any code was written.
model UnregisteredVolunteer { id String @id @default(cuid()) organizationId String firstName String lastName String phone String email String? invitedAt DateTime? claimedAt DateTime? claimedByUserId String? claimedByUser User? @relation( "ClaimedAccounts", fields: [claimedByUserId], references: [id]) organization Organization @relation(...) signups UnregisteredSignup[] } model VolunteerAvailability { id String @id @default(cuid()) userId String dayOfWeek Int // 0 = Sunday through 6 = Saturday morning Boolean @default(false) afternoon Boolean @default(false) evening Boolean @default(false) user User @relation(...) @@unique([userId, dayOfWeek]) }
Each stage has a matching prompt and checklist. This is a partial view of Stage 7, which covers the unified roster, volunteer directory, and recurring occurrence generation.
- Roster shows both
VolunteerSignupandUnregisteredSignuprows in one table - "No account" badge on unregistered rows, "Platform" badge on registered
- One-click Check In updates row in place without a page reload
- Invite button sends email and sets
invitedAton record - Claim link creates account, migrates signups, sets
claimedAt - Recurring event saves RRULE and generates occurrences up to 12 months out
- Edit recurring event shows "this / future / all" prompt
- Cron occurrence-generation endpoint protected by
CRON_SECRETheader
claimedByUserIdwas a bare FK with no@relation— would have brokenprisma generate- Stage 13 enabled Cloud Run but never set up Cloud Scheduler — two cron jobs would have had no trigger after deployment
- Emergency notification only queried
VolunteerSignup— unregistered volunteers with email addresses would have been silently skipped sendEmailonly accepted auserId— no code path to reach unregistered volunteers who have no accountOrganization.groupsreverse relation missing from Phase 2 migration notes — Prisma would have rejected the schema at build time