Initial Commit
This commit is contained in:
182
src/tui/screens/SeedInput.tsx
Normal file
182
src/tui/screens/SeedInput.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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 'ink-text-input';
|
||||
import { Screen } from '../components/Screen.js';
|
||||
import { Button, ButtonRow } 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 { walletController, showError, setWalletInitialized } = 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 via controller
|
||||
await walletController.initialize(seed);
|
||||
|
||||
showStatus('Wallet initialized successfully!', 'success');
|
||||
setStatus('Wallet ready');
|
||||
setWalletInitialized(true);
|
||||
|
||||
// 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, walletController, navigate, showStatus, setStatus, setWalletInitialized]);
|
||||
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user