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

Stop writing ticket notes.
Start generating them.

AI-guided decision trees that walk your engineers through troubleshooting — and automatically document every step, ready for your PSA ticket.

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/editor
Flow Editor
Session Runner
Flow Library
Session History
Team Analytics
Outlook Not Syncing
Yes
Check profile config
No
Verify credentials
{/* 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 your engineers never had to write another ticket note?

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

Three steps. Zero note-writing.

Build once, run forever. Every session generates documentation automatically.

Build a Flow

Use the visual Flow Editor to create branching decision trees for any troubleshooting scenario. Drag, connect, and enrich steps with commands, notes, and AI suggestions.

▶ Start
Check DNS
Yes / No?
✓ Resolved

Run a Session

An engineer launches the flow on a live ticket. FlowPilot — your AI copilot — acts as a virtual senior engineer, guiding decisions and capturing every action in real time.

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

Export to Ticket

When the session ends, full documentation is generated — formatted for your PSA. Paste it directly into ConnectWise, Atera, or Syncro. Done.

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

Everything your team needs to
resolve faster and document better.

} title="FlowPilot — Your AI Copilot" description="Like having a senior engineer on every call. FlowPilot suggests next steps, provides context-aware guidance, and automatically captures documentation as a byproduct of the troubleshooting session." /> } title="Visual Flow Editor" description="Build branching decision trees with a drag-and-drop canvas. Add steps, conditions, commands, and notes — no code required." /> } title="Auto-Documentation" description="Every session generates timestamped, detailed notes — formatted for your PSA. Engineers never write another ticket note." /> } title="Team Knowledge Sharing" description="Share flows across your team. When one engineer solves a new problem, the whole team benefits from that path — instantly." /> } title="Session History & Analytics" description="Track which flows are used most, identify bottlenecks, and see how your team resolves issues over time." /> } 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 stop writing ticket notes?

Join the beta and see what happens when documentation becomes automatic.

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}
) }