Files
xo-cli/src/tui/hooks/useAppContext.tsx
2026-02-08 02:32:50 +00:00

206 lines
4.8 KiB
TypeScript

/**
* App context hook for accessing AppService and app-level functions.
*/
import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
import { AppService } from '../../services/app.js';
import type { AppConfig } from '../../app.js';
import type { AppContextType, DialogState } from '../types.js';
/**
* App context.
*/
const AppContext = createContext<AppContextType | null>(null);
/**
* Dialog context for managing modal dialogs.
*/
interface DialogContextType {
dialog: DialogState | null;
setDialog: (dialog: DialogState | null) => void;
}
const DialogContext = createContext<DialogContextType | null>(null);
/**
* Status context for managing status bar.
*/
interface StatusContextType {
status: string;
setStatus: (status: string) => void;
}
const StatusContext = createContext<StatusContextType | null>(null);
/**
* App provider props.
*/
interface AppProviderProps {
children: ReactNode;
config: AppConfig;
onExit: () => void;
}
/**
* App provider component.
* Provides AppService, dialog management, and app-level functions to children.
*/
export function AppProvider({
children,
config,
onExit,
}: AppProviderProps): React.ReactElement {
const [appService, setAppService] = useState<AppService | null>(null);
const [dialog, setDialog] = useState<DialogState | null>(null);
const [status, setStatusState] = useState<string>('Ready');
const [isWalletInitialized, setWalletInitialized] = useState(false);
/**
* Initialize wallet with seed phrase and create AppService.
*/
const initializeWallet = useCallback(async (seed: string) => {
try {
// Create the AppService with the seed
const service = await AppService.create(seed, {
syncServerUrl: config.syncServerUrl,
engineConfig: {
databasePath: config.databasePath,
databaseFilename: config.databaseFilename,
},
invitationStoragePath: config.invitationStoragePath,
});
// Start the AppService (loads existing invitations)
await service.start();
// Set the service and mark as initialized
setAppService(service);
setWalletInitialized(true);
} catch (error) {
// Re-throw the error so the caller can handle it
throw error;
}
}, [config]);
/**
* Show an error dialog.
*/
const showError = useCallback((message: string) => {
setDialog({
visible: true,
type: 'error',
message,
onCancel: () => setDialog(null),
});
}, []);
/**
* Show an info dialog.
*/
const showInfo = useCallback((message: string) => {
setDialog({
visible: true,
type: 'info',
message,
onCancel: () => setDialog(null),
});
}, []);
/**
* Show a confirmation dialog and wait for user response.
*/
const confirm = useCallback((message: string): Promise<boolean> => {
return new Promise((resolve) => {
setDialog({
visible: true,
type: 'confirm',
message,
onConfirm: () => {
setDialog(null);
resolve(true);
},
onCancel: () => {
setDialog(null);
resolve(false);
},
});
});
}, []);
/**
* Update status bar message.
*/
const setStatus = useCallback((message: string) => {
setStatusState(message);
}, []);
const appValue: AppContextType = {
appService,
initializeWallet,
isWalletInitialized,
config,
showError,
showInfo,
confirm,
exit: onExit,
setStatus,
};
const dialogValue: DialogContextType = {
dialog,
setDialog,
};
const statusValue: StatusContextType = {
status,
setStatus,
};
return (
<AppContext.Provider value={appValue}>
<DialogContext.Provider value={dialogValue}>
<StatusContext.Provider value={statusValue}>
{children}
</StatusContext.Provider>
</DialogContext.Provider>
</AppContext.Provider>
);
}
/**
* Hook to access app context.
* @returns App context
* @throws Error if used outside AppProvider
*/
export function useAppContext(): AppContextType {
const context = useContext(AppContext);
if (!context) {
throw new Error('useAppContext must be used within an AppProvider');
}
return context;
}
/**
* Hook to access dialog context.
* @returns Dialog context
*/
export function useDialog(): DialogContextType {
const context = useContext(DialogContext);
if (!context) {
throw new Error('useDialog must be used within an AppProvider');
}
return context;
}
/**
* Hook to access status context.
* @returns Status context
*/
export function useStatus(): StatusContextType {
const context = useContext(StatusContext);
if (!context) {
throw new Error('useStatus must be used within an AppProvider');
}
return context;
}