- Remove unused MULTIPLIER variable in Send.tsx - Remove unused Check import in Settings.tsx - Add vite-env.d.ts for ImportMeta.env types
331 lines
10 KiB
TypeScript
331 lines
10 KiB
TypeScript
import { useState } from 'react';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import {
|
|
Server,
|
|
Shield,
|
|
Key,
|
|
AlertTriangle,
|
|
RefreshCw,
|
|
Download,
|
|
} from 'lucide-react';
|
|
import { useWalletStore } from '../store/wallet';
|
|
import { useAutoUpdater } from '../hooks/useAutoUpdater';
|
|
|
|
export default function Settings() {
|
|
const { networkStatus, connectNode } = useWalletStore();
|
|
const {
|
|
checking,
|
|
available,
|
|
downloading,
|
|
error: updateError,
|
|
updateInfo,
|
|
checkForUpdates,
|
|
installUpdate,
|
|
} = useAutoUpdater();
|
|
|
|
const [rpcUrl, setRpcUrl] = useState(
|
|
'http://localhost:17110'
|
|
);
|
|
const [wsUrl, setWsUrl] = useState('ws://localhost:17111');
|
|
const [connecting, setConnecting] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
const [showMnemonic, setShowMnemonic] = useState(false);
|
|
const [mnemonicPassword, setMnemonicPassword] = useState('');
|
|
const [mnemonic, setMnemonic] = useState('');
|
|
const [exportError, setExportError] = useState('');
|
|
const [exporting, setExporting] = useState(false);
|
|
|
|
const handleConnect = async () => {
|
|
setConnecting(true);
|
|
setError('');
|
|
try {
|
|
await connectNode(rpcUrl, wsUrl || undefined);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to connect');
|
|
} finally {
|
|
setConnecting(false);
|
|
}
|
|
};
|
|
|
|
const handleExportMnemonic = async () => {
|
|
setExporting(true);
|
|
setExportError('');
|
|
try {
|
|
const result = await invoke<string>('export_mnemonic', {
|
|
password: mnemonicPassword,
|
|
});
|
|
setMnemonic(result);
|
|
setShowMnemonic(true);
|
|
} catch (err) {
|
|
setExportError(
|
|
err instanceof Error ? err.message : 'Failed to export mnemonic'
|
|
);
|
|
} finally {
|
|
setExporting(false);
|
|
setMnemonicPassword('');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-2xl space-y-6">
|
|
<h1 className="text-2xl font-bold text-white">Settings</h1>
|
|
|
|
{/* Network Settings */}
|
|
<div className="card">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Server size={20} className="text-gray-400" />
|
|
<h2 className="text-lg font-semibold text-white">Network</h2>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm text-gray-400 mb-2">
|
|
RPC Endpoint
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={rpcUrl}
|
|
onChange={(e) => setRpcUrl(e.target.value)}
|
|
className="input"
|
|
placeholder="http://localhost:17110"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm text-gray-400 mb-2">
|
|
WebSocket Endpoint (Optional)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={wsUrl}
|
|
onChange={(e) => setWsUrl(e.target.value)}
|
|
className="input"
|
|
placeholder="ws://localhost:17111"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<button
|
|
onClick={handleConnect}
|
|
disabled={connecting}
|
|
className="btn btn-primary"
|
|
>
|
|
{connecting ? (
|
|
<>
|
|
<RefreshCw size={18} className="animate-spin" />
|
|
Connecting...
|
|
</>
|
|
) : (
|
|
'Connect'
|
|
)}
|
|
</button>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<div
|
|
className={`w-2 h-2 rounded-full ${
|
|
networkStatus.connected ? 'bg-green-500' : 'bg-red-500'
|
|
}`}
|
|
/>
|
|
<span className="text-sm text-gray-400">
|
|
{networkStatus.connected
|
|
? `Connected to ${networkStatus.network}`
|
|
: 'Disconnected'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<p className="text-red-400 text-sm flex items-center gap-2">
|
|
<AlertTriangle size={16} />
|
|
{error}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preset Networks */}
|
|
<div className="card">
|
|
<h3 className="font-medium text-white mb-3">Quick Connect</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
<button
|
|
onClick={() => {
|
|
setRpcUrl('http://localhost:17110');
|
|
setWsUrl('ws://localhost:17111');
|
|
}}
|
|
className="btn btn-ghost text-sm"
|
|
>
|
|
Local Testnet
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
setRpcUrl('https://testnet-rpc.synor.io');
|
|
setWsUrl('wss://testnet-ws.synor.io');
|
|
}}
|
|
className="btn btn-ghost text-sm"
|
|
>
|
|
Public Testnet
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
setRpcUrl('https://rpc.synor.io');
|
|
setWsUrl('wss://ws.synor.io');
|
|
}}
|
|
className="btn btn-ghost text-sm"
|
|
>
|
|
Mainnet
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Security Settings */}
|
|
<div className="card">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Shield size={20} className="text-gray-400" />
|
|
<h2 className="text-lg font-semibold text-white">Security</h2>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{/* Export Mnemonic */}
|
|
<div className="p-4 bg-gray-950 rounded-lg">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Key size={16} className="text-yellow-500" />
|
|
<h3 className="font-medium text-white">Recovery Phrase</h3>
|
|
</div>
|
|
|
|
{showMnemonic ? (
|
|
<div className="space-y-3">
|
|
<div className="bg-gray-900 rounded-lg p-3 border border-yellow-700/50">
|
|
<p className="font-mono text-sm text-yellow-200 break-all">
|
|
{mnemonic}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() => {
|
|
setShowMnemonic(false);
|
|
setMnemonic('');
|
|
}}
|
|
className="btn btn-secondary"
|
|
>
|
|
Hide Recovery Phrase
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
<p className="text-sm text-gray-400">
|
|
Enter your password to reveal your recovery phrase.
|
|
</p>
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="password"
|
|
value={mnemonicPassword}
|
|
onChange={(e) => setMnemonicPassword(e.target.value)}
|
|
className="input flex-1"
|
|
placeholder="Enter password"
|
|
/>
|
|
<button
|
|
onClick={handleExportMnemonic}
|
|
disabled={exporting || !mnemonicPassword}
|
|
className="btn btn-secondary"
|
|
>
|
|
{exporting ? 'Loading...' : 'Reveal'}
|
|
</button>
|
|
</div>
|
|
{exportError && (
|
|
<p className="text-red-400 text-sm flex items-center gap-2">
|
|
<AlertTriangle size={16} />
|
|
{exportError}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Warning */}
|
|
<div className="flex gap-3 p-4 bg-yellow-900/20 border border-yellow-700/50 rounded-lg">
|
|
<AlertTriangle className="text-yellow-500 shrink-0" size={20} />
|
|
<div>
|
|
<p className="text-sm text-yellow-200 font-medium">
|
|
Keep your recovery phrase safe
|
|
</p>
|
|
<p className="text-sm text-yellow-200/70 mt-1">
|
|
Anyone with access to your recovery phrase can steal your funds.
|
|
Never share it with anyone.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Updates */}
|
|
<div className="card">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Download size={20} className="text-gray-400" />
|
|
<h2 className="text-lg font-semibold text-white">Updates</h2>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{available && updateInfo ? (
|
|
<div className="p-4 bg-blue-900/20 border border-blue-700/50 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-blue-200 font-medium">
|
|
Version {updateInfo.version} available
|
|
</p>
|
|
{updateInfo.body && (
|
|
<p className="text-sm text-blue-300/70 mt-1">
|
|
{updateInfo.body}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={installUpdate}
|
|
disabled={downloading}
|
|
className="btn btn-primary"
|
|
>
|
|
{downloading ? (
|
|
<>
|
|
<RefreshCw size={18} className="animate-spin" />
|
|
Downloading...
|
|
</>
|
|
) : (
|
|
'Install Update'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-gray-400 text-sm">
|
|
{checking ? 'Checking for updates...' : 'No updates available'}
|
|
</p>
|
|
<button
|
|
onClick={checkForUpdates}
|
|
disabled={checking}
|
|
className="btn btn-secondary"
|
|
>
|
|
{checking ? (
|
|
<RefreshCw size={18} className="animate-spin" />
|
|
) : (
|
|
'Check for Updates'
|
|
)}
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{updateError && (
|
|
<p className="text-red-400 text-sm flex items-center gap-2">
|
|
<AlertTriangle size={16} />
|
|
{updateError}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Version info */}
|
|
<div className="text-center text-sm text-gray-600">
|
|
Synor Wallet v0.1.0
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|