Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 40x 22x 40x 40x 2x | import { useQuery } from "@tanstack/react-query";
import { adminRrmApi } from "../../lib/api/admin/rrm";
import { queryKeys } from "../../lib/query-keys";
import { STALE_2MIN } from "../../lib/stale-times";
export type AdminNavBadges = {
rrmInbox?: number;
rrmTasks?: number;
rrmSignups?: number;
};
/**
* Live counts for the admin sidebar.
*
* Deliberately does NOT poll. The sidebar renders on every admin page, so a
* refetch interval here would poll the RRM funnel from screens that have
* nothing to do with the campaign. It shares a cache key with
* `useRrmFunnel`, so whenever a real RRM screen is open the badge is as fresh
* as that screen; elsewhere it refreshes on navigation, which is enough to
* notice a backlog without taxing every other page.
*
* Failure is silent by design — a sidebar that renders nothing because a
* count could not be fetched would be a worse outcome than a missing badge.
*/
export function useAdminNavBadges(): AdminNavBadges {
const { data } = useQuery({
queryKey: queryKeys.admin.rrm.funnel(),
queryFn: () => adminRrmApi.getFunnel(),
staleTime: STALE_2MIN,
retry: false,
});
// `request<T>` returns the { success, data, error } envelope, not the
// payload — the count lives one level in.
const funnel = data?.data;
if (!funnel) return {};
return {
rrmInbox: funnel.unansweredCount,
// Organic sign-ups nobody has replied to yet (3.1). Optional chaining
// even though the contract makes `organic` required: this hook must
// never throw the sidebar into an error state over one stale field
// while the API deploys concurrently — a missing badge beats a broken page.
rrmSignups: funnel.organic?.unrepliedCount,
};
}
|