/** * Main App component for the XO Wallet CLI. * Uses Ink for terminal rendering with React components. */ import React from 'react'; import { Box, Text, useApp } from 'ink'; import { NavigationProvider, useNavigation } from './hooks/useNavigation.js'; import { AppProvider, useAppContext, useDialog, useStatus } from './hooks/useAppContext.js'; import { InputLayerProvider, useBlockableInput } from './hooks/useInputLayer.js'; import type { AppConfig } from '../app.js'; import { colors, logoSmall } from './theme.js'; // Screen imports import { SeedInputScreen } from './screens/SeedInput.js'; import { WalletStateScreen } from './screens/WalletState.js'; import { TemplateListScreen } from './screens/TemplateList.js'; import { ActionWizardScreen } from './screens/action-wizard/ActionWizardScreen.js'; import { InvitationScreen } from './screens/invitations/InvitationScreen.js'; import { TransactionScreen } from './screens/Transaction.js'; import { MessageDialog } from './components/Dialog.js'; /** * Props for the App component. */ interface AppProps { config: AppConfig; } /** * Router component that renders the current screen. */ function Router(): React.ReactElement { const { screen } = useNavigation(); switch (screen) { case 'seed-input': return ; case 'wallet': return ; case 'templates': return ; case 'wizard': return ; case 'invitations': return ; case 'transaction': return ; default: return Unknown screen: {screen}; } } /** * Status bar component shown at the bottom of the screen. */ function StatusBar(): React.ReactElement { const { status } = useStatus(); const { screen, canGoBack } = useNavigation(); return ( {logoSmall} {status} {canGoBack ? 'ESC: Back | ' : ''}q: Quit ); } /** * Dialog overlay component for modals. */ function DialogOverlay(): React.ReactElement | null { const { dialog } = useDialog(); if (!dialog?.visible) return null; const borderColor = dialog.type === 'error' ? colors.error : dialog.type === 'confirm' ? colors.warning : colors.info; return ( {})} type={dialog.type as 'error' | 'info' | 'success'} /> ); } /** * Main content wrapper with global keybindings. */ function MainContent(): React.ReactElement { const { exit } = useApp(); const { goBack, canGoBack } = useNavigation(); const { screen } = useNavigation(); const appContext = useAppContext(); // Global keybindings — auto-blocked when any dialog/overlay is capturing input. useBlockableInput((input, key) => { // Quit on Ctrl+C if (key.ctrl && input === 'c') { appContext.exit(); exit(); } // Go back on Escape if (key.escape && canGoBack) { goBack(); // If we went back to the seed input screen, remove the current engine // TODO: This was to support going back to seed input then re-opening your seed, but there is a bug in the engine which prevents it from closing the current // storage instance, giving us an error about the database already being opened. if (screen === 'seed-input') { appContext.appService?.engine.stop(); appContext.appService = null; } } }); return ( {/* Main content area */} {/* Status bar */} {/* Dialog overlay */} ); } /** * Main App component. * Sets up providers and renders the main content. */ export function App({ config }: AppProps): React.ReactElement { const { exit } = useApp(); // Cleanup will be handled by React when components unmount const handleExit = () => { exit(); }; return ( ); }