Completes the FN-3166 multi-step removal of the legacy dashboard roadmap feature (backend routes, frontend API wrappers, nav state), along with FN-3792 deep-link URL support, and initial reports plugin scaffold with WhatsApp dedupe retention pruning. Fusion-Task-Id: FN-3166
188 lines
6.1 KiB
TypeScript
188 lines
6.1 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import { fetchConfig, fetchSettings, updateSettings, updateGlobalSettings } from "../api";
|
|
import { setAutoReloadEnabled } from "../versionCheck";
|
|
|
|
/**
|
|
* Settings state and actions consumed by the dashboard App shell.
|
|
*/
|
|
export interface UseAppSettingsResult {
|
|
maxConcurrent: number;
|
|
rootDir: string;
|
|
autoMerge: boolean;
|
|
globalPaused: boolean;
|
|
enginePaused: boolean;
|
|
taskStuckTimeoutMs: number | undefined;
|
|
showQuickChatFAB: boolean;
|
|
prAuthAvailable: boolean;
|
|
settingsLoaded: boolean;
|
|
experimentalFeatures: Record<string, boolean>;
|
|
insightsEnabled: boolean;
|
|
memoryEnabled: boolean;
|
|
devServerEnabled: boolean;
|
|
todosEnabled: boolean;
|
|
autoReloadOnVersionChange: boolean;
|
|
toggleAutoMerge: () => Promise<void>;
|
|
toggleGlobalPause: () => Promise<void>;
|
|
toggleEnginePause: () => Promise<void>;
|
|
toggleShowQuickChatFAB: () => Promise<void>;
|
|
toggleAutoReloadOnVersionChange: () => Promise<void>;
|
|
/** Re-fetches settings from the backend to pick up changes made externally (e.g., by SettingsModal). */
|
|
refresh: () => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Loads per-project dashboard settings and exposes optimistic toggle handlers.
|
|
*/
|
|
export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
|
const [maxConcurrent, setMaxConcurrent] = useState(2);
|
|
const [rootDir, setRootDir] = useState<string>(".");
|
|
const [autoMerge, setAutoMerge] = useState(true);
|
|
const [globalPaused, setGlobalPaused] = useState(false);
|
|
const [enginePaused, setEnginePaused] = useState(false);
|
|
const [taskStuckTimeoutMs, setTaskStuckTimeoutMs] = useState<number | undefined>(undefined);
|
|
const [showQuickChatFAB, setShowQuickChatFAB] = useState(false);
|
|
const [prAuthAvailable, setPrAuthAvailable] = useState(false);
|
|
const [settingsLoaded, setSettingsLoaded] = useState(false);
|
|
const [experimentalFeatures, setExperimentalFeatures] = useState<Record<string, boolean>>({});
|
|
const [insightsEnabled, setInsightsEnabled] = useState(false);
|
|
const [memoryEnabled, setMemoryEnabled] = useState(false);
|
|
const [devServerEnabled, setDevServerEnabled] = useState(false);
|
|
const [todosEnabled, setTodosEnabled] = useState(false);
|
|
const [autoReloadOnVersionChange, setAutoReloadOnVersionChangeState] = useState(true);
|
|
|
|
/**
|
|
* Fetches config and settings from the backend and updates local state.
|
|
* Shared between the mount-time useEffect and the refresh() function.
|
|
*/
|
|
const refresh = useCallback(async () => {
|
|
const [configResult, settingsResult] = await Promise.allSettled([
|
|
fetchConfig(projectId),
|
|
fetchSettings(projectId),
|
|
]);
|
|
|
|
if (configResult.status === "fulfilled") {
|
|
setMaxConcurrent(configResult.value.maxConcurrent);
|
|
setRootDir(configResult.value.rootDir);
|
|
}
|
|
|
|
if (settingsResult.status === "fulfilled") {
|
|
const settings = settingsResult.value;
|
|
setAutoMerge(Boolean(settings.autoMerge));
|
|
setGlobalPaused(Boolean(settings.globalPause));
|
|
setEnginePaused(Boolean(settings.enginePaused));
|
|
setPrAuthAvailable(Boolean(settings.prAuthAvailable));
|
|
setTaskStuckTimeoutMs(settings.taskStuckTimeoutMs);
|
|
setShowQuickChatFAB(settings.showQuickChatFAB === true);
|
|
setExperimentalFeatures(settings.experimentalFeatures ?? {});
|
|
const features = settings.experimentalFeatures ?? {};
|
|
setInsightsEnabled(features.insights === true);
|
|
setMemoryEnabled(features.memoryView === true);
|
|
setDevServerEnabled(features.devServerView === true || features.devServer === true);
|
|
setTodosEnabled(features.todoView === true);
|
|
// Sync the module-level auto-reload guard with the persisted setting
|
|
const autoReload = settings.autoReloadOnVersionChange !== false;
|
|
setAutoReloadOnVersionChangeState(autoReload);
|
|
setAutoReloadEnabled(autoReload);
|
|
}
|
|
|
|
setSettingsLoaded(true);
|
|
}, [projectId]);
|
|
|
|
useEffect(() => {
|
|
setSettingsLoaded(false);
|
|
setExperimentalFeatures({});
|
|
setInsightsEnabled(false);
|
|
setMemoryEnabled(false);
|
|
setDevServerEnabled(false);
|
|
setTodosEnabled(false);
|
|
void refresh();
|
|
}, [refresh]);
|
|
|
|
const toggleAutoMerge = useCallback(async () => {
|
|
const next = !autoMerge;
|
|
setAutoMerge(next);
|
|
|
|
try {
|
|
await updateSettings({ autoMerge: next }, projectId);
|
|
} catch {
|
|
setAutoMerge(!next);
|
|
}
|
|
}, [autoMerge, projectId]);
|
|
|
|
const toggleGlobalPause = useCallback(async () => {
|
|
const next = !globalPaused;
|
|
setGlobalPaused(next);
|
|
|
|
try {
|
|
await updateSettings(
|
|
{
|
|
globalPause: next,
|
|
globalPauseReason: next ? "manual" : undefined,
|
|
},
|
|
projectId,
|
|
);
|
|
} catch {
|
|
setGlobalPaused(!next);
|
|
}
|
|
}, [globalPaused, projectId]);
|
|
|
|
const toggleEnginePause = useCallback(async () => {
|
|
const next = !enginePaused;
|
|
setEnginePaused(next);
|
|
|
|
try {
|
|
await updateSettings({ enginePaused: next }, projectId);
|
|
} catch {
|
|
setEnginePaused(!next);
|
|
}
|
|
}, [enginePaused, projectId]);
|
|
|
|
const toggleShowQuickChatFAB = useCallback(async () => {
|
|
const next = !showQuickChatFAB;
|
|
setShowQuickChatFAB(next);
|
|
|
|
try {
|
|
await updateSettings({ showQuickChatFAB: next }, projectId);
|
|
} catch {
|
|
setShowQuickChatFAB(!next);
|
|
}
|
|
}, [showQuickChatFAB, projectId]);
|
|
|
|
const toggleAutoReloadOnVersionChange = useCallback(async () => {
|
|
const next = !autoReloadOnVersionChange;
|
|
setAutoReloadOnVersionChangeState(next);
|
|
setAutoReloadEnabled(next);
|
|
|
|
try {
|
|
await updateGlobalSettings({ autoReloadOnVersionChange: next });
|
|
} catch {
|
|
setAutoReloadOnVersionChangeState(!next);
|
|
setAutoReloadEnabled(!next);
|
|
}
|
|
}, [autoReloadOnVersionChange]);
|
|
|
|
return {
|
|
maxConcurrent,
|
|
rootDir,
|
|
autoMerge,
|
|
globalPaused,
|
|
enginePaused,
|
|
taskStuckTimeoutMs,
|
|
showQuickChatFAB,
|
|
prAuthAvailable,
|
|
settingsLoaded,
|
|
experimentalFeatures,
|
|
insightsEnabled,
|
|
memoryEnabled,
|
|
devServerEnabled,
|
|
todosEnabled,
|
|
autoReloadOnVersionChange,
|
|
toggleAutoMerge,
|
|
toggleGlobalPause,
|
|
toggleEnginePause,
|
|
toggleShowQuickChatFAB,
|
|
toggleAutoReloadOnVersionChange,
|
|
refresh,
|
|
};
|
|
}
|