切换账号在同一 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>
82 lines
2.3 KiB
TypeScript
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,
|
|
});
|
|
}
|