synor/apps/desktop-wallet/src/pages/Settings.tsx
Gulshan Yadav cca23a4019 fix: resolve TypeScript errors in desktop wallet
- Remove unused MULTIPLIER variable in Send.tsx
- Remove unused Check import in Settings.tsx
- Add vite-env.d.ts for ImportMeta.env types
2026-02-02 02:26:08 +05:30

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