"use client"; import { useEffect, useCallback, useRef, useState } from "react"; import { listSentryIssueWatches, createSentryIssueWatch, updateSentryIssueWatch, deleteSentryIssueWatch, triggerSentryIssueWatch, previewResetSentryIssueWatch, resetSentryIssueWatch, } from "@/lib/api/domains/sentry-api"; import type { CreateSentryIssueWatchRequest, SentryIssueWatch, UpdateSentryIssueWatchRequest, } from "@/lib/types/sentry"; /** * useSentryIssueWatches owns the Sentry-watcher list: * - workspaceId: string → fetch and operate on watches in one workspace * - workspaceId: undefined → fetch every watch across all workspaces * - workspaceId: null → don't fetch * * Mirrors `useLinearIssueWatches`: update/delete/trigger pass the watch's * workspace id as the `workspace_id` query param so the backend can guard * cross-workspace mutations. */ export function useSentryIssueWatches(workspaceId?: string | null) { const [items, setItems] = useState([]); const [loaded, setLoaded] = useState(false); const [loading, setLoading] = useState(false); // Track the raw workspaceId (not `?? null`) so the three scopes stay distinct: // undefined = fetch all, null = don't fetch, string = one workspace. Collapsing // undefined→null would hide an "all → none" transition and leave stale data. const initialized = useRef(false); const lastWorkspaceId = useRef(undefined); useEffect(() => { if (initialized.current && lastWorkspaceId.current !== workspaceId) { // Reset cached list on scope change (incl. → null). Also clear `loading`: // a fetch from the old scope can no longer complete into the new one (its // .finally is gated by `ignore`), and a → null scope starts no replacement // fetch, so without this the hook would stay stuck at loading=true. /* eslint-disable react-hooks/set-state-in-effect -- resetting cached state when scope changes */ setItems([]); setLoaded(false); setLoading(false); /* eslint-enable react-hooks/set-state-in-effect */ } initialized.current = true; lastWorkspaceId.current = workspaceId; }, [workspaceId]); useEffect(() => { if (workspaceId === null || loaded) return; // `loading` is intentionally NOT a dependency: setLoading(true) below would // otherwise re-run this effect, whose cleanup sets ignore=true on the // in-flight request, so its .finally skips setLoading(false) and the hook // sticks at loading=true forever. The `loaded` guard already prevents a // duplicate fetch, and `ignore` handles a workspaceId change mid-flight. let ignore = false; // eslint-disable-next-line react-hooks/set-state-in-effect -- starting external fetch setLoading(true); listSentryIssueWatches(workspaceId ?? undefined, { cache: "no-store" }) .then((res) => { if (ignore) return; setItems(res ?? []); setLoaded(true); }) .catch(() => { if (ignore) return; setItems([]); setLoaded(true); }) .finally(() => { if (!ignore) setLoading(false); }); return () => { ignore = true; }; }, [workspaceId, loaded]); const create = useCallback(async (req: CreateSentryIssueWatchRequest) => { const watch = await createSentryIssueWatch(req); setItems((prev) => [...prev, watch]); return watch; }, []); const update = useCallback( async (id: string, watchWorkspaceId: string, req: UpdateSentryIssueWatchRequest) => { const watch = await updateSentryIssueWatch(id, watchWorkspaceId, req); setItems((prev) => prev.map((w) => (w.id === watch.id ? watch : w))); return watch; }, [], ); const remove = useCallback(async (id: string, watchWorkspaceId: string) => { await deleteSentryIssueWatch(id, watchWorkspaceId); setItems((prev) => prev.filter((w) => w.id !== id)); }, []); const trigger = useCallback(async (id: string, watchWorkspaceId: string) => { return triggerSentryIssueWatch(id, watchWorkspaceId); }, []); const previewReset = useCallback(async (id: string, watchWorkspaceId: string) => { return previewResetSentryIssueWatch(id, watchWorkspaceId); }, []); const reset = useCallback(async (id: string, watchWorkspaceId: string) => { const res = await resetSentryIssueWatch(id, watchWorkspaceId); // Force the next render to refetch so the now-empty dedup set / cleared // last_polled_at land in the UI immediately. setLoaded(false); return res; }, []); return { items, loaded, loading, create, update, remove, trigger, previewReset, reset }; }