solvox / src /renderer /pages /Dashboard.tsx
muthuk1's picture
๐Ÿš€ Final: +ContactsPage +ScanPage +Sparklines, types.ts synced, TS 0 errors, Coinbase design, complete README
9ff7e0c verified
import React, { useEffect, useState } from 'react';
import { Num, AssetRow, PipelineTrace, Sparkline } from '../components/ui/index';
interface Props { balance: { sol: number; usdt: number }; publicKey: string | null; onRefresh: () => void; onNavigate: (p: any) => void; }
export default function Dashboard({ balance, publicKey, onRefresh, onNavigate }: Props) {
const [txs, setTxs] = useState<any[]>([]);
const [aiStatus, setAiStatus] = useState<any>(null);
useEffect(() => {
if (!window.solvox) return;
Promise.all([window.solvox.wallet.getHistory(5), window.solvox.ai.getStatus()])
.then(([h, s]) => { if (h.success) setTxs(h.history || []); setAiStatus(s); }).catch(() => {});
}, []);
const total = balance.sol * 170 + balance.usdt;
const mods = aiStatus ? [
{ k: 'llm', n: 'LLM', p: '@qvac/llm-llamacpp' }, { k: 'transcription', n: 'Speech-to-Text', p: '@qvac/transcription-whispercpp' },
{ k: 'tts', n: 'Text-to-Speech', p: '@qvac/tts-onnx' }, { k: 'embed', n: 'Embeddings', p: '@qvac/embed-llamacpp' },
{ k: 'translation', n: 'Translation', p: '@qvac/translation-nmtcpp' }, { k: 'ocr', n: 'OCR', p: '@qvac/ocr-onnx' },
] : [];
return (
<div className="max-w-content mx-auto px-8 py-section">
{/* โ”€โ”€ Hero Band โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
<section className="bg-surface-dark rounded-xl p-xl mb-12 relative overflow-hidden">
<div className="relative z-10">
<div className="text-on-dark-soft text-body-sm mb-1">Total portfolio value</div>
<div className="text-on-dark display-text text-display-lg mb-2"><Num value={total} decimals={2} prefix="$" /></div>
<div className="flex items-center gap-3 mt-4">
<button onClick={() => onNavigate('send')} className="btn-primary">Send</button>
<button onClick={() => onNavigate('voice')} className="btn-secondary-dark">Voice AI</button>
<button onClick={onRefresh} className="btn-outline-dark">Refresh</button>
</div>
</div>
{/* Floating product-UI card โ€” Coinbase signature */}
<div className="absolute right-8 top-6 w-[260px]">
<div className="card-dark p-4 mb-2 shadow-soft">
<div className="text-on-dark-soft text-caption mb-1">SOL Balance</div>
<div className="text-on-dark number-mono text-number-display"><Num value={balance.sol} decimals={4} /></div>
<div className="text-on-dark-soft text-caption mt-1">โ‰ˆ $<Num value={balance.sol * 170} decimals={2} /></div>
</div>
<div className="card-dark p-4 shadow-soft ml-4">
<div className="text-on-dark-soft text-caption mb-1">USDT Balance</div>
<div className="text-on-dark number-mono text-number-display"><Num value={balance.usdt} decimals={2} /></div>
</div>
</div>
</section>
{/* โ”€โ”€ Assets โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
<section className="mb-12">
<div className="text-title-lg display-text text-ink mb-6">Assets</div>
<div className="card">
<div className="asset-row px-4">
<div className="asset-icon mr-3 text-sm font-bold text-ink">โ—Ž</div>
<div className="flex-1"><div className="text-title-sm text-ink">Solana</div><div className="text-caption text-muted">SOL</div></div>
<Sparkline data={[145, 152, 148, 160, 155, 170, 165, 172, 168, 170]} width={80} height={28} />
<div className="text-right ml-4">
<div className="number-mono text-number-display text-ink">${(balance.sol * 170).toLocaleString(undefined, { minimumFractionDigits: 2 })}</div>
<div className="number-mono text-caption text-semantic-up">+2.34%</div>
</div>
</div>
<div className="asset-row px-4">
<div className="asset-icon mr-3 text-sm font-bold text-ink">โ‚ฎ</div>
<div className="flex-1"><div className="text-title-sm text-ink">Tether</div><div className="text-caption text-muted">USDT</div></div>
<Sparkline data={[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00]} width={80} height={28} />
<div className="text-right ml-4">
<div className="number-mono text-number-display text-ink">${balance.usdt.toLocaleString(undefined, { minimumFractionDigits: 2 })}</div>
<div className="number-mono text-caption text-semantic-up">+0.01%</div>
</div>
</div>
</div>
</section>
{/* โ”€โ”€ AI Engine + Transactions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
<div className="grid grid-cols-5 gap-6">
<section className="col-span-3">
<div className="text-title-lg display-text text-ink mb-6">QVAC AI Engine</div>
<div className="card">
{mods.map(m => (
<div key={m.k} className="flex items-center justify-between py-3 border-b border-hairline-soft last:border-0">
<div>
<div className="text-title-sm text-ink">{m.n}</div>
<div className="text-caption text-muted font-mono">{m.p}</div>
</div>
<span className={`badge-pill text-[10px] ${(aiStatus as any)?.[m.k] ? 'badge-pill-green' : ''}`}>
{(aiStatus as any)?.[m.k] ? 'ACTIVE' : 'LOADING'}
</span>
</div>
))}
<div className="pt-3 mt-1 text-caption text-muted flex items-center gap-1.5">
<div className="w-1.5 h-1.5 rounded-full bg-semantic-up" />
All inference runs 100% locally via QVAC Fabric (Vulkan GPU)
</div>
</div>
</section>
<section className="col-span-2">
<div className="text-title-lg display-text text-ink mb-6">Recent activity</div>
<div className="card">
{txs.length === 0 ? (
<div className="text-center py-8">
<div className="text-body text-muted">No transactions yet</div>
<div className="text-caption text-muted-soft mt-1">Try "Send 1 SOL toโ€ฆ"</div>
</div>
) : txs.map((tx, i) => (
<div key={i} className="flex items-center gap-3 py-3 border-b border-hairline-soft last:border-0">
<div className={`w-7 h-7 rounded-full flex items-center justify-center text-caption-strong ${tx.status === 'success' ? 'bg-semantic-up/10 text-semantic-up' : 'bg-semantic-down/10 text-semantic-down'}`}>
{tx.status === 'success' ? 'โœ“' : 'โœ—'}
</div>
<div className="flex-1 min-w-0">
<div className="text-caption font-mono text-muted truncate">{tx.signature?.slice(0, 16)}โ€ฆ</div>
<div className="text-caption text-muted-soft">{tx.timestamp ? new Date(tx.timestamp).toLocaleDateString() : 'โ€”'}</div>
</div>
<span className={`badge-pill text-[10px] ${tx.status === 'success' ? 'badge-pill-green' : 'badge-pill-red'}`}>{tx.status}</span>
</div>
))}
</div>
</section>
</div>
{/* โ”€โ”€ Wallet Address โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
<section className="mt-12">
<div className="bg-surface-soft rounded-xl p-5 flex items-center justify-between">
<div>
<div className="text-caption-strong text-muted uppercase tracking-wider">Wallet address</div>
<div className="text-body-sm font-mono text-muted mt-1">{publicKey || 'โ€”'}</div>
</div>
<button onClick={() => publicKey && navigator.clipboard.writeText(publicKey)} className="btn-secondary text-body-sm py-2 px-4">Copy</button>
</div>
</section>
</div>
);
}