solvox / src /renderer /App.tsx
muthuk1's picture
🚀 Final: +ContactsPage +ScanPage +Sparklines, types.ts synced, TS 0 errors, Coinbase design, complete README
9ff7e0c verified
import React, { useState, useEffect } from 'react';
import './types';
import { ToastProvider, useToast } from './components/ui/index';
import LockScreen from './pages/LockScreen';
import OnboardingScreen from './pages/OnboardingScreen';
import Dashboard from './pages/Dashboard';
import SendPage from './pages/SendPage';
import HistoryPage from './pages/HistoryPage';
import VoicePage from './pages/VoicePage';
import SecurityPage from './pages/SecurityPage';
import SettingsPage from './pages/SettingsPage';
import ContactsPage from './pages/ContactsPage';
import ScanPage from './pages/ScanPage';
import Sidebar from './components/Sidebar';
import TopBar from './components/TopBar';
type Page = 'dashboard' | 'send' | 'history' | 'voice' | 'security' | 'settings' | 'contacts' | 'scan';
type AppState = 'loading' | 'onboarding' | 'locked' | 'unlocked';
function AppContent() {
const [appState, setAppState] = useState<AppState>('loading');
const [currentPage, setCurrentPage] = useState<Page>('dashboard');
const [publicKey, setPublicKey] = useState<string | null>(null);
const [balance, setBalance] = useState({ sol: 0, usdt: 0 });
const [aiStatus, setAiStatus] = useState<any>(null);
const { addToast } = useToast();
useEffect(() => { init(); }, []);
useEffect(() => { if (window.solvox) return window.solvox.on.locked(() => setAppState('locked')); }, []);
const init = async () => {
try {
if (!window.solvox) { setAppState('unlocked'); return; }
const exists = await window.solvox.wallet.exists();
if (!exists) { setAppState('onboarding'); return; }
const unlocked = await window.solvox.wallet.isUnlocked();
setAppState(unlocked ? 'unlocked' : 'locked');
if (unlocked) { setPublicKey(await window.solvox.wallet.getPublicKey()); refreshBalance(); }
} catch { setAppState('onboarding'); }
};
const refreshBalance = async () => {
try { if (!window.solvox) return; const r = await window.solvox.wallet.getBalance(); if (r.success) setBalance({ sol: r.sol || 0, usdt: r.usdt || 0 }); } catch {}
};
const handleUnlock = (pk: string) => {
setPublicKey(pk); setAppState('unlocked'); refreshBalance();
addToast({ type: 'success', title: 'Wallet unlocked' });
if (window.solvox) window.solvox.ai.initialize().then(r => { if (r.success) window.solvox.ai.getStatus().then(setAiStatus); });
};
const handleLock = async () => { if (window.solvox) await window.solvox.wallet.lock(); setAppState('locked'); };
if (appState === 'loading') return <Loading />;
if (appState === 'onboarding') return <OnboardingScreen onComplete={pk => { setPublicKey(pk); setAppState('unlocked'); refreshBalance(); }} />;
if (appState === 'locked') return <LockScreen onUnlock={handleUnlock} />;
const pages: Record<Page, JSX.Element> = {
dashboard: <Dashboard balance={balance} publicKey={publicKey} onRefresh={refreshBalance} onNavigate={setCurrentPage} />,
send: <SendPage balance={balance} onSent={refreshBalance} />,
history: <HistoryPage />,
voice: <VoicePage aiStatus={aiStatus} />,
contacts: <ContactsPage />,
scan: <ScanPage />,
security: <SecurityPage />,
settings: <SettingsPage onLock={handleLock} />,
};
return (
<div className="flex h-screen bg-canvas">
<Sidebar currentPage={currentPage} onNavigate={setCurrentPage} />
<div className="flex-1 flex flex-col overflow-hidden">
<TopBar publicKey={publicKey} balance={balance} aiStatus={aiStatus} onLock={handleLock} />
<main className="flex-1 overflow-y-auto">
<div className="page-enter" key={currentPage}>{pages[currentPage]}</div>
</main>
</div>
</div>
);
}
export default function App() { return <ToastProvider><AppContent /></ToastProvider>; }
function Loading() {
return (
<div className="h-screen flex flex-col items-center justify-center bg-canvas">
<div className="w-12 h-12 rounded-full bg-primary flex items-center justify-center mb-6">
<span className="text-on-primary font-bold text-title-md">SV</span>
</div>
<div className="display-text text-display-sm text-ink mb-2">SolVox</div>
<div className="text-body-md text-body">Initializing local AI engine…</div>
<div className="mt-8 w-48 h-1 bg-surface-strong rounded-pill overflow-hidden">
<div className="h-full bg-primary rounded-pill" style={{ width: '60%', animation: 'shimmer 1.5s infinite', background: 'linear-gradient(90deg, #0052ff 0%, #003ecc 50%, #0052ff 100%)', backgroundSize: '200% 100%' }} />
</div>
<div className="text-caption text-muted mt-4">Powered by QVAC SDK · 100% Local AI</div>
</div>
);
}