Big changes and fixes. Uses action history. Improve role selection. Remove unused logs
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
/**
|
||||
* Wallet State Screen - Displays wallet balances and UTXOs.
|
||||
* Wallet State Screen - Displays wallet balances and history.
|
||||
*
|
||||
* Shows:
|
||||
* - Total balance
|
||||
* - List of unspent outputs
|
||||
* - Wallet history (invitations, reservations)
|
||||
* - Navigation to other actions
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, Text, useInput } from 'ink';
|
||||
import SelectInput from 'ink-select-input';
|
||||
import { type ListItem } from '../components/List.js';
|
||||
import { useNavigation } from '../hooks/useNavigation.js';
|
||||
import { useAppContext, useStatus } from '../hooks/useAppContext.js';
|
||||
import { colors, formatSatoshis, formatHex, logoSmall } from '../theme.js';
|
||||
import type { HistoryItem } from '../../services/history.js';
|
||||
|
||||
/**
|
||||
* Menu action items.
|
||||
@@ -26,20 +26,9 @@ const menuItems = [
|
||||
{ label: 'Refresh', value: 'refresh' },
|
||||
];
|
||||
|
||||
/**
|
||||
* UTXO display item.
|
||||
*/
|
||||
interface UTXOItem {
|
||||
key: string;
|
||||
satoshis: bigint;
|
||||
txid: string;
|
||||
index: number;
|
||||
reserved: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wallet State Screen Component.
|
||||
* Displays wallet balance, UTXOs, and action menu.
|
||||
* Displays wallet balance, history, and action menu.
|
||||
*/
|
||||
export function WalletStateScreen(): React.ReactElement {
|
||||
const { navigate } = useNavigation();
|
||||
@@ -48,9 +37,9 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
|
||||
// State
|
||||
const [balance, setBalance] = useState<{ totalSatoshis: bigint; utxoCount: number } | null>(null);
|
||||
const [utxos, setUtxos] = useState<UTXOItem[]>([]);
|
||||
const [focusedPanel, setFocusedPanel] = useState<'menu' | 'utxos'>('menu');
|
||||
const [selectedUtxoIndex, setSelectedUtxoIndex] = useState(0);
|
||||
const [history, setHistory] = useState<HistoryItem[]>([]);
|
||||
const [focusedPanel, setFocusedPanel] = useState<'menu' | 'history'>('menu');
|
||||
const [selectedHistoryIndex, setSelectedHistoryIndex] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
/**
|
||||
@@ -66,23 +55,21 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
setIsLoading(true);
|
||||
setStatus('Loading wallet state...');
|
||||
|
||||
// Get UTXOs
|
||||
// Get UTXOs for balance calculation
|
||||
const utxoData = await appService.engine.listUnspentOutputsData();
|
||||
setUtxos(utxoData.map((utxo) => ({
|
||||
key: `${utxo.outpointTransactionHash}:${utxo.outpointIndex}`,
|
||||
satoshis: BigInt(utxo.valueSatoshis),
|
||||
txid: utxo.outpointTransactionHash,
|
||||
index: utxo.outpointIndex,
|
||||
reserved: utxo.reserved ?? false,
|
||||
})));
|
||||
|
||||
// Get balance
|
||||
const balanceData = utxoData.reduce((acc, utxo) => acc + BigInt(utxo.valueSatoshis), BigInt(0));
|
||||
// Calculate balance
|
||||
const selectableUtxos = utxoData.filter(utxo => utxo.selectable);
|
||||
const balanceData = selectableUtxos.reduce((acc, utxo) => acc + BigInt(utxo.valueSatoshis), BigInt(0));
|
||||
setBalance({
|
||||
totalSatoshis: balanceData,
|
||||
utxoCount: utxoData.length,
|
||||
utxoCount: selectableUtxos.length,
|
||||
});
|
||||
|
||||
// Get wallet history from the history service
|
||||
const historyData = await appService.history.getHistory();
|
||||
setHistory(historyData);
|
||||
|
||||
setStatus('Wallet ready');
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
@@ -162,17 +149,19 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
// Handle keyboard navigation between panels
|
||||
useInput((input, key) => {
|
||||
if (key.tab) {
|
||||
setFocusedPanel(prev => prev === 'menu' ? 'utxos' : 'menu');
|
||||
setFocusedPanel(prev => prev === 'menu' ? 'history' : 'menu');
|
||||
}
|
||||
|
||||
// Navigate history items when focused
|
||||
if (focusedPanel === 'history' && history.length > 0) {
|
||||
if (key.upArrow) {
|
||||
setSelectedHistoryIndex(prev => Math.max(0, prev - 1));
|
||||
} else if (key.downArrow) {
|
||||
setSelectedHistoryIndex(prev => Math.min(history.length - 1, prev + 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Convert UTXOs to list items
|
||||
const utxoListItems: ListItem[] = utxos.map((utxo, index) => ({
|
||||
key: utxo.key,
|
||||
label: `${formatSatoshis(utxo.satoshis)} | ${formatHex(utxo.txid, 16)}:${utxo.index}`,
|
||||
description: utxo.reserved ? '[Reserved]' : undefined,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box flexDirection='column' flexGrow={1}>
|
||||
{/* Header */}
|
||||
@@ -251,33 +240,96 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* UTXO list */}
|
||||
{/* Wallet History */}
|
||||
<Box marginTop={1} flexGrow={1}>
|
||||
<Box
|
||||
borderStyle='single'
|
||||
borderColor={focusedPanel === 'utxos' ? colors.focus : colors.border}
|
||||
borderColor={focusedPanel === 'history' ? colors.focus : colors.border}
|
||||
flexDirection='column'
|
||||
paddingX={1}
|
||||
width='100%'
|
||||
height={14}
|
||||
overflow='hidden'
|
||||
>
|
||||
<Text color={colors.primary} bold> Unspent Outputs (UTXOs) </Text>
|
||||
<Text color={colors.primary} bold> Wallet History {history.length > 0 ? `(${selectedHistoryIndex + 1}/${history.length})` : ''}</Text>
|
||||
<Box marginTop={1} flexDirection='column'>
|
||||
{isLoading ? (
|
||||
<Text color={colors.textMuted}>Loading...</Text>
|
||||
) : utxoListItems.length === 0 ? (
|
||||
<Text color={colors.textMuted}>No unspent outputs found</Text>
|
||||
) : history.length === 0 ? (
|
||||
<Text color={colors.textMuted}>No history found</Text>
|
||||
) : (
|
||||
utxoListItems.map((item, index) => (
|
||||
<Box key={item.key}>
|
||||
<Text color={index === selectedUtxoIndex && focusedPanel === 'utxos' ? colors.focus : colors.text}>
|
||||
{index === selectedUtxoIndex && focusedPanel === 'utxos' ? '▸ ' : ' '}
|
||||
{index + 1}. {item.label}
|
||||
</Text>
|
||||
{item.description && (
|
||||
<Text color={colors.warning}> {item.description}</Text>
|
||||
)}
|
||||
</Box>
|
||||
))
|
||||
// Show a scrolling window of items
|
||||
(() => {
|
||||
const maxVisible = 10;
|
||||
const halfWindow = Math.floor(maxVisible / 2);
|
||||
let startIndex = Math.max(0, selectedHistoryIndex - halfWindow);
|
||||
const endIndex = Math.min(history.length, startIndex + maxVisible);
|
||||
// Adjust start if we're near the end
|
||||
if (endIndex - startIndex < maxVisible) {
|
||||
startIndex = Math.max(0, endIndex - maxVisible);
|
||||
}
|
||||
const visibleItems = history.slice(startIndex, endIndex);
|
||||
|
||||
return visibleItems.map((item, idx) => {
|
||||
const actualIndex = startIndex + idx;
|
||||
const isSelected = actualIndex === selectedHistoryIndex && focusedPanel === 'history';
|
||||
const indicator = isSelected ? '▸ ' : ' ';
|
||||
const dateStr = item.timestamp
|
||||
? new Date(item.timestamp).toLocaleDateString()
|
||||
: '';
|
||||
|
||||
// Format the history item based on type
|
||||
if (item.type === 'invitation_created') {
|
||||
return (
|
||||
<Box key={item.id} flexDirection='row' justifyContent='space-between'>
|
||||
<Text color={isSelected ? colors.focus : colors.text}>
|
||||
{indicator}[Invitation] {item.description}
|
||||
</Text>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
</Box>
|
||||
);
|
||||
} else if (item.type === 'utxo_reserved') {
|
||||
const sats = item.valueSatoshis ?? 0n;
|
||||
return (
|
||||
<Box key={item.id} flexDirection='row' justifyContent='space-between'>
|
||||
<Box>
|
||||
<Text color={isSelected ? colors.focus : colors.warning}>
|
||||
{indicator}[Reserved] {formatSatoshis(sats)}
|
||||
</Text>
|
||||
<Text color={colors.textMuted}> {item.description}</Text>
|
||||
</Box>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
</Box>
|
||||
);
|
||||
} else if (item.type === 'utxo_received') {
|
||||
const sats = item.valueSatoshis ?? 0n;
|
||||
const reservedTag = item.reserved ? ' [Reserved]' : '';
|
||||
return (
|
||||
<Box key={item.id} flexDirection='row' justifyContent='space-between'>
|
||||
<Box flexDirection='row'>
|
||||
<Text color={isSelected ? colors.focus : colors.success}>
|
||||
{indicator}{formatSatoshis(sats)}
|
||||
</Text>
|
||||
<Text color={colors.textMuted}>
|
||||
{' '}{item.description}{reservedTag}
|
||||
</Text>
|
||||
</Box>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback for other types
|
||||
return (
|
||||
<Box key={item.id} flexDirection='row' justifyContent='space-between'>
|
||||
<Text color={isSelected ? colors.focus : colors.text}>
|
||||
{indicator}{item.type}: {item.description}
|
||||
</Text>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
})()
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user