From bf92126cf5d55b606a6bb31c028cb5bed3b1110d Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Thu, 16 Apr 2026 19:09:52 -0500 Subject: [PATCH] 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 --- src/app/(auth)/workspace-setup/page.tsx | 12 +- src/app/(marketing)/features/ai-ocr/page.tsx | 122 +++++++ .../(marketing)/features/analytics/page.tsx | 107 ++++++ .../features/collection-days/page.tsx | 94 +++++ .../features/integrations/page.tsx | 170 +++++++++ .../(marketing)/features/multi-site/page.tsx | 90 +++++ src/app/(marketing)/features/page.tsx | 159 ++++++++ .../features/scanner-integration/page.tsx | 119 ++++++ .../(marketing)/features/security/page.tsx | 111 ++++++ .../features/team-collaboration/page.tsx | 121 +++++++ src/app/(marketing)/pricing/page.tsx | 339 ++++++++++++++++++ src/app/(marketing)/welcome/page.tsx | 171 +++------ src/auth.ts | 5 +- .../marketing/feature-page-shell.tsx | 124 +++++++ src/components/marketing/footer.tsx | 47 +++ src/components/marketing/nav.tsx | 129 +++++++ src/middleware.ts | 10 +- 17 files changed, 1787 insertions(+), 143 deletions(-) create mode 100644 src/app/(marketing)/features/ai-ocr/page.tsx create mode 100644 src/app/(marketing)/features/analytics/page.tsx create mode 100644 src/app/(marketing)/features/collection-days/page.tsx create mode 100644 src/app/(marketing)/features/integrations/page.tsx create mode 100644 src/app/(marketing)/features/multi-site/page.tsx create mode 100644 src/app/(marketing)/features/page.tsx create mode 100644 src/app/(marketing)/features/scanner-integration/page.tsx create mode 100644 src/app/(marketing)/features/security/page.tsx create mode 100644 src/app/(marketing)/features/team-collaboration/page.tsx create mode 100644 src/app/(marketing)/pricing/page.tsx create mode 100644 src/components/marketing/feature-page-shell.tsx create mode 100644 src/components/marketing/footer.tsx create mode 100644 src/components/marketing/nav.tsx diff --git a/src/app/(auth)/workspace-setup/page.tsx b/src/app/(auth)/workspace-setup/page.tsx index bf6ec59..ee968d3 100644 --- a/src/app/(auth)/workspace-setup/page.tsx +++ b/src/app/(auth)/workspace-setup/page.tsx @@ -1,18 +1,16 @@ "use client"; import { useState } from "react"; -import { useRouter } from "next/navigation"; import { useSession } from "next-auth/react"; import { ScanLine, Building2, Zap, Loader2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; export default function WorkspaceSetupPage() { - const router = useRouter(); const { update: updateSession } = useSession(); const [loading, setLoading] = useState<"create" | "skip" | null>(null); - async function handleCreate() { - router.push("/onboarding"); + function handleCreate() { + setLoading("create"); + window.location.href = "/onboarding"; } async function handleSkip() { @@ -25,8 +23,10 @@ export default function WorkspaceSetupPage() { setLoading(null); return; } + // Refresh the JWT so the middleware sees the new org await updateSession(); - router.push("/"); + // Hard navigate to ensure fresh middleware evaluation + window.location.href = "/"; } catch (err) { console.error("Failed to create personal workspace:", err); setLoading(null); diff --git a/src/app/(marketing)/features/ai-ocr/page.tsx b/src/app/(marketing)/features/ai-ocr/page.tsx new file mode 100644 index 0000000..b807246 --- /dev/null +++ b/src/app/(marketing)/features/ai-ocr/page.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { + ScanLine, + Eye, + FileCheck, + RefreshCw, + Brain, + Layers, + CheckCircle, + Sparkles, +} from "lucide-react"; +import { FeaturePageShell, DetailBlock, StatRow } from "@/components/marketing/feature-page-shell"; + +const extractedFields = [ + "Full name", + "Gender", + "Date of birth", + "Marital status", + "Cell & home phone", + "Email address", + "Mailing address", + "Visit type (first-time, returning, etc.)", + "Prayer requests", + "Confidential prayer flags", + "Spiritual decisions (baptism, salvation)", + "Service attended", + "Campus preference", + "How they heard about you", + "Message topics of interest", + "Next-step commitments", +]; + +export default function AiOcrPage() { + return ( + +
+
+ + + +

+ Unlike traditional OCR that relies on fixed zones and templates, Echo + uses multimodal vision AI to understand the entire card. It reads + handwritten text, identifies checked boxes, interprets context, and + extracts structured data — even from cards it has never seen before. +

+

+ This means you don’t need to redesign your cards or train the + system. Whether you use pre-printed forms, custom cards, or a mix of + both, Echo figures it out. +

+
+ + +

+ Scan a full stack of double-sided cards through your office scanner. Echo + automatically pairs front and back pages, rotates them for readability, + and extracts data from both sides as a single record. +

+

+ Supports PDF (multi-page), JPEG, PNG, and WebP. Files up to 50 MB each. +

+
+ + +

+ Every card is analyzed for all of the following fields. Echo only fills + in what it can confidently read — and flags the rest for human review. +

+
+ {extractedFields.map((field) => ( +
+ + {field} +
+ ))} +
+
+ + +

+ Every extraction includes a confidence score. High-confidence fields are + accepted automatically, while low-confidence results are highlighted so + your team can quickly verify and correct them. Nothing syncs to your CRM + until a human approves it. +

+
+ + +

+ Echo intelligently assigns first-time guest dates and salvation dates + based on card context and your service schedule. A card scanned on Monday + morning? It knows it came from last Sunday’s service. +

+
+ + +

+ Made changes to a card image? Want to try again with different AI + settings? Reprocess individual cards, entire batches, or specific jobs + with one click. The original images are always preserved. +

+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/features/analytics/page.tsx b/src/app/(marketing)/features/analytics/page.tsx new file mode 100644 index 0000000..bbf9853 --- /dev/null +++ b/src/app/(marketing)/features/analytics/page.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { + BarChart3, + LayoutDashboard, + Filter, + Download, + Table, + Activity, + TrendingUp, +} from "lucide-react"; +import { FeaturePageShell, DetailBlock } from "@/components/marketing/feature-page-shell"; + +export default function AnalyticsPage() { + return ( + +
+
+ +

+ The moment you log in, you see the numbers that matter: total cards + processed, cards awaiting review, OCR success rate, and cards assigned + to you. Quick-action buttons let you jump straight to uploading, + reviewing, or managing your team. +

+

+ A getting-started guide walks new users through setup, then + dismisses itself so the dashboard stays clean. +

+
+ + +

+ The response card grid is built on TanStack Table — the same engine + behind enterprise data tools. You get: +

+
    +
  • Full-text search across names, emails, phone numbers, and notes
  • +
  • Filter by OCR status, review status, visit type, date range, and more
  • +
  • Sort by any column — click once for ascending, twice for descending
  • +
  • Column visibility toggles to show only what matters to you
  • +
  • Pagination with configurable page sizes
  • +
  • Row selection for bulk operations
  • +
+
+ + +

+ Quick-filter chips at the top of the cards view let you jump between + views with one click: +

+
    +
  • All cards — everything in the system
  • +
  • Completed — successfully processed by AI
  • +
  • Errors — cards that need attention
  • +
  • Unreviewed — processed but not yet verified by a human
  • +
  • My cards — assigned to the current user
  • +
+

+ Each chip shows its count in real-time so you always know what’s + waiting for you. +

+
+ + +

+ Every card captures rich context about your visitors: whether they’re + first-time or returning guests, what spiritual next steps they’re + considering, prayer requests, how they heard about your church, and + which service they attended. +

+

+ This data powers your follow-up strategy. Filter for all first-time + guests from last Sunday, see who requested prayer, and identify new + believers — then route them to the right team member. +

+
+ + +

+ Click any card to see the complete record: front and back images, + every extracted field, confidence scores, assignment history, review + status, and a timestamped activity log. Edit any field directly, + add notes, and trigger reprocessing if needed. +

+
+ + +

+ Export the current view — with all active filters applied — as a CSV + file. This means you can filter for “all first-time guests in + April” and export just that subset. The CSV includes all visible + columns, ready to import into Excel, Google Sheets, or any reporting + tool. +

+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/features/collection-days/page.tsx b/src/app/(marketing)/features/collection-days/page.tsx new file mode 100644 index 0000000..9eead9a --- /dev/null +++ b/src/app/(marketing)/features/collection-days/page.tsx @@ -0,0 +1,94 @@ +"use client"; + +import { + CalendarDays, + Repeat, + Wand2, + ListChecks, + Calendar, + MapPin, + Settings, +} from "lucide-react"; +import { FeaturePageShell, DetailBlock } from "@/components/marketing/feature-page-shell"; + +export default function CollectionDaysPage() { + return ( + +
+
+ +

+ Create collection days that repeat on a schedule: every Sunday at + 9 AM, the first Wednesday of the month, bi-weekly small groups — whatever + fits your church calendar. +

+

+ Echo uses the RFC 5545 recurrence standard (the same one Google Calendar + uses) so it can handle complex patterns like “every Sunday except + holidays” or “first and third Saturdays.” +

+
+ + +

+ When cards are processed, Echo looks at the timestamp, your location, + and your event schedule to automatically assign each card to the most + recent matching service. A batch scanned Monday morning? Echo knows it + came from last Sunday’s service. +

+

+ Three assignment strategies, configurable per organization: +

+
    +
  • Most recent — auto-assign to the nearest past event within your time window
  • +
  • Prompt user — suggest a match but let the reviewer confirm
  • +
  • Manual only — leave assignment to team members
  • +
+
+ + +

+ Not everything is recurring. Create one-off collection days for special + events like Easter services, VBS, community outreach days, or + conferences. These work exactly like recurring events but with a single + fixed date. +

+
+ + +

+ Each collection day belongs to a specific location. This means your + downtown campus can have different service times than your north campus, + and cards scanned at each location are assigned to the correct event + automatically. +

+
+ + +

+ Browse all cards collected during a specific service. Filter your card + table by event to see exactly who visited on Easter Sunday at your + Main Street campus vs. your Northside campus. Great for targeted + follow-up. +

+
+ + +

+ The auto-assignment engine looks backward in time for a matching event. + The default window is 48 hours, but you can adjust this per organization. + A 72-hour window works well for churches that scan cards on Monday or + Tuesday. +

+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/features/integrations/page.tsx b/src/app/(marketing)/features/integrations/page.tsx new file mode 100644 index 0000000..47842ac --- /dev/null +++ b/src/app/(marketing)/features/integrations/page.tsx @@ -0,0 +1,170 @@ +"use client"; + +import { + Heart, + Globe, + FileText, + BarChart3, + Webhook, + ArrowRight, + Link2, + RefreshCw, + Settings, + Zap, +} from "lucide-react"; +import Link from "next/link"; +import { FeaturePageShell, DetailBlock } from "@/components/marketing/feature-page-shell"; + +const integrationCards = [ + { + name: "Planning Center", + tag: "Most Popular", + icon: Heart, + description: + "OAuth-connected. Match or create People records, sync contact info, add to lists. Full field mapping with custom field support.", + plan: "Growth", + }, + { + name: "Google Sheets", + icon: FileText, + description: + "OAuth-connected. Append rows to any shared spreadsheet. Great for custom reporting or bridging to other tools.", + plan: "Starter", + }, + { + name: "Monday.com", + icon: Globe, + description: + "API-connected. Create board items with mapped columns for project-style follow-up tracking and automation.", + plan: "Growth", + }, + { + name: "Airtable", + icon: BarChart3, + description: + "API-connected. Push records with full field mapping into any base. Perfect for flexible data modeling.", + plan: "Growth", + }, + { + name: "Webhooks", + icon: Webhook, + description: + "Send signed JSON payloads to any URL on card events. Connect to Zapier, Make, n8n, or your custom backend.", + plan: "Starter", + }, + { + name: "CSV Export", + icon: FileText, + description: + "Export your full card table or filtered views as CSV. Import into any system or archive for records.", + plan: "Free", + }, +]; + +export default function IntegrationsPage() { + return ( + +
+
+
+ {integrationCards.map((integration) => ( +
+ {integration.tag && ( + + {integration.tag} + + )} +
+ +
+

{integration.name}

+

+ {integration.description} +

+ + {integration.plan} plan & above + +
+ ))} +
+ +
+ +

+ Every integration fires on the events you choose. Configure each + connection to trigger on: +

+
    +
  • OCR complete — as soon as a card is processed
  • +
  • Card reviewed — after a team member verifies the data
  • +
  • Card exported — when marked as sent to your CRM
  • +
+

+ Most churches trigger on “reviewed” so only human-verified + data enters their systems. +

+
+ + +

+ Map Echo’s extracted fields to your destination’s fields + with a visual mapper. For Planning Center, Echo can even pull your + existing custom field definitions so everything lines up perfectly. +

+

+ Change your card layout? Just update the field mapping — no code required. +

+
+ + +

+ The Planning Center integration goes beyond basic syncing: +

+
    +
  • OAuth connection — no API keys to manage, tokens refresh automatically
  • +
  • Smart person matching by email address or name
  • +
  • Creates new People records for first-time guests
  • +
  • Updates existing records with new contact info
  • +
  • Adds people to PCO Lists (e.g. “First Time Guests April 2026”)
  • +
  • Syncs phone numbers, emails, and addresses as typed sub-records
  • +
+
+ + +

+ Every webhook payload is signed with your secret key using HMAC-SHA256. + Your receiving endpoint can verify the signature to ensure the data + came from Echo and wasn’t tampered with. Full payload includes + card data, event type, and timestamp. +

+
+
+ +
+

+ Don’t see your tool? +

+

+ Use webhooks to connect Echo to Zapier, Make, n8n, or any platform + that accepts HTTP callbacks. Or reach out about a custom integration + on our Enterprise plan. +

+ + Request an integration + + +
+
+
+
+ ); +} diff --git a/src/app/(marketing)/features/multi-site/page.tsx b/src/app/(marketing)/features/multi-site/page.tsx new file mode 100644 index 0000000..25c7879 --- /dev/null +++ b/src/app/(marketing)/features/multi-site/page.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { + Building2, + MapPin, + Users, + ArrowLeftRight, + Settings, + Globe, +} from "lucide-react"; +import { FeaturePageShell, DetailBlock, StatRow } from "@/components/marketing/feature-page-shell"; + +export default function MultiSitePage() { + return ( + +
+
+ + + +

+ Each location in your organization has its own name, address, timezone + override, and set of collection days. Your downtown campus might have + services at 9 AM and 11 AM on Sundays, while your satellite campus + meets at 10 AM — Echo tracks both separately. +

+
+ + +

+ Each campus can have its own scanner configuration. Northside emails + scans to one inbox, Downtown uses FTP, and the mobile campus uploads + from phones. All three feed into the same processing pipeline and + appear in the same dashboard. +

+
+ + +

+ Assign team members to review cards from specific locations. Your + downtown volunteer coordinator only sees downtown cards, while the + executive pastor sees everything. Role-based permissions work + alongside location context. +

+
+ + +

+ See aggregate stats across all locations or drill down by campus. + How many first-time guests did we have across all sites this month? + Which campus had the most prayer requests? The dashboard gives you + both the big picture and the details. +

+
+ + +

+ Organization-level settings (AI provider, integration connections, + team permissions) apply everywhere. Location-specific settings + (timezone, scanner config, collection day schedules) can be + customized per campus. Change your Planning Center connection once + and it applies to all locations. +

+
+ + +

+ Consultants, denominational leaders, or staff who serve multiple + churches can switch between organizations from the sidebar. Each + org is fully isolated — separate data, separate teams, separate + billing. +

+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/features/page.tsx b/src/app/(marketing)/features/page.tsx new file mode 100644 index 0000000..1b15963 --- /dev/null +++ b/src/app/(marketing)/features/page.tsx @@ -0,0 +1,159 @@ +"use client"; + +import Link from "next/link"; +import { + ScanLine, + Mail, + Link2, + Users, + CalendarDays, + Building2, + BarChart3, + Shield, + ArrowRight, +} from "lucide-react"; +import { MarketingNav } from "@/components/marketing/nav"; +import { MarketingFooter } from "@/components/marketing/footer"; + +const features = [ + { + href: "/features/ai-ocr", + icon: ScanLine, + title: "AI-Powered OCR", + description: + "Vision AI extracts 20+ fields from every card — names, contact info, prayer requests, spiritual decisions — even messy handwriting.", + }, + { + href: "/features/scanner-integration", + icon: Mail, + title: "Scanner Integration", + description: + "Upload from your phone, scan to email, or send batches via FTP. Echo picks up files automatically and starts processing.", + }, + { + href: "/features/integrations", + icon: Link2, + title: "Integrations", + description: + "Sync directly to Planning Center, Google Sheets, Monday.com, Airtable, or any system via webhooks and CSV export.", + }, + { + href: "/features/team-collaboration", + icon: Users, + title: "Team Collaboration", + description: + "Invite your guest services team with five distinct roles. Assign cards for review and track follow-up across your team.", + }, + { + href: "/features/collection-days", + icon: CalendarDays, + title: "Collection Days & Events", + description: + "Define recurring services and events. Echo auto-assigns scanned cards to the correct service based on your schedule.", + }, + { + href: "/features/multi-site", + icon: Building2, + title: "Multi-Site Management", + description: + "Run multiple campuses under one organization. Independent schedules, scanners, and teams — one unified dashboard.", + }, + { + href: "/features/analytics", + icon: BarChart3, + title: "Dashboard & Analytics", + description: + "Real-time stats, powerful filtering, card detail views with full history, and CSV export for custom reporting.", + }, + { + href: "/features/security", + icon: Shield, + title: "Security & Privacy", + description: + "Encryption, role-based access, invitation-only teams, email verification, SSO support, and full audit trails.", + }, +]; + +export default function FeaturesIndexPage() { + return ( +
+ + +
+
+
+

+ Everything your guest services team{" "} + + needs. + +

+

+ Purpose-built for the way churches actually work — from the + welcome center to the pastor’s desk. +

+
+
+ +
+
+
+ {features.map((feature) => ( + +
+
+ +
+
+
+

{feature.title}

+ +
+

+ {feature.description} +

+
+
+ + ))} +
+
+
+ +
+
+
+
+

+ Ready to see it in action? +

+

+ Start free with 50 cards per month. No credit card required. +

+
+ + Get Started Free + + + + View Pricing + +
+
+
+
+ + +
+ ); +} diff --git a/src/app/(marketing)/features/scanner-integration/page.tsx b/src/app/(marketing)/features/scanner-integration/page.tsx new file mode 100644 index 0000000..b883bb8 --- /dev/null +++ b/src/app/(marketing)/features/scanner-integration/page.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { + Mail, + Printer, + Upload, + Smartphone, + Server, + Inbox, + FolderSync, + Clock, +} from "lucide-react"; +import { FeaturePageShell, DetailBlock, StatRow } from "@/components/marketing/feature-page-shell"; + +export default function ScannerIntegrationPage() { + return ( + +
+
+ + + +

+ The simplest path: open Echo on your phone, tablet, or computer, and + drag files onto the card grid. Or click the upload button in the + header. Supports PDF, JPEG, PNG, and WebP. +

+

+ Perfect for volunteers who snap photos of cards with their phone right + after the service. Multi-file upload means you can select an entire + folder at once. +

+
+ + +

+ Configure your office multi-function printer to email scanned documents + to a dedicated inbox. Echo polls that inbox every two minutes, downloads + PDF and image attachments, and queues them for processing automatically. +

+

+ After processing, Echo can mark emails as read or move them to a + “processed” folder — so your inbox stays clean. +

+

+ Works with any IMAP-compatible email provider: Gmail, Outlook, + Dreamhost, Yahoo, or your church’s hosted email. +

+
+ + +

+ For high-volume environments, configure your scanner to send batches to + an FTP server. Echo connects via FTP/FTPS, downloads new files from the + incoming directory, processes them, and moves them to a “processed” + folder. +

+

+ This is ideal for churches that run multiple scanners at different + campuses — each scanner sends to the same FTP endpoint and Echo handles + all of them. +

+
+ + +

+ Ricoh, Xerox, Canon, HP, Brother, Epson — if your printer has a + “Scan to Email” or “Scan to FTP” option in its + address book (most do), it works with Echo. No special software, no + drivers, no plugins. +

+

+ Don’t have a dedicated scanner? A smartphone camera works too. + Take a photo, upload it, and Echo reads it just the same. +

+
+ + +

+ Echo filters email attachments by type and size — it only processes + PDFs and images, ignoring signatures, logos, and other irrelevant + attachments. Multi-attachment emails are supported: each valid file + becomes its own processing job. +

+
+ + +

+ Both email and FTP watchers run on a two-minute cron cycle. Cards + scanned and emailed during the Sunday service start appearing in your + dashboard within minutes — without anyone touching a computer. +

+
+ + +

+ Every import is tracked as a processing job with source metadata: + where the file came from (upload, email, FTP), when it arrived, how + many cards were extracted, and the processing status. You always know + what happened and when. +

+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/features/security/page.tsx b/src/app/(marketing)/features/security/page.tsx new file mode 100644 index 0000000..be3d9a3 --- /dev/null +++ b/src/app/(marketing)/features/security/page.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { + Shield, + Lock, + KeyRound, + UserCheck, + MailCheck, + Server, + ShieldCheck, + Eye, +} from "lucide-react"; +import { FeaturePageShell, DetailBlock } from "@/components/marketing/feature-page-shell"; + +export default function SecurityPage() { + return ( + +
+
+ +

+ All data is encrypted in transit using TLS 1.3 and at rest using + AES-256 encryption. This applies to your database (Supabase + PostgreSQL), file storage (Supabase Storage), and every API + connection between Echo and third-party services. +

+

+ Passwords are hashed with bcrypt — they are never stored or + transmitted in plain text. +

+
+ + +

+ Five distinct roles (Owner, Admin, Editor, Reviewer, Viewer) control + exactly what each team member can see and do. Permissions are enforced + on every API route — not just in the UI. Even if someone bookmarks a + URL, they can’t access data beyond their role. +

+
+ + +

+ No public signup on your organization’s instance. New team members + must be invited by an Admin or Owner. Invitations include a pre-assigned + role and expire after 7 days. This prevents unauthorized access and + keeps your member data private. +

+
+ + +

+ Every account requires a verified email address. Users who haven’t + verified see a persistent banner and can request a new verification + email at any time. This ensures that account recovery and + notifications reach the right person. +

+
+ + +

+ Enterprise plans support OIDC/SSO integration with identity providers + like Authentik, Okta, Azure AD, and Google Workspace. This lets large + churches and denominations use their existing identity infrastructure + — single sign-on, centralized user management, and automatic + deprovisioning when staff leave. +

+
+ + +

+ Every significant action is logged: who created a card, who reviewed + it, who changed a setting, when integrations fired, and what data was + synced. The activity log is timestamped and user-attributed, giving + you a complete chain of custody for sensitive data like prayer + requests and personal decisions. +

+
+ + +

+ Echo runs on Vercel’s global edge network with Supabase (built + on AWS) for database and storage. Both platforms maintain SOC 2 + Type II compliance, regular security audits, and automatic backups. +

+

+ For organizations with strict data residency requirements, the + Enterprise plan includes self-hosted deployment via Docker — + your data never leaves your own infrastructure. +

+
+ + +

+ All outbound webhooks are signed with HMAC-SHA256 using your secret + key. Receiving systems can verify that payloads originated from Echo + and were not modified in transit. This prevents spoofed data from + entering your CRM or other connected systems. +

+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/features/team-collaboration/page.tsx b/src/app/(marketing)/features/team-collaboration/page.tsx new file mode 100644 index 0000000..0e30ac5 --- /dev/null +++ b/src/app/(marketing)/features/team-collaboration/page.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { + Users, + ShieldCheck, + UserPlus, + Eye, + Pencil, + Crown, + ClipboardCheck, + Bell, +} from "lucide-react"; +import { FeaturePageShell, DetailBlock } from "@/components/marketing/feature-page-shell"; + +const roles = [ + { + icon: Crown, + name: "Owner", + description: "Full control. Manages billing, org settings, and all users. Created during setup.", + }, + { + icon: ShieldCheck, + name: "Admin", + description: "Manages team members, all settings, integrations, and every card operation.", + }, + { + icon: Pencil, + name: "Editor", + description: "Creates, edits, deletes cards. Runs OCR, manages imports and collection days.", + }, + { + icon: ClipboardCheck, + name: "Reviewer", + description: "Edits cards assigned to them. Marks cards as reviewed for syncing.", + }, + { + icon: Eye, + name: "Viewer", + description: "Read-only access to cards, events, and stats. Perfect for senior pastors who just want visibility.", + }, +]; + +export default function TeamCollaborationPage() { + return ( + +
+
+
+

Five roles, clear boundaries

+
+ {roles.map((role) => ( +
+
+ +
+
+

{role.name}

+

{role.description}

+
+
+ ))} +
+
+ + +

+ No self-registration on your instance. Admins send email invitations + with a pre-assigned role. The invitee clicks a link, creates their + account, and is immediately placed on your team with the right permissions. +

+

+ Invitations expire after 7 days and can be revoked at any time from + the team management page. +

+
+ + +

+ After cards are scanned, assign them to specific team members for + review. Reviewers see only their assigned cards, verify the AI’s + extraction, make corrections, and mark them as reviewed. +

+

+ Once reviewed, cards can be synced to Planning Center or your CRM + with confidence that the data is accurate. The full assignment and + review history is tracked in the activity log. +

+
+ + +

+ Team members receive notifications when cards are assigned to them, + when processing completes, and when errors need attention. A + notification center in the header keeps everyone informed without + email overload. +

+
+ + +

+ Most churches have a mix of paid staff and volunteers on their guest + services team. Echo makes it easy to give volunteers limited access + (Reviewer or Viewer) while staff have broader control (Editor or Admin). +

+

+ The Owner role is reserved for the person who set up the account — + typically the executive pastor or admin director — ensuring there’s + always a single point of accountability. +

+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/pricing/page.tsx b/src/app/(marketing)/pricing/page.tsx new file mode 100644 index 0000000..843cafb --- /dev/null +++ b/src/app/(marketing)/pricing/page.tsx @@ -0,0 +1,339 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { Check, ArrowRight, HelpCircle } from "lucide-react"; +import { MarketingNav } from "@/components/marketing/nav"; +import { MarketingFooter } from "@/components/marketing/footer"; + +const tiers = [ + { + name: "Free", + price: "$0", + period: "forever", + description: "Perfect for trying Echo with your team.", + cards: "50 cards/month", + features: [ + "AI-powered OCR extraction", + "Web upload (PDF, JPEG, PNG)", + "2 team members", + "1 location", + "CSV export", + "Community support", + ], + cta: "Get Started Free", + ctaHref: "/signup", + highlighted: false, + }, + { + name: "Starter", + price: "$29", + period: "/month", + annualPrice: "$24", + description: "For churches ready to automate their guest workflow.", + cards: "250 cards/month", + features: [ + "Everything in Free, plus:", + "Email & FTP scanner integration", + "5 team members", + "Collection day management", + "Google Sheets integration", + "Webhook support", + "Email support", + ], + cta: "Start 14-Day Trial", + ctaHref: "/signup?plan=starter", + highlighted: false, + }, + { + name: "Growth", + price: "$79", + period: "/month", + annualPrice: "$66", + description: "Full integration power for growing congregations.", + cards: "1,000 cards/month", + features: [ + "Everything in Starter, plus:", + "Planning Center sync", + "Monday.com & Airtable", + "15 team members", + "3 locations", + "Auto-assign to events", + "API access", + "Priority support", + ], + cta: "Start 14-Day Trial", + ctaHref: "/signup?plan=growth", + highlighted: true, + }, + { + name: "Enterprise", + price: "Custom", + period: "", + description: "For multi-site churches and denominations.", + cards: "Unlimited cards", + features: [ + "Everything in Growth, plus:", + "Unlimited team members", + "Unlimited locations", + "SSO / SAML authentication", + "Custom integrations", + "Dedicated account manager", + "99.9% uptime SLA", + "Self-hosted deployment option", + ], + cta: "Contact Sales", + ctaHref: "mailto:sales@echoocr.com", + highlighted: false, + }, +]; + +const comparisonRows = [ + { feature: "Cards per month", free: "50", starter: "250", growth: "1,000", enterprise: "Unlimited" }, + { feature: "Team members", free: "2", starter: "5", growth: "15", enterprise: "Unlimited" }, + { feature: "Locations", free: "1", starter: "1", growth: "3", enterprise: "Unlimited" }, + { feature: "AI-powered OCR", free: true, starter: true, growth: true, enterprise: true }, + { feature: "Web upload", free: true, starter: true, growth: true, enterprise: true }, + { feature: "Email scanning", free: false, starter: true, growth: true, enterprise: true }, + { feature: "FTP scanning", free: false, starter: true, growth: true, enterprise: true }, + { feature: "Collection days", free: false, starter: true, growth: true, enterprise: true }, + { feature: "CSV export", free: true, starter: true, growth: true, enterprise: true }, + { feature: "Google Sheets", free: false, starter: true, growth: true, enterprise: true }, + { feature: "Webhooks", free: false, starter: true, growth: true, enterprise: true }, + { feature: "Planning Center", free: false, starter: false, growth: true, enterprise: true }, + { feature: "Monday.com", free: false, starter: false, growth: true, enterprise: true }, + { feature: "Airtable", free: false, starter: false, growth: true, enterprise: true }, + { feature: "Auto-assign to events", free: false, starter: false, growth: true, enterprise: true }, + { feature: "API access", free: false, starter: false, growth: true, enterprise: true }, + { feature: "SSO / SAML", free: false, starter: false, growth: false, enterprise: true }, + { feature: "Custom integrations", free: false, starter: false, growth: false, enterprise: true }, + { feature: "Self-hosted option", free: false, starter: false, growth: false, enterprise: true }, + { feature: "Dedicated account manager", free: false, starter: false, growth: false, enterprise: true }, + { feature: "Uptime SLA", free: false, starter: false, growth: false, enterprise: true }, +]; + +function CellValue({ value }: { value: boolean | string }) { + if (typeof value === "string") { + return {value}; + } + return value ? ( + + ) : ( + + ); +} + +export default function PricingPage() { + const [annual, setAnnual] = useState(false); + + return ( +
+ + +
+
+
+

+ Simple, transparent{" "} + + pricing. + +

+

+ Start free. Upgrade when you’re ready. Every plan saves your team + hours of manual data entry every single week. +

+
+ + +
+
+
+ +
+
+
+ {tiers.map((tier) => ( +
+ {tier.highlighted && ( +
+ Most Popular +
+ )} +

{tier.name}

+
+ + {annual && tier.annualPrice ? tier.annualPrice : tier.price} + + {tier.period && ( + + {tier.period} + + )} +
+ {annual && tier.annualPrice && ( +
+ Billed annually ({tier.annualPrice === "$24" ? "$288" : "$792"}/year) +
+ )} +

+ {tier.description} +

+
+ {tier.cards} +
+
    + {tier.features.map((feature) => ( +
  • + + {feature} +
  • + ))} +
+ + {tier.cta} + +
+ ))} +
+

+ All paid plans include a 14-day free trial. No credit card required to + start. Need more cards? Overage billed at $0.08/card. +

+
+
+ +
+
+

+ Compare plans +

+
+
+ + + + + + + + + + + + {comparisonRows.map((row) => ( + + + + + + + + ))} + +
FeatureFreeStarterGrowthEnterprise
{row.feature}
+
+
+
+
+ +
+
+

+ Common questions about pricing +

+
+ {[ + { + q: "What counts as a \"card\"?", + a: "Each individual response card extracted from your upload counts as one card. A 10-page duplex PDF with 5 card pairs = 5 cards. A single photo of one card = 1 card.", + }, + { + q: "What happens if I exceed my monthly limit?", + a: "We'll never cut you off mid-month. Overage is billed at $0.08 per card at the end of the billing cycle. If you're consistently over, we'll suggest upgrading to save money.", + }, + { + q: "Can I change plans at any time?", + a: "Yes. Upgrade instantly and we'll prorate the difference. Downgrade at the end of your current billing period. No penalties, no contracts.", + }, + { + q: "Do you offer nonprofit discounts?", + a: "Our pricing is already designed to be accessible for churches and nonprofits. If you're a small church with budget constraints, contact us — we're happy to work something out.", + }, + { + q: "Is there really no credit card required?", + a: "Correct. The Free plan is fully functional with no credit card. Paid plan trials also start without payment — we only ask for billing info when you're ready to continue after the trial.", + }, + ].map((faq) => ( +
+
+ +
+

{faq.q}

+

{faq.a}

+
+
+
+ ))} +
+
+
+ +
+
+
+
+

+ Ready to give your team Sunday evenings back? +

+

+ Start free. No credit card. No commitment. +

+ + Get Started Free + + +
+
+
+ + +
+ ); +} diff --git a/src/app/(marketing)/welcome/page.tsx b/src/app/(marketing)/welcome/page.tsx index 92fd46c..a0b098d 100644 --- a/src/app/(marketing)/welcome/page.tsx +++ b/src/app/(marketing)/welcome/page.tsx @@ -23,87 +23,9 @@ import { CalendarDays, FileText, Webhook, - Sun, - Moon, } from "lucide-react"; -import { useTheme } from "next-themes"; - -function ThemeToggle() { - const { theme, setTheme } = useTheme(); - return ( - - ); -} - -function Nav() { - const [mobileOpen, setMobileOpen] = useState(false); - return ( - - ); -} +import { MarketingNav } from "@/components/marketing/nav"; +import { MarketingFooter } from "@/components/marketing/footer"; function Hero() { return ( @@ -246,54 +168,63 @@ const features = [ { icon: ScanLine, title: "AI-Powered OCR", + href: "/features/ai-ocr", description: "Advanced vision AI reads handwriting, checkboxes, and printed text. Extracts 20+ fields per card including names, contact info, prayer requests, and spiritual decisions.", }, { icon: Mail, title: "Email & FTP Scanning", + href: "/features/scanner-integration", description: "Configure your office scanner to email or FTP batches directly. Echo automatically picks them up and starts processing — no manual upload needed.", }, { icon: Upload, title: "Drag & Drop Upload", + href: "/features/scanner-integration", description: "Upload photos or PDFs from your phone, tablet, or computer. Supports multi-page duplex-scanned PDFs with automatic front/back pairing.", }, { icon: CalendarDays, title: "Collection Days & Events", + href: "/features/collection-days", description: "Define recurring services and events. Echo auto-assigns scanned cards to the most recent Sunday service or event, keeping everything organized.", }, { icon: Users, title: "Team Collaboration", + href: "/features/team-collaboration", description: "Invite your pastoral care team with role-based access. Assign cards for follow-up, track review status, and keep everyone on the same page.", }, { icon: Building2, title: "Multi-Site Ready", + href: "/features/multi-site", description: "Manage multiple campuses under one organization. Each location gets its own collection days, scanner setup, and team assignments.", }, { icon: BarChart3, title: "Dashboard & Analytics", + href: "/features/analytics", description: "See first-time guests, salvations, prayer requests, and follow-up status at a glance. Filter, sort, and export your data anytime.", }, { icon: Shield, title: "Secure by Default", + href: "/features/security", description: "Role-based permissions, encrypted storage, email verification, and invitation-only team access protect your congregation’s personal data.", }, { icon: Clock, title: "Instant Follow-Up", + href: "/features/ai-ocr", description: "Stop waiting until Tuesday to start reaching out. Cards scanned Sunday afternoon are in your system by Sunday evening.", }, @@ -315,21 +246,34 @@ function Features() {
{features.map((feature) => ( -
-
+
-

{feature.title}

+
+

{feature.title}

+ +

-

+ ))}
+
+ + Explore all features + + +
); @@ -403,6 +347,15 @@ function Integrations() { ))} +
+ + See all integrations in detail + + +
); @@ -751,54 +704,10 @@ function FinalCTA() { ); } -function Footer() { - return ( - - ); -} - export default function WelcomePage() { return (
-
); } diff --git a/src/auth.ts b/src/auth.ts index b59ee46..364e532 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -53,13 +53,12 @@ export const { handlers, auth, signIn, signOut } = NextAuth({ : []), ], callbacks: { - async jwt({ token, user, trigger }) { + async jwt({ token, user }) { if (user) { token.id = user.id; } - // Refresh org data on sign-in or when session update is triggered - if (token.id && (user || trigger === "update")) { + if (token.id) { const dbUser = await prisma.user.findUnique({ where: { id: token.id as string }, include: { diff --git a/src/components/marketing/feature-page-shell.tsx b/src/components/marketing/feature-page-shell.tsx new file mode 100644 index 0000000..c548f3f --- /dev/null +++ b/src/components/marketing/feature-page-shell.tsx @@ -0,0 +1,124 @@ +"use client"; + +import Link from "next/link"; +import { ArrowRight, type LucideIcon } from "lucide-react"; +import { MarketingNav } from "./nav"; +import { MarketingFooter } from "./footer"; + +interface FeaturePageShellProps { + badge: string; + badgeIcon: LucideIcon; + title: string; + titleAccent: string; + subtitle: string; + children: React.ReactNode; +} + +export function FeaturePageShell({ + badge, + badgeIcon: BadgeIcon, + title, + titleAccent, + subtitle, + children, +}: FeaturePageShellProps) { + return ( +
+ + +
+
+
+
+ + {badge} +
+

+ {title}{" "} + + {titleAccent} + +

+

+ {subtitle} +

+
+
+ + {children} + +
+
+
+
+

+ Ready to see it in action? +

+

+ Start free with 50 cards per month. No credit card required. +

+
+ + Get Started Free + + + + View Pricing + +
+
+
+
+ + +
+ ); +} + +interface DetailBlockProps { + icon: LucideIcon; + title: string; + children: React.ReactNode; + reversed?: boolean; +} + +export function DetailBlock({ icon: Icon, title, children, reversed }: DetailBlockProps) { + return ( +
+
+ +
+
+

{title}

+
+ {children} +
+
+
+ ); +} + +interface StatRowProps { + stats: { value: string; label: string }[]; +} + +export function StatRow({ stats }: StatRowProps) { + return ( +
+
2 ? "md:grid-cols-" + stats.length : ""} gap-8 text-center`}> + {stats.map((stat) => ( +
+
{stat.value}
+
{stat.label}
+
+ ))} +
+
+ ); +} diff --git a/src/components/marketing/footer.tsx b/src/components/marketing/footer.tsx new file mode 100644 index 0000000..6e80b48 --- /dev/null +++ b/src/components/marketing/footer.tsx @@ -0,0 +1,47 @@ +import Link from "next/link"; +import { ScanLine } from "lucide-react"; + +export function MarketingFooter() { + return ( +
+
+
+
+ +
+ +
+ Echo + +

+ AI-powered response card scanning for churches, ministries, and + nonprofits. Turn paper into people — faster. +

+
+
+

Product

+
    +
  • AI-Powered OCR
  • +
  • Integrations
  • +
  • Scanner Integration
  • +
  • Pricing
  • +
  • FAQ
  • +
+
+
+

Company

+ +
+
+
+ © {new Date().getFullYear()} Echo. All rights reserved. +
+
+
+ ); +} diff --git a/src/components/marketing/nav.tsx b/src/components/marketing/nav.tsx new file mode 100644 index 0000000..e3dc9e9 --- /dev/null +++ b/src/components/marketing/nav.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { ScanLine, Sun, Moon, ChevronDown } from "lucide-react"; +import { useTheme } from "next-themes"; + +function ThemeToggle() { + const { theme, setTheme } = useTheme(); + return ( + + ); +} + +const featureLinks = [ + { href: "/features/ai-ocr", label: "AI-Powered OCR" }, + { href: "/features/scanner-integration", label: "Scanner Integration" }, + { href: "/features/integrations", label: "Integrations" }, + { href: "/features/team-collaboration", label: "Team Collaboration" }, + { href: "/features/collection-days", label: "Collection Days" }, + { href: "/features/multi-site", label: "Multi-Site Management" }, + { href: "/features/analytics", label: "Dashboard & Analytics" }, + { href: "/features/security", label: "Security & Privacy" }, +]; + +export function MarketingNav() { + const [mobileOpen, setMobileOpen] = useState(false); + const [featuresOpen, setFeaturesOpen] = useState(false); + + return ( + + ); +} diff --git a/src/middleware.ts b/src/middleware.ts index 9e1d17c..4199998 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -3,6 +3,8 @@ import { getToken } from "next-auth/jwt"; const publicPaths = [ "/welcome", + "/features", + "/pricing", "/login", "/signup", "/invite", @@ -69,9 +71,11 @@ export async function middleware(req: NextRequest) { }); if (!token) { - const loginUrl = new URL("/login", req.url); - loginUrl.searchParams.set("callbackUrl", pathname); - return NextResponse.redirect(loginUrl); + if (pathname === "/login" || pathname === "/signup") { + return NextResponse.next(); + } + const welcomeUrl = new URL("/welcome", req.url); + return NextResponse.redirect(welcomeUrl); } const hasOrg = !!token.orgId;