Skip to content
File

Blob: src/client/hooks/use-shared-inbox.ts

typescript82 lines
1import { useEffect } from "react";
2import { useQuery } from "@tanstack/react-query";
3import { queryClient } from "@/client/lib/query-client";
4import { sharedInboxQueryKey, sharedInboxQueryOptions } from "@/client/lib/queries/shared-inbox";
5import { useAuthStore, selectIsAuthenticated } from "@/client/stores/auth-store";
6import { useSharedInboxItems, useSharedInboxWorkspaceSummaries } from "@/client/stores/shared-inbox";
7import { sharedInboxCommands } from "@/client/stores/db/shared-inbox";
8import { api } from "@/client/lib/api";
9import type { SharedPagesResponse, SharedWithMeItem, SharedInboxWorkspaceSummary } from "@/shared/types";
10 
11/**
12 * Imperative one-shot fetch used by pre-navigation decision flows (e.g.
13 * `EmptyWorkspaceView` deciding whether to auto-redirect to the shared inbox).
14 * Goes through the shared QueryClient so in-flight calls dedupe with any
15 * reactive `useSharedInbox()` subscriber, and writes through to Dexie so the
16 * runtime projection stays aligned. `staleTime: 0` forces a fresh fetch each
17 * call — decision flows should not rely on stale data.
18 */
19export async function fetchSharedInbox(): Promise<SharedPagesResponse> {
20 const response = await queryClient.fetchQuery({
21 queryKey: sharedInboxQueryKey,
22 queryFn: () => api.shares.sharedWithMe(),
23 staleTime: 0,
24 retry: false,
25 });
26 await sharedInboxCommands.replaceAll({
27 items: response.items,
28 workspaceSummaries: response.workspace_summaries,
29 });
30 return response;
31}
32 
33export type SharedInboxStatus = "idle" | "loading" | "error";
34 
35export interface SharedInboxView {
36 /** Cross-workspace items only — pages shared with the user in workspaces
37 * where they are not a member. */
38 items: SharedWithMeItem[];
39 /** Same-workspace summary: the user belongs to these workspaces and can
40 * find the shared pages inside the normal workspace tree. */
41 workspaceSummaries: SharedInboxWorkspaceSummary[];
42 status: SharedInboxStatus;
43 refresh: () => Promise<void>;
44}
45 
46export function useSharedInbox(): SharedInboxView {
47 const isAuthenticated = useAuthStore(selectIsAuthenticated);
48 const items = useSharedInboxItems();
49 const workspaceSummaries = useSharedInboxWorkspaceSummaries();
50 
51 const query = useQuery({
52 ...sharedInboxQueryOptions,
53 enabled: isAuthenticated,
54 });
55 
56 useEffect(() => {
57 if (query.data) {
58 void sharedInboxCommands.replaceAll({
59 items: query.data.items,
60 workspaceSummaries: query.data.workspace_summaries,
61 });
62 }
63 }, [query.data]);
64 
65 let status: SharedInboxStatus;
66 if (!isAuthenticated) {
67 status = items.length > 0 ? "idle" : "error";
68 } else if (query.isError) {
69 status = items.length > 0 ? "idle" : "error";
70 } else if (query.isPending) {
71 status = items.length > 0 ? "idle" : "loading";
72 } else {
73 status = "idle";
74 }
75 
76 const refresh = async () => {
77 await query.refetch();
78 };
79 
80 return { items, workspaceSummaries, status, refresh };
81}