fix(FN-2572): remove dead onboarding modal exports
- Delete obsolete AppModals code that was accidentally left in ModelOnboardingModal.tsx - Restore the Task type import used by the onboarding modal after dead code cleanup - Drop the unused SettingsModal.css import tied to removed code paths - Extend ModelOnboardingModal tests to guard against dead export regressions
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import "./ModelOnboardingModal.css";
|
||||
import "./SettingsModal.css";
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { X, Loader2, CheckCircle, Key, Zap, GitPullRequest, Rocket, Plus, ChevronRight } from "lucide-react";
|
||||
import type { Task } from "@fusion/core";
|
||||
import type { AuthProvider, ModelInfo } from "../api";
|
||||
import {
|
||||
fetchAuthStatus,
|
||||
@@ -502,7 +502,6 @@ import {
|
||||
type OnboardingStep,
|
||||
} from "./model-onboarding-state";
|
||||
import { trackOnboardingEvent } from "./onboarding-events";
|
||||
import type { SectionId } from "./SettingsModal";
|
||||
|
||||
export interface ModelOnboardingModalProps {
|
||||
/** Called when onboarding is complete or dismissed */
|
||||
@@ -2492,349 +2491,3 @@ export function ModelOnboardingModal({
|
||||
);
|
||||
}
|
||||
|
||||
import type { ProjectInfo } from "../api";
|
||||
import type { ColorTheme, Column, MergeResult, Task, ThemeMode } from "@fusion/core";
|
||||
import type { UseProjectActionsResult } from "../hooks/useProjectActions";
|
||||
import type { ModalManager } from "../hooks/useModalManager";
|
||||
import type { UseTaskHandlersResult } from "../hooks/useTaskHandlers";
|
||||
import type { Toast } from "../hooks/useToast";
|
||||
import { ModalErrorBoundary } from "./ErrorBoundary";
|
||||
import { TaskDetailModal } from "./TaskDetailModal";
|
||||
import { SettingsModal } from "./SettingsModal";
|
||||
import { GitHubImportModal } from "./GitHubImportModal";
|
||||
import { PlanningModeModal } from "./PlanningModeModal";
|
||||
import { SubtaskBreakdownModal } from "./SubtaskBreakdownModal";
|
||||
import { TerminalModal } from "./TerminalModal";
|
||||
import { ScriptsModal } from "./ScriptsModal";
|
||||
import { FileBrowserModal } from "./FileBrowserModal";
|
||||
import { UsageIndicator } from "./UsageIndicator";
|
||||
import { ScheduledTasksModal } from "./ScheduledTasksModal";
|
||||
import { NewTaskModal } from "./NewTaskModal";
|
||||
import { ActivityLogModal } from "./ActivityLogModal";
|
||||
import { GitManagerModal } from "./GitManagerModal";
|
||||
import { WorkflowStepManager } from "./WorkflowStepManager";
|
||||
import { AgentListModal } from "./AgentListModal";
|
||||
import { SetupWizardModal } from "./SetupWizardModal";
|
||||
import { ToastContainer } from "./ToastContainer";
|
||||
|
||||
interface AppModalsProps {
|
||||
projectId?: string;
|
||||
tasks: Task[];
|
||||
projects: ProjectInfo[];
|
||||
currentProject: ProjectInfo | null;
|
||||
addToast: (message: string, type?: ToastType) => void;
|
||||
toasts: Toast[];
|
||||
removeToast: (id: number) => void;
|
||||
modalManager: ModalManager;
|
||||
projectActions: Pick<UseProjectActionsResult, "handleSetupComplete" | "handleModelOnboardingComplete">;
|
||||
taskHandlers: Pick<UseTaskHandlersResult, "handleModalCreate" | "handlePlanningTaskCreated" | "handlePlanningTasksCreated" | "handleSubtaskTasksCreated" | "handleGitHubImport">;
|
||||
taskOperations: {
|
||||
moveTask: (taskId: string, column: Column, position?: number) => Promise<Task>;
|
||||
deleteTask: (taskId: string) => Promise<Task>;
|
||||
mergeTask: (taskId: string) => Promise<MergeResult>;
|
||||
retryTask: (taskId: string) => Promise<Task>;
|
||||
duplicateTask: (taskId: string) => Promise<Task>;
|
||||
};
|
||||
deepLink: {
|
||||
handleDetailClose: () => void;
|
||||
};
|
||||
settings: {
|
||||
prAuthAvailable: boolean;
|
||||
themeMode: ThemeMode;
|
||||
colorTheme: ColorTheme;
|
||||
setThemeMode: (mode: ThemeMode) => void;
|
||||
setColorTheme: (theme: ColorTheme) => void;
|
||||
};
|
||||
/** Optional override for the settings modal close handler. When provided, this is called instead of modalManager.closeSettings. */
|
||||
onSettingsClose?: () => void;
|
||||
}
|
||||
|
||||
export function AppModals({
|
||||
projectId,
|
||||
tasks,
|
||||
projects,
|
||||
currentProject,
|
||||
addToast,
|
||||
toasts,
|
||||
removeToast,
|
||||
modalManager,
|
||||
projectActions,
|
||||
taskHandlers,
|
||||
taskOperations,
|
||||
deepLink,
|
||||
settings,
|
||||
onSettingsClose,
|
||||
}: AppModalsProps) {
|
||||
// Use the override handler if provided, otherwise fall back to modalManager.closeSettings
|
||||
const handleSettingsClose = onSettingsClose ?? modalManager.closeSettings;
|
||||
|
||||
// Handlers for onboarding CTAs
|
||||
const handleOpenNewTask = useCallback(() => {
|
||||
modalManager.openNewTask();
|
||||
}, [modalManager]);
|
||||
|
||||
const handleOpenGitHubImport = useCallback(() => {
|
||||
modalManager.openGitHubImport();
|
||||
}, [modalManager]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalManager.detailTask && (
|
||||
<ModalErrorBoundary>
|
||||
<TaskDetailModal
|
||||
task={modalManager.detailTask}
|
||||
projectId={projectId}
|
||||
tasks={tasks}
|
||||
onClose={deepLink.handleDetailClose}
|
||||
onOpenDetail={modalManager.openDetailTask}
|
||||
onMoveTask={taskOperations.moveTask}
|
||||
onDeleteTask={taskOperations.deleteTask}
|
||||
onMergeTask={taskOperations.mergeTask}
|
||||
onRetryTask={taskOperations.retryTask}
|
||||
onDuplicateTask={taskOperations.duplicateTask}
|
||||
onTaskUpdated={modalManager.updateDetailTask}
|
||||
addToast={addToast}
|
||||
prAuthAvailable={settings.prAuthAvailable}
|
||||
initialTab={modalManager.detailTaskInitialTab}
|
||||
/>
|
||||
</ModalErrorBoundary>
|
||||
)}
|
||||
|
||||
{modalManager.settingsOpen && (
|
||||
<ModalErrorBoundary>
|
||||
<SettingsModal
|
||||
onClose={handleSettingsClose}
|
||||
addToast={addToast}
|
||||
initialSection={modalManager.settingsInitialSection}
|
||||
projectId={projectId}
|
||||
themeMode={settings.themeMode}
|
||||
colorTheme={settings.colorTheme}
|
||||
onThemeModeChange={settings.setThemeMode}
|
||||
onColorThemeChange={settings.setColorTheme}
|
||||
/>
|
||||
</ModalErrorBoundary>
|
||||
)}
|
||||
|
||||
<GitHubImportModal
|
||||
isOpen={modalManager.githubImportOpen}
|
||||
onClose={modalManager.closeGitHubImport}
|
||||
onImport={taskHandlers.handleGitHubImport}
|
||||
tasks={tasks}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<ModalErrorBoundary>
|
||||
<PlanningModeModal
|
||||
isOpen={modalManager.isPlanningOpen}
|
||||
onClose={modalManager.closePlanning}
|
||||
onTaskCreated={taskHandlers.handlePlanningTaskCreated}
|
||||
onTasksCreated={taskHandlers.handlePlanningTasksCreated}
|
||||
tasks={tasks}
|
||||
initialPlan={modalManager.planningInitialPlan ?? undefined}
|
||||
projectId={projectId}
|
||||
resumeSessionId={modalManager.planningResumeSessionId}
|
||||
/>
|
||||
</ModalErrorBoundary>
|
||||
|
||||
<ModalErrorBoundary>
|
||||
<SubtaskBreakdownModal
|
||||
isOpen={modalManager.isSubtaskOpen}
|
||||
onClose={modalManager.closeSubtask}
|
||||
initialDescription={modalManager.subtaskInitialDescription ?? ""}
|
||||
onTasksCreated={taskHandlers.handleSubtaskTasksCreated}
|
||||
projectId={projectId}
|
||||
resumeSessionId={modalManager.subtaskResumeSessionId}
|
||||
/>
|
||||
</ModalErrorBoundary>
|
||||
|
||||
<TerminalModal
|
||||
isOpen={modalManager.terminalOpen}
|
||||
onClose={modalManager.closeTerminal}
|
||||
initialCommand={modalManager.terminalInitialCommand}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<ScriptsModal
|
||||
isOpen={modalManager.scriptsOpen}
|
||||
onClose={modalManager.closeScripts}
|
||||
addToast={addToast}
|
||||
onRunScript={modalManager.runScript}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
{modalManager.filesOpen && (
|
||||
<FileBrowserModal
|
||||
initialWorkspace={modalManager.fileBrowserWorkspace}
|
||||
isOpen={true}
|
||||
onClose={modalManager.closeFiles}
|
||||
onWorkspaceChange={modalManager.setFileWorkspace}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
|
||||
<UsageIndicator
|
||||
isOpen={modalManager.usageOpen}
|
||||
onClose={modalManager.closeUsage}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
{modalManager.schedulesOpen && (
|
||||
<ScheduledTasksModal
|
||||
onClose={modalManager.closeSchedules}
|
||||
addToast={addToast}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ModalErrorBoundary>
|
||||
<NewTaskModal
|
||||
isOpen={modalManager.newTaskModalOpen}
|
||||
onClose={modalManager.closeNewTask}
|
||||
tasks={tasks}
|
||||
onCreateTask={taskHandlers.handleModalCreate}
|
||||
addToast={addToast}
|
||||
projectId={projectId}
|
||||
onPlanningMode={modalManager.openPlanningWithInitialPlan}
|
||||
onSubtaskBreakdown={modalManager.openSubtaskBreakdown}
|
||||
/>
|
||||
</ModalErrorBoundary>
|
||||
|
||||
<ActivityLogModal
|
||||
isOpen={modalManager.activityLogOpen}
|
||||
onClose={modalManager.closeActivityLog}
|
||||
tasks={tasks}
|
||||
projectId={projectId}
|
||||
projects={projects}
|
||||
currentProject={currentProject}
|
||||
onOpenTaskDetail={(taskId) => {
|
||||
const task = tasks.find((candidate) => candidate.id === taskId);
|
||||
if (task) {
|
||||
modalManager.openDetailTask(task);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<ModalErrorBoundary>
|
||||
<GitManagerModal
|
||||
isOpen={modalManager.gitManagerOpen}
|
||||
onClose={modalManager.closeGitManager}
|
||||
tasks={tasks}
|
||||
addToast={addToast}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</ModalErrorBoundary>
|
||||
|
||||
<ModalErrorBoundary>
|
||||
<WorkflowStepManager
|
||||
isOpen={modalManager.workflowStepsOpen}
|
||||
onClose={modalManager.closeWorkflowSteps}
|
||||
addToast={addToast}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</ModalErrorBoundary>
|
||||
|
||||
<AgentListModal
|
||||
isOpen={modalManager.agentsOpen}
|
||||
onClose={modalManager.closeAgents}
|
||||
addToast={addToast}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
{modalManager.setupWizardOpen && (
|
||||
<SetupWizardModal
|
||||
onProjectRegistered={projectActions.handleSetupComplete}
|
||||
onClose={modalManager.closeSetupWizard}
|
||||
/>
|
||||
)}
|
||||
|
||||
{modalManager.modelOnboardingOpen && (
|
||||
<ModelOnboardingModal
|
||||
onComplete={projectActions.handleModelOnboardingComplete}
|
||||
addToast={addToast}
|
||||
onOpenNewTask={handleOpenNewTask}
|
||||
onOpenGitHubImport={handleOpenGitHubImport}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ToastContainer toasts={toasts} onRemove={removeToast} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export interface UseAuthOnboardingOptions {
|
||||
projectId?: string;
|
||||
openModelOnboarding: () => void;
|
||||
openSettings: (section?: SectionId) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs auth/onboarding checks and opens the appropriate setup modal.
|
||||
*
|
||||
* This hook implements a one-shot guard: the auto-trigger logic runs at most
|
||||
* once per hook instance (regardless of effect re-runs due to dependency changes).
|
||||
* This prevents repeat auto-opens on incidental rerenders or project context churn.
|
||||
*
|
||||
* Trigger behavior:
|
||||
* - First-run (onboarding incomplete): opens model onboarding wizard
|
||||
* - Completed onboarding + unauthenticated providers: opens Settings → Authentication
|
||||
* - Already configured: no auto-open
|
||||
*/
|
||||
export function useAuthOnboarding({
|
||||
projectId,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}: UseAuthOnboardingOptions): void {
|
||||
// One-shot guard: prevents the auto-trigger logic from running more than once
|
||||
// per hook instance, even if the effect re-runs due to dependency changes.
|
||||
const hasTriggeredRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Skip if we've already triggered (one-shot guard)
|
||||
if (hasTriggeredRef.current) return;
|
||||
// Mark as triggered immediately to prevent any race condition on re-runs
|
||||
hasTriggeredRef.current = true;
|
||||
|
||||
let shouldOpenOnboarding = false;
|
||||
let shouldOpenSettings = false;
|
||||
|
||||
fetchAuthStatus()
|
||||
.then(({ providers }) => {
|
||||
const hasAuthenticatedProvider = providers.some((provider) => provider.authenticated);
|
||||
const needsSetup = providers.length > 0 && !hasAuthenticatedProvider;
|
||||
|
||||
if (needsSetup || (providers.length > 0 && hasAuthenticatedProvider)) {
|
||||
return fetchGlobalSettings()
|
||||
.then((globalSettings) => {
|
||||
const hasDefaultModel = !!(
|
||||
globalSettings.defaultProvider && globalSettings.defaultModelId
|
||||
);
|
||||
// Explicit first-run detection: onboarding is incomplete when
|
||||
// modelOnboardingComplete is false or undefined
|
||||
const onboardingIncomplete =
|
||||
globalSettings.modelOnboardingComplete === false ||
|
||||
globalSettings.modelOnboardingComplete === undefined;
|
||||
const setupIncomplete = !hasAuthenticatedProvider || !hasDefaultModel;
|
||||
|
||||
if (onboardingIncomplete && setupIncomplete) {
|
||||
shouldOpenOnboarding = true;
|
||||
} else if (!hasAuthenticatedProvider) {
|
||||
// Completed onboarding but no authenticated provider → fallback
|
||||
// to Settings Authentication section
|
||||
shouldOpenSettings = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// Execute after the promise chain resolves
|
||||
if (shouldOpenOnboarding) {
|
||||
openModelOnboarding();
|
||||
} else if (shouldOpenSettings) {
|
||||
openSettings("authentication");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Fail silently - non-blocking behavior preserves dashboard usability.
|
||||
// Onboarding can be manually triggered later via Settings if needed.
|
||||
});
|
||||
}, [projectId, openModelOnboarding, openSettings]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user