Nanny7's picture
initial deploy
bcf46c3
Raw
History Blame Contribute Delete
14.3 kB
import React, { useState, useEffect, useRef } from 'react';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { initLocalEngine, routeInference } from '../utils/inferenceRouter';
import HCaptcha from '@hcaptcha/react-hcaptcha';
import { supabase, useAuth, GATEWAY_URL, LEMON_CHECKOUT_URL } from '../context';
import ApiKeyCard from './ApiKeyCard';
import UsageCard from './UsageCard';
import BillingCard from './BillingCard';
import CodePlayground from './CodePlayground';
import OpticParseSection from './OpticParseSection';
import PhishVisionSection from './PhishVisionSection';
import QuickTestSection from './QuickTestSection';
import BulkScannerSection from './BulkScannerSection';
import WatchDashboardSection from './WatchDashboardSection';
import QuickStatsBar from './QuickStatsBar';
import FeedbackModal from './FeedbackModal';
import LogsSection from './LogsSection';
import SettingsKeysSection from './SettingsKeysSection';
import IntegrationsSection from './IntegrationsSection';
import ExperimentalFeaturesSection from './ExperimentalFeaturesSection';
const Icons = {
dashboard: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>,
key: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>,
chart: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>,
credit: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>,
code: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>,
mail: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>,
logout: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
};
export default function Dashboard() {
const { user, signOut } = useAuth()
const [page, setPage] = useState('dashboard')
const [apiKey, setApiKey] = useState(null)
const [usage, setUsage] = useState({ tier: 'free', monthly_limit: 100, current_usage: 0, usage_reset_at: null })
const [isFeedbackOpen, setIsFeedbackOpen] = useState(false)
// Fetch usage and key on mount
useEffect(() => {
if (user?.id) {
fetch(`${GATEWAY_URL}/gateway/usage/${user.id}`)
.then(r => r.json())
.then(setUsage)
.catch(() => {})
const savedKey = localStorage.getItem(`opticparse_apikey_${user.id}`)
if (savedKey) {
setApiKey(savedKey)
} else {
handleGenerateKey()
}
}
}, [user])
const handleGenerateKey = async () => {
try {
const res = await fetch(`${GATEWAY_URL}/gateway/keys/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: user.id, email: user.email }),
})
if (res.status === 400) {
// Hit the 3 key limit, auto-regenerate instead to clear them
await handleRegenerate(true)
return
}
const data = await res.json()
if (data.api_key) {
setApiKey(data.api_key)
localStorage.setItem(`opticparse_apikey_${user.id}`, data.api_key)
}
} catch (err) {
console.error('Key generation failed:', err)
}
}
const handleRegenerate = async (silent = false) => {
if (!silent && !confirm('Are you sure? Your old key will stop working immediately.')) return;
try {
const response = await fetch(`${GATEWAY_URL}/gateway/keys/regenerate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ user_id: user.id })
});
if (!response.ok) throw new Error('Regenerate failed');
const data = await response.json();
setApiKey(data.api_key);
localStorage.setItem(`opticparse_apikey_${user.id}`, data.api_key);
if (!silent) alert('API key regenerated successfully!');
} catch (err) {
if (!silent) alert('Failed to regenerate key: ' + err.message);
}
}
const navItems = [
{ id: 'dashboard', label: 'Dashboard', icon: Icons.dashboard },
{ id: 'keys', label: 'Settings', icon: Icons.key },
{ id: 'usage', label: 'Usage', icon: Icons.chart },
{ id: 'billing', label: 'Billing', icon: Icons.credit },
{ id: 'integrations', label: 'Integrations', icon: Icons.code },
{ id: 'extensions', label: 'Extensions', icon: Icons.code },
{ id: 'docs', label: 'API Docs', icon: Icons.code },
{ id: 'playground', label: 'Code Playground', icon: Icons.code },
]
const initial = user?.email?.charAt(0)?.toUpperCase() || '?'
return (
<div className="app-layout">
{/* Sidebar */}
<aside className="sidebar">
<div className="sidebar-logo" style={{fontSize: '1rem'}}>OpticParse & PhishVision</div>
<nav className="sidebar-nav">
{navItems.map(item => (
<button
key={item.id}
className={`sidebar-link ${page === item.id ? 'active' : ''}`}
onClick={() => setPage(item.id)}
>
{item.icon}
{item.label}
</button>
))}
</nav>
<div className="sidebar-footer">
<div className="user-profile">
<div className="avatar">{user?.email?.charAt(0).toUpperCase()}</div>
<div className="user-info">
<span className="email">{user?.email}</span>
</div>
</div>
<div style={{ marginTop: '1rem', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<a href="https://opticparse.com" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'var(--muted)', fontSize: '0.85rem', textDecoration: 'none', padding: '0.5rem', borderRadius: '4px' }}>
← Back to Website
</a>
<button className="sidebar-link" onClick={() => setIsFeedbackOpen(true)} style={{ color: 'var(--text)', background: 'transparent', border: 'none', width: '100%', textAlign: 'left', cursor: 'pointer' }}>
{Icons.mail}
Send Feedback
</button>
<button className="sidebar-link" onClick={signOut} style={{ color: 'var(--red)' }}>
{Icons.logout}
Sign Out
</button>
</div>
<div style={{ display: 'flex', gap: '1rem', marginTop: '1rem', fontSize: '0.75rem', justifyContent: 'center' }}>
<a href="https://opticparse.com/privacy.html" target="_blank" rel="noreferrer" style={{ color: 'var(--muted)', textDecoration: 'none' }}>Privacy Policy</a>
<a href="https://opticparse.com/terms.html" target="_blank" rel="noreferrer" style={{ color: 'var(--muted)', textDecoration: 'none' }}>Terms of Service</a>
</div>
</div>
</aside>
{/* Main Content */}
<main className="main-content">
{page === 'dashboard' && (
<>
<div className="page-header">
<h1>OpticParse & PhishVision Dashboard</h1>
<p>Welcome back. Here's your API overview.</p>
</div>
<QuickStatsBar usage={usage} />
<div className="card-grid">
<UsageCard usage={usage} user={user} apiKey={apiKey} />
<BillingCard tier={usage.tier} userId={user?.id} />
</div>
<ApiKeyCard apiKey={apiKey} onRegenerate={handleRegenerate} />
<OpticParseSection apiKey={apiKey} />
<PhishVisionSection />
<BulkScannerSection />
<QuickTestSection apiKey={apiKey} />
<WatchDashboardSection usage={usage} setPage={setPage} />
</>
)}
{page === 'keys' && (
<>
<div className="page-header">
<h1>API Keys & Security</h1>
<p>Manage your API authentication credentials and environments.</p>
</div>
<SettingsKeysSection user={user} />
</>
)}
{page === 'usage' && (
<>
<div className="page-header">
<h1>Usage</h1>
<p>Monitor your API consumption across services.</p>
</div>
<div className="card-grid">
<UsageCard usage={usage} />
<div className="card animate-in">
<div className="card-label">Remaining Calls</div>
<div className="card-value text-green">{Math.max(0, usage.monthly_limit - usage.current_usage).toLocaleString()}</div>
</div>
</div>
<LogsSection user={user} />
</>
)}
{page === 'billing' && (
<>
<div className="page-header">
<h1>Billing</h1>
<p>Manage your subscription and payment.</p>
</div>
<BillingCard tier={usage.tier} userId={user?.id} />
</>
)}
{page === 'integrations' && (
<>
<div className="page-header">
<h1>Integrations & Webhooks</h1>
<p>Connect your APIs to external tools.</p>
</div>
<IntegrationsSection user={user} />
</>
)}
{page === 'extensions' && (
<>
<div className="page-header">
<h1>Experimental Features</h1>
<p>Try out new tools in early access.</p>
</div>
<ExperimentalFeaturesSection />
</>
)}
{page === 'playground' && (
<>
<div className="page-header">
<h1>Code Playground</h1>
<p>Copy-paste ready integration snippets with your API key.</p>
</div>
<CodePlayground apiKey={apiKey} />
</>
)}
{page === 'docs' && (
<>
<div className="page-header">
<h1>API Documentation</h1>
<p>Quick start guides for OpticParse and PhishVision.</p>
</div>
<div className="card animate-in mb-2" style={{marginBottom: '1rem'}}>
<div className="card-label">Example 1 — OpticParse (cURL)</div>
<div className="code-block" style={{position:'relative'}}>
<button className="code-copy" onClick={() => navigator.clipboard.writeText(`curl -X POST https://opticparse-python-sg.onrender.com/api/vision-scrape \\\n -H "X-API-Key: ${apiKey || 'YOUR_API_KEY'}" \\\n -H "Content-Type: application/json" \\\n -d '{\n "url": "https://example.com",\n "query": "Extract main heading",\n "response_schema": {"heading": "string"}\n }'`)}>Copy</button>
<pre style={{margin:0, fontSize:'0.85rem', color:'var(--text)', whiteSpace:'pre-wrap'}}>{`curl -X POST https://opticparse-python-sg.onrender.com/api/vision-scrape \\
-H "X-API-Key: ${apiKey || 'YOUR_API_KEY'}" \\
-H "Content-Type: application/json" \\
-d '{
"url": "https://example.com",
"query": "Extract main heading",
"response_schema": {"heading": "string"}
}'`}</pre>
</div>
</div>
<div className="card animate-in mb-2" style={{marginBottom: '1rem'}}>
<div className="card-label">Example 2 — PhishVision (cURL)</div>
<div className="code-block" style={{position:'relative'}}>
<button className="code-copy" onClick={() => navigator.clipboard.writeText(`curl -X POST https://opticparse-1opticparse-node-sg.onrender.com/api/phish-detect \\\n -H "Content-Type: application/json" \\\n -d '{"url": "https://suspicious-site.com"}'`)}>Copy</button>
<pre style={{margin:0, fontSize:'0.85rem', color:'var(--text)', whiteSpace:'pre-wrap'}}>{`curl -X POST https://opticparse-1opticparse-node-sg.onrender.com/api/phish-detect \\
-H "Content-Type: application/json" \\
-d '{"url": "https://suspicious-site.com"}'`}</pre>
</div>
</div>
<div className="card animate-in mb-2">
<div className="card-label">Example 3 — Python SDK style</div>
<div className="code-block" style={{position:'relative'}}>
<button className="code-copy" onClick={() => navigator.clipboard.writeText(`import requests\n\nresponse = requests.post(\n "https://opticparse-python-sg.onrender.com/api/vision-scrape",\n headers={"X-API-Key": "${apiKey || 'YOUR_API_KEY'}"},\n json={\n "url": "https://example.com",\n "query": "Extract data",\n "response_schema": {"data": "string"}\n }\n)\nprint(response.json())`)}>Copy</button>
<pre style={{margin:0, fontSize:'0.85rem', color:'var(--text)', whiteSpace:'pre-wrap'}}>{`import requests
response = requests.post(
"https://opticparse-python-sg.onrender.com/api/vision-scrape",
headers={"X-API-Key": "${apiKey || 'YOUR_API_KEY'}"},
json={
"url": "https://example.com",
"query": "Extract data",
"response_schema": {"data": "string"}
}
)
print(response.json())`}</pre>
</div>
</div>
</>
)}
</main>
<FeedbackModal
isOpen={isFeedbackOpen}
onClose={() => setIsFeedbackOpen(false)}
user={user}
/>
</div>
)
}