solvox / src /renderer /pages /SecurityPage.tsx
muthuk1's picture
🎨 Complete Coinbase design system rebuild: white canvas, single blue accent, pill CTAs, editorial spacing, hairline borders, dark hero bands, asset rows, mono numbers, 96px section rhythm
5f5514e verified
import React, { useState, useEffect } from 'react';
import { useToast } from '../components/ui/index';
export default function SecurityPage() {
const [s, setS] = useState<any>({});
const [wl, setWl] = useState<any[]>([]);
const [anomalies, setAnomalies] = useState<any[]>([]);
const [addr, setAddr] = useState(''); const [label, setLabel] = useState('');
const [err, setErr] = useState('');
const { addToast } = useToast();
useEffect(() => { load(); }, []);
const load = async () => { if (!window.solvox) return; try { const [a, b, c] = await Promise.all([window.solvox.security.getSettings(), window.solvox.security.getWhitelist(), window.solvox.security.getAnomalies()]); setS(a); setWl(b); setAnomalies(c); } catch {} };
const upd = async (k: string, v: any) => { const n = { ...s, [k]: v }; setS(n); if (window.solvox) { await window.solvox.security.updateSettings(n); addToast({ type: 'success', title: 'Saved' }); } };
const addWl = async () => { if (!addr.trim() || !label.trim()) return setErr('Both required'); if (window.solvox) { const r = await window.solvox.security.addWhitelist(addr.trim(), label.trim()); if (r.success) { setAddr(''); setLabel(''); setErr(''); load(); } else setErr(r.error || 'Failed'); } };
const rmWl = async (a: string) => { if (window.solvox) { await window.solvox.security.removeWhitelist(a); load(); } };
const Toggle = ({ k, label, desc }: { k: string; label: string; desc: string }) => (
<div className="flex items-center justify-between py-3 border-b border-hairline-soft last:border-0">
<div><div className="text-title-sm text-ink">{label}</div><div className="text-caption text-muted">{desc}</div></div>
<button onClick={() => upd(k, !s[k])} className="toggle">
<div className={`toggle-track ${s[k] ? 'active' : ''}`} />
<div className={`toggle-thumb ${s[k] ? 'active' : ''}`} />
</button>
</div>
);
return (
<div className="max-w-3xl mx-auto px-8 py-section">
<h2 className="display-text text-title-lg text-ink mb-8">Security</h2>
{/* Limits */}
<div className="card mb-6">
<div className="text-caption-strong text-muted uppercase tracking-wider mb-4">Transaction limits</div>
<div className="grid grid-cols-2 gap-4">
{[
{ k: 'maxSingleTx', l: 'Max per transaction', u: 'tokens', v: s.maxSingleTx || 1000 },
{ k: 'maxDailyVolume', l: 'Daily limit', u: '/day', v: s.maxDailyVolume || 5000 },
{ k: 'velocityLimit', l: 'Max TX/hour', u: '', v: s.velocityLimit || 10 },
{ k: 'cooldownMinutes', l: 'Cooldown', u: 'min', v: s.cooldownMinutes || 1 },
].map(f => (
<div key={f.k}>
<label className="text-caption text-muted block mb-1">{f.l}</label>
<div className="flex items-center gap-2">
<input type="number" value={f.v} onChange={e => upd(f.k, Number(e.target.value))} className="input-field text-body-sm" />
{f.u && <span className="text-caption text-muted whitespace-nowrap">{f.u}</span>}
</div>
</div>
))}
</div>
</div>
{/* Toggles */}
<div className="card mb-6">
<div className="text-caption-strong text-muted uppercase tracking-wider mb-4">Features</div>
<Toggle k="whitelistEnabled" label="Address whitelisting" desc="Only allow sends to approved addresses" />
<Toggle k="anomalyDetection" label="AI anomaly detection" desc="LLM analyzes spending patterns locally" />
<Toggle k="requireConfirmation" label="Transaction confirmation" desc="Always require explicit approval" />
</div>
{/* Whitelist */}
<div className="card mb-6">
<div className="text-caption-strong text-muted uppercase tracking-wider mb-3">Address whitelist</div>
<div className="flex gap-2 mb-3">
<input value={label} onChange={e => setLabel(e.target.value)} placeholder="Label" className="input-field text-body-sm w-28" />
<input value={addr} onChange={e => setAddr(e.target.value)} placeholder="Solana address" className="input-field text-body-sm flex-1 font-mono" />
<button onClick={addWl} className="btn-primary text-body-sm py-2 px-4">Add</button>
</div>
{err && <div className="text-caption text-semantic-down mb-2">{err}</div>}
{wl.length === 0 ? <div className="text-body-sm text-muted text-center py-4">No addresses whitelisted</div> : (
<div className="space-y-2">
{wl.map((e, i) => (
<div key={i} className="flex items-center justify-between bg-surface-soft rounded-lg p-3">
<div><div className="text-title-sm text-ink">{e.label}</div><div className="text-caption font-mono text-muted">{e.address.slice(0, 10)}…{e.address.slice(-6)}</div></div>
<button onClick={() => rmWl(e.address)} className="btn-text text-semantic-down text-body-sm">Remove</button>
</div>
))}
</div>
)}
</div>
{/* Anomalies */}
<div className="card">
<div className="flex items-center justify-between mb-3">
<div className="text-caption-strong text-muted uppercase tracking-wider">Anomaly log</div>
<span className="badge-pill-blue badge-pill text-[10px]">AI-POWERED</span>
</div>
{anomalies.length === 0 ? (
<div className="text-center py-6">
<div className="text-body-sm text-semantic-up">All clear — no anomalies detected ✓</div>
<div className="text-caption text-muted mt-1">Analyzed by @qvac/llm-llamacpp + @qvac/embed-llamacpp</div>
</div>
) : (
<div className="space-y-2">
{anomalies.map((a, i) => (
<div key={i} className={`rounded-lg p-3 border ${a.severity === 'high' ? 'bg-semantic-down/5 border-semantic-down/15' : a.severity === 'medium' ? 'bg-accent-yellow/5 border-accent-yellow/15' : 'bg-surface-soft border-hairline'}`}>
<div className="flex items-center gap-2 mb-1">
<span className={`badge-pill text-[10px] ${a.severity === 'high' ? 'badge-pill-red' : ''}`}>{a.severity.toUpperCase()}</span>
<span className="text-caption text-muted">{a.type}</span>
</div>
<div className="text-body-sm text-ink">{a.description}</div>
<div className="text-caption text-muted-soft mt-1">{new Date(a.timestamp).toLocaleString()}</div>
</div>
))}
</div>
)}
</div>
</div>
);
}