import { useState, useEffect, useRef } from 'react'; import gsap from 'gsap'; const API_BASE = '/api'; export default function App() { const [niche, setNiche] = useState(''); const [location, setLocation] = useState(''); const [limit, setLimit] = useState(10); const [jobStatus, setJobStatus] = useState({ status: 'idle', message: 'Awaiting transmission...', progress: 0 }); const [results, setResults] = useState([]); const [activeTab, setActiveTab] = useState('search'); const heroRef = useRef(null); const panelRef = useRef(null); const gridRef = useRef(null); const particlesRef = useRef([]); const isRunning = ['scraping','enriching','saving'].includes(jobStatus.status); /* ── Entrance GSAP animation ── */ useEffect(() => { const ctx = gsap.context(() => { gsap.fromTo(heroRef.current, { opacity: 0, y: -40 }, { opacity: 1, y: 0, duration: 1.1, ease: 'power4.out' } ); gsap.fromTo(panelRef.current, { opacity: 0, y: 30, scale: 0.97 }, { opacity: 1, y: 0, scale: 1, duration: 0.9, delay: 0.3, ease: 'back.out(1.4)' } ); }); return () => ctx.revert(); }, []); /* ── Animate particles ── */ useEffect(() => { particlesRef.current.forEach((el, i) => { if (!el) return; gsap.to(el, { y: `random(-30, 30)`, x: `random(-20, 20)`, opacity: `random(0.2, 0.8)`, duration: `random(3, 6)`, repeat: -1, yoyo: true, ease: 'sine.inOut', delay: i * 0.3, }); }); }, []); /* ── Poll status while running ── */ useEffect(() => { if (!isRunning) return; const id = setInterval(async () => { try { const res = await fetch(`${API_BASE}/status`); const data = await res.json(); setJobStatus(data); if (data.status === 'complete') { clearInterval(id); loadResults(); } else if (data.status === 'error') { clearInterval(id); } } catch (_) {} }, 1000); return () => clearInterval(id); }, [isRunning]); /* ── Animate result rows in ── */ useEffect(() => { if (results.length > 0 && gridRef.current) { const rows = gridRef.current.querySelectorAll('tbody tr'); gsap.fromTo(rows, { opacity: 0, x: -15 }, { opacity: 1, x: 0, stagger: 0.06, duration: 0.4, ease: 'power2.out' }); } }, [results]); const loadResults = async () => { try { const r = await fetch(`${API_BASE}/results`); setResults(await r.json()); setActiveTab('results'); } catch (_) {} }; const handleSubmit = async (e) => { e.preventDefault(); if (!niche.trim() || !location.trim()) return; setResults([]); setJobStatus({ status: 'scraping', message: 'Initiating agent...', progress: 3 }); try { await fetch(`${API_BASE}/scrape`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ niche, location, limit }), }); } catch (_) { setJobStatus({ status: 'error', message: 'Cannot reach backend on :8000', progress: 0 }); } }; const exportCSV = () => { const cols = ['name','website','phone','email','rating','facebook','instagram','linkedin','status']; const lines = [cols.join(','), ...results.map(r => cols.map(k => `"${(r[k]||'').replace(/"/g,'""')}"`).join(','))]; const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([lines.join('\n')], { type: 'text/csv' })); a.download = `leads_${niche}_${location}.csv`; a.click(); }; const safeHost = url => { try { return new URL(url).hostname; } catch { return url; } }; const statusColor = { idle:'#4a5568', scraping:'#00f2ff', enriching:'#8b5cf6', saving:'#10b981', complete:'#22c55e', error:'#ef4444' }; /* ── Grid layout ── */ return (
AGENTIC BUSINESS INTELLIGENCE ENGINE v2.0
📡 No data yet. Run a search first.
| BUSINESS | CONTACT | RATING | CHANNELS | STATUS |
|---|---|---|---|---|
|
{row.name}
{row.website && {safeHost(row.website)}}
{row.phone && {row.phone} }
|
{row.email || —} |
★ {row.rating || '—'} | {row.status || '—'} |