"use client"; import { useEffect, useCallback, useRef } from "react"; import { listLinearIssueWatches, createLinearIssueWatch, updateLinearIssueWatch, deleteLinearIssueWatch, triggerLinearIssueWatch, previewResetLinearIssueWatch, resetLinearIssueWatch, } from "@/lib/api/domains/linear-api"; import { useAppStore } from "@/components/state-provider"; import type { CreateLinearIssueWatchInput, UpdateLinearIssueWatchInput } from "@/lib/types/linear"; // WORKSPACE_REQUIRED is thrown by per-row mutation callbacks when the // install-wide listing case forgets to forward the row's workspaceId. const WORKSPACE_REQUIRED = "workspaceId required"; /** * useLinearIssueWatches owns the Linear-watcher list: * - workspaceId: string → fetch and operate on watches in one workspace * - workspaceId: undefined → fetch every watch across all workspaces; the * caller supplies workspaceId to update/remove/trigger * calls per-row (those endpoints still validate it * against the watch's stored workspace as an IDOR guard) * - workspaceId: null → don't fetch * * Mirrors `useJiraIssueWatches`. Workspace changes reset the cached list so * the user doesn't see the previous workspace's stale rows during the swap. */ export function useLinearIssueWatches(workspaceId?: string | null) { const items = useAppStore((s) => s.linearIssueWatches.items); const loaded = useAppStore((s) => s.linearIssueWatches.loaded); const loading = useAppStore((s) => s.linearIssueWatches.loading); const setWatches = useAppStore((s) => s.setLinearIssueWatches); const resetWatches = useAppStore((s) => s.resetLinearIssueWatches); const setLoading = useAppStore((s) => s.setLinearIssueWatchesLoading); const addWatch = useAppStore((s) => s.addLinearIssueWatch); const updateWatch = useAppStore((s) => s.updateLinearIssueWatch); const removeWatch = useAppStore((s) => s.removeLinearIssueWatch); const lastScope = useRef(undefined); const scope: string | null = workspaceId ?? null; useEffect(() => { if (workspaceId === null) return; if (lastScope.current !== undefined && lastScope.current !== scope) { resetWatches(); } lastScope.current = scope; }, [workspaceId, scope, resetWatches]); useEffect(() => { if (workspaceId === null || loaded || loading) return; setLoading(true); listLinearIssueWatches(workspaceId ?? undefined, { cache: "no-store" }) .then((res) => setWatches(res ?? [])) .catch(() => setWatches([])) .finally(() => setLoading(false)); }, [workspaceId, loaded, loading, setWatches, setLoading]); const create = useCallback( async (req: CreateLinearIssueWatchInput) => { const watch = await createLinearIssueWatch(req); addWatch(watch); return watch; }, [addWatch], ); // Per-row mutations require the row's own workspace_id to satisfy the // backend's IDOR guard. Callers pass it explicitly when the hook itself // wasn't bound to a single workspace (the install-wide listing case). const update = useCallback( async (id: string, req: UpdateLinearIssueWatchInput, rowWorkspaceId?: string) => { const ws = rowWorkspaceId ?? workspaceId; if (!ws) throw new Error(WORKSPACE_REQUIRED); const watch = await updateLinearIssueWatch(ws, id, req); updateWatch(watch); return watch; }, [workspaceId, updateWatch], ); const remove = useCallback( async (id: string, rowWorkspaceId?: string) => { const ws = rowWorkspaceId ?? workspaceId; if (!ws) throw new Error(WORKSPACE_REQUIRED); await deleteLinearIssueWatch(ws, id); removeWatch(id); }, [workspaceId, removeWatch], ); const trigger = useCallback( async (id: string, rowWorkspaceId?: string) => { const ws = rowWorkspaceId ?? workspaceId; if (!ws) throw new Error(WORKSPACE_REQUIRED); return triggerLinearIssueWatch(ws, id); }, [workspaceId], ); const previewReset = useCallback( async (id: string, rowWorkspaceId?: string) => { const ws = rowWorkspaceId ?? workspaceId; if (!ws) throw new Error(WORKSPACE_REQUIRED); return previewResetLinearIssueWatch(ws, id); }, [workspaceId], ); const reset = useCallback( async (id: string, rowWorkspaceId?: string) => { const ws = rowWorkspaceId ?? workspaceId; if (!ws) throw new Error(WORKSPACE_REQUIRED); const res = await resetLinearIssueWatch(ws, id); resetWatches(); return res; }, [workspaceId, resetWatches], ); return { items, loaded, loading, create, update, remove, trigger, previewReset, reset }; }