Files
fusion/packages/dashboard/app/hooks/useDeepLink.ts
gsxdsm 26732e7e43 feat(FN-1719): rebuild eslint baseline and add memory lessons
- Rebuild eslint config with context-aware flat config for better TypeScript/JSX handling
- Add memory lessons for lint/type/test baseline restoration
- Skip pre-existing flaky stream test (flushes a final complete event)
- Fix api.ts require import path
- Add ProjectEngineManager.startReconciliation mock to tests for main compatibility
2026-04-13 18:21:31 -07:00

107 lines
2.9 KiB
TypeScript

import { useCallback, useEffect, useRef } from "react";
import type { TaskDetail } from "@fusion/core";
import { fetchTaskDetail, type ProjectInfo } from "../api";
import type { ToastType } from "./useToast";
interface UseDeepLinkOptions {
projectId?: string;
projects: ProjectInfo[];
projectsLoading: boolean;
currentProject: ProjectInfo | null;
setCurrentProject: (project: ProjectInfo) => void;
addToast: (message: string, type?: ToastType) => void;
openTaskDetail: (task: TaskDetail) => void;
closeTaskDetail: () => void;
}
export interface UseDeepLinkResult {
/**
* Call when the task detail modal closes.
* Cleans ?task=... from URL if the modal was opened via deep-link.
*/
handleDetailClose: () => void;
}
/**
* Handles task deep-link behavior (?project=...&task=...).
*/
export function useDeepLink(options: UseDeepLinkOptions): UseDeepLinkResult {
const {
projectId,
projects,
projectsLoading,
currentProject,
setCurrentProject,
addToast,
openTaskDetail,
closeTaskDetail,
} = options;
// Prevent duplicate fetches when project switching causes the effect to re-run.
const deepLinkFetchedRef = useRef(false);
// Track whether the currently open detail modal came from a deep-link.
const deepLinkTaskIdRef = useRef<string | null>(null);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const projectParam = params.get("project");
const taskId = params.get("task");
if (!taskId) return;
if (projectsLoading) return;
if (projectParam) {
const matchingProject = projects.find((project) => project.id === projectParam);
if (!matchingProject) {
addToast(`Project '${projectParam}' not found`, "error");
return;
}
if (currentProject?.id !== matchingProject.id) {
setCurrentProject(matchingProject);
}
}
if (deepLinkFetchedRef.current) return;
deepLinkFetchedRef.current = true;
const taskProjectId = projectParam ?? projectId;
fetchTaskDetail(taskId, taskProjectId)
.then((detail) => {
openTaskDetail(detail);
deepLinkTaskIdRef.current = taskId;
})
.catch(() => {
addToast(`Task ${taskId} not found`, "error");
});
}, [
projectId,
projects,
projectsLoading,
currentProject,
setCurrentProject,
addToast,
openTaskDetail,
// deepLinkFetchedRef intentionally excluded - it's a mutable ref, not state
]);
const handleDetailClose = useCallback(() => {
if (deepLinkTaskIdRef.current) {
const params = new URLSearchParams(window.location.search);
params.delete("task");
const query = params.toString();
window.history.replaceState(
null,
"",
query ? `${window.location.pathname}?${query}` : window.location.pathname,
);
deepLinkTaskIdRef.current = null;
}
closeTaskDetail();
}, [closeTaskDetail]);
return { handleDetailClose };
}