import { useMutation, useQueryClient } from "@tanstack/react-query"; import { api } from "../api"; import { inboxKeys } from "./queries"; import { useWorkspaceId } from "../hooks"; import type { InboxItem } from "../types"; export function useMarkInboxRead() { const qc = useQueryClient(); const wsId = useWorkspaceId(); return useMutation({ mutationFn: (id: string) => api.markInboxRead(id), onMutate: async (id) => { await qc.cancelQueries({ queryKey: inboxKeys.all(wsId) }); const prev = qc.getQueryData(inboxKeys.list(wsId)); const prevArchived = qc.getQueryData(inboxKeys.archived(wsId)); const markRead = (old: InboxItem[] | undefined) => old?.map((item) => (item.id === id ? { ...item, read: true } : item)); qc.setQueryData(inboxKeys.list(wsId), markRead); // Opening a notification from the archived sub-view marks it read too — // patch that cache as well, or its unread dot would sit there until the // next refetch. qc.setQueryData(inboxKeys.archived(wsId), markRead); return { prev, prevArchived }; }, onError: (_err, _id, ctx) => { if (ctx?.prev) qc.setQueryData(inboxKeys.list(wsId), ctx.prev); if (ctx?.prevArchived) qc.setQueryData(inboxKeys.archived(wsId), ctx.prevArchived); }, onSettled: () => { qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) }); }, }); } export function useArchiveInbox() { const qc = useQueryClient(); const wsId = useWorkspaceId(); return useMutation({ mutationFn: (id: string) => api.archiveInbox(id), onMutate: async (id) => { await qc.cancelQueries({ queryKey: inboxKeys.list(wsId) }); const prev = qc.getQueryData(inboxKeys.list(wsId)); // Archive all items for the same issue (same behavior as store) const target = prev?.find((i) => i.id === id); const issueId = target?.issue_id; qc.setQueryData(inboxKeys.list(wsId), (old) => old?.map((item) => item.id === id || (issueId && item.issue_id === issueId) ? { ...item, archived: true } : item, ), ); return { prev }; }, onError: (_err, _id, ctx) => { if (ctx?.prev) qc.setQueryData(inboxKeys.list(wsId), ctx.prev); }, onSettled: () => { // Both lists: the item just moved from the main inbox into the archive. qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) }); }, }); } /** * Restore an archived notification to the main inbox. * * Optimistic on the ARCHIVED cache only: flipping `archived` there makes the * row leave the archived list at once (the dedup helper filters on it), the * user stays put, and rollback is a single snapshot restore. The main list is * left to `onSettled` — its contents after a restore are the server's call * (which sibling rows come back, their read state, their order), so it is * invalidated rather than reconstructed client-side. */ export function useUnarchiveInbox() { const qc = useQueryClient(); const wsId = useWorkspaceId(); return useMutation({ mutationFn: (id: string) => api.unarchiveInbox(id), onMutate: async (id) => { await qc.cancelQueries({ queryKey: inboxKeys.archived(wsId) }); const prev = qc.getQueryData(inboxKeys.archived(wsId)); // Restore every sibling for the same issue — the server unarchives the // whole issue group, so the optimistic patch must too or the rest of the // group would linger in the archived list until the refetch lands. const target = prev?.find((i) => i.id === id); const issueId = target?.issue_id; qc.setQueryData(inboxKeys.archived(wsId), (old) => old?.map((item) => item.id === id || (issueId && item.issue_id === issueId) ? { ...item, archived: false } : item, ), ); return { prev }; }, onError: (_err, _id, ctx) => { if (ctx?.prev) qc.setQueryData(inboxKeys.archived(wsId), ctx.prev); }, onSettled: () => { // Both lists: the item moves from one to the other, and the unread badge // rises again when it was archived unread. qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) }); qc.invalidateQueries({ queryKey: inboxKeys.unreadSummary() }); }, }); } export function useMarkAllInboxRead() { const qc = useQueryClient(); const wsId = useWorkspaceId(); return useMutation({ mutationFn: () => api.markAllInboxRead(), onMutate: async () => { await qc.cancelQueries({ queryKey: inboxKeys.list(wsId) }); const prev = qc.getQueryData(inboxKeys.list(wsId)); qc.setQueryData(inboxKeys.list(wsId), (old) => old?.map((item) => !item.archived ? { ...item, read: true } : item, ), ); return { prev }; }, onError: (_err, _vars, ctx) => { if (ctx?.prev) qc.setQueryData(inboxKeys.list(wsId), ctx.prev); }, onSettled: () => { qc.invalidateQueries({ queryKey: inboxKeys.list(wsId) }); }, }); } // The three batch-archive mutations below all move items into the archive, so // each invalidates BOTH lists on settle. export function useArchiveAllInbox() { const qc = useQueryClient(); const wsId = useWorkspaceId(); return useMutation({ mutationFn: () => api.archiveAllInbox(), onSettled: () => { qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) }); }, }); } export function useArchiveAllReadInbox() { const qc = useQueryClient(); const wsId = useWorkspaceId(); return useMutation({ mutationFn: () => api.archiveAllReadInbox(), onSettled: () => { qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) }); }, }); } export function useArchiveCompletedInbox() { const qc = useQueryClient(); const wsId = useWorkspaceId(); return useMutation({ mutationFn: () => api.archiveCompletedInbox(), onSettled: () => { qc.invalidateQueries({ queryKey: inboxKeys.all(wsId) }); }, }); }