/** * 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(null); /** * Dialog context for managing modal dialogs. */ interface DialogContextType { dialog: DialogState | null; setDialog: (dialog: DialogState | null) => void; } const DialogContext = createContext(null); /** * Status context for managing status bar. */ interface StatusContextType { status: string; setStatus: (status: string) => void; } const StatusContext = createContext(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(null); const [dialog, setDialog] = useState(null); const [status, setStatusState] = useState('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 => { 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 ( {children} ); } /** * 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; }