Files
deerflow-code/frontend-web/src/core/notifications/hooks.ts
T
xuhdandClaude Opus 4.7 6155a2ec2b fix: curator + notifications react-query cache 按 user_id 隔离
切换账号在同一 tab 里时,旧 cache key (["curator","config"] 等) 会
让新账号"看到"上一个账号的值。如果用户没意识到、直接点保存,就会
把上一个账号的配置原样写到新账号的 per-user JSON 里去。

修复:在 useCuratorKeys / useNotificationKeys hook 里读
getStoredAuth().user_id 织进 queryKey。每个用户拥有独立 cache
桶,切换账号绝不命中对方的数据。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 18:20:41 +08:00

82 lines
2.3 KiB
TypeScript

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo } from "react";
import { getStoredAuth } from "@/core/auth";
import {
deleteNotification,
getUnreadCount,
listNotifications,
markAllNotificationsRead,
markNotificationRead,
} from "./api";
// Notifications are per-user data, so the cache key MUST be scoped by user_id.
// Otherwise switching accounts in the same tab would show the previous user's
// inbox until the next fetch lands.
function useNotificationKeys() {
const userId = useMemo(() => getStoredAuth()?.user_id ?? "anonymous", []);
return useMemo(() => {
const root = ["notifications", userId] as const;
return {
root,
list: (limit: number, unreadOnly: boolean) =>
[...root, "list", { limit, unreadOnly }] as const,
count: [...root, "unread-count"] as const,
};
}, [userId]);
}
export function useNotifications(options: { limit?: number; unreadOnly?: boolean } = {}) {
const { limit = 20, unreadOnly = false } = options;
const keys = useNotificationKeys();
return useQuery({
queryKey: keys.list(limit, unreadOnly),
queryFn: () => listNotifications({ limit, unreadOnly }),
});
}
export function useUnreadCount(refetchInterval = 30000) {
const keys = useNotificationKeys();
return useQuery({
queryKey: keys.count,
queryFn: () => getUnreadCount(),
refetchInterval,
// Keep the badge fresh when the user comes back to the tab without
// waiting for the next interval tick.
refetchOnWindowFocus: true,
});
}
function useInvalidateNotifications() {
const queryClient = useQueryClient();
const keys = useNotificationKeys();
return () => {
void queryClient.invalidateQueries({ queryKey: keys.root });
};
}
export function useMarkNotificationRead() {
const invalidate = useInvalidateNotifications();
return useMutation({
mutationFn: (id: string) => markNotificationRead(id),
onSuccess: invalidate,
});
}
export function useMarkAllNotificationsRead() {
const invalidate = useInvalidateNotifications();
return useMutation({
mutationFn: () => markAllNotificationsRead(),
onSuccess: invalidate,
});
}
export function useDeleteNotification() {
const invalidate = useInvalidateNotifications();
return useMutation({
mutationFn: (id: string) => deleteNotification(id),
onSuccess: invalidate,
});
}