| 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> |
| ); |
| } |
|
|