Add dynamic fields, people directory, analytics, security hardening, and UX polish
Phase 1 - Security & Bug Fixes:
- Add requireApiAuth helper and protect all 25 unprotected API routes
- Add org-tenant scoping to all card, job, stats, and notification queries
- Fix SSRF in ai-test, mask secrets in settings API, fix middleware bypass
- Fix cards pagination routing, stat filter sync, drag-drop file passing
- Add PUT /api/auth/me for profile persistence, stuck job recovery
- Fix email watcher MIME type detection
Phase 2 - Dynamic Fields & Digital Survey:
- Add FormTemplate, FormField, Person, PasswordResetToken models to schema
- Add fieldData, formTemplateId, firstName, lastName, personId to ResponseCard
- Build FormTemplate CRUD API with field management and org scoping
- Build Form Builder UI with field ordering, type config, and section management
- Refactor card detail page to render fields dynamically from templates
- Add dynamic OCR prompt/schema generation from template fields
- Build public survey page at /s/[orgSlug]/[formSlug] with branding
- Add QR code generation API and share section component
Phase 3 - People & Analytics:
- Build People CRUD API with merge and batch auto-link endpoints
- Build People list and detail pages with search, merge dialog
- Add auto-link logic in OCR completion to match/create Person records
- Add /api/stats/trends endpoint with time series and team activity
- Build Reports page with Recharts (area charts, bar charts, pipeline)
- Upgrade dashboard with sparklines and People stat card
Phase 4 - UX Polish:
- Replace silent error handling with toast notifications across all pages
- Add loading skeletons, differentiated empty states
- Add ARIA labels, skip-to-content link, accessible column toggle
- Add forgot password flow, Cmd+K command palette, Collection Days pages
- Unify Echo branding and theme toggle consistency
Made-with: Cursor
2026-04-17 00:29:26 -04:00
import { Prisma } from "@/generated/prisma/client" ;
import { prisma } from "./db" ;
2026-05-21 09:30:27 -04:00
// Every default field maps to a canonical top-level column on ResponseCard,
// so they're all marked `isCore: true`. That way, edits flow through the
// `edits` channel and write to top-level columns (read by the list view,
// search, integrations, etc.) instead of the `fieldData` JSON blob.
// `fieldData` is reserved for user-defined custom fields.
Add dynamic fields, people directory, analytics, security hardening, and UX polish
Phase 1 - Security & Bug Fixes:
- Add requireApiAuth helper and protect all 25 unprotected API routes
- Add org-tenant scoping to all card, job, stats, and notification queries
- Fix SSRF in ai-test, mask secrets in settings API, fix middleware bypass
- Fix cards pagination routing, stat filter sync, drag-drop file passing
- Add PUT /api/auth/me for profile persistence, stuck job recovery
- Fix email watcher MIME type detection
Phase 2 - Dynamic Fields & Digital Survey:
- Add FormTemplate, FormField, Person, PasswordResetToken models to schema
- Add fieldData, formTemplateId, firstName, lastName, personId to ResponseCard
- Build FormTemplate CRUD API with field management and org scoping
- Build Form Builder UI with field ordering, type config, and section management
- Refactor card detail page to render fields dynamically from templates
- Add dynamic OCR prompt/schema generation from template fields
- Build public survey page at /s/[orgSlug]/[formSlug] with branding
- Add QR code generation API and share section component
Phase 3 - People & Analytics:
- Build People CRUD API with merge and batch auto-link endpoints
- Build People list and detail pages with search, merge dialog
- Add auto-link logic in OCR completion to match/create Person records
- Add /api/stats/trends endpoint with time series and team activity
- Build Reports page with Recharts (area charts, bar charts, pipeline)
- Upgrade dashboard with sparklines and People stat card
Phase 4 - UX Polish:
- Replace silent error handling with toast notifications across all pages
- Add loading skeletons, differentiated empty states
- Add ARIA labels, skip-to-content link, accessible column toggle
- Add forgot password flow, Cmd+K command palette, Collection Days pages
- Unify Echo branding and theme toggle consistency
Made-with: Cursor
2026-04-17 00:29:26 -04:00
const DEFAULT_FIELDS = [
{ key : "firstName" , label : "First Name" , type : "text" , section : "personal" , required : true , removable : false , isCore : true , sortOrder : 0 } ,
{ key : "lastName" , label : "Last Name" , type : "text" , section : "personal" , required : true , removable : false , isCore : true , sortOrder : 1 } ,
2026-05-21 09:30:27 -04:00
{ key : "email" , label : "Email" , type : "email" , section : "contact" , isCore : true , sortOrder : 2 } ,
{ key : "cellPhone" , label : "Cell Phone" , type : "phone" , section : "contact" , isCore : true , sortOrder : 3 } ,
{ key : "homePhone" , label : "Home Phone" , type : "phone" , section : "contact" , isCore : true , sortOrder : 4 } ,
{ key : "gender" , label : "Gender" , type : "select" , section : "personal" , options : [ "Male" , "Female" ] , isCore : true , sortOrder : 5 } ,
{ key : "dateOfBirth" , label : "Date of Birth" , type : "date" , section : "personal" , isCore : true , sortOrder : 6 } ,
{ key : "maritalStatus" , label : "Marital Status" , type : "select" , section : "personal" , options : [ "Married" , "Single" , "Other" ] , isCore : true , sortOrder : 7 } ,
{ key : "visitType" , label : "Visit Type" , type : "select" , section : "survey" , options : [ "First/Second Time Guest" , "Update My Information" ] , isCore : true , sortOrder : 8 } ,
{ key : "address" , label : "Address" , type : "text" , section : "address" , isCore : true , sortOrder : 9 } ,
{ key : "aptNumber" , label : "Apt #" , type : "text" , section : "address" , isCore : true , sortOrder : 10 } ,
{ key : "city" , label : "City" , type : "text" , section : "address" , isCore : true , sortOrder : 11 } ,
{ key : "state" , label : "State" , type : "text" , section : "address" , isCore : true , sortOrder : 12 } ,
{ key : "zip" , label : "Zip" , type : "text" , section : "address" , isCore : true , sortOrder : 13 } ,
{ key : "prayerRequests" , label : "Prayer Requests" , type : "textarea" , section : "survey" , isCore : true , sortOrder : 14 } ,
{ key : "prayerForTeam" , label : "For Prayer Team" , type : "checkbox" , section : "survey" , isCore : true , sortOrder : 15 } ,
{ key : "prayerConfidential" , label : "Confidential" , type : "checkbox" , section : "survey" , isCore : true , sortOrder : 16 } ,
{ key : "messageTopics" , label : "Message Topics" , type : "multiselect" , section : "survey" , options : [ "Stress/Anxiety" , "Marriage" , "Hearing God's Voice" , "Dealing With Doubt" , "Parenting" , "Grief & Loss" , "Forgiveness" , "Finances" , "Purpose/Calling" , "Prayer" , "Healthy Boundaries" , "Understanding The Bible" , "Emotional Health" , "Sharing My Faith" , "Decision Making" , "Spiritual Disciplines" , "Spiritual Gifts" ] , isCore : true , sortOrder : 17 } ,
{ key : "nextStep" , label : "Next Steps" , type : "multiselect" , section : "survey" , options : [ "Baptism" , "Next Steps" ] , isCore : true , sortOrder : 18 } ,
{ key : "attendanceDuration" , label : "Attendance Duration" , type : "radio" , section : "survey" , options : [ "Less than 6 months" , "6 Months - 1 Year" , "1-3 Years" , "4-6 Years" , "7+ Years" ] , isCore : true , sortOrder : 19 } ,
{ key : "campusPreference" , label : "Campus Preference" , type : "multiselect" , section : "survey" , options : [ "Beulah" , "Pace/Milton" , "Gulf Breeze" , "Warrington" ] , isCore : true , sortOrder : 20 } ,
{ key : "howHeard" , label : "How Did You Hear About Us?" , type : "multiselect" , section : "survey" , options : [ "This is my church home" , "Regular Attender" , "Drove by" , "Social Media" , "Google" , "Personal Invite" ] , isCore : true , sortOrder : 21 } ,
{ key : "serviceAttended" , label : "Service Attended" , type : "select" , section : "survey" , options : [ "A" , "B" , "C" , "D" ] , isCore : true , sortOrder : 22 } ,
{ key : "followUp" , label : "Follow-Up" , type : "text" , section : "followup" , isCore : true , sortOrder : 23 } ,
{ key : "notes" , label : "Notes" , type : "textarea" , section : "followup" , isCore : true , sortOrder : 24 } ,
Add dynamic fields, people directory, analytics, security hardening, and UX polish
Phase 1 - Security & Bug Fixes:
- Add requireApiAuth helper and protect all 25 unprotected API routes
- Add org-tenant scoping to all card, job, stats, and notification queries
- Fix SSRF in ai-test, mask secrets in settings API, fix middleware bypass
- Fix cards pagination routing, stat filter sync, drag-drop file passing
- Add PUT /api/auth/me for profile persistence, stuck job recovery
- Fix email watcher MIME type detection
Phase 2 - Dynamic Fields & Digital Survey:
- Add FormTemplate, FormField, Person, PasswordResetToken models to schema
- Add fieldData, formTemplateId, firstName, lastName, personId to ResponseCard
- Build FormTemplate CRUD API with field management and org scoping
- Build Form Builder UI with field ordering, type config, and section management
- Refactor card detail page to render fields dynamically from templates
- Add dynamic OCR prompt/schema generation from template fields
- Build public survey page at /s/[orgSlug]/[formSlug] with branding
- Add QR code generation API and share section component
Phase 3 - People & Analytics:
- Build People CRUD API with merge and batch auto-link endpoints
- Build People list and detail pages with search, merge dialog
- Add auto-link logic in OCR completion to match/create Person records
- Add /api/stats/trends endpoint with time series and team activity
- Build Reports page with Recharts (area charts, bar charts, pipeline)
- Upgrade dashboard with sparklines and People stat card
Phase 4 - UX Polish:
- Replace silent error handling with toast notifications across all pages
- Add loading skeletons, differentiated empty states
- Add ARIA labels, skip-to-content link, accessible column toggle
- Add forgot password flow, Cmd+K command palette, Collection Days pages
- Unify Echo branding and theme toggle consistency
Made-with: Cursor
2026-04-17 00:29:26 -04:00
] ;
export async function seedDefaultTemplate ( organizationId : string ) {
const existing = await prisma . formTemplate . findFirst ( {
where : { organizationId , isDefault : true } ,
} ) ;
if ( existing ) return existing ;
const template = await prisma . formTemplate . create ( {
data : {
organizationId ,
name : "Connect Card" ,
slug : "connect-card" ,
description : "Default connect card template" ,
isDefault : true ,
isActive : true ,
fields : {
create : DEFAULT_FIELDS.map ( ( f ) = > ( {
key : f.key ,
label : f.label ,
type : f . type ,
section : f.section ,
required : f.required ? ? false ,
removable : f.removable ? ? true ,
isCore : f.isCore ? ? false ,
sortOrder : f.sortOrder ,
options : f.options ? ? Prisma . JsonNull ,
} ) ) ,
} ,
} ,
include : { fields : true } ,
} ) ;
return template ;
}
export { DEFAULT_FIELDS } ;