desktop to web wallet version

This commit is contained in:
Gulshan Yadav 2026-02-02 15:16:29 +05:30
parent f08eb965c2
commit b6522c21ef
51 changed files with 574 additions and 57 deletions

View file

@ -1,12 +1,34 @@
import { getCurrentWindow } from '@tauri-apps/api/window'; import { useState, useEffect } from 'react';
import { getCurrentWindow, mockWindow, isTauri } from '../lib/tauri';
import { Minus, Square, X } from 'lucide-react'; import { Minus, Square, X } from 'lucide-react';
type AppWindow = typeof mockWindow;
/** /**
* Custom title bar for frameless window mode. * Custom title bar for frameless window mode.
* Provides drag region and window controls. * Provides drag region and window controls.
*/ */
export default function TitleBar() { export default function TitleBar() {
const appWindow = getCurrentWindow(); const [appWindow, setAppWindow] = useState<AppWindow>(mockWindow);
useEffect(() => {
getCurrentWindow().then(setAppWindow);
}, []);
// Hide title bar in browser mode (no native window controls needed)
if (!isTauri()) {
return (
<div className="h-8 flex items-center justify-between bg-gray-900 border-b border-gray-800 select-none">
<div className="flex-1 h-full flex items-center px-4">
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded bg-gradient-to-br from-synor-400 to-synor-600" />
<span className="text-xs font-medium text-gray-400">Synor Wallet</span>
<span className="text-xs text-yellow-500 ml-2">(Browser Preview Mode)</span>
</div>
</div>
</div>
);
}
return ( return (
<div <div

View file

@ -5,8 +5,7 @@
*/ */
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { listen } from '@tauri-apps/api/event'; import { listen, invoke } from '../lib/tauri';
import { invoke } from '@tauri-apps/api/core';
export interface UpdateInfo { export interface UpdateInfo {
version: string; version: string;

View file

@ -5,7 +5,7 @@
*/ */
import { useEffect } from 'react'; import { useEffect } from 'react';
import { listen } from '@tauri-apps/api/event'; import { listen } from '../lib/tauri';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useWalletStore } from '../store/wallet'; import { useWalletStore } from '../store/wallet';

View file

@ -0,0 +1,503 @@
/**
* Tauri Adapter - Provides mock implementations when running in browser
*
* This allows the wallet UI to be previewed in a browser without the Rust backend.
* When running in Tauri, it uses real invoke() calls.
* When running in browser, it returns mock data for UI development/preview.
*/
// Detect if we're running inside Tauri
export const isTauri = (): boolean => {
return typeof window !== 'undefined' && '__TAURI__' in window;
};
// Mock data generators
const mockGenerators = {
// Wallet commands
create_wallet: async (_args?: { password: string }) => ({
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
address: 'tsynor1qz8v5zp2q0ew9d2k8n7j3x4c5v6b7n8m9k0l1p2q3r4s5t6u7v8w9x0y',
}),
import_wallet: async (_args?: { request: { mnemonic: string; password: string } }) =>
'tsynor1qz8v5zp2q0ew9d2k8n7j3x4c5v6b7n8m9k0l1p2q3r4s5t6u7v8w9x0y',
unlock_wallet: async (_args?: { password: string }) => true,
lock_wallet: async () => undefined,
get_balance: async () => ({
balance: 12345678900000,
balanceHuman: '123,456.789 SYN',
pending: 500000000,
}),
get_addresses: async () => [
{ address: 'tsynor1qz8v5zp2q0ew9d2k8n7j3x4c5v6b7n8m9k0l1p2q3r4s5t6u7v8w9x0y', index: 0, isChange: false, label: 'Main' },
{ address: 'tsynor1qa1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8', index: 1, isChange: false, label: 'Savings' },
],
connect_node: async (_args?: { rpcUrl: string; wsUrl?: string }) => ({
connected: true,
network: 'testnet',
blockHeight: 1234567,
peerCount: 8,
synced: true,
}),
// Node commands
node_start: async () => undefined,
node_stop: async () => undefined,
node_status: async () => ({
isRunning: true,
isConnected: true,
isSyncing: false,
network: 'testnet',
blockHeight: 1234567,
blueScore: 1234500,
peerCount: 8,
version: '0.1.0',
}),
get_peers: async () => [
{ id: 'peer1', address: '192.168.1.100:19420', connected: true, latency: 45 },
{ id: 'peer2', address: '10.0.0.50:19420', connected: true, latency: 120 },
],
// Mining commands
mining_start: async () => undefined,
mining_stop: async () => undefined,
mining_stats: async () => ({
isMining: true,
hashrate: 1250000,
blocksFound: 3,
threads: 4,
difficulty: 1234567890,
coinbaseAddress: 'tsynor1qz8v5zp2q0ew9d2k8n7j3x4c5v6b7n8m9k0l1p2q3r4s5t6u7v8w9x0y',
}),
// Transaction commands
get_transaction_history: async () => [
{ txid: 'abc123...', type: 'receive', amount: 1000000000, timestamp: Date.now() - 86400000, confirmations: 100 },
{ txid: 'def456...', type: 'send', amount: 500000000, timestamp: Date.now() - 172800000, confirmations: 200 },
],
create_transaction: async () => ({ txHex: '0x...', fee: 1000 }),
sign_transaction: async () => '0x...',
broadcast_transaction: async () => 'txid_abc123...',
// Staking commands
get_staking_info: async () => ({
isStaking: true,
stakedAmount: 5000000000000,
pendingRewards: 25000000000,
apr: 8.5,
validators: [{ address: 'val1', stake: 1000000000000, commission: 5 }],
}),
// Decoy wallets
list_decoy_wallets: async () => [
{ id: 'decoy1', name: 'Shopping', balance: 10000000000, isPrimary: false, createdAt: Date.now() - 604800000 },
{ id: 'decoy2', name: 'Travel', balance: 5000000000, isPrimary: false, createdAt: Date.now() - 1209600000 },
],
create_decoy_wallet: async () => ({ id: 'new_decoy', name: 'New Decoy', balance: 0, isPrimary: false, createdAt: Date.now() }),
delete_decoy_wallet: async () => undefined,
// Fee analytics
get_fee_analytics: async () => ({
currentFeeRate: 1,
lowFeeRate: 1,
mediumFeeRate: 5,
highFeeRate: 10,
mempoolSize: 1234,
averageBlockTime: 1000,
feeHistory: Array.from({ length: 24 }, (_, i) => ({ timestamp: Date.now() - i * 3600000, feeRate: Math.random() * 5 + 1 })),
}),
// Watch-only
list_watch_addresses: async () => [
{ address: 'tsynor1watch1...', label: 'Cold Storage', balance: 100000000000000 },
{ address: 'tsynor1watch2...', label: 'Exchange', balance: 50000000000000 },
],
add_watch_address: async () => undefined,
remove_watch_address: async () => undefined,
// Vaults (Time-locked)
list_vaults: async () => [
{ id: 'vault1', name: 'Retirement', amount: 50000000000000, unlockTime: Date.now() + 31536000000, status: 'locked' },
{ id: 'vault2', name: 'Savings', amount: 10000000000000, unlockTime: Date.now() + 2592000000, status: 'locked' },
],
create_vault: async () => ({ id: 'new_vault', name: 'New Vault', amount: 0, unlockTime: Date.now() + 86400000, status: 'locked' }),
unlock_vault: async () => undefined,
// Recovery
get_recovery_status: async () => ({
hasBackup: true,
lastBackupDate: Date.now() - 604800000,
recoveryMethod: 'mnemonic',
socialRecoveryEnabled: false,
guardians: [],
}),
create_backup: async () => 'backup_data_encrypted...',
verify_backup: async () => true,
// Mixer
list_mixer_pools: async () => [
{ denomination: 100000000, poolSize: 50, minParticipants: 5, fee: 0.001 },
{ denomination: 1000000000, poolSize: 30, minParticipants: 5, fee: 0.001 },
{ denomination: 10000000000, poolSize: 15, minParticipants: 5, fee: 0.001 },
],
get_mixer_requests: async () => [
{ id: 'mix1', denomination: 1000000000, status: 'pending', createdAt: Date.now() - 3600000 },
],
create_mix_request: async () => ({ id: 'new_mix', denomination: 1000000000, status: 'pending', createdAt: Date.now() }),
cancel_mix_request: async () => undefined,
// Limit orders
list_limit_orders: async () => [
{ id: 'order1', pair: 'SYN/BTC', type: 'buy', price: 0.00001, amount: 1000000000, filled: 500000000, status: 'partial' },
{ id: 'order2', pair: 'SYN/ETH', type: 'sell', price: 0.0001, amount: 2000000000, filled: 0, status: 'open' },
],
get_order_book: async () => ({
bids: [{ price: 0.00001, amount: 5000000000 }, { price: 0.000009, amount: 10000000000 }],
asks: [{ price: 0.000011, amount: 3000000000 }, { price: 0.000012, amount: 8000000000 }],
}),
create_limit_order: async () => ({ id: 'new_order', pair: 'SYN/BTC', type: 'buy', price: 0.00001, amount: 1000000000, filled: 0, status: 'open' }),
cancel_limit_order: async () => undefined,
// Yield
list_yield_opportunities: async () => [
{ id: 'yield1', protocol: 'SynorLend', asset: 'SYN', apy: 12.5, tvl: 1000000000000000, risk: 'low' },
{ id: 'yield2', protocol: 'SynorFarm', asset: 'SYN-LP', apy: 45.0, tvl: 500000000000000, risk: 'medium' },
],
list_yield_positions: async () => [
{ id: 'pos1', opportunityId: 'yield1', amount: 10000000000000, rewardsEarned: 125000000000, startTime: Date.now() - 2592000000 },
],
deposit_yield: async () => ({ id: 'new_pos', opportunityId: 'yield1', amount: 5000000000000, rewardsEarned: 0, startTime: Date.now() }),
withdraw_yield: async () => undefined,
// Portfolio
get_portfolio_summary: async () => ({
totalValueUsd: 125000.50,
dayChangeUsd: 2500.25,
dayChangePercent: 2.04,
totalPnlUsd: 25000.00,
totalPnlPercent: 25.0,
totalCostBasisUsd: 100000.50,
}),
list_portfolio_holdings: async () => [
{ asset: 'Synor', symbol: 'SYN', balance: 1234567800000000, balanceFormatted: '12,345.678 SYN', valueUsd: 100000, pnlPercent: 25, allocationPercent: 80 },
{ asset: 'Bitcoin', symbol: 'BTC', balance: 10000000, balanceFormatted: '0.1 BTC', valueUsd: 25000.50, pnlPercent: 50, allocationPercent: 20 },
],
get_tax_report: async () => [
{ id: 'tx1', timestamp: Date.now() - 86400000, txType: 'buy', asset: 'SYN', amount: 100000000000, totalUsd: 1000, gainLossUsd: undefined, isLongTerm: false },
{ id: 'tx2', timestamp: Date.now() - 31536000000, txType: 'sell', asset: 'SYN', amount: 50000000000, totalUsd: 750, gainLossUsd: 250, isLongTerm: true },
],
export_tax_report: async () => 'Date,Type,Asset,Amount,Total USD,Gain/Loss\n2025-01-01,buy,SYN,100,1000,\n2024-01-01,sell,SYN,50,750,250',
// Alerts
list_alerts: async () => [
{ id: 'alert1', type: 'price_above', asset: 'SYN', threshold: 100, enabled: true, triggered: false },
{ id: 'alert2', type: 'price_below', asset: 'SYN', threshold: 50, enabled: true, triggered: true },
{ id: 'alert3', type: 'balance_below', asset: 'SYN', threshold: 1000, enabled: false, triggered: false },
],
create_alert: async () => ({ id: 'new_alert', type: 'price_above', asset: 'SYN', threshold: 100, enabled: true, triggered: false }),
update_alert: async () => undefined,
delete_alert: async () => undefined,
// CLI
execute_cli_command: async (args?: { command: string }) => ({
success: true,
output: `Executed: ${args?.command || 'help'}\n\nMock CLI output for browser preview.\nAvailable commands: help, balance, send, receive, history`,
executionTime: 150,
}),
// RPC Profiles
list_rpc_profiles: async () => [
{ id: 'mainnet', name: 'Mainnet', rpcUrl: 'https://rpc.synor.network', wsUrl: 'wss://ws.synor.network', isActive: false },
{ id: 'testnet', name: 'Testnet', rpcUrl: 'https://testnet.synor.network', wsUrl: 'wss://testnet-ws.synor.network', isActive: true },
{ id: 'local', name: 'Local Node', rpcUrl: 'http://localhost:19423', wsUrl: 'ws://localhost:19424', isActive: false },
],
create_rpc_profile: async () => ({ id: 'new_profile', name: 'New Profile', rpcUrl: '', wsUrl: '', isActive: false }),
update_rpc_profile: async () => undefined,
delete_rpc_profile: async () => undefined,
set_active_rpc_profile: async () => undefined,
// Address book
list_contacts: async () => [
{ id: 'contact1', name: 'Alice', address: 'tsynor1alice...', notes: 'Friend' },
{ id: 'contact2', name: 'Bob', address: 'tsynor1bob...', notes: 'Business partner' },
],
add_contact: async () => ({ id: 'new_contact', name: 'New Contact', address: '', notes: '' }),
update_contact: async () => undefined,
delete_contact: async () => undefined,
// Multi-sig
list_multisig_wallets: async () => [
{ id: 'ms1', name: 'Team Wallet', threshold: 2, signers: ['addr1', 'addr2', 'addr3'], balance: 100000000000000 },
],
create_multisig_wallet: async () => ({ id: 'new_ms', name: 'New Multisig', threshold: 2, signers: [], balance: 0 }),
// Backup
get_backup_status: async () => ({
hasLocalBackup: true,
hasCloudBackup: false,
lastBackupDate: Date.now() - 604800000,
}),
create_local_backup: async () => 'backup_created',
restore_from_backup: async () => true,
// QR
generate_qr_code: async () => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
// Hardware wallet
list_hardware_wallets: async () => [],
connect_hardware_wallet: async () => ({ id: 'hw1', type: 'ledger', connected: true }),
// Portfolio commands (snake_case for backend compatibility)
portfolio_get_summary: async () => ({
total_value_usd: 125000.50,
day_change_usd: 2500.25,
day_change_percent: 2.04,
total_pnl_usd: 25000.00,
total_pnl_percent: 25.0,
total_cost_basis_usd: 100000.50,
}),
portfolio_get_holdings: async () => [
{ asset: 'Synor', symbol: 'SYN', balance: 1234567800000000, balance_formatted: '12,345.678 SYN', price_usd: 8.1, value_usd: 100000, cost_basis_usd: 80000, pnl_usd: 20000, pnl_percent: 25, allocation_percent: 80 },
{ asset: 'Bitcoin', symbol: 'BTC', balance: 10000000, balance_formatted: '0.1 BTC', price_usd: 250000, value_usd: 25000.50, cost_basis_usd: 16700, pnl_usd: 8300.50, pnl_percent: 50, allocation_percent: 20 },
],
portfolio_get_tax_report: async () => [
{ id: 'tx1', timestamp: Date.now() - 86400000, tx_type: 'buy', asset: 'SYN', amount: 100000000000, price_usd: 10, total_usd: 1000, cost_basis_usd: 1000, gain_loss_usd: null, is_long_term: false },
{ id: 'tx2', timestamp: Date.now() - 31536000000, tx_type: 'sell', asset: 'SYN', amount: 50000000000, price_usd: 15, total_usd: 750, cost_basis_usd: 500, gain_loss_usd: 250, is_long_term: true },
],
portfolio_export_tax_report: async () => 'Date,Type,Asset,Amount,Total USD,Gain/Loss\n2025-01-01,buy,SYN,100,1000,\n2024-01-01,sell,SYN,50,750,250',
portfolio_get_history: async () => Array.from({ length: 30 }, (_, i) => ({
timestamp: Date.now() - i * 86400000,
value_usd: 100000 + Math.random() * 50000,
})),
// Wallet manager commands
wallets_list: async () => [
{ id: 'wallet1', name: 'Main Wallet', address: 'tsynor1qz8v5zp2q0ew9d2k8n7j3x4c5v6b7n8m9k0l1p2q3r4s5t6u7v8w9x0y', isActive: true, createdAt: Date.now() - 86400000 * 30 },
],
wallets_create: async () => ({ id: 'new_wallet', name: 'New Wallet', address: 'tsynor1new...', isActive: false, createdAt: Date.now() }),
wallets_import: async () => ({ id: 'imported', name: 'Imported Wallet', address: 'tsynor1imp...', isActive: false, createdAt: Date.now() }),
wallets_switch: async () => undefined,
wallets_delete: async () => undefined,
wallets_rename: async () => undefined,
// Yield commands (snake_case)
yield_list_opportunities: async () => [
{ id: 'yield1', protocol: 'SynorLend', asset: 'SYN', apy: 12.5, tvl: 1000000000000000, risk: 'low', min_deposit: 100000000 },
{ id: 'yield2', protocol: 'SynorFarm', asset: 'SYN-LP', apy: 45.0, tvl: 500000000000000, risk: 'medium', min_deposit: 500000000 },
],
yield_list_positions: async () => [
{ id: 'pos1', opportunity_id: 'yield1', amount: 10000000000000, rewards_earned: 125000000000, start_time: Date.now() - 2592000000 },
],
yield_deposit: async () => ({ id: 'new_pos', opportunity_id: 'yield1', amount: 5000000000000, rewards_earned: 0, start_time: Date.now() }),
yield_withdraw: async () => undefined,
// Alerts commands
alerts_list: async () => [
{ id: 'alert1', alert_type: 'price_above', asset: 'SYN', threshold: 100, enabled: true, triggered: false, created_at: Date.now() - 86400000 },
{ id: 'alert2', alert_type: 'price_below', asset: 'SYN', threshold: 50, enabled: true, triggered: true, created_at: Date.now() - 172800000 },
],
alerts_create: async () => ({ id: 'new_alert', alert_type: 'price_above', asset: 'SYN', threshold: 100, enabled: true, triggered: false, created_at: Date.now() }),
alerts_update: async () => undefined,
alerts_delete: async () => undefined,
// CLI commands
cli_execute: async (args?: { command: string }) => ({
success: true,
output: `$ ${args?.command || 'help'}\n\n[Mock CLI Output]\nCommand executed successfully in browser preview mode.\n\nAvailable commands:\n help - Show this help\n balance - Show wallet balance\n send - Send SYN to an address\n receive - Show receive address\n history - Transaction history`,
execution_time: 150,
}),
// Mixer commands (snake_case)
mixer_get_denominations: async () => [100000000, 1000000000, 10000000000, 100000000000],
mixer_list_pools: async () => [
{ denomination: 100000000, pool_size: 50, min_participants: 5, fee: 0.001, status: 'active' },
{ denomination: 1000000000, pool_size: 30, min_participants: 5, fee: 0.001, status: 'active' },
{ denomination: 10000000000, pool_size: 15, min_participants: 5, fee: 0.001, status: 'active' },
],
mixer_get_pool_status: async () => ({ denomination: 1000000000, pool_size: 30, participants: 12, status: 'active' }),
mixer_get_requests: async () => [
{ id: 'mix1', denomination: 1000000000, status: 'pending', created_at: Date.now() - 3600000 },
],
mixer_create_request: async () => ({ id: 'new_mix', denomination: 1000000000, status: 'pending', created_at: Date.now() }),
mixer_cancel_request: async () => undefined,
// Limit orders (snake_case)
limit_order_list: async () => [
{ id: 'order1', pair: 'SYN/BTC', order_type: 'buy', price: 0.00001, amount: 1000000000, filled: 500000000, status: 'partial', created_at: Date.now() - 86400000 },
{ id: 'order2', pair: 'SYN/ETH', order_type: 'sell', price: 0.0001, amount: 2000000000, filled: 0, status: 'open', created_at: Date.now() - 3600000 },
],
limit_order_get_orderbook: async () => ({
bids: [{ price: 0.00001, amount: 5000000000 }, { price: 0.000009, amount: 10000000000 }],
asks: [{ price: 0.000011, amount: 3000000000 }, { price: 0.000012, amount: 8000000000 }],
}),
limit_order_create: async () => ({ id: 'new_order', pair: 'SYN/BTC', order_type: 'buy', price: 0.00001, amount: 1000000000, filled: 0, status: 'open', created_at: Date.now() }),
limit_order_cancel: async () => undefined,
// RPC Profiles (snake_case)
rpc_profiles_list: async () => [
{ id: 'mainnet', name: 'Mainnet', rpc_url: 'https://rpc.synor.network', ws_url: 'wss://ws.synor.network', is_active: false },
{ id: 'testnet', name: 'Testnet', rpc_url: 'https://testnet.synor.network', ws_url: 'wss://testnet-ws.synor.network', is_active: true },
{ id: 'local', name: 'Local Node', rpc_url: 'http://localhost:19423', ws_url: 'ws://localhost:19424', is_active: false },
],
rpc_profiles_create: async () => ({ id: 'new_profile', name: 'New Profile', rpc_url: '', ws_url: '', is_active: false }),
rpc_profiles_update: async () => undefined,
rpc_profiles_delete: async () => undefined,
rpc_profiles_set_active: async () => undefined,
// Vaults (snake_case)
vault_list: async () => [
{ id: 'vault1', name: 'Retirement', amount: 50000000000000, unlock_time: Date.now() + 31536000000, status: 'locked', created_at: Date.now() - 86400000 * 365 },
{ id: 'vault2', name: 'Savings', amount: 10000000000000, unlock_time: Date.now() + 2592000000, status: 'locked', created_at: Date.now() - 86400000 * 30 },
],
vault_get_summary: async () => ({
total_locked: 60000000000000,
total_vaults: 2,
next_unlock: Date.now() + 2592000000,
}),
vault_create: async () => ({ id: 'new_vault', name: 'New Vault', amount: 0, unlock_time: Date.now() + 86400000, status: 'locked', created_at: Date.now() }),
vault_withdraw: async () => 'txid_vault_withdraw...',
vault_time_remaining: async () => 2592000000,
// Recovery
recovery_get_status: async () => ({
has_backup: true,
last_backup_date: Date.now() - 604800000,
recovery_method: 'mnemonic',
social_recovery_enabled: false,
guardians: [],
}),
recovery_create_backup: async () => 'backup_data_encrypted...',
recovery_verify_backup: async () => true,
// Decoy wallets (snake_case)
decoy_is_enabled: async () => true,
decoy_list: async () => [
{ id: 'decoy1', name: 'Shopping', balance: 10000000000, is_primary: false, created_at: Date.now() - 604800000 },
{ id: 'decoy2', name: 'Travel', balance: 5000000000, is_primary: false, created_at: Date.now() - 1209600000 },
],
decoy_create: async () => ({ id: 'new_decoy', name: 'New Decoy', balance: 0, is_primary: false, created_at: Date.now() }),
decoy_delete: async () => undefined,
decoy_transfer: async () => undefined,
// Fee analytics
fee_get_analytics: async () => ({
current_fee_rate: 1,
low_fee_rate: 1,
medium_fee_rate: 5,
high_fee_rate: 10,
mempool_size: 1234,
average_block_time: 1000,
fee_history: Array.from({ length: 24 }, (_, i) => ({
timestamp: Date.now() - i * 3600000,
fee_rate: Math.random() * 5 + 1,
})),
}),
fee_calculate: async () => 1500,
// Watch-only
watch_only_list: async () => [
{ address: 'tsynor1watch1abc123...', label: 'Cold Storage', balance: 100000000000000 },
{ address: 'tsynor1watch2def456...', label: 'Exchange', balance: 50000000000000 },
],
watch_only_add: async () => undefined,
watch_only_remove: async () => undefined,
// Default fallback for unknown commands
default: async (cmd: string) => {
console.warn(`[Mock] Unknown command: ${cmd}`);
return null;
},
};
type MockGenerators = typeof mockGenerators;
type CommandName = keyof MockGenerators;
/**
* Invoke a Tauri command with automatic mock fallback for browser
*/
export async function invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T> {
if (isTauri()) {
// Running in Tauri - use real invoke
const { invoke: tauriInvoke } = await import('@tauri-apps/api/core');
return tauriInvoke<T>(cmd, args);
}
// Running in browser - use mock
console.log(`[Mock] ${cmd}`, args || '');
const generator = mockGenerators[cmd as CommandName] || mockGenerators.default;
const result = await generator(args as never);
// Simulate network latency
await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200));
return result as T;
}
/**
* Listen to Tauri events with mock fallback
*/
export async function listen<T>(
event: string,
handler: (payload: { payload: T }) => void
): Promise<() => void> {
if (isTauri()) {
const { listen: tauriListen } = await import('@tauri-apps/api/event');
return tauriListen<T>(event, handler);
}
// In browser, set up mock event emitter
console.log(`[Mock] Listening to event: ${event}`);
// Return a no-op unsubscribe function
return () => {
console.log(`[Mock] Unsubscribed from event: ${event}`);
};
}
// Export a flag for components to check
export const BROWSER_MOCK_MODE = !isTauri();
// Type for unlisten function
export type UnlistenFn = () => void;
/**
* Mock window API for browser
*/
export const mockWindow = {
async minimize() {
console.log('[Mock] Window minimize');
},
async maximize() {
console.log('[Mock] Window maximize');
},
async close() {
console.log('[Mock] Window close');
},
async toggleMaximize() {
console.log('[Mock] Window toggleMaximize');
},
async setTitle(title: string) {
console.log('[Mock] Window setTitle:', title);
document.title = title;
},
async isMaximized() {
return false;
},
};
/**
* Get the current window with mock fallback
*/
export async function getCurrentWindow() {
if (isTauri()) {
const { getCurrentWindow: tauriGetCurrentWindow } = await import('@tauri-apps/api/window');
return tauriGetCurrentWindow();
}
return mockWindow;
}

View file

@ -10,7 +10,6 @@ import {
Wallet, Wallet,
} from 'lucide-react'; } from 'lucide-react';
import { useDecoyStore, DecoyWallet } from '../../store/decoy'; import { useDecoyStore, DecoyWallet } from '../../store/decoy';
import { LoadingSpinner } from '../../components/LoadingStates';
function SetupDecoyModal({ onClose }: { onClose: () => void }) { function SetupDecoyModal({ onClose }: { onClose: () => void }) {
const { setup, isLoading } = useDecoyStore(); const { setup, isLoading } = useDecoyStore();

View file

@ -2,7 +2,6 @@ import { useEffect, useState, useMemo } from 'react';
import { import {
Activity, Activity,
TrendingUp, TrendingUp,
TrendingDown,
Clock, Clock,
Zap, Zap,
RefreshCw, RefreshCw,
@ -15,11 +14,9 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { import {
useFeeAnalyticsStore, useFeeAnalyticsStore,
useSelectedRecommendation,
getCongestionColor, getCongestionColor,
getCongestionBgColor, getCongestionBgColor,
formatDuration, formatDuration,
formatFeeRate,
FeeRecommendation, FeeRecommendation,
} from '../../store/feeAnalytics'; } from '../../store/feeAnalytics';
import { LoadingSpinner } from '../../components/LoadingStates'; import { LoadingSpinner } from '../../components/LoadingStates';
@ -215,7 +212,7 @@ function FeeHistoryChart() {
* Fee calculator component * Fee calculator component
*/ */
function FeeCalculator() { function FeeCalculator() {
const { analytics, selectedTier, calculateFee } = useFeeAnalyticsStore(); const { analytics, selectedTier } = useFeeAnalyticsStore();
const [txSize, setTxSize] = useState(250); // Default tx size const [txSize, setTxSize] = useState(250); // Default tx size
const [calculatedFee, setCalculatedFee] = useState<number | null>(null); const [calculatedFee, setCalculatedFee] = useState<number | null>(null);

View file

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
import { import {
ArrowUpRight, ArrowUpRight,
ArrowDownLeft, ArrowDownLeft,

View file

@ -9,7 +9,7 @@ import {
Upload, Upload,
RefreshCw, RefreshCw,
} from 'lucide-react'; } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../../lib/tauri';
interface PaymentRequest { interface PaymentRequest {
address: string; address: string;

View file

@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { Copy, Check, Plus, QrCode } from 'lucide-react'; import { Copy, Check, Plus, QrCode } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
import { useWalletStore } from '../store/wallet'; import { useWalletStore } from '../store/wallet';
export default function Receive() { export default function Receive() {

View file

@ -8,7 +8,6 @@ import {
CheckCircle, CheckCircle,
Clock, Clock,
Users, Users,
Settings,
Mail, Mail,
Wallet, Wallet,
Info, Info,

View file

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Server, Plus, Trash2, CheckCircle, RefreshCw, Info, Wifi, WifiOff } from 'lucide-react'; import { Server, Plus, Trash2, CheckCircle, RefreshCw, Info, Wifi } from 'lucide-react';
interface RpcProfile { interface RpcProfile {
id: string; id: string;

View file

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
import { Send as SendIcon, AlertTriangle, Check } from 'lucide-react'; import { Send as SendIcon, AlertTriangle, Check } from 'lucide-react';
import { useWalletStore } from '../store/wallet'; import { useWalletStore } from '../store/wallet';

View file

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
import { import {
Server, Server,
Shield, Shield,

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface PriceAlert { export interface PriceAlert {
id: string; id: string;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* A recipient in a batch transaction * A recipient in a batch transaction

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface CliResult { export interface CliResult {
command: string; command: string;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Sanitized error logging * Sanitized error logging

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* A decoy wallet for plausible deniability * A decoy wallet for plausible deniability

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Mempool statistics from the network * Mempool statistics from the network

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface LimitOrder { export interface LimitOrder {
id: string; id: string;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,7 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke, listen, type UnlistenFn } from '../lib/tauri';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
/** /**
* Sanitized error logging * Sanitized error logging

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
// Types matching backend // Types matching backend
export interface MixPoolStatus { export interface MixPoolStatus {

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Sanitized error logging * Sanitized error logging

View file

@ -1,7 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke, listen, type UnlistenFn } from '../lib/tauri';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
/** /**
* Sanitized error logging * Sanitized error logging

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface PluginInfo { export interface PluginInfo {
id: string; id: string;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface PortfolioSummary { export interface PortfolioSummary {
totalValueUsd: number; totalValueUsd: number;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* A guardian for social recovery * A guardian for social recovery

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface RpcProfile { export interface RpcProfile {
id: string; id: string;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Sanitized error logging * Sanitized error logging

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface TxOutput { export interface TxOutput {
address: string; address: string;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Time-locked vault * Time-locked vault

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Sanitized error logging - prevents sensitive data from being logged * Sanitized error logging - prevents sensitive data from being logged

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Summary info for a wallet * Summary info for a wallet

View file

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
/** /**
* Watch-only address entry * Watch-only address entry

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
export interface YieldOpportunity { export interface YieldOpportunity {
id: string; id: string;

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '../lib/tauri';
function logError(context: string, error: unknown): void { function logError(context: string, error: unknown): void {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {