Commit graph

63 commits

Author SHA1 Message Date
Randall Stillwell
7aedae02fc Keep card name and contact fields in sync after edits
Two bugs caused the cards list and integrations to show stale data
after a user edited a card in the detail view:

1. ResponseCard.name is a denormalized display string set only at
   OCR / survey-submit time. Editing firstName or lastName never
   recomputed it, so the table header and Name column kept the old
   value. PUT /api/cards/[id] now recomputes name from first + last
   whenever either changes (unless the caller passed an explicit
   name). The detail page header reads from in-flight edits so the
   title updates live as the user types.

2. The default form template marked only firstName/lastName as
   isCore. Every other field (email, cellPhone, address, etc.) was
   non-core, so dynamic-field edits landed in ResponseCard.fieldData
   JSON and never touched the top-level columns the list view,
   search, CSV export, and integrations read from. The PUT route
   now promotes any fieldData keys that match canonical columns up
   to those columns; the default template marks all canonical
   fields as isCore so new orgs avoid the problem in the first
   place.

Adds scripts/backfill-core-fields.ts (dry-run by default; pass
--apply to commit) to flip existing FormField rows to isCore = true
where the key matches a canonical column and to promote any
existing fieldData values into empty top-level columns + recompute
stale name values.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 08:30:27 -05:00
Randall Stillwell
532a818995 Fix RSC build error: pass rendered icon element to DocArticle
DocArticle is a client component, so passing a Lucide icon *component
type* (a function) from the server-rendered doc pages tripped the RSC
"Functions cannot be passed directly to Client Components" check during
prerender of /docs/*. Switch the `icon` prop to React.ReactNode and pass
already-rendered <Icon /> elements from each doc page.

Made-with: Cursor
2026-04-24 15:17:55 -05:00
Randall Stillwell
3a75bfa7de Add in-app Contact Support dialog and Help Center docs
Route all support channels to support@stillwell.cloud for Libredesk
email ingest, add a global Contact Support dialog (category/subject/
message) that posts to POST /api/support with per-user rate limiting,
and build a /docs Help Center hub with Getting Started, Uploading
Cards, Forms & Templates, Reports & Exports, FAQ, and Troubleshooting
sections. Replaces stale echoocr.com/echoocr.app addresses across
marketing footer, terms, privacy, pricing, welcome, and features.

Made-with: Cursor
2026-04-24 15:10:56 -05:00
Randall Stillwell
be7e3dc502 Normalize role-based permissions across API and UI
Centralizes role/permission enforcement so each role (owner, admin, editor,
reviewer, viewer) behaves consistently in the API and UI.

- Extend src/lib/permissions.ts with an expanded action map (cards.reprocess,
  cards.assign, uploads.create, integrations.manage, etc.) plus helper
  predicates (isAdminRole, canEditContent).
- Add requireApiAuthWithPermission(action) to src/lib/api-auth.ts with a
  narrowed OrgSession return type and PermissionError -> 403 handling.
- Replace hand-rolled role checks in card, org, integration, form-template,
  settings, upload, and location routes with the shared helpers so 403s are
  uniform and derived from one permission map.
- Close the editor UI gap: the dashboard upload button, row-level mark
  reviewed/reprocess/delete, and card detail edit/reprocess/export/assign
  now flow from can(role, action) instead of ad-hoc isAdmin checks.
- Gate /settings/* at the middleware layer for non-admins and hide the
  Settings entry in the sidebar and top-bar menu when the role cannot
  access it.
- Use isAdminRole() in the team members settings page for consistency.

Made-with: Cursor
2026-04-23 12:17:35 -05:00
Randall Stillwell
11773a2ad2 Self-heal stale email verification banner
The banner was wired off session.user.isEmailVerified, which lives on
the JWT cookie. If a user verified their email in another tab or was
auto-verified by the invite flow after an existing session was issued,
the token kept saying unverified until sign-out, so the banner hung
around indefinitely.

- Add GET /api/auth/verify-email/status, a tiny authenticated endpoint
  that returns the authoritative emailVerified state straight from the
  DB.
- Banner now fetches that on mount and only renders if the DB agrees
  the user is unverified. If the DB says verified but the cookie is
  stale, we call useSession().update() to refresh the JWT so the rest
  of the app sees the correct state on the next render.

Made-with: Cursor
2026-04-19 10:53:36 -05:00
Randall Stillwell
f2c7d13a3e Unify invite landing with inline signup/login flow
Rewrite /invite/[token] so invited users can create an account and
join a workspace in a single page instead of bouncing to /signup.
The page branches on verify response + session state:

- New user: inline signup form (name + password, email locked), then
  auto signs in and redirects to the dashboard.
- Existing user, signed out: password-only login form that signs in
  and auto-calls /api/invitations/accept.
- Existing user, signed in with matching email: single Accept button.
- Existing user, signed in with mismatched email: amber notice plus
  one-click "Sign out and continue" that returns to the same invite
  URL logged out.

Also:
- Expired / already-accepted / not-found states now show the inviter's
  name and org context so users know who to contact for a new link.
- Extract the password + confirm-password + strength meter into a
  reusable <PasswordFields> component consumed by both the signup and
  invite pages so the UI stays in lockstep. The component supports
  hiding the confirm field and overriding autoComplete for login use.

Made-with: Cursor
2026-04-19 10:45:51 -05:00
Randall Stillwell
47da94fc95 Enrich invitation verify API and auto-verify invited signups
- /api/invitations/verify now returns inviterName and a structured
  failure reason ("not_found" | "expired" | "accepted") plus orgName,
  so the invite page can show contextual error messages and know who
  to blame for an expired link.
- /api/auth/register marks emailVerified immediately and skips the
  verification email when an inviteToken is present, since clicking
  the tokenized invite link already proves email ownership.

Made-with: Cursor
2026-04-19 10:45:41 -05:00
Randall Stillwell
4431778c0a Replace hardcoded echoocr.com fallbacks with resolved site URL
Introduces src/lib/site-url.ts with getSiteUrl(), getSiteUrlFromRequest(),
and getSiteHostname() helpers that cascade through NEXT_PUBLIC_SITE_URL,
AUTH_URL, VERCEL_URL, and localhost so no code path ever falls back to a
third-party domain we may not own.

- forgot-password route now derives the base URL from the incoming request
  origin so reset links always match the host the user hit
- email-sender, layout metadata, email preview, and QR code routes use the
  new helper; email footers display the derived hostname instead of a
  hardcoded brand string
- .env.example clarifies the real expected values for AUTH_URL and
  NEXT_PUBLIC_SITE_URL per environment

Made-with: Cursor
2026-04-19 10:12:44 -05:00
Randall Stillwell
3e0a4458fe Add unified field mapping UI for integrations
Adds a reusable FieldMappingEditor component to the integration
detail page that works across all providers (Monday.com, Google
Sheets, Airtable, Planning Center). Users can visually map card
fields (core + dynamic FormTemplate fields) to external columns.

- New GET /api/integrations/source-fields returns curated core
  card fields plus the org's default FormTemplate dynamic fields
  (prefixed with fieldData.)
- flattenCardFieldData helper hoists fieldData entries to top-
  level keys before pushCard so dynamic fields are mappable
- Monday provider now upserts: re-syncing a card updates the
  existing item instead of creating duplicates, and the
  mondayItemId is persisted back to the ResponseCard

Made-with: Cursor
2026-04-17 15:53:49 -05:00
Randall Stillwell
a83c41694f Add change password to profile, persist all profile fields to DB
- Add jobTitle, company, bio columns to User model (previously localStorage only)
- Replace legacy Authentik GET /api/auth/me with session-based profile endpoint
- Update PUT /api/auth/me to persist all profile fields to DB
- Add PUT /api/auth/change-password endpoint with current password verification
- Add Security card to profile page with change password form
- Update user-profile provider to fetch from API instead of localStorage

Made-with: Cursor
2026-04-17 15:35:24 -05:00
Randall Stillwell
028840a887 Add SFTP support: replace FTP-only TLS toggle with protocol selector
Dreamhost (and most modern hosts) use SFTP (port 22), not FTP (port 21).
The previous implementation only supported FTP/FTPS via basic-ftp, causing
timeouts when connecting to SFTP servers.

Changes:
- Add ssh2-sftp-client for SFTP connections
- Replace ftpTls boolean with ftpProtocol ("sftp" | "ftp" | "ftps") in schema
- Rewrite ftp-watcher.ts to support both SFTP and FTP/FTPS protocols
- Update UI with protocol dropdown that auto-switches the default port
- Add ssh2/ssh2-sftp-client to serverExternalPackages in next.config
- Default to SFTP on port 22

Made-with: Cursor
2026-04-17 15:12:54 -05:00
Randall Stillwell
58d8d99625 Fix masked passwords being sent to test endpoints and saved to DB
The settings GET endpoint masks emailImapPass and ftpPass for security.
The test endpoints and save handler were using these masked values, causing
IMAP/FTP test failures and potentially overwriting real passwords. Now the
test endpoints fall back to the DB-stored password when they receive the
masked value, and the save handler strips masked passwords from the payload.

Made-with: Cursor
2026-04-17 14:37:55 -05:00
Randall Stillwell
d0844ecadd Add team member management: role changes, member removal, invitation resend/revoke
- Create PATCH/DELETE /api/org/members/[id] for role updates and member removal
  with owner/admin guards, self-action prevention, and activeOrgId cleanup
- Create POST/DELETE /api/org/invitations/[id] for resending and revoking invites
  with automatic expiry extension on resend
- Filter accepted invitations from GET /api/org/invitations response
- Overhaul Team Members UI with inline role dropdowns, remove buttons,
  resend/revoke controls, expired invitation indicators, and admin-only guards

Made-with: Cursor
2026-04-17 12:48:59 -05:00
Randall Stillwell
3e5449cf4f Fix migration test issues: upload button, forgot password, IMAP, FTP UI, surveys nav, integration tests
- Mount UploadModal globally in AppShell so upload works from any page
- Add /forgot-password, /reset-password, /s, /api/survey/submit to public paths in middleware
- Fix IMAP test URL (/api/email/test -> /api/email-watch/test) and body shape
- Fix IMAP scan URL to use /api/email-watch with action body
- Add FTP server settings section to Upload Sources page with test connection
- Add "Surveys" nav item in sidebar with dedicated page showing links, QR codes
- Include org slug in form-templates API response for survey URL construction
- Add pre-creation "Test Connection" button on new integration page
- Create /api/integrations/test endpoint for pre-creation connection testing
- Replace overflow-hidden with overflow-clip on Card to fix click/z-index issues

Made-with: Cursor
2026-04-17 12:17:11 -05:00
Randall Stillwell
70134b4967 Polish login and signup UX: fix stale copy, add password toggles, strength meter, invite context, terms footer
Made-with: Cursor
2026-04-17 09:56:06 -05:00
Randall Stillwell
2664f78b00 Migrate Echo Life Church data, add invite acceptance for existing users
Data migration:
- Associate all 978 cards with Echo Life Church organization
- Split name field into firstName/lastName on all cards
- Seed default Connect Card form template with 25 fields
- Migrate legacy column data into fieldData JSON
- Create Echo Life Church location with 3 Sunday services (8:00, 9:30, 11:00)
- Add all users as members of Echo Life Church
- Populate 819 people from card data with dedup matching

Invitation flow fix:
- Add POST /api/invitations/accept for logged-in users to join orgs
- Update /api/invitations/verify to return org name and existing account flag
- Rewrite invite page to handle 3 scenarios:
  1. Logged in + email matches: one-click "Accept Invitation" button
  2. Logged in + email mismatch: prompt to switch accounts
  3. Not logged in + has account: "Sign In to Accept" button
  4. Not logged in + new user: "Accept & Create Account" (existing flow)

Made-with: Cursor
2026-04-17 08:15:31 -05:00
Randall Stillwell
f53b08f99f 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-16 23:29:26 -05:00
Randall Stillwell
1bceeb0418 Move onboarding to auth layout for consistent setup experience
The onboarding wizard was wrapped in the dashboard layout (sidebar +
topbar), which felt jarring when coming from the clean workspace-setup
page. Moved it to the (auth) layout so the entire setup flow stays in
the same centered, chrome-free experience. Widened the auth layout to
max-w-3xl and added max-w-md to each narrow auth page individually.

Made-with: Cursor
2026-04-16 21:43:15 -05:00
Randall Stillwell
150ec3524d Update governing law to Florida (Escambia County)
Made-with: Cursor
2026-04-16 20:54:42 -05:00
Randall Stillwell
2c765e05c4 Add privacy policy and terms of service pages
- Create comprehensive privacy policy covering all data categories:
  account info, org data, scanned card PII (20+ field types),
  AI processing (OpenAI via Vercel AI Gateway), subprocessors
  (Supabase, Vercel, Upstash, Brevo), configurable retention,
  data controller/processor roles, and individual rights
- Create terms of service covering eligibility, accounts, acceptable
  use, data responsibilities, subscriptions/billing, free tier,
  IP, third-party integrations, availability, liability limitation,
  indemnification, termination, and governing law
- Add /privacy and /terms to middleware public paths
- Update footer links from placeholder # hrefs to actual pages
- Add metadata layouts for both pages

Made-with: Cursor
2026-04-16 20:53:25 -05:00
Randall Stillwell
7ab2fe6894 Redesign transactional email templates to match Echo brand
- Replace bare inline HTML with a shared emailShell template system
- Use brand colors (warm neutrals), Quicksand font, and rounded card layout
- Add hidden preheader text for better inbox previews
- Proper table-based layout for email client compatibility (Outlook, Gmail, Apple Mail)
- Include Mso conditionals for Outlook button rendering
- Add dev-only email preview route at /api/dev/email-preview (gated behind NODE_ENV)
- Fix invitation email copy bug ("has invited you" / "You've been invited")

Made-with: Cursor
2026-04-16 20:46:05 -05:00
Randall Stillwell
c9f5b1e2ee Add OG/Twitter sharing metadata and screenshot for all marketing pages
- Update root layout with full Open Graph, Twitter Card, and SEO metadata
- Add OG image screenshot (1200x630) of the landing page hero
- Create layout.tsx with page-specific metadata for each marketing page
  (welcome, features index, 8 feature detail pages, pricing)
- Use Next.js title template ("%s | Echo") for consistent page titles
- Add NEXT_PUBLIC_SITE_URL to .env.example for metadataBase config

Made-with: Cursor
2026-04-16 20:34:26 -05:00
Randall Stillwell
7c4ec7bb9d Refresh JWT before hard navigation on onboarding completion
After completing onboarding, the JWT cookie still has stale org data.
Fetching /api/auth/session triggers the JWT callback to query the DB
and write the updated cookie before the hard navigate to /.

Made-with: Cursor
2026-04-16 19:12:21 -05:00
Randall Stillwell
4db78b7176 Fix workspace-setup redirect loop after org creation
The JWT callback was optimized to only query the DB on sign-in or
explicit session update, but the middleware reads the JWT cookie which
was stale after org changes. Reverted to always refreshing org data in
the JWT callback (matching original behavior). Also switched workspace-
setup and onboarding completion to hard navigation to guarantee the
middleware re-evaluates with the latest JWT cookie.

Made-with: Cursor
2026-04-16 19:10:41 -05:00
Randall Stillwell
bf92126cf5 Add feature pages, pricing page, and redirect unauthenticated users to landing page
- Middleware now redirects unauthenticated users to /welcome instead of /login
- Added /features and /pricing to public paths
- Created 8 dedicated feature pages: AI OCR, Scanner Integration, Integrations,
  Team Collaboration, Collection Days, Multi-Site, Analytics, Security
- Created features index page at /features with links to all detail pages
- Created standalone pricing page at /pricing with comparison table
- Extracted shared MarketingNav (with features dropdown) and MarketingFooter
- Created FeaturePageShell for consistent feature page layout
- Updated landing page to use shared components and link to feature pages

Made-with: Cursor
2026-04-16 19:09:52 -05:00
Randall Stillwell
52714c4e50 Add landing page at /welcome with pricing and feature overview
Marketing page targeted at churches/nonprofits with hero, features,
integrations showcase, 4-tier pricing table (Free/Starter/Growth/Enterprise),
testimonials, FAQ, and CTAs. Added /welcome to public middleware paths.

Made-with: Cursor
2026-04-16 18:59:33 -05:00
Randall Stillwell
ab873f7f08 Add multi-workspace support with org switcher and open registration
- Open registration to all users (remove invite-only gate)
- Add domain auto-join: new users matching org allowedDomains get auto-added
- Track active workspace via activeOrgId on User model
- Refactor JWT callback to resolve active org from all memberships
- Add org switch, list, and create-personal API endpoints
- Add workspace-setup page for users without an org
- Build OrgSwitcher dropdown in sidebar header
- Add allowed email domains management to org settings

Made-with: Cursor
2026-04-16 18:08:08 -05:00
Randall Stillwell
bf47fd262c Replace nodemailer with Brevo HTTP API for transactional email
- Swap nodemailer SMTP transport for @getbrevo/brevo SDK, which uses
  HTTP with built-in retries (better for Vercel serverless)
- Add sendInvitationEmail function and wire it into the org invitations
  POST route so new invites trigger an email automatically
- Update .env.example with BREVO_API_KEY, EMAIL_FROM_NAME,
  EMAIL_FROM_ADDRESS replacing the old SMTP_* vars

Made-with: Cursor
2026-04-16 12:51:17 -05:00
Randall Stillwell
42df7fc995 Fix admin role check to include owner role
The org membership role is "owner" but UI components only checked for
"admin", hiding action buttons (reprocess, push, export, etc.) for
org owners. Update isAdmin checks in card detail page and dashboard,
plus the delete API route, to include "owner".

Made-with: Cursor
2026-04-16 00:22:32 -05:00
Randall Stillwell
21c863da01 Migrate to Vercel + Supabase + Upstash production stack
- Replace pdf2pic/GraphicsMagick with pdfjs-dist + @napi-rs/canvas for
  Vercel-compatible PDF rasterization
- Replace MinIO with Supabase Storage (S3-compatible); rename minio.ts
  to storage.ts and update all imports
- Replace in-memory job queue with Upstash QStash; upload route now
  persists files to storage before enqueuing, /api/jobs/process handles
  the QStash callback
- Convert email watcher from persistent IMAP connection to stateless
  scanInbox() polled by Vercel Cron every 2 minutes
- Add FTP watcher (basic-ftp) with cron polling for scanner integration
  via Dreamhost FTP drop directory
- Add FTP config fields to AppSettings schema
- Remove folder watcher (chokidar), standalone output, Docker-only code
- Update next.config.ts, middleware, instrumentation for serverless
- Add vercel.json with cron schedules for email and FTP polling
- Add migration scripts for database (pg_dump/restore) and storage
  (S3-to-S3 copy) with verification

Made-with: Cursor
2026-04-15 23:51:44 -05:00
Randall Stillwell
20bc4cbb03 Add missing org API routes and fix registration gate
- Create /api/org (GET details, PUT update)
- Create /api/org/members (GET list)
- Create /api/org/invitations (GET list, POST create)
- Create /api/org/locations (GET list with collection days, POST create)
- Fix registration: check org count instead of user count so
  a fresh domain setup works even if orphaned users exist
  from a prior AUTH_SECRET rotation

Made-with: Cursor
2026-04-15 14:47:15 -05:00
Randall Stillwell
1b669bb1b7 Fix onboarding redirect and integrations empty state
- Middleware now redirects to /onboarding when user has no org
  (previously required orgRole=owner which was undefined for new users)
- Refresh JWT cookie after onboarding completes so middleware sees
  updated orgId/onboardingComplete on next navigation
- Integrations page shows meaningful error instead of empty grid
  when API returns 401 due to missing org

Made-with: Cursor
2026-04-15 14:18:51 -05:00
Randall Stillwell
95a672a6cb Fix build: wrap useSearchParams in Suspense boundary
Next.js requires useSearchParams() to be inside a Suspense boundary
for static page generation. Wraps the new integration page component.

Made-with: Cursor
2026-04-15 01:35:54 -05:00
Randall Stillwell
a975043670 Add onboarding wizard, email verification, integration architecture, and settings restructure
- Auto-sign-in after registration instead of redirect to login
- Email verification system with token generation, send/confirm API routes, and persistent banner
- 7-step onboarding wizard (org, location, services, upload source, AI, integrations, complete)
- Middleware redirects owners with incomplete onboarding to /onboarding
- Integration provider plugin architecture with registry and 6 providers (Planning Center, Monday.com, Airtable, Google Sheets, Webhook, CSV Export)
- Full integration CRUD API with test, sync, fields, and OAuth authorize/callback routes
- Refactored fireIntegrationEvent to use Integration model with legacy AppSettings fallback
- Migration script for existing Monday.com/webhook config to Integration rows
- Settings page restructured from monolithic 1290-line file into focused sub-routes with section navigation
- Integration hub UI with provider tiles, connect flow, and individual config pages
- Post-onboarding contextual guidance cards on dashboard with dismissible hints
- Schema: Integration model, onboardingComplete/onboardingStep on Organization, dismissedHints on OrgMember

Made-with: Cursor
2026-04-15 01:29:13 -05:00
Randall Stillwell
d3e7374439 Add SaaS foundation: Auth.js, dashboard shell, org model, auto-assignment
Major architectural upgrade preparing Echo OCR for self-hosted SaaS deployment:

- Auth: Built-in Auth.js v5 with credentials + Authentik OIDC SSO, JWT sessions,
  middleware route protection, login/signup/setup pages, registration API
- UI: Dashboard layout with collapsible sidebar nav, AppShell wrapper, route
  groups for (dashboard) and (auth), new pages for events/people/reports
- Schema: Auth.js tables (Account, Session, VerificationToken), Organization,
  OrgMember, Location, CollectionDay, Invitation, SystemConfig, ApiKey models;
  proper User relations to ResponseCard/ActivityLog/Notification
- Permissions: Role hierarchy (owner/admin/editor/reviewer/viewer) with
  action-based permission map and requirePermission/requireAuth helpers
- Onboarding: Multi-step setup wizard for first-user bootstrap (account, org,
  location) with SystemConfig tracking
- Events: CollectionDay model with rrule support for recurring church services
- Auto-assign: Event-aware card assignment engine replacing getPreviousSunday()
- Migration: seed-migration.ts script for upgrading existing deployments

Made-with: Cursor
2026-04-14 23:59:38 -05:00
Randall Stillwell
0ab9932599 Add multi-user card review workflow with role-based access
- Add User model synced from Authentik headers with admin/reviewer/viewer roles
- Add assignment fields (assignedToId, assignedById, reviewedById, etc.) to ResponseCard
- Add userId tracking to ActivityLog and Notification models
- Create auth.ts with getOrCreateUser() and role mapping from Authentik groups
- Create /api/users endpoint and /api/cards/assign batch assignment endpoint
- Gate card mutations behind role checks (viewers read-only, reviewers edit assigned only)
- Gate Monday.com push behind reviewStatus=reviewed instead of ocr_complete
- Add "My Cards" stat card, Assigned To filter, and assignment columns to table
- Add Assign button with user picker to batch selection toolbar (admin only)
- Update card detail: assignment banner, Mark Complete button, prev/next nav, reassign
- Make all field components accept readOnly prop for role-based editing
- Gate settings page behind admin role
- Add userId to stats API for per-user card counts
- Expose dbUser and role through UserProfileProvider context

Made-with: Cursor
2026-04-11 00:00:11 -05:00
Randall Stillwell
aacf9c776b Add manual Scan Inbox button for email monitoring
- Add scanInbox function that opens a separate IMAP connection and
  processes all unseen messages in the configured folder
- Wire to email-watch API as action "scan"
- Add Scan Inbox button to settings page next to Start/Stop Monitoring
- Useful for processing emails that arrived before monitoring started,
  since IMAP IDLE only notifies about new messages

Made-with: Cursor
2026-04-10 13:04:45 -05:00
Randall Stillwell
b2c29120cd Add First Time Guest Date and Salvation Date fields
- Add firstTimeGuestDate and salvationDate (DateTime?) to Prisma schema
- Auto-compute dates during OCR: find previous Sunday from card createdAt
  when visitType indicates first/second time guest or nextStep includes
  Baptism
- Add editable date inputs on card detail page in Workflow section
- Add to Monday.com mappable fields in settings for column mapping
- Add table columns (hidden by default) for both date fields
- Handle full ISO datetime strings in Monday.com date parser
- Apply same logic during reprocessing

Made-with: Cursor
2026-04-09 17:20:24 -05:00
Randall Stillwell
d6d209cf9d Add Monday.com sync-all with push+update support and batch toolbar button
- Sync-all endpoint now supports mode param: "push" (new only), "update"
  (existing only), or "all" (both); also accepts cardIds for batch ops
- Response includes separate created/updated/failed counts
- Settings button renamed to "Sync All to Monday.com" and shows breakdown
- Add "Monday.com" button to the floating selection toolbar for syncing
  selected cards in batch

Made-with: Cursor
2026-04-09 15:14:48 -05:00
Randall Stillwell
daec81884c Restore row-click navigation, add image lightbox, refine OCR prompts
- Change cell copy from click-to-copy to a small hover button so clicking
  a table row navigates to the card detail page again
- Add fullscreen lightbox popup on card detail page images with Escape to
  close and click-outside dismiss
- Further strengthen nextStep OCR prompts to emphasize inspecting each
  checkbox square independently and note most respondents check only one

Made-with: Cursor
2026-04-08 21:10:45 -05:00
Randall Stillwell
13fb94fee9 Make survey fields editable and improve OCR option normalization
- Replace read-only badge chips with toggleable multi-select chips for
  messageTopics, nextStep, campusPreference, and howHeard on card detail page
- Add editable Switch toggles for all boolean fields (prayerForTeam,
  prayerConfidential, iSaidYesBookSent, ftGuestLetterSent)
- Change edits state to Record<string, unknown> to support array and
  boolean values alongside strings
- Update OCR prompts to normalize visitType variations (e.g., "I am a
  first or second time guest at Echo Life" -> "First/Second Time Guest")
- Update message topics list in OCR schema to match actual survey form
  options (Stress/Anxiety, Hearing God's Voice, Dealing With Doubt, etc.)

Made-with: Cursor
2026-04-08 20:22:36 -05:00
Randall Stillwell
27bdf9d415 Add survey side recovery from source PDFs
Card-level reprocessing only re-OCRs existing images. When the old
page count bug caused survey pages to never be extracted, those images
don't exist to reprocess.

New /api/cards/recover-survey endpoint:
- Finds all cards missing frontImagePath (survey side)
- Locates their source PDFs in MinIO via the ProcessingJob
- Re-extracts the correct survey page for each card
- Stores the image and runs OCR to fill in survey data
- Processes sequentially with rate-limit delays

Settings page shows amber banner when missing backs are detected
with a one-click "Recover Missing Backs" button.

Made-with: Cursor
2026-04-07 23:10:59 -05:00
Randall Stillwell
1d20932ef5 Add OCR rate limit handling and batch reprocess action
Rate limiting:
- Retry with exponential backoff (5s, 10s, 20s, 40s) on rate limit errors
- Reduce concurrent OCR jobs from 2 to 1
- Add 2s delay between AI calls within a card (response vs survey side)
- Add 3s delay between cards in a multi-page PDF
- Add 3s delay between jobs in the processing queue

Batch reprocess:
- New /api/cards/reprocess-batch endpoint processes cards sequentially
  with delays to respect rate limits
- Reprocess button added to selection toolbar on dashboard

Made-with: Cursor
2026-04-07 17:22:33 -05:00
Randall Stillwell
d7d859f49d Fix Monday.com push: format column values by column type
The root cause was sending raw text to checkbox, status, dropdown, and
date columns. Monday.com requires specific JSON structures per type.

- Store column types in _columnTypes when Fetch Columns is clicked
- Format boolean/checkbox: {"checked": "true"/"false"}
- Format status/color: {"label": "value"}
- Format dropdown: {"labels": ["val1", "val2"]}
- Format date: parse MM/DD/YYYY to {"date": "YYYY-MM-DD"}
- Format email/phone/link with proper nested structures
- Text columns pass through as plain strings

Made-with: Cursor
2026-04-07 16:01:44 -05:00
Randall Stillwell
a0f5022de2 Fix Monday.com push failures and improve error visibility
- Fix toast showing success when all cards fail (now shows error/warning)
- Fix column value formatting: booleans use checkbox format, JSON arrays
  join as comma-separated strings, objects serialize properly
- Filter out empty/null/undefined string values before sending to API
- Add verbose logging to Monday.com GraphQL client for debugging

Made-with: Cursor
2026-04-07 13:50:44 -05:00
Randall Stillwell
034c73128f Add runtime safeguards: OCR concurrency limiter, upload size cap, and batched Monday sync
- Processing queue limits OCR to 2 concurrent jobs across all sources (upload, email, folder watcher)
- 50 MB file size cap enforced on uploads, email attachments, and folder watcher
- Monday.com bulk sync now processes in batches of 5 with 1s delay between batches

Made-with: Cursor
2026-04-07 13:44:04 -05:00
Randall Stillwell
7aefe7c4a1 Add manual push to Monday.com (single card + bulk sync)
- Extract reusable pushCardToMonday() that creates or updates Monday items
- New /api/integrations/monday/push/[id] endpoint for single-card push
- New /api/integrations/monday/sync-all endpoint for bulk push of all
  unsynced cards (ocrStatus=complete, no mondayItemId)
- Add "Push to Monday.com" button on card detail page header (shows
  "Update Monday" when card already has a Monday item)
- Add "Push All to Monday.com" button in Monday.com settings card

Made-with: Cursor
2026-04-07 13:32:15 -05:00
Randall Stillwell
2fcdd687f7 Fix duplex scan rotation and swap front/back image labels
Duplex scanners flip the sheet between sides, so odd pages (back) are
180° rotated relative to even pages (front). Landscape back pages now
rotate CCW instead of CW, and portrait back pages get a 180° flip.
Also swapped "Front"/"Back" labels to match the physical card sides.

Made-with: Cursor
2026-04-07 13:27:16 -05:00
Randall Stillwell
d58285785e Add Monday.com board fields and expand column mapper
- Add followUp, notes, serviceTime, planningCenter, iSaidYesBookSent,
  ftGuestLetterSent fields to ResponseCard to match Monday.com board
- Expand column mapper from 8 fields to all 32 ResponseCard fields with
  friendly labels matching the Easter Survey spreadsheet columns
- Add Workflow & Tracking section on card detail page for the new fields
- Fix pre-existing Buffer type error in images API route

Made-with: Cursor
2026-04-07 12:50:03 -05:00
Randall Stillwell
58f38d92ed Fix broken images by proxying through the app instead of redirecting to internal MinIO
The presigned URLs pointed to a private IP unreachable from the browser.
The image route now streams bytes directly and the card API returns
proxy paths matching the dashboard's existing approach.

Made-with: Cursor
2026-04-07 12:38:08 -05:00