Files
xo-cli/src/tui/screens/SeedInput.tsx
2026-03-16 06:48:29 +00:00

181 lines
5.3 KiB
TypeScript

/**
* Seed Input Screen - Initial screen for wallet seed phrase entry.
*
* Allows users to enter their BIP39 seed phrase to initialize the wallet.
*/
import React, { useState, useCallback } from 'react';
import { Box, Text, useInput } from 'ink';
import TextInput from '../components/TextInput.js';
import { Button } from '../components/Button.js';
import { useNavigation } from '../hooks/useNavigation.js';
import { useAppContext, useStatus } from '../hooks/useAppContext.js';
import { colors, logo } from '../theme.js';
/**
* Status message type.
*/
type StatusType = 'idle' | 'loading' | 'error' | 'success';
/**
* Seed Input Screen Component.
* Provides seed phrase entry for wallet initialization.
*/
export function SeedInputScreen(): React.ReactElement {
const { navigate } = useNavigation();
const { initializeWallet } = useAppContext();
const { setStatus } = useStatus();
// State
const [seedPhrase, setSeedPhrase] = useState('');
const [statusMessage, setStatusMessage] = useState('');
const [statusType, setStatusType] = useState<StatusType>('idle');
const [focusedElement, setFocusedElement] = useState<'input' | 'button'>('input');
const [isSubmitting, setIsSubmitting] = useState(false);
/**
* Shows a status message with the given type.
*/
const showStatus = useCallback((message: string, type: StatusType) => {
setStatusMessage(message);
setStatusType(type);
}, []);
/**
* Handles seed phrase submission.
*/
const handleSubmit = useCallback(async () => {
const seed = seedPhrase.trim();
// Basic validation
if (!seed) {
showStatus('Please enter your seed phrase', 'error');
return;
}
const wordCount = seed.split(/\s+/).length;
if (wordCount !== 12 && wordCount !== 24) {
showStatus(`Invalid seed phrase. Expected 12 or 24 words, got ${wordCount}`, 'error');
return;
}
// Show loading status
showStatus('Initializing wallet...', 'loading');
setStatus('Initializing wallet...');
setIsSubmitting(true);
try {
// Initialize wallet and create AppService
await initializeWallet(seed);
showStatus('Wallet initialized successfully!', 'success');
setStatus('Wallet ready');
// Clear sensitive data before navigating
setSeedPhrase('');
// Navigate to wallet state screen
setTimeout(() => {
navigate('wallet');
}, 500);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to initialize wallet';
showStatus(message, 'error');
setStatus('Initialization failed');
setIsSubmitting(false);
}
}, [seedPhrase, initializeWallet, navigate, showStatus, setStatus]);
// Handle keyboard navigation
useInput((input, key) => {
if (isSubmitting) return;
// Tab to switch focus
if (key.tab) {
setFocusedElement(prev => prev === 'input' ? 'button' : 'input');
}
// Enter on button submits
if (key.return && focusedElement === 'button') {
handleSubmit();
}
});
// Get status color
const statusColor = statusType === 'error' ? colors.error :
statusType === 'success' ? colors.success :
statusType === 'loading' ? colors.info :
colors.textMuted;
// Get border color based on status
const inputBorderColor = statusType === 'error' ? colors.error :
statusType === 'success' ? colors.success :
focusedElement === 'input' ? colors.focus :
colors.border;
return (
<Box flexDirection='column' alignItems='center' paddingY={1}>
{/* Logo */}
<Box marginBottom={1}>
<Text color={colors.primary}>{logo}</Text>
</Box>
{/* Title */}
<Text color={colors.text} bold>Welcome to XO Wallet CLI</Text>
<Text color={colors.textMuted}>Enter your seed phrase to get started</Text>
{/* Spacer */}
<Box marginY={1} />
{/* Input section */}
<Box flexDirection='column' width={64}>
<Text color={colors.text} bold>Seed Phrase (12 or 24 words):</Text>
<Box
borderStyle='single'
borderColor={inputBorderColor}
paddingX={1}
marginTop={1}
>
<TextInput
value={seedPhrase}
onChange={setSeedPhrase}
onSubmit={handleSubmit}
placeholder='Enter your seed phrase...'
focus={focusedElement === 'input' && !isSubmitting}
/>
</Box>
{/* Status message */}
<Box marginTop={1} height={1}>
{statusMessage && (
<Text color={statusColor}>
{statusType === 'loading' && '⏳ '}
{statusType === 'error' && '✗ '}
{statusType === 'success' && '✓ '}
{statusMessage}
</Text>
)}
</Box>
{/* Submit button */}
<Box justifyContent='center' marginTop={1}>
<Button
label='Continue'
focused={focusedElement === 'button'}
disabled={isSubmitting}
shortcut='Enter'
/>
</Box>
</Box>
{/* Help text */}
<Box marginTop={2}>
<Text color={colors.textMuted} dimColor>
Tab: navigate Enter: submit q: quit
</Text>
</Box>
</Box>
);
}