183 lines
6.3 KiB
TypeScript
183 lines
6.3 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import { Terminal, Info, Send, Loader2 } from 'lucide-react';
|
|
import { useCliStore } from '../../store/cli';
|
|
|
|
export default function CliDashboard() {
|
|
const {
|
|
history,
|
|
isExecuting,
|
|
execute,
|
|
loadHistory,
|
|
clearOutput,
|
|
navigateHistory,
|
|
} = useCliStore();
|
|
|
|
const [input, setInput] = useState('');
|
|
const outputRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
loadHistory();
|
|
}, [loadHistory]);
|
|
|
|
// Auto-scroll to bottom when history changes
|
|
useEffect(() => {
|
|
if (outputRef.current) {
|
|
outputRef.current.scrollTop = outputRef.current.scrollHeight;
|
|
}
|
|
}, [history]);
|
|
|
|
const handleCommand = async () => {
|
|
if (!input.trim() || isExecuting) return;
|
|
const cmd = input.trim();
|
|
setInput('');
|
|
await execute(cmd);
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter') {
|
|
handleCommand();
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
const prev = navigateHistory('up');
|
|
if (prev !== null) setInput(prev);
|
|
} else if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
const next = navigateHistory('down');
|
|
if (next !== null) setInput(next);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold flex items-center gap-3">
|
|
<Terminal className="text-synor-400" />
|
|
CLI Mode
|
|
</h1>
|
|
<p className="text-gray-400 mt-1">Terminal interface for power users</p>
|
|
</div>
|
|
<button
|
|
onClick={clearOutput}
|
|
className="px-3 py-1 bg-gray-800 rounded text-sm hover:bg-gray-700"
|
|
>
|
|
Clear
|
|
</button>
|
|
</div>
|
|
|
|
<div className="bg-gray-900 rounded-xl border border-gray-800 overflow-hidden">
|
|
<div
|
|
ref={outputRef}
|
|
className="p-4 font-mono text-sm bg-black/50 h-96 overflow-y-auto"
|
|
>
|
|
{/* Welcome message if no history */}
|
|
{history.length === 0 && (
|
|
<div className="text-gray-500">
|
|
<p className="text-synor-400">{'>'} Welcome to Synor CLI Mode</p>
|
|
<p className="text-synor-400">{'>'} Type "help" for available commands</p>
|
|
<p className="text-synor-400">{'>'}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Command history */}
|
|
{history.map((result, i) => (
|
|
<div key={i} className="mb-2">
|
|
<div className="text-synor-400">
|
|
{'>'} {result.command}
|
|
</div>
|
|
<div className={`whitespace-pre-wrap ${result.isError ? 'text-red-400' : 'text-gray-300'}`}>
|
|
{result.output}
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{/* Loading indicator */}
|
|
{isExecuting && (
|
|
<div className="flex items-center gap-2 text-gray-500">
|
|
<Loader2 size={14} className="animate-spin" />
|
|
<span>Executing...</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="border-t border-gray-800 p-3 flex gap-2">
|
|
<span className="text-synor-400 font-mono">{'>'}</span>
|
|
<input
|
|
type="text"
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder="Enter command..."
|
|
className="flex-1 bg-transparent text-white font-mono outline-none"
|
|
autoFocus
|
|
disabled={isExecuting}
|
|
/>
|
|
<button
|
|
onClick={handleCommand}
|
|
disabled={isExecuting || !input.trim()}
|
|
className="p-2 bg-synor-600 rounded-lg hover:bg-synor-700 disabled:opacity-50"
|
|
>
|
|
{isExecuting ? <Loader2 size={16} className="animate-spin" /> : <Send size={16} />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Commands */}
|
|
<div className="bg-gray-900 rounded-xl p-4 border border-gray-800">
|
|
<h3 className="font-medium mb-3">Quick Commands</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
{['help', 'balance', 'address', 'status', 'utxos', 'peers'].map((cmd) => (
|
|
<button
|
|
key={cmd}
|
|
onClick={() => { setInput(cmd); }}
|
|
className="px-3 py-1 bg-gray-800 rounded font-mono text-sm hover:bg-gray-700"
|
|
>
|
|
{cmd}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Command Reference */}
|
|
<div className="bg-gray-900 rounded-xl p-4 border border-gray-800">
|
|
<h3 className="font-medium mb-3">Command Reference</h3>
|
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
<div className="font-mono text-synor-400">help</div>
|
|
<div className="text-gray-400">Show all available commands</div>
|
|
|
|
<div className="font-mono text-synor-400">balance</div>
|
|
<div className="text-gray-400">Show wallet balance</div>
|
|
|
|
<div className="font-mono text-synor-400">address</div>
|
|
<div className="text-gray-400">Show wallet addresses</div>
|
|
|
|
<div className="font-mono text-synor-400">send <addr> <amount></div>
|
|
<div className="text-gray-400">Send SYN to address</div>
|
|
|
|
<div className="font-mono text-synor-400">utxos</div>
|
|
<div className="text-gray-400">List unspent outputs</div>
|
|
|
|
<div className="font-mono text-synor-400">history</div>
|
|
<div className="text-gray-400">Transaction history</div>
|
|
|
|
<div className="font-mono text-synor-400">status</div>
|
|
<div className="text-gray-400">Network status</div>
|
|
|
|
<div className="font-mono text-synor-400">peers</div>
|
|
<div className="text-gray-400">Connected peers</div>
|
|
|
|
<div className="font-mono text-synor-400">clear</div>
|
|
<div className="text-gray-400">Clear output</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gray-900/50 rounded-lg p-4 border border-gray-800 flex items-start gap-3">
|
|
<Info className="text-gray-500 mt-0.5" size={18} />
|
|
<p className="text-sm text-gray-400">
|
|
CLI mode provides a terminal interface for advanced users who prefer keyboard-driven
|
|
interaction. Use ↑/↓ arrows to navigate command history.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|