import { useState, useEffect, useCallback, useRef } from 'react' import { Link } from 'react-router-dom' import { PageMeta } from '@/components/common/PageMeta' import '@/styles/landing.css' const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' export default function LandingPage() { const [navScrolled, setNavScrolled] = useState(false) const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const [betaEmail, setBetaEmail] = useState('') const [betaStatus, setBetaStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle') const mobileMenuRef = useRef(null) // Nav scroll effect useEffect(() => { const handleScroll = () => setNavScrolled(window.scrollY > 40) window.addEventListener('scroll', handleScroll) return () => window.removeEventListener('scroll', handleScroll) }, []) // Close mobile menu on click outside useEffect(() => { function handleClickOutside(e: MouseEvent) { if (mobileMenuRef.current && !mobileMenuRef.current.contains(e.target as Node)) { setMobileMenuOpen(false) } } if (mobileMenuOpen) { document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) } }, [mobileMenuOpen]) // Close mobile menu on scroll to section const handleMobileNavClick = () => setMobileMenuOpen(false) // Scroll reveal useEffect(() => { const els = document.querySelectorAll('.landing-reveal') const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) entry.target.classList.add('visible') }) }, { threshold: 0.15 } ) els.forEach(el => observer.observe(el)) return () => observer.disconnect() }, []) const handleBetaSubmit = useCallback(async (e: React.FormEvent) => { e.preventDefault() if (!betaEmail.trim() || betaStatus === 'sending') return setBetaStatus('sending') try { const resp = await fetch(`${API_URL}/api/v1/beta-signup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: betaEmail }), }) if (!resp.ok) throw new Error('Signup failed') setBetaStatus('sent') setBetaEmail('') } catch { setBetaStatus('error') setTimeout(() => setBetaStatus('idle'), 3000) } }, [betaEmail, betaStatus]) return ( <>
{/* Navigation */} {/* Hero */}
Now in Beta — Join early access

Resolve tickets faster.
Notes write themselves.

ResolutionFlow is your AI troubleshooting copilot. Describe the issue, get expert guidance fixing it, and get clean ticket documentation — without writing a single note.

Start Free See How It Works
{/* Social Proof Bar */}

Built by MSP engineers, for MSP engineers

15+
Years MSP Experience
70%
Less Time on Documentation
100%
Auto-Generated Documentation
{/* App Preview */}
ResolutionFlow ×
🔒 app.resolutionflow.com/pilot
FlowPilot
Session History
Guided Flows
Scripts
Analytics
You: User can't access shared drive after password reset
FlowPilot: This is likely a cached credential issue. Let's check a few things:
FlowPilot: 1. Run klist purge to clear Kerberos tickets
FlowPilot: 2. Open Credential Manager → remove saved entries for the share
Auto-doc: 3 steps captured ✓
{/* Problem Section */}
The Problem

Documentation is broken.
Everyone knows it.

Engineers don't want to write it. Managers hate chasing it. Clients never see it. The same issues get solved from scratch every time.
{/* Brand Equation */}
The Answer
Resolution + Documentation Time = ResolutionFlow

What if documentation was a byproduct of solving the issue — not a separate task? What if every ticket your team touched had clean, detailed notes — without anyone writing them?

{/* How It Works */}
How It Works

Three steps. Zero note-writing.

Just describe the issue. FlowPilot handles the rest.

Describe the Issue

Type what's happening, paste an error message, or drop a screenshot. FlowPilot understands MSP environments — AD, Exchange, networking, VPN, you name it.

💬 “User can't access shared drive after password reset, getting Access Denied in Event Viewer”

Troubleshoot Together

FlowPilot acts like a senior engineer on the call with you. It suggests next steps, provides commands to run, and captures every action — documentation builds itself as you work.

FlowPilot: Is the user on VPN?
Engineer: Yes, Cisco AnyConnect
FlowPilot: Check split tunnel config →
Auto-doc: Step captured ✓

Resolve & Document

Hit resolve and get clean, timestamped ticket notes — ready to paste into ConnectWise, Atera, or Syncro. Every step you took, every command you ran, documented automatically.

ConnectWise Ticket #48291
10:04Verified VPN connection active
10:06Split tunnel misconfigured — fixed
10:08Confirmed Outlook sync restored
10:09Resolution: VPN split tunnel updated
{/* Features */}
Features

Troubleshoot faster.
Document everything. Automatically.

} title="FlowPilot — Your AI Copilot" description="Like having a senior engineer on every call. Describe the issue, get expert troubleshooting guidance, and documentation writes itself — as a byproduct of solving the problem." /> } title="Guided Troubleshooting Flows" description="Build step-by-step troubleshooting paths your team can follow. Great for standard procedures, onboarding new engineers, or ensuring consistency." /> } title="Zero Empty Ticket Notes" description="Every troubleshooting session generates timestamped, detailed notes — formatted for your PSA. Your team will never close a ticket with empty notes again." /> } title="Team Knowledge That Grows" description="Every resolved session makes your team smarter. Solutions are saved and surfaced automatically when the next engineer hits a similar issue." /> } title="Session History & Analytics" description="See every troubleshooting session your team has run. Track resolution times, identify common issues, and measure team performance." /> } title="PSA Integration" description="Connect directly to ConnectWise, Atera, and Syncro. Export session docs straight to tickets — no copy-paste needed." />
{/* Pricing */}
Pricing

Simple pricing. No surprises.

Start free. Upgrade when your team is ready.

Need Enterprise (25+ techs, SSO, custom branding)?{' '} Contact us

{/* Testimonial */}
We used to spend more time writing ticket notes than solving the actual issue. Now it just… happens. The documentation writes itself while we work.
Beta Tester — MSP Engineer, Southeast US
{/* CTA */}

Ready to never write ticket notes again?

Join the beta. Troubleshoot your next ticket with FlowPilot and see the documentation write itself.

setBetaEmail(e.target.value)} required />
{betaStatus === 'sent' && (

Thanks! We'll be in touch with beta access details.

)} {betaStatus === 'error' && (

Something went wrong. Please try again.

)}

Free to start. No credit card required.

{/* Footer */}
© 2026 ResolutionFlow. All rights reserved.
) } /* ---- Sub-components ---- */ function ProblemCard({ icon, color, title, description }: { icon: string; color: string; title: string; description: string }) { return (
{icon}

{title}

{description}

) } function FeatureCard({ icon, title, description, highlight }: { icon: React.ReactNode; title: string; description: string; highlight?: boolean }) { return (
{icon}

{title}

{description}

) } function PricingCard({ name, target, amount, period, note, features, btnLabel, btnStyle, featured }: { name: string; target: string; amount: string; period?: string; note: string features: string[]; btnLabel: string; btnStyle: 'outline' | 'filled'; featured?: boolean }) { return (
{name}
{target}
{amount} {period && {period}}
{note}
    {features.map(f =>
  • {f}
  • )}
{btnLabel}
) }