use-inbox-counts.ts2.6 KBView on GitHub import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
import { useSession } from '@/modules/auth/utils/auth-client';
import { getInboxSlug, useInboxes } from '@/modules/threads/hooks/use-inboxes';
import { useInboxCompiledQueries } from '@/modules/threads/hooks/use-inbox-compiled-queries';
import { unreadOnlyQueryHash, withUnreadOnly } from '@/modules/threads/lib/unread-filter';
import { useCedarStore } from '@/modules/store';
/**
* Per-inbox thread counts for the tab badges sourced from Gmail query counts.
* `byId` is keyed by inbox id (including system inboxes), and `done` is the
* archived-thread estimate from Gmail.
*/
export function useInboxCounts() {
const trpc = useTRPC();
const { data: session } = useSession();
const { inboxes } = useInboxes();
const compiledByInboxId = useInboxCompiledQueries();
// ⇧U-filtered tabs count their UNREAD, not their all-mail total: the badge names the list
// the tab opens, and that tab now opens a filtered one.
const unreadOnlyByFolder = useCedarStore((state) => state.unreadOnlyByFolder);
const splitInputs = useMemo(
() =>
inboxes.map((inbox) => {
const { compiledQuery, queryHash, inboxName } = compiledByInboxId[inbox.id]!;
const unreadOnly = !!unreadOnlyByFolder[getInboxSlug(inbox)];
// A CRM-filtered inbox cannot be counted through Gmail — the server looks its rule up by
// `inboxId` and routes it to an exact SQL count over the mirror instead, which is what
// makes the badge agree with the list the tab opens.
return {
inboxId: inbox.id,
inboxName,
compiledQuery: withUnreadOnly(compiledQuery, unreadOnly),
queryHash: unreadOnlyQueryHash(queryHash, unreadOnly),
};
}),
[inboxes, compiledByInboxId, unreadOnlyByFolder],
);
const query = useQuery(
trpc.mail.getSplitCounts.queryOptions(
{ splits: splitInputs },
{
enabled: !!session?.user.id,
staleTime: 30_000,
// ⇧U re-keys this query too. Holding the previous counts keeps the badges in place
// for the one round trip rather than blanking every tab, then they settle on the
// filtered numbers.
placeholderData: (previousData) => previousData,
},
),
);
return useMemo(() => {
const byId: Record<string, { count: number; isExact: boolean }> = { ...query.data?.byId };
const done = query.data?.done ?? { count: 0, isExact: true };
return { byId, done, isLoading: query.isLoading };
}, [query.data, query.isLoading]);
}