181 lines
5.3 KiB
TypeScript
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>
|
|
);
|
|
}
|