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
This commit is contained in:
parent
52714c4e50
commit
bf92126cf5
17 changed files with 1787 additions and 143 deletions
|
|
@ -1,18 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { ScanLine, Building2, Zap, Loader2 } from "lucide-react";
|
import { ScanLine, Building2, Zap, Loader2 } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
export default function WorkspaceSetupPage() {
|
export default function WorkspaceSetupPage() {
|
||||||
const router = useRouter();
|
|
||||||
const { update: updateSession } = useSession();
|
const { update: updateSession } = useSession();
|
||||||
const [loading, setLoading] = useState<"create" | "skip" | null>(null);
|
const [loading, setLoading] = useState<"create" | "skip" | null>(null);
|
||||||
|
|
||||||
async function handleCreate() {
|
function handleCreate() {
|
||||||
router.push("/onboarding");
|
setLoading("create");
|
||||||
|
window.location.href = "/onboarding";
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSkip() {
|
async function handleSkip() {
|
||||||
|
|
@ -25,8 +23,10 @@ export default function WorkspaceSetupPage() {
|
||||||
setLoading(null);
|
setLoading(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Refresh the JWT so the middleware sees the new org
|
||||||
await updateSession();
|
await updateSession();
|
||||||
router.push("/");
|
// Hard navigate to ensure fresh middleware evaluation
|
||||||
|
window.location.href = "/";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to create personal workspace:", err);
|
console.error("Failed to create personal workspace:", err);
|
||||||
setLoading(null);
|
setLoading(null);
|
||||||
|
|
|
||||||
122
src/app/(marketing)/features/ai-ocr/page.tsx
Normal file
122
src/app/(marketing)/features/ai-ocr/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Core Technology"
|
||||||
|
badgeIcon={ScanLine}
|
||||||
|
title="AI that reads handwriting"
|
||||||
|
titleAccent="like your best volunteer."
|
||||||
|
subtitle="Echo's vision AI extracts 20+ fields from every response card — names, phone numbers, prayer requests, spiritual decisions — even when the handwriting is messy."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 space-y-6">
|
||||||
|
<StatRow
|
||||||
|
stats={[
|
||||||
|
{ value: "20+", label: "Fields extracted per card" },
|
||||||
|
{ value: "95%", label: "Accuracy on printed text" },
|
||||||
|
{ value: "85-95%", label: "Accuracy on handwriting" },
|
||||||
|
{ value: "<30s", label: "Average processing time" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DetailBlock icon={Eye} title="Vision AI, not template matching">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Layers} title="Duplex PDF support with automatic page pairing" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Supports PDF (multi-page), JPEG, PNG, and WebP. Files up to 50 MB each.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Brain} title="Smart field extraction">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<div className="grid sm:grid-cols-2 gap-x-6 gap-y-1.5 mt-3">
|
||||||
|
{extractedFields.map((field) => (
|
||||||
|
<div key={field} className="flex items-center gap-2">
|
||||||
|
<CheckCircle className="h-3.5 w-3.5 text-primary shrink-0" />
|
||||||
|
<span>{field}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Sparkles} title="Confidence scoring & human review" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={FileCheck} title="Automatic date and event assignment">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={RefreshCw} title="One-click reprocessing" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
107
src/app/(marketing)/features/analytics/page.tsx
Normal file
107
src/app/(marketing)/features/analytics/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Analytics"
|
||||||
|
badgeIcon={BarChart3}
|
||||||
|
title="Know your guests."
|
||||||
|
titleAccent="See the trends."
|
||||||
|
subtitle="A real-time dashboard shows total cards, processing status, review progress, and follow-up metrics at a glance. Filter, sort, and export your data anytime."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 space-y-6">
|
||||||
|
<DetailBlock icon={LayoutDashboard} title="Dashboard at a glance">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
A getting-started guide walks new users through setup, then
|
||||||
|
dismisses itself so the dashboard stays clean.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Table} title="Powerful card table with advanced filtering" reversed>
|
||||||
|
<p>
|
||||||
|
The response card grid is built on TanStack Table — the same engine
|
||||||
|
behind enterprise data tools. You get:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 mt-2">
|
||||||
|
<li>Full-text search across names, emails, phone numbers, and notes</li>
|
||||||
|
<li>Filter by OCR status, review status, visit type, date range, and more</li>
|
||||||
|
<li>Sort by any column — click once for ascending, twice for descending</li>
|
||||||
|
<li>Column visibility toggles to show only what matters to you</li>
|
||||||
|
<li>Pagination with configurable page sizes</li>
|
||||||
|
<li>Row selection for bulk operations</li>
|
||||||
|
</ul>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Activity} title="Status-based stat cards">
|
||||||
|
<p>
|
||||||
|
Quick-filter chips at the top of the cards view let you jump between
|
||||||
|
views with one click:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 mt-2">
|
||||||
|
<li><strong>All cards</strong> — everything in the system</li>
|
||||||
|
<li><strong>Completed</strong> — successfully processed by AI</li>
|
||||||
|
<li><strong>Errors</strong> — cards that need attention</li>
|
||||||
|
<li><strong>Unreviewed</strong> — processed but not yet verified by a human</li>
|
||||||
|
<li><strong>My cards</strong> — assigned to the current user</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Each chip shows its count in real-time so you always know what’s
|
||||||
|
waiting for you.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={TrendingUp} title="Actionable guest insights" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Filter} title="Card detail view with full history">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Download} title="CSV export" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
94
src/app/(marketing)/features/collection-days/page.tsx
Normal file
94
src/app/(marketing)/features/collection-days/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Event Management"
|
||||||
|
badgeIcon={CalendarDays}
|
||||||
|
title="Organize cards by"
|
||||||
|
titleAccent="service, not just date."
|
||||||
|
subtitle="Define your recurring services and events. Echo auto-assigns scanned cards to the right event so you always know which Sunday, which campus, and which service a card came from."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 space-y-6">
|
||||||
|
<DetailBlock icon={Repeat} title="Recurring events with flexible scheduling">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.”
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Wand2} title="Automatic card-to-event assignment" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Three assignment strategies, configurable per organization:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 mt-2">
|
||||||
|
<li><strong>Most recent</strong> — auto-assign to the nearest past event within your time window</li>
|
||||||
|
<li><strong>Prompt user</strong> — suggest a match but let the reviewer confirm</li>
|
||||||
|
<li><strong>Manual only</strong> — leave assignment to team members</li>
|
||||||
|
</ul>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Calendar} title="One-off events too">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={MapPin} title="Tied to locations" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={ListChecks} title="See cards by event">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Settings} title="Configurable assignment window" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
170
src/app/(marketing)/features/integrations/page.tsx
Normal file
170
src/app/(marketing)/features/integrations/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Integrations"
|
||||||
|
badgeIcon={Link2}
|
||||||
|
title="Connects to"
|
||||||
|
titleAccent="where your team already works."
|
||||||
|
subtitle="Echo doesn't replace your church management system — it feeds it. Scanned card data flows directly into Planning Center, Google Sheets, Monday.com, Airtable, or any system via webhooks."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-5xl px-6 space-y-12">
|
||||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||||
|
{integrationCards.map((integration) => (
|
||||||
|
<div key={integration.name} className="glass-card rounded-xl p-6 relative">
|
||||||
|
{integration.tag && (
|
||||||
|
<span className="absolute top-4 right-4 text-[10px] font-bold uppercase tracking-wider bg-primary/10 text-primary px-2 py-0.5 rounded-full">
|
||||||
|
{integration.tag}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div className="mb-4 h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
||||||
|
<integration.icon className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold mb-2">{integration.name}</h3>
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed mb-3">
|
||||||
|
{integration.description}
|
||||||
|
</p>
|
||||||
|
<span className="text-[10px] font-medium text-muted-foreground/70 uppercase tracking-wider">
|
||||||
|
{integration.plan} plan & above
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<DetailBlock icon={Zap} title="Event-driven triggers">
|
||||||
|
<p>
|
||||||
|
Every integration fires on the events you choose. Configure each
|
||||||
|
connection to trigger on:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 mt-2">
|
||||||
|
<li><strong>OCR complete</strong> — as soon as a card is processed</li>
|
||||||
|
<li><strong>Card reviewed</strong> — after a team member verifies the data</li>
|
||||||
|
<li><strong>Card exported</strong> — when marked as sent to your CRM</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Most churches trigger on “reviewed” so only human-verified
|
||||||
|
data enters their systems.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Settings} title="Full field mapping" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Change your card layout? Just update the field mapping — no code required.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Heart} title="Planning Center deep integration">
|
||||||
|
<p>
|
||||||
|
The Planning Center integration goes beyond basic syncing:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 mt-2">
|
||||||
|
<li>OAuth connection — no API keys to manage, tokens refresh automatically</li>
|
||||||
|
<li>Smart person matching by email address or name</li>
|
||||||
|
<li>Creates new People records for first-time guests</li>
|
||||||
|
<li>Updates existing records with new contact info</li>
|
||||||
|
<li>Adds people to PCO Lists (e.g. “First Time Guests April 2026”)</li>
|
||||||
|
<li>Syncs phone numbers, emails, and addresses as typed sub-records</li>
|
||||||
|
</ul>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={RefreshCw} title="Webhook signatures for security" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="glass-card rounded-2xl p-8 sm:p-10 text-center">
|
||||||
|
<h3 className="text-lg font-semibold mb-3">
|
||||||
|
Don’t see your tool?
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-muted-foreground mb-5 max-w-lg mx-auto">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="mailto:sales@echoocr.com"
|
||||||
|
className="inline-flex items-center gap-2 text-sm font-semibold text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Request an integration
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
90
src/app/(marketing)/features/multi-site/page.tsx
Normal file
90
src/app/(marketing)/features/multi-site/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Multi-Site"
|
||||||
|
badgeIcon={Building2}
|
||||||
|
title="One organization."
|
||||||
|
titleAccent="Every campus."
|
||||||
|
subtitle="Run multiple locations under a single Echo account. Each campus gets its own collection days, scanner setup, and team assignments — all feeding into one unified dashboard."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 space-y-6">
|
||||||
|
<StatRow
|
||||||
|
stats={[
|
||||||
|
{ value: "1", label: "Unified dashboard" },
|
||||||
|
{ value: "N", label: "Locations per org" },
|
||||||
|
{ value: "Per-site", label: "Event schedules" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DetailBlock icon={MapPin} title="Locations with independent schedules">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={ArrowLeftRight} title="Separate scanners, unified pipeline" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Users} title="Per-location team assignments">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Globe} title="Organization-wide reporting" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Settings} title="Centralized settings, local overrides">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Building2} title="Organization switcher for multi-org users" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
159
src/app/(marketing)/features/page.tsx
Normal file
159
src/app/(marketing)/features/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<MarketingNav />
|
||||||
|
|
||||||
|
<section className="relative pt-32 pb-16 sm:pt-40 sm:pb-20 overflow-hidden">
|
||||||
|
<div className="absolute inset-0 gradient-mesh pointer-events-none" />
|
||||||
|
<div className="relative mx-auto max-w-4xl px-6 text-center">
|
||||||
|
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight leading-[1.1] mb-6">
|
||||||
|
Everything your guest services team{" "}
|
||||||
|
<span className="bg-gradient-to-r from-primary via-foreground to-primary bg-clip-text text-transparent">
|
||||||
|
needs.
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto leading-relaxed">
|
||||||
|
Purpose-built for the way churches actually work — from the
|
||||||
|
welcome center to the pastor’s desk.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-8 sm:py-12">
|
||||||
|
<div className="mx-auto max-w-5xl px-6">
|
||||||
|
<div className="grid sm:grid-cols-2 gap-5">
|
||||||
|
{features.map((feature) => (
|
||||||
|
<Link
|
||||||
|
key={feature.href}
|
||||||
|
href={feature.href}
|
||||||
|
className="glass-card rounded-xl p-6 group hover:scale-[1.01] transition-transform"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0 group-hover:bg-primary/20 transition-colors">
|
||||||
|
<feature.icon className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="font-semibold">{feature.title}</h3>
|
||||||
|
<ArrowRight className="h-4 w-4 text-muted-foreground group-hover:text-primary group-hover:translate-x-0.5 transition-all" />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed mt-1.5">
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16 sm:py-24 relative">
|
||||||
|
<div className="absolute inset-0 gradient-mesh pointer-events-none" />
|
||||||
|
<div className="relative mx-auto max-w-3xl px-6 text-center">
|
||||||
|
<div className="glass-card rounded-2xl p-10 sm:p-14">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight mb-4">
|
||||||
|
Ready to see it in action?
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground text-lg mb-8 max-w-xl mx-auto">
|
||||||
|
Start free with 50 cards per month. No credit card required.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||||
|
<Link
|
||||||
|
href="/signup"
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl bg-primary px-8 py-3.5 text-base font-semibold text-primary-foreground shadow-lg hover:opacity-90 transition-opacity"
|
||||||
|
>
|
||||||
|
Get Started Free
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl border border-border/60 bg-card/60 px-8 py-3.5 text-base font-medium text-foreground hover:bg-accent/40 transition-colors glass"
|
||||||
|
>
|
||||||
|
View Pricing
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<MarketingFooter />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
119
src/app/(marketing)/features/scanner-integration/page.tsx
Normal file
119
src/app/(marketing)/features/scanner-integration/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Ingestion"
|
||||||
|
badgeIcon={Printer}
|
||||||
|
title="Three ways in."
|
||||||
|
titleAccent="Zero manual steps."
|
||||||
|
subtitle="Upload from your phone, scan to email, or send batches via FTP. Echo picks up files automatically and starts processing — no one has to sit at a computer."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 space-y-6">
|
||||||
|
<StatRow
|
||||||
|
stats={[
|
||||||
|
{ value: "3", label: "Ingestion methods" },
|
||||||
|
{ value: "2 min", label: "Auto-poll interval" },
|
||||||
|
{ value: "50 MB", label: "Max file size" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DetailBlock icon={Upload} title="Drag & drop upload">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Mail} title="Scan to Email" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
After processing, Echo can mark emails as read or move them to a
|
||||||
|
“processed” folder — so your inbox stays clean.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Works with any IMAP-compatible email provider: Gmail, Outlook,
|
||||||
|
Dreamhost, Yahoo, or your church’s hosted email.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Server} title="Scan to FTP">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Smartphone} title="Works with any scanner" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Don’t have a dedicated scanner? A smartphone camera works too.
|
||||||
|
Take a photo, upload it, and Echo reads it just the same.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Inbox} title="Smart attachment handling">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Clock} title="Automatic polling on a schedule" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={FolderSync} title="Full audit trail">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
111
src/app/(marketing)/features/security/page.tsx
Normal file
111
src/app/(marketing)/features/security/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Security & Privacy"
|
||||||
|
badgeIcon={Shield}
|
||||||
|
title="Your congregation's data,"
|
||||||
|
titleAccent="protected."
|
||||||
|
subtitle="Echo is built with the same security standards expected of healthcare and financial applications. Because your members' personal information and prayer requests deserve nothing less."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 space-y-6">
|
||||||
|
<DetailBlock icon={Lock} title="Encryption everywhere">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Passwords are hashed with bcrypt — they are never stored or
|
||||||
|
transmitted in plain text.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={ShieldCheck} title="Role-based access control" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={UserCheck} title="Invitation-only access">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={MailCheck} title="Email verification" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={KeyRound} title="SSO & enterprise authentication">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Eye} title="Audit trail" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Server} title="Enterprise-grade infrastructure">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For organizations with strict data residency requirements, the
|
||||||
|
Enterprise plan includes self-hosted deployment via Docker —
|
||||||
|
your data never leaves your own infrastructure.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Lock} title="Webhook signature verification" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
121
src/app/(marketing)/features/team-collaboration/page.tsx
Normal file
121
src/app/(marketing)/features/team-collaboration/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<FeaturePageShell
|
||||||
|
badge="Team Management"
|
||||||
|
badgeIcon={Users}
|
||||||
|
title="Your whole team,"
|
||||||
|
titleAccent="with the right access."
|
||||||
|
subtitle="Invite volunteers, staff, and pastors with role-based permissions. Everyone sees exactly what they need — nothing more, nothing less."
|
||||||
|
>
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-6 space-y-6">
|
||||||
|
<div className="glass-card rounded-2xl p-8 sm:p-10">
|
||||||
|
<h3 className="text-lg font-semibold mb-6 text-center">Five roles, clear boundaries</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{roles.map((role) => (
|
||||||
|
<div key={role.name} className="flex items-start gap-4 p-4 rounded-xl hover:bg-accent/20 transition-colors">
|
||||||
|
<div className="h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
|
||||||
|
<role.icon className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-sm">{role.name}</h4>
|
||||||
|
<p className="text-sm text-muted-foreground mt-0.5">{role.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DetailBlock icon={UserPlus} title="Invitation-based onboarding">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Invitations expire after 7 days and can be revoked at any time from
|
||||||
|
the team management page.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={ClipboardCheck} title="Card assignment & review workflow" reversed>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Bell} title="In-app notifications">
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
|
||||||
|
<DetailBlock icon={Users} title="Built for church teams" reversed>
|
||||||
|
<p>
|
||||||
|
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).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</DetailBlock>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</FeaturePageShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
339
src/app/(marketing)/pricing/page.tsx
Normal file
339
src/app/(marketing)/pricing/page.tsx
Normal file
|
|
@ -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 <span className="text-sm font-medium">{value}</span>;
|
||||||
|
}
|
||||||
|
return value ? (
|
||||||
|
<Check className="h-4 w-4 text-primary mx-auto" />
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground/30">—</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PricingPage() {
|
||||||
|
const [annual, setAnnual] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<MarketingNav />
|
||||||
|
|
||||||
|
<section className="relative pt-32 pb-16 sm:pt-40 sm:pb-20 overflow-hidden">
|
||||||
|
<div className="absolute inset-0 gradient-mesh pointer-events-none" />
|
||||||
|
<div className="relative mx-auto max-w-4xl px-6 text-center">
|
||||||
|
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight leading-[1.1] mb-6">
|
||||||
|
Simple, transparent{" "}
|
||||||
|
<span className="bg-gradient-to-r from-primary via-foreground to-primary bg-clip-text text-transparent">
|
||||||
|
pricing.
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto leading-relaxed mb-8">
|
||||||
|
Start free. Upgrade when you’re ready. Every plan saves your team
|
||||||
|
hours of manual data entry every single week.
|
||||||
|
</p>
|
||||||
|
<div className="inline-flex items-center gap-3 rounded-full border border-border/60 bg-card/60 p-1 glass">
|
||||||
|
<button
|
||||||
|
onClick={() => setAnnual(false)}
|
||||||
|
className={`rounded-full px-5 py-2 text-sm font-medium transition-all ${
|
||||||
|
!annual
|
||||||
|
? "bg-primary text-primary-foreground shadow-sm"
|
||||||
|
: "text-muted-foreground hover:text-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Monthly
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setAnnual(true)}
|
||||||
|
className={`rounded-full px-5 py-2 text-sm font-medium transition-all ${
|
||||||
|
annual
|
||||||
|
? "bg-primary text-primary-foreground shadow-sm"
|
||||||
|
: "text-muted-foreground hover:text-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Annual
|
||||||
|
<span className="ml-1.5 text-xs opacity-80">Save 17%</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pb-16">
|
||||||
|
<div className="mx-auto max-w-6xl px-6">
|
||||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||||
|
{tiers.map((tier) => (
|
||||||
|
<div
|
||||||
|
key={tier.name}
|
||||||
|
className={`rounded-2xl p-6 flex flex-col ${
|
||||||
|
tier.highlighted
|
||||||
|
? "glass-card ring-2 ring-primary/30 shadow-xl scale-[1.02]"
|
||||||
|
: "glass-card"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tier.highlighted && (
|
||||||
|
<div className="text-[10px] font-bold uppercase tracking-wider text-primary mb-4">
|
||||||
|
Most Popular
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h3 className="text-lg font-semibold">{tier.name}</h3>
|
||||||
|
<div className="mt-3 mb-1">
|
||||||
|
<span className="text-3xl font-bold">
|
||||||
|
{annual && tier.annualPrice ? tier.annualPrice : tier.price}
|
||||||
|
</span>
|
||||||
|
{tier.period && (
|
||||||
|
<span className="text-sm text-muted-foreground ml-1">
|
||||||
|
{tier.period}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{annual && tier.annualPrice && (
|
||||||
|
<div className="text-xs text-muted-foreground mb-3">
|
||||||
|
Billed annually ({tier.annualPrice === "$24" ? "$288" : "$792"}/year)
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
{tier.description}
|
||||||
|
</p>
|
||||||
|
<div className="text-sm font-semibold text-foreground mb-4 pb-4 border-b border-border/40">
|
||||||
|
{tier.cards}
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-3 mb-8 flex-1">
|
||||||
|
{tier.features.map((feature) => (
|
||||||
|
<li key={feature} className="flex items-start gap-2.5 text-sm">
|
||||||
|
<Check className="h-4 w-4 text-primary mt-0.5 shrink-0" />
|
||||||
|
<span className="text-muted-foreground">{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Link
|
||||||
|
href={tier.ctaHref}
|
||||||
|
className={`block text-center rounded-lg py-2.5 text-sm font-semibold transition-all ${
|
||||||
|
tier.highlighted
|
||||||
|
? "bg-primary text-primary-foreground shadow-sm hover:opacity-90"
|
||||||
|
: "border border-border/60 bg-card/60 text-foreground hover:bg-accent/40"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tier.cta}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-center text-xs text-muted-foreground mt-8">
|
||||||
|
All paid plans include a 14-day free trial. No credit card required to
|
||||||
|
start. Need more cards? Overage billed at $0.08/card.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16">
|
||||||
|
<div className="mx-auto max-w-5xl px-6">
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight text-center mb-10">
|
||||||
|
Compare plans
|
||||||
|
</h2>
|
||||||
|
<div className="glass-card rounded-2xl overflow-hidden">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-border/40">
|
||||||
|
<th className="text-left p-4 font-semibold w-[200px]">Feature</th>
|
||||||
|
<th className="text-center p-4 font-semibold">Free</th>
|
||||||
|
<th className="text-center p-4 font-semibold">Starter</th>
|
||||||
|
<th className="text-center p-4 font-semibold bg-primary/5">Growth</th>
|
||||||
|
<th className="text-center p-4 font-semibold">Enterprise</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{comparisonRows.map((row) => (
|
||||||
|
<tr key={row.feature} className="border-b border-border/20 last:border-0">
|
||||||
|
<td className="p-4 text-muted-foreground">{row.feature}</td>
|
||||||
|
<td className="p-4 text-center"><CellValue value={row.free} /></td>
|
||||||
|
<td className="p-4 text-center"><CellValue value={row.starter} /></td>
|
||||||
|
<td className="p-4 text-center bg-primary/5"><CellValue value={row.growth} /></td>
|
||||||
|
<td className="p-4 text-center"><CellValue value={row.enterprise} /></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16">
|
||||||
|
<div className="mx-auto max-w-3xl px-6">
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight text-center mb-10">
|
||||||
|
Common questions about pricing
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
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) => (
|
||||||
|
<div key={faq.q} className="glass-card rounded-xl p-6">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<HelpCircle className="h-5 w-5 text-primary mt-0.5 shrink-0" />
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-sm mb-2">{faq.q}</h3>
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed">{faq.a}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16 sm:py-24 relative">
|
||||||
|
<div className="absolute inset-0 gradient-mesh pointer-events-none" />
|
||||||
|
<div className="relative mx-auto max-w-3xl px-6 text-center">
|
||||||
|
<div className="glass-card rounded-2xl p-10 sm:p-14">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight mb-4">
|
||||||
|
Ready to give your team Sunday evenings back?
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground text-lg mb-8 max-w-xl mx-auto">
|
||||||
|
Start free. No credit card. No commitment.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/signup"
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl bg-primary px-8 py-3.5 text-base font-semibold text-primary-foreground shadow-lg hover:opacity-90 transition-opacity"
|
||||||
|
>
|
||||||
|
Get Started Free
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<MarketingFooter />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -23,87 +23,9 @@ import {
|
||||||
CalendarDays,
|
CalendarDays,
|
||||||
FileText,
|
FileText,
|
||||||
Webhook,
|
Webhook,
|
||||||
Sun,
|
|
||||||
Moon,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { MarketingNav } from "@/components/marketing/nav";
|
||||||
|
import { MarketingFooter } from "@/components/marketing/footer";
|
||||||
function ThemeToggle() {
|
|
||||||
const { theme, setTheme } = useTheme();
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
||||||
className="rounded-full p-2 text-muted-foreground hover:text-foreground transition-colors"
|
|
||||||
aria-label="Toggle theme"
|
|
||||||
>
|
|
||||||
<Sun className="h-4 w-4 hidden dark:block" />
|
|
||||||
<Moon className="h-4 w-4 block dark:hidden" />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Nav() {
|
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
|
||||||
return (
|
|
||||||
<nav className="fixed top-0 inset-x-0 z-50 glass-panel">
|
|
||||||
<div className="mx-auto max-w-6xl flex items-center justify-between px-6 h-16">
|
|
||||||
<Link href="/welcome" className="flex items-center gap-2.5">
|
|
||||||
<div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center">
|
|
||||||
<ScanLine className="h-4 w-4 text-primary-foreground" />
|
|
||||||
</div>
|
|
||||||
<span className="text-lg font-semibold tracking-tight">Echo</span>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-muted-foreground">
|
|
||||||
<a href="#features" className="hover:text-foreground transition-colors">Features</a>
|
|
||||||
<a href="#how-it-works" className="hover:text-foreground transition-colors">How It Works</a>
|
|
||||||
<a href="#integrations" className="hover:text-foreground transition-colors">Integrations</a>
|
|
||||||
<a href="#pricing" className="hover:text-foreground transition-colors">Pricing</a>
|
|
||||||
<a href="#faq" className="hover:text-foreground transition-colors">FAQ</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<ThemeToggle />
|
|
||||||
<Link
|
|
||||||
href="/login"
|
|
||||||
className="hidden sm:inline-flex text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/signup"
|
|
||||||
className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground shadow-sm hover:opacity-90 transition-opacity"
|
|
||||||
>
|
|
||||||
Get Started Free
|
|
||||||
</Link>
|
|
||||||
<button
|
|
||||||
className="md:hidden p-2 text-muted-foreground"
|
|
||||||
onClick={() => setMobileOpen(!mobileOpen)}
|
|
||||||
aria-label="Toggle menu"
|
|
||||||
>
|
|
||||||
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
{mobileOpen ? (
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
||||||
) : (
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
||||||
)}
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{mobileOpen && (
|
|
||||||
<div className="md:hidden border-t border-border/50 px-6 py-4 space-y-3 text-sm font-medium glass">
|
|
||||||
<a href="#features" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>Features</a>
|
|
||||||
<a href="#how-it-works" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>How It Works</a>
|
|
||||||
<a href="#integrations" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>Integrations</a>
|
|
||||||
<a href="#pricing" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>Pricing</a>
|
|
||||||
<a href="#faq" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>FAQ</a>
|
|
||||||
<Link href="/login" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>Sign in</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Hero() {
|
function Hero() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -246,54 +168,63 @@ const features = [
|
||||||
{
|
{
|
||||||
icon: ScanLine,
|
icon: ScanLine,
|
||||||
title: "AI-Powered OCR",
|
title: "AI-Powered OCR",
|
||||||
|
href: "/features/ai-ocr",
|
||||||
description:
|
description:
|
||||||
"Advanced vision AI reads handwriting, checkboxes, and printed text. Extracts 20+ fields per card including names, contact info, prayer requests, and spiritual decisions.",
|
"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,
|
icon: Mail,
|
||||||
title: "Email & FTP Scanning",
|
title: "Email & FTP Scanning",
|
||||||
|
href: "/features/scanner-integration",
|
||||||
description:
|
description:
|
||||||
"Configure your office scanner to email or FTP batches directly. Echo automatically picks them up and starts processing — no manual upload needed.",
|
"Configure your office scanner to email or FTP batches directly. Echo automatically picks them up and starts processing — no manual upload needed.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Upload,
|
icon: Upload,
|
||||||
title: "Drag & Drop Upload",
|
title: "Drag & Drop Upload",
|
||||||
|
href: "/features/scanner-integration",
|
||||||
description:
|
description:
|
||||||
"Upload photos or PDFs from your phone, tablet, or computer. Supports multi-page duplex-scanned PDFs with automatic front/back pairing.",
|
"Upload photos or PDFs from your phone, tablet, or computer. Supports multi-page duplex-scanned PDFs with automatic front/back pairing.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: CalendarDays,
|
icon: CalendarDays,
|
||||||
title: "Collection Days & Events",
|
title: "Collection Days & Events",
|
||||||
|
href: "/features/collection-days",
|
||||||
description:
|
description:
|
||||||
"Define recurring services and events. Echo auto-assigns scanned cards to the most recent Sunday service or event, keeping everything organized.",
|
"Define recurring services and events. Echo auto-assigns scanned cards to the most recent Sunday service or event, keeping everything organized.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Users,
|
icon: Users,
|
||||||
title: "Team Collaboration",
|
title: "Team Collaboration",
|
||||||
|
href: "/features/team-collaboration",
|
||||||
description:
|
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.",
|
"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,
|
icon: Building2,
|
||||||
title: "Multi-Site Ready",
|
title: "Multi-Site Ready",
|
||||||
|
href: "/features/multi-site",
|
||||||
description:
|
description:
|
||||||
"Manage multiple campuses under one organization. Each location gets its own collection days, scanner setup, and team assignments.",
|
"Manage multiple campuses under one organization. Each location gets its own collection days, scanner setup, and team assignments.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: BarChart3,
|
icon: BarChart3,
|
||||||
title: "Dashboard & Analytics",
|
title: "Dashboard & Analytics",
|
||||||
|
href: "/features/analytics",
|
||||||
description:
|
description:
|
||||||
"See first-time guests, salvations, prayer requests, and follow-up status at a glance. Filter, sort, and export your data anytime.",
|
"See first-time guests, salvations, prayer requests, and follow-up status at a glance. Filter, sort, and export your data anytime.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Shield,
|
icon: Shield,
|
||||||
title: "Secure by Default",
|
title: "Secure by Default",
|
||||||
|
href: "/features/security",
|
||||||
description:
|
description:
|
||||||
"Role-based permissions, encrypted storage, email verification, and invitation-only team access protect your congregation’s personal data.",
|
"Role-based permissions, encrypted storage, email verification, and invitation-only team access protect your congregation’s personal data.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Clock,
|
icon: Clock,
|
||||||
title: "Instant Follow-Up",
|
title: "Instant Follow-Up",
|
||||||
|
href: "/features/ai-ocr",
|
||||||
description:
|
description:
|
||||||
"Stop waiting until Tuesday to start reaching out. Cards scanned Sunday afternoon are in your system by Sunday evening.",
|
"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() {
|
||||||
</div>
|
</div>
|
||||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||||
{features.map((feature) => (
|
{features.map((feature) => (
|
||||||
<div
|
<Link
|
||||||
key={feature.title}
|
key={feature.title}
|
||||||
className="glass-card rounded-xl p-6 hover:scale-[1.01] transition-transform"
|
href={feature.href}
|
||||||
|
className="glass-card rounded-xl p-6 hover:scale-[1.01] transition-transform group"
|
||||||
>
|
>
|
||||||
<div className="mb-4 h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
<div className="mb-4 h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||||
<feature.icon className="h-5 w-5 text-primary" />
|
<feature.icon className="h-5 w-5 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold mb-2">{feature.title}</h3>
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<h3 className="font-semibold">{feature.title}</h3>
|
||||||
|
<ArrowRight className="h-3.5 w-3.5 text-muted-foreground opacity-0 group-hover:opacity-100 group-hover:translate-x-0.5 transition-all" />
|
||||||
|
</div>
|
||||||
<p
|
<p
|
||||||
className="text-sm text-muted-foreground leading-relaxed"
|
className="text-sm text-muted-foreground leading-relaxed"
|
||||||
dangerouslySetInnerHTML={{ __html: feature.description }}
|
dangerouslySetInnerHTML={{ __html: feature.description }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-center mt-10">
|
||||||
|
<Link
|
||||||
|
href="/features"
|
||||||
|
className="inline-flex items-center gap-2 text-sm font-semibold text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Explore all features
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
@ -403,6 +347,15 @@ function Integrations() {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-center mt-10">
|
||||||
|
<Link
|
||||||
|
href="/features/integrations"
|
||||||
|
className="inline-flex items-center gap-2 text-sm font-semibold text-primary hover:underline"
|
||||||
|
>
|
||||||
|
See all integrations in detail
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
@ -751,54 +704,10 @@ function FinalCTA() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Footer() {
|
|
||||||
return (
|
|
||||||
<footer className="border-t border-border/40 py-12">
|
|
||||||
<div className="mx-auto max-w-6xl px-6">
|
|
||||||
<div className="grid sm:grid-cols-4 gap-8">
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<div className="flex items-center gap-2.5 mb-4">
|
|
||||||
<div className="h-7 w-7 rounded-lg bg-primary flex items-center justify-center">
|
|
||||||
<ScanLine className="h-3.5 w-3.5 text-primary-foreground" />
|
|
||||||
</div>
|
|
||||||
<span className="text-base font-semibold tracking-tight">Echo</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground max-w-xs leading-relaxed">
|
|
||||||
AI-powered response card scanning for churches, ministries, and
|
|
||||||
nonprofits. Turn paper into people — faster.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-sm font-semibold mb-4">Product</h4>
|
|
||||||
<ul className="space-y-2.5 text-sm text-muted-foreground">
|
|
||||||
<li><a href="#features" className="hover:text-foreground transition-colors">Features</a></li>
|
|
||||||
<li><a href="#integrations" className="hover:text-foreground transition-colors">Integrations</a></li>
|
|
||||||
<li><a href="#pricing" className="hover:text-foreground transition-colors">Pricing</a></li>
|
|
||||||
<li><a href="#faq" className="hover:text-foreground transition-colors">FAQ</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-sm font-semibold mb-4">Company</h4>
|
|
||||||
<ul className="space-y-2.5 text-sm text-muted-foreground">
|
|
||||||
<li><a href="mailto:support@echoocr.com" className="hover:text-foreground transition-colors">Support</a></li>
|
|
||||||
<li><a href="mailto:sales@echoocr.com" className="hover:text-foreground transition-colors">Sales</a></li>
|
|
||||||
<li><a href="#" className="hover:text-foreground transition-colors">Privacy Policy</a></li>
|
|
||||||
<li><a href="#" className="hover:text-foreground transition-colors">Terms of Service</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-10 pt-6 border-t border-border/30 text-center text-xs text-muted-foreground">
|
|
||||||
© {new Date().getFullYear()} Echo. All rights reserved.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function WelcomePage() {
|
export default function WelcomePage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
<Nav />
|
<MarketingNav />
|
||||||
<Hero />
|
<Hero />
|
||||||
<ProblemStatement />
|
<ProblemStatement />
|
||||||
<HowItWorks />
|
<HowItWorks />
|
||||||
|
|
@ -808,7 +717,7 @@ export default function WelcomePage() {
|
||||||
<Pricing />
|
<Pricing />
|
||||||
<FAQ />
|
<FAQ />
|
||||||
<FinalCTA />
|
<FinalCTA />
|
||||||
<Footer />
|
<MarketingFooter />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,12 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, user, trigger }) {
|
async jwt({ token, user }) {
|
||||||
if (user) {
|
if (user) {
|
||||||
token.id = user.id;
|
token.id = user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh org data on sign-in or when session update is triggered
|
if (token.id) {
|
||||||
if (token.id && (user || trigger === "update")) {
|
|
||||||
const dbUser = await prisma.user.findUnique({
|
const dbUser = await prisma.user.findUnique({
|
||||||
where: { id: token.id as string },
|
where: { id: token.id as string },
|
||||||
include: {
|
include: {
|
||||||
|
|
|
||||||
124
src/components/marketing/feature-page-shell.tsx
Normal file
124
src/components/marketing/feature-page-shell.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<MarketingNav />
|
||||||
|
|
||||||
|
<section className="relative pt-32 pb-16 sm:pt-40 sm:pb-20 overflow-hidden">
|
||||||
|
<div className="absolute inset-0 gradient-mesh pointer-events-none" />
|
||||||
|
<div className="relative mx-auto max-w-4xl px-6 text-center">
|
||||||
|
<div className="inline-flex items-center gap-2 rounded-full border border-border/60 bg-card/60 px-4 py-1.5 text-xs font-medium text-muted-foreground mb-8 glass">
|
||||||
|
<BadgeIcon className="h-3.5 w-3.5" />
|
||||||
|
{badge}
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight leading-[1.1] mb-6">
|
||||||
|
{title}{" "}
|
||||||
|
<span className="bg-gradient-to-r from-primary via-foreground to-primary bg-clip-text text-transparent">
|
||||||
|
{titleAccent}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto leading-relaxed">
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
|
||||||
|
<section className="py-16 sm:py-24 relative">
|
||||||
|
<div className="absolute inset-0 gradient-mesh pointer-events-none" />
|
||||||
|
<div className="relative mx-auto max-w-3xl px-6 text-center">
|
||||||
|
<div className="glass-card rounded-2xl p-10 sm:p-14">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight mb-4">
|
||||||
|
Ready to see it in action?
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground text-lg mb-8 max-w-xl mx-auto">
|
||||||
|
Start free with 50 cards per month. No credit card required.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||||
|
<Link
|
||||||
|
href="/signup"
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl bg-primary px-8 py-3.5 text-base font-semibold text-primary-foreground shadow-lg hover:opacity-90 transition-opacity"
|
||||||
|
>
|
||||||
|
Get Started Free
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl border border-border/60 bg-card/60 px-8 py-3.5 text-base font-medium text-foreground hover:bg-accent/40 transition-colors glass"
|
||||||
|
>
|
||||||
|
View Pricing
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<MarketingFooter />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DetailBlockProps {
|
||||||
|
icon: LucideIcon;
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
reversed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DetailBlock({ icon: Icon, title, children, reversed }: DetailBlockProps) {
|
||||||
|
return (
|
||||||
|
<div className={`glass-card rounded-2xl p-8 sm:p-10 flex flex-col ${reversed ? "sm:flex-row-reverse" : "sm:flex-row"} gap-8 items-start`}>
|
||||||
|
<div className="shrink-0 h-14 w-14 rounded-xl bg-primary/10 flex items-center justify-center">
|
||||||
|
<Icon className="h-7 w-7 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-3">{title}</h3>
|
||||||
|
<div className="text-sm text-muted-foreground leading-relaxed space-y-3">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StatRowProps {
|
||||||
|
stats: { value: string; label: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatRow({ stats }: StatRowProps) {
|
||||||
|
return (
|
||||||
|
<div className="glass-card rounded-2xl p-8 sm:p-10">
|
||||||
|
<div className={`grid grid-cols-2 ${stats.length > 2 ? "md:grid-cols-" + stats.length : ""} gap-8 text-center`}>
|
||||||
|
{stats.map((stat) => (
|
||||||
|
<div key={stat.label}>
|
||||||
|
<div className="text-3xl sm:text-4xl font-bold text-primary mb-2">{stat.value}</div>
|
||||||
|
<div className="text-sm text-muted-foreground">{stat.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
src/components/marketing/footer.tsx
Normal file
47
src/components/marketing/footer.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import { ScanLine } from "lucide-react";
|
||||||
|
|
||||||
|
export function MarketingFooter() {
|
||||||
|
return (
|
||||||
|
<footer className="border-t border-border/40 py-12">
|
||||||
|
<div className="mx-auto max-w-6xl px-6">
|
||||||
|
<div className="grid sm:grid-cols-4 gap-8">
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<Link href="/welcome" className="flex items-center gap-2.5 mb-4">
|
||||||
|
<div className="h-7 w-7 rounded-lg bg-primary flex items-center justify-center">
|
||||||
|
<ScanLine className="h-3.5 w-3.5 text-primary-foreground" />
|
||||||
|
</div>
|
||||||
|
<span className="text-base font-semibold tracking-tight">Echo</span>
|
||||||
|
</Link>
|
||||||
|
<p className="text-sm text-muted-foreground max-w-xs leading-relaxed">
|
||||||
|
AI-powered response card scanning for churches, ministries, and
|
||||||
|
nonprofits. Turn paper into people — faster.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-semibold mb-4">Product</h4>
|
||||||
|
<ul className="space-y-2.5 text-sm text-muted-foreground">
|
||||||
|
<li><Link href="/features/ai-ocr" className="hover:text-foreground transition-colors">AI-Powered OCR</Link></li>
|
||||||
|
<li><Link href="/features/integrations" className="hover:text-foreground transition-colors">Integrations</Link></li>
|
||||||
|
<li><Link href="/features/scanner-integration" className="hover:text-foreground transition-colors">Scanner Integration</Link></li>
|
||||||
|
<li><Link href="/pricing" className="hover:text-foreground transition-colors">Pricing</Link></li>
|
||||||
|
<li><Link href="/welcome#faq" className="hover:text-foreground transition-colors">FAQ</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-semibold mb-4">Company</h4>
|
||||||
|
<ul className="space-y-2.5 text-sm text-muted-foreground">
|
||||||
|
<li><a href="mailto:support@echoocr.com" className="hover:text-foreground transition-colors">Support</a></li>
|
||||||
|
<li><a href="mailto:sales@echoocr.com" className="hover:text-foreground transition-colors">Sales</a></li>
|
||||||
|
<li><a href="#" className="hover:text-foreground transition-colors">Privacy Policy</a></li>
|
||||||
|
<li><a href="#" className="hover:text-foreground transition-colors">Terms of Service</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-10 pt-6 border-t border-border/30 text-center text-xs text-muted-foreground">
|
||||||
|
© {new Date().getFullYear()} Echo. All rights reserved.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
129
src/components/marketing/nav.tsx
Normal file
129
src/components/marketing/nav.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||||
|
className="rounded-full p-2 text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
>
|
||||||
|
<Sun className="h-4 w-4 hidden dark:block" />
|
||||||
|
<Moon className="h-4 w-4 block dark:hidden" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<nav className="fixed top-0 inset-x-0 z-50 glass-panel">
|
||||||
|
<div className="mx-auto max-w-6xl flex items-center justify-between px-6 h-16">
|
||||||
|
<Link href="/welcome" className="flex items-center gap-2.5">
|
||||||
|
<div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center">
|
||||||
|
<ScanLine className="h-4 w-4 text-primary-foreground" />
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-semibold tracking-tight">Echo</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-muted-foreground">
|
||||||
|
<div
|
||||||
|
className="relative group"
|
||||||
|
onMouseEnter={() => setFeaturesOpen(true)}
|
||||||
|
onMouseLeave={() => setFeaturesOpen(false)}
|
||||||
|
>
|
||||||
|
<button className="flex items-center gap-1 hover:text-foreground transition-colors">
|
||||||
|
Features
|
||||||
|
<ChevronDown className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
{featuresOpen && (
|
||||||
|
<div className="absolute top-full left-1/2 -translate-x-1/2 pt-2">
|
||||||
|
<div className="glass-card rounded-xl p-2 w-56 shadow-xl">
|
||||||
|
{featureLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="block px-3 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-accent/40 transition-colors"
|
||||||
|
onClick={() => setFeaturesOpen(false)}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Link href="/pricing" className="hover:text-foreground transition-colors">Pricing</Link>
|
||||||
|
<Link href="/welcome#faq" className="hover:text-foreground transition-colors">FAQ</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ThemeToggle />
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="hidden sm:inline-flex text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/signup"
|
||||||
|
className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground shadow-sm hover:opacity-90 transition-opacity"
|
||||||
|
>
|
||||||
|
Get Started Free
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
className="md:hidden p-2 text-muted-foreground"
|
||||||
|
onClick={() => setMobileOpen(!mobileOpen)}
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
{mobileOpen ? (
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
) : (
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mobileOpen && (
|
||||||
|
<div className="md:hidden border-t border-border/50 px-6 py-4 space-y-3 text-sm font-medium glass">
|
||||||
|
<div className="text-xs font-bold text-muted-foreground/60 uppercase tracking-wider mb-2">Features</div>
|
||||||
|
{featureLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="block pl-2 text-muted-foreground hover:text-foreground"
|
||||||
|
onClick={() => setMobileOpen(false)}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<div className="pt-2 border-t border-border/30">
|
||||||
|
<Link href="/pricing" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>Pricing</Link>
|
||||||
|
</div>
|
||||||
|
<Link href="/welcome#faq" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>FAQ</Link>
|
||||||
|
<Link href="/login" className="block text-muted-foreground hover:text-foreground" onClick={() => setMobileOpen(false)}>Sign in</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ import { getToken } from "next-auth/jwt";
|
||||||
|
|
||||||
const publicPaths = [
|
const publicPaths = [
|
||||||
"/welcome",
|
"/welcome",
|
||||||
|
"/features",
|
||||||
|
"/pricing",
|
||||||
"/login",
|
"/login",
|
||||||
"/signup",
|
"/signup",
|
||||||
"/invite",
|
"/invite",
|
||||||
|
|
@ -69,9 +71,11 @@ export async function middleware(req: NextRequest) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
const loginUrl = new URL("/login", req.url);
|
if (pathname === "/login" || pathname === "/signup") {
|
||||||
loginUrl.searchParams.set("callbackUrl", pathname);
|
return NextResponse.next();
|
||||||
return NextResponse.redirect(loginUrl);
|
}
|
||||||
|
const welcomeUrl = new URL("/welcome", req.url);
|
||||||
|
return NextResponse.redirect(welcomeUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasOrg = !!token.orgId;
|
const hasOrg = !!token.orgId;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue